feat: 添加多链支持和 Lending 借贷系统

- 新增 ARB Sepolia + BNB Testnet 多链支持
- 添加 LendingPanel 借贷系统组件
- 添加 LendingAdminPanel 管理面板
- 添加 USDCPanel USDC 操作组件
- 添加 HoldersPanel 持有人信息组件
- 添加 AutoTestPanel 自动化测试组件
- 重构 LP 组件为模块化结构 (LP/)
- 添加多个调试和测试脚本
- 修复 USDC 精度动态配置
- 优化合约配置支持多链切换

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-26 23:32:29 +08:00
parent c8427caa01
commit 5cb64f881e
80 changed files with 18342 additions and 1117 deletions

View File

@@ -0,0 +1,377 @@
/**
* 批量用户买入模拟脚本
*
* 功能:
* 1. 从 HD 钱包派生多个测试账户
* 2. 从主账户给测试账户转 WUSD
* 3. 批量执行 depositYT 买入交易
*
* 使用方法:
* npx tsx scripts/batch-buy-simulation.ts
*/
import {
createPublicClient,
createWalletClient,
http,
parseUnits,
formatUnits,
type Address,
type Hex
} from 'viem'
import { arbitrumSepolia } from 'viem/chains'
import { privateKeyToAccount, mnemonicToAccount } from 'viem/accounts'
// ============ 配置 ============
// 合约地址
const CONTRACTS = {
WUSD: '0x6d2bf81a631dFE19B2f348aE92cF6Ef41ca2DF98' as Address,
FACTORY: '0x982716f32F10BCB5B5944c1473a8992354bF632b' as Address,
VAULTS: {
YT_A: '0x0cA35994F033685E7a57ef9bc5d00dd3cf927330' as Address,
YT_B: '0x333805C9EE75f59Aa2Cc79DfDe2499F920c7b408' as Address,
YT_C: '0x6DF0ED6f0345F601A206974973dE9fC970598587' as Address,
}
}
// 模拟配置
const CONFIG = {
// 主账户私钥 (用于分发 WUSD)
MAIN_PRIVATE_KEY: '0xa082a7037105ebd606bee80906687e400d89899bbb6ba0273a61528c2f5fab89' as Hex,
// HD 钱包助记词 (用于生成测试账户)
// 测试用,请勿在生产环境使用
TEST_MNEMONIC: 'test test test test test test test test test test test junk',
// 模拟用户数量
USER_COUNT: 3,
// 每个用户的买入金额范围 (WUSD)
MIN_BUY_AMOUNT: 10,
MAX_BUY_AMOUNT: 100,
// 目标金库
TARGET_VAULT: CONTRACTS.VAULTS.YT_A,
// 交易间隔 (毫秒)
TX_INTERVAL: 2000,
// RPC URL
RPC_URL: 'https://sepolia-rollup.arbitrum.io/rpc',
}
// ============ ABI ============
const WUSD_ABI = [
{
inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }],
name: 'transfer',
outputs: [{ type: 'bool' }],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }],
name: 'approve',
outputs: [{ type: 'bool' }],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ name: 'account', type: 'address' }],
name: 'balanceOf',
outputs: [{ type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
] as const
const VAULT_ABI = [
{
inputs: [{ name: '_wusdAmount', type: 'uint256' }],
name: 'depositYT',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ name: '_wusdAmount', type: 'uint256' }],
name: 'previewBuy',
outputs: [{ type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: 'account', type: 'address' }],
name: 'balanceOf',
outputs: [{ type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'symbol',
outputs: [{ type: 'string' }],
stateMutability: 'view',
type: 'function',
},
] as const
// ============ 工具函数 ============
function randomAmount(min: number, max: number): bigint {
const amount = Math.random() * (max - min) + min
return parseUnits(amount.toFixed(2), 18)
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
// ============ 主逻辑 ============
async function main() {
console.log('🚀 批量用户买入模拟开始\n')
// 创建客户端
const publicClient = createPublicClient({
chain: arbitrumSepolia,
transport: http(CONFIG.RPC_URL),
})
// 主账户
const mainAccount = privateKeyToAccount(CONFIG.MAIN_PRIVATE_KEY)
const mainWalletClient = createWalletClient({
account: mainAccount,
chain: arbitrumSepolia,
transport: http(CONFIG.RPC_URL),
})
console.log(`📍 主账户: ${mainAccount.address}`)
// 检查主账户 WUSD 余额
const mainBalance = await publicClient.readContract({
address: CONTRACTS.WUSD,
abi: WUSD_ABI,
functionName: 'balanceOf',
args: [mainAccount.address],
})
console.log(`💰 主账户 WUSD 余额: ${formatUnits(mainBalance, 18)}`)
// 获取金库 symbol
const vaultSymbol = await publicClient.readContract({
address: CONFIG.TARGET_VAULT,
abi: VAULT_ABI,
functionName: 'symbol',
})
console.log(`🏦 目标金库: ${vaultSymbol} (${CONFIG.TARGET_VAULT})`)
// 生成测试账户
console.log(`\n👥 生成 ${CONFIG.USER_COUNT} 个测试账户...\n`)
const testAccounts = []
for (let i = 0; i < CONFIG.USER_COUNT; i++) {
const account = mnemonicToAccount(CONFIG.TEST_MNEMONIC, { addressIndex: i })
const buyAmount = randomAmount(CONFIG.MIN_BUY_AMOUNT, CONFIG.MAX_BUY_AMOUNT)
testAccounts.push({ account, buyAmount })
console.log(` 账户 ${i + 1}: ${account.address} | 计划买入: ${formatUnits(buyAmount, 18)} WUSD`)
}
// 计算总需要的 WUSD
const totalNeeded = testAccounts.reduce((sum, a) => sum + a.buyAmount, 0n)
console.log(`\n📊 总计需要: ${formatUnits(totalNeeded, 18)} WUSD`)
if (mainBalance < totalNeeded) {
console.error(`❌ 主账户余额不足! 需要 ${formatUnits(totalNeeded, 18)}, 只有 ${formatUnits(mainBalance, 18)}`)
return
}
// 第一步: 给测试账户转 ETH (gas 费)
console.log('\n⛽ 步骤 1: 分发 ETH (gas 费) 到测试账户...\n')
const gasAmount = parseUnits('0.05', 18) // 0.05 ETH 足够多次交易 (Arbitrum Sepolia gas 较高)
for (let i = 0; i < testAccounts.length; i++) {
const { account } = testAccounts[i]
// 检查 ETH 余额
const ethBalance = await publicClient.getBalance({ address: account.address })
if (ethBalance >= gasAmount) {
console.log(` ✓ 账户 ${i + 1} 已有足够 ETH: ${formatUnits(ethBalance, 18)} ETH`)
continue
}
const transferAmount = gasAmount - ethBalance
console.log(` → 转 ETH 给账户 ${i + 1}: ${formatUnits(transferAmount, 18)} ETH...`)
try {
const hash = await mainWalletClient.sendTransaction({
to: account.address,
value: transferAmount,
})
await publicClient.waitForTransactionReceipt({ hash })
console.log(` ✓ ETH 转账成功`)
} catch (error: any) {
console.error(` ✗ ETH 转账失败: ${error.message}`)
}
await sleep(500)
}
// 第二步: 给测试账户转 WUSD
console.log('\n📤 步骤 2: 分发 WUSD 到测试账户...\n')
for (let i = 0; i < testAccounts.length; i++) {
const { account, buyAmount } = testAccounts[i]
// 检查是否已有足够余额
const currentBalance = await publicClient.readContract({
address: CONTRACTS.WUSD,
abi: WUSD_ABI,
functionName: 'balanceOf',
args: [account.address],
})
if (currentBalance >= buyAmount) {
console.log(` ✓ 账户 ${i + 1} 已有足够余额: ${formatUnits(currentBalance, 18)} WUSD`)
continue
}
const transferAmount = buyAmount - currentBalance
console.log(` → 转账给账户 ${i + 1}: ${formatUnits(transferAmount, 18)} WUSD...`)
try {
const hash = await mainWalletClient.writeContract({
address: CONTRACTS.WUSD,
abi: WUSD_ABI,
functionName: 'transfer',
args: [account.address, transferAmount],
})
await publicClient.waitForTransactionReceipt({ hash })
console.log(` ✓ 转账成功: ${hash}`)
} catch (error: any) {
console.error(` ✗ 转账失败: ${error.message}`)
}
await sleep(1000)
}
// 第三步: 批量买入
console.log('\n🛒 步骤 3: 执行批量买入...\n')
const results: { address: string; amount: string; ytReceived: string; status: string; hash?: string }[] = []
for (let i = 0; i < testAccounts.length; i++) {
const { account, buyAmount } = testAccounts[i]
console.log(`\n[${i + 1}/${testAccounts.length}] 账户: ${account.address}`)
console.log(` 买入金额: ${formatUnits(buyAmount, 18)} WUSD`)
const walletClient = createWalletClient({
account,
chain: arbitrumSepolia,
transport: http(CONFIG.RPC_URL),
})
try {
// 预览买入
const previewYT = await publicClient.readContract({
address: CONFIG.TARGET_VAULT,
abi: VAULT_ABI,
functionName: 'previewBuy',
args: [buyAmount],
})
console.log(` 预计获得: ${formatUnits(previewYT, 18)} ${vaultSymbol}`)
// 授权
console.log(` → 授权 WUSD...`)
const approveHash = await walletClient.writeContract({
address: CONTRACTS.WUSD,
abi: WUSD_ABI,
functionName: 'approve',
args: [CONFIG.TARGET_VAULT, buyAmount],
})
await publicClient.waitForTransactionReceipt({ hash: approveHash })
console.log(` ✓ 授权成功`)
// 买入
console.log(` → 执行买入...`)
const buyHash = await walletClient.writeContract({
address: CONFIG.TARGET_VAULT,
abi: VAULT_ABI,
functionName: 'depositYT',
args: [buyAmount],
})
await publicClient.waitForTransactionReceipt({ hash: buyHash })
// 检查 YT 余额
const ytBalance = await publicClient.readContract({
address: CONFIG.TARGET_VAULT,
abi: VAULT_ABI,
functionName: 'balanceOf',
args: [account.address],
})
console.log(` ✓ 买入成功! YT 余额: ${formatUnits(ytBalance, 18)} ${vaultSymbol}`)
console.log(` 交易哈希: ${buyHash}`)
results.push({
address: account.address,
amount: formatUnits(buyAmount, 18),
ytReceived: formatUnits(ytBalance, 18),
status: 'success',
hash: buyHash,
})
} catch (error: any) {
console.error(` ✗ 买入失败: ${error.message}`)
results.push({
address: account.address,
amount: formatUnits(buyAmount, 18),
ytReceived: '0',
status: 'failed',
})
}
// 交易间隔
if (i < testAccounts.length - 1) {
console.log(` ⏳ 等待 ${CONFIG.TX_INTERVAL / 1000} 秒...`)
await sleep(CONFIG.TX_INTERVAL)
}
}
// 输出汇总
console.log('\n' + '='.repeat(60))
console.log('📊 模拟结果汇总')
console.log('='.repeat(60))
const successCount = results.filter(r => r.status === 'success').length
const failCount = results.filter(r => r.status === 'failed').length
const totalBought = results
.filter(r => r.status === 'success')
.reduce((sum, r) => sum + parseFloat(r.amount), 0)
const totalYT = results
.filter(r => r.status === 'success')
.reduce((sum, r) => sum + parseFloat(r.ytReceived), 0)
console.log(`\n成功: ${successCount}`)
console.log(`失败: ${failCount}`)
console.log(`总买入 WUSD: ${totalBought.toFixed(2)}`)
console.log(`总获得 ${vaultSymbol}: ${totalYT.toFixed(2)}`)
console.log('\n详细记录:')
console.table(results.map(r => ({
地址: r.address.slice(0, 10) + '...',
买入WUSD: r.amount,
获得YT: r.ytReceived,
状态: r.status,
})))
console.log('\n✅ 模拟完成!')
}
// 运行
main().catch(console.error)

View File

@@ -0,0 +1,343 @@
/**
* 批量用户卖出模拟脚本
*
* 功能:
* 1. 读取测试账户的 YT 余额
* 2. 批量执行 withdrawYT 卖出交易(进入排队)
* 3. 可选:管理员批量处理队列
*
* 使用方法:
* npx tsx scripts/batch-sell-simulation.ts
*/
import {
createPublicClient,
createWalletClient,
http,
parseUnits,
formatUnits,
type Address,
type Hex
} from 'viem'
import { arbitrumSepolia } from 'viem/chains'
import { privateKeyToAccount, mnemonicToAccount } from 'viem/accounts'
// ============ 配置 ============
const CONTRACTS = {
WUSD: '0x6d2bf81a631dFE19B2f348aE92cF6Ef41ca2DF98' as Address,
FACTORY: '0x982716f32F10BCB5B5944c1473a8992354bF632b' as Address,
VAULTS: {
YT_A: '0x0cA35994F033685E7a57ef9bc5d00dd3cf927330' as Address,
YT_B: '0x333805C9EE75f59Aa2Cc79DfDe2499F920c7b408' as Address,
YT_C: '0x6DF0ED6f0345F601A206974973dE9fC970598587' as Address,
}
}
const CONFIG = {
// 主账户私钥 (Owner用于处理队列)
MAIN_PRIVATE_KEY: '0xa082a7037105ebd606bee80906687e400d89899bbb6ba0273a61528c2f5fab89' as Hex,
// HD 钱包助记词 (与买入脚本相同)
TEST_MNEMONIC: 'test test test test test test test test test test test junk',
// 模拟用户数量
USER_COUNT: 10,
// 卖出比例 (0.5 = 卖出 50% 的 YT)
SELL_RATIO: 0.5,
// 目标金库
TARGET_VAULT: CONTRACTS.VAULTS.YT_A,
// 交易间隔 (毫秒)
TX_INTERVAL: 2000,
// 是否自动处理队列
AUTO_PROCESS_QUEUE: true,
// 批量处理大小
BATCH_PROCESS_SIZE: 10,
// RPC URL
RPC_URL: 'https://sepolia-rollup.arbitrum.io/rpc',
}
// ============ ABI ============
const VAULT_ABI = [
{
inputs: [{ name: '_ytAmount', type: 'uint256' }],
name: 'withdrawYT',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ name: '_ytAmount', type: 'uint256' }],
name: 'previewSell',
outputs: [{ type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: 'account', type: 'address' }],
name: 'balanceOf',
outputs: [{ type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'symbol',
outputs: [{ type: 'string' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'canRedeemNow',
outputs: [{ type: 'bool' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getQueueProgress',
outputs: [
{ name: 'currentIndex', type: 'uint256' },
{ name: 'totalRequests', type: 'uint256' },
{ name: 'pendingCount', type: 'uint256' },
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ name: '_batchSize', type: 'uint256' }],
name: 'processBatchWithdrawals',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
] as const
// ============ 工具函数 ============
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
// ============ 主逻辑 ============
async function main() {
console.log('🚀 批量用户卖出模拟开始\n')
const publicClient = createPublicClient({
chain: arbitrumSepolia,
transport: http(CONFIG.RPC_URL),
})
// 主账户 (Owner)
const mainAccount = privateKeyToAccount(CONFIG.MAIN_PRIVATE_KEY)
const mainWalletClient = createWalletClient({
account: mainAccount,
chain: arbitrumSepolia,
transport: http(CONFIG.RPC_URL),
})
console.log(`📍 Owner 账户: ${mainAccount.address}`)
// 获取金库信息
const vaultSymbol = await publicClient.readContract({
address: CONFIG.TARGET_VAULT,
abi: VAULT_ABI,
functionName: 'symbol',
})
console.log(`🏦 目标金库: ${vaultSymbol} (${CONFIG.TARGET_VAULT})`)
// 检查赎回状态
const canRedeem = await publicClient.readContract({
address: CONFIG.TARGET_VAULT,
abi: VAULT_ABI,
functionName: 'canRedeemNow',
})
console.log(`📅 赎回状态: ${canRedeem ? '✅ 可赎回' : '❌ 锁定中'}`)
if (!canRedeem) {
console.error('\n⚠ 当前不在赎回期,无法执行卖出操作')
console.log('请先通过管理员设置赎回时间: setVaultNextRedemptionTime')
return
}
// 获取队列状态
const queueProgress = await publicClient.readContract({
address: CONFIG.TARGET_VAULT,
abi: VAULT_ABI,
functionName: 'getQueueProgress',
})
console.log(`📋 队列状态: 已处理 ${queueProgress[0]}, 总请求 ${queueProgress[1]}, 待处理 ${queueProgress[2]}`)
// 获取测试账户
console.log(`\n👥 检查 ${CONFIG.USER_COUNT} 个测试账户的 YT 余额...\n`)
const testAccounts = []
for (let i = 0; i < CONFIG.USER_COUNT; i++) {
const account = mnemonicToAccount(CONFIG.TEST_MNEMONIC, { addressIndex: i })
const ytBalance = await publicClient.readContract({
address: CONFIG.TARGET_VAULT,
abi: VAULT_ABI,
functionName: 'balanceOf',
args: [account.address],
})
if (ytBalance > 0n) {
const sellAmount = (ytBalance * BigInt(Math.floor(CONFIG.SELL_RATIO * 100))) / 100n
testAccounts.push({ account, ytBalance, sellAmount })
console.log(` 账户 ${i + 1}: ${account.address} | YT余额: ${formatUnits(ytBalance, 18)} | 计划卖出: ${formatUnits(sellAmount, 18)}`)
} else {
console.log(` 账户 ${i + 1}: ${account.address} | YT余额: 0 (跳过)`)
}
}
if (testAccounts.length === 0) {
console.log('\n⚠ 没有账户有 YT 余额,请先运行 npm run sim:buy')
return
}
// 执行卖出
console.log('\n💸 步骤 1: 执行批量卖出 (进入排队)...\n')
const results: { address: string; sellAmount: string; expectedWusd: string; status: string; hash?: string }[] = []
for (let i = 0; i < testAccounts.length; i++) {
const { account, sellAmount } = testAccounts[i]
console.log(`\n[${i + 1}/${testAccounts.length}] 账户: ${account.address}`)
console.log(` 卖出金额: ${formatUnits(sellAmount, 18)} ${vaultSymbol}`)
const walletClient = createWalletClient({
account,
chain: arbitrumSepolia,
transport: http(CONFIG.RPC_URL),
})
try {
// 预览卖出
const previewWusd = await publicClient.readContract({
address: CONFIG.TARGET_VAULT,
abi: VAULT_ABI,
functionName: 'previewSell',
args: [sellAmount],
})
console.log(` 预计获得: ${formatUnits(previewWusd, 18)} WUSD`)
// 执行卖出 (进入队列)
console.log(` → 提交卖出请求...`)
const sellHash = await walletClient.writeContract({
address: CONFIG.TARGET_VAULT,
abi: VAULT_ABI,
functionName: 'withdrawYT',
args: [sellAmount],
})
await publicClient.waitForTransactionReceipt({ hash: sellHash })
console.log(` ✓ 请求已进入队列`)
console.log(` 交易哈希: ${sellHash}`)
results.push({
address: account.address,
sellAmount: formatUnits(sellAmount, 18),
expectedWusd: formatUnits(previewWusd, 18),
status: 'queued',
hash: sellHash,
})
} catch (error: any) {
console.error(` ✗ 卖出失败: ${error.message}`)
results.push({
address: account.address,
sellAmount: formatUnits(sellAmount, 18),
expectedWusd: '0',
status: 'failed',
})
}
if (i < testAccounts.length - 1) {
console.log(` ⏳ 等待 ${CONFIG.TX_INTERVAL / 1000} 秒...`)
await sleep(CONFIG.TX_INTERVAL)
}
}
// 获取更新后的队列状态
const newQueueProgress = await publicClient.readContract({
address: CONFIG.TARGET_VAULT,
abi: VAULT_ABI,
functionName: 'getQueueProgress',
})
console.log(`\n📋 更新后队列状态: 已处理 ${newQueueProgress[0]}, 总请求 ${newQueueProgress[1]}, 待处理 ${newQueueProgress[2]}`)
// 处理队列 (如果启用)
if (CONFIG.AUTO_PROCESS_QUEUE && newQueueProgress[2] > 0n) {
console.log('\n⚙ 步骤 2: 管理员批量处理队列...\n')
const pendingCount = Number(newQueueProgress[2])
const batches = Math.ceil(pendingCount / CONFIG.BATCH_PROCESS_SIZE)
for (let batch = 0; batch < batches; batch++) {
const batchSize = Math.min(CONFIG.BATCH_PROCESS_SIZE, pendingCount - batch * CONFIG.BATCH_PROCESS_SIZE)
console.log(` 处理批次 ${batch + 1}/${batches} (${batchSize} 笔)...`)
try {
const processHash = await mainWalletClient.writeContract({
address: CONFIG.TARGET_VAULT,
abi: VAULT_ABI,
functionName: 'processBatchWithdrawals',
args: [BigInt(batchSize)],
})
await publicClient.waitForTransactionReceipt({ hash: processHash })
console.log(` ✓ 批次处理完成: ${processHash}`)
} catch (error: any) {
console.error(` ✗ 处理失败: ${error.message}`)
break
}
await sleep(2000)
}
// 最终队列状态
const finalQueue = await publicClient.readContract({
address: CONFIG.TARGET_VAULT,
abi: VAULT_ABI,
functionName: 'getQueueProgress',
})
console.log(`\n📋 最终队列状态: 已处理 ${finalQueue[0]}, 总请求 ${finalQueue[1]}, 待处理 ${finalQueue[2]}`)
}
// 汇总
console.log('\n' + '='.repeat(60))
console.log('📊 模拟结果汇总')
console.log('='.repeat(60))
const queuedCount = results.filter(r => r.status === 'queued').length
const failCount = results.filter(r => r.status === 'failed').length
const totalSold = results
.filter(r => r.status === 'queued')
.reduce((sum, r) => sum + parseFloat(r.sellAmount), 0)
console.log(`\n成功排队: ${queuedCount}`)
console.log(`失败: ${failCount}`)
console.log(`总卖出 ${vaultSymbol}: ${totalSold.toFixed(2)}`)
console.log('\n详细记录:')
console.table(results.map(r => ({
地址: r.address.slice(0, 10) + '...',
卖出YT: r.sellAmount,
预期WUSD: r.expectedWusd,
状态: r.status,
})))
console.log('\n✅ 模拟完成!')
}
main().catch(console.error)

View File

@@ -0,0 +1,169 @@
import { createPublicClient, http, parseUnits, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const YT_A = getAddress('0x97204190B35D9895a7a47aa7BaC61ac08De3cF05')
const USDC = getAddress('0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d')
const USER = getAddress('0xa013422A5918CD099c63c8CC35283EACa99a705d')
const LENDING_ABI = [
{
inputs: [
{ internalType: 'address', name: 'account', type: 'address' }
],
name: 'borrowBalanceOf',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [
{ internalType: 'address', name: 'account', type: 'address' }
],
name: 'balanceOf',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [
{ internalType: 'address', name: 'account', type: 'address' },
{ internalType: 'address', name: 'asset', type: 'address' }
],
name: 'collateralBalanceOf',
outputs: [{ internalType: 'uint128', name: '', type: 'uint128' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [],
name: 'baseToken',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [],
name: 'baseMinForRewards',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [],
name: 'baseBorrowMin',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}
]
const ERC20_ABI = [
{
inputs: [
{ internalType: 'address', name: 'account', type: 'address' }
],
name: 'balanceOf',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [
{ internalType: 'address', name: 'owner', type: 'address' },
{ internalType: 'address', name: 'spender', type: 'address' }
],
name: 'allowance',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}
]
async function main() {
console.log('\n检查用户账户状态...\n')
console.log('用户地址:', USER)
try {
// 检查 YT-A 余额和授权
const ytBalance = await client.readContract({
address: YT_A,
abi: ERC20_ABI,
functionName: 'balanceOf',
args: [USER]
})
const ytAllowance = await client.readContract({
address: YT_A,
abi: ERC20_ABI,
functionName: 'allowance',
args: [USER, LENDING_PROXY]
})
console.log('\n=== YT-A 代币状态 ===')
console.log('钱包余额:', Number(ytBalance) / 1e18, 'YT-A')
console.log('授权额度:', Number(ytAllowance) / 1e18, 'YT-A')
console.log('想要存入:', 10, 'YT-A')
console.log('余额足够:', Number(ytBalance) >= 10e18 ? '✓' : '✗')
console.log('授权足够:', Number(ytAllowance) >= 10e18 ? '✓' : '✗')
// 检查 USDC 余额
const usdcBalance = await client.readContract({
address: USDC,
abi: ERC20_ABI,
functionName: 'balanceOf',
args: [USER]
})
console.log('\n=== USDC 代币状态 ===')
console.log('USDC 余额:', Number(usdcBalance) / 1e6, 'USDC')
// 检查 Lending 账户状态
const baseBalance = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'balanceOf',
args: [USER]
})
const borrowBalance = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'borrowBalanceOf',
args: [USER]
})
const collateralBalance = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'collateralBalanceOf',
args: [USER, YT_A]
})
console.log('\n=== Lending 账户状态 ===')
console.log('供应余额 (USDC):', Number(baseBalance) / 1e6, 'USDC')
console.log('借款余额 (USDC):', Number(borrowBalance) / 1e6, 'USDC')
console.log('抵押品余额 (YT-A):', Number(collateralBalance) / 1e18, 'YT-A')
// 检查合约参数
const baseBorrowMin = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'baseBorrowMin'
})
console.log('\n=== 合约参数 ===')
console.log('最小借款额:', Number(baseBorrowMin) / 1e6, 'USDC')
} catch (error) {
console.error('✗ 检查失败:', error.message)
console.error('详细错误:', error)
}
}
main()

View File

@@ -0,0 +1,30 @@
import { createPublicClient, http, formatEther } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
import { mnemonicToAccount } from 'viem/accounts'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://sepolia-rollup.arbitrum.io/rpc'),
})
const MAIN_ADDRESS = '0xa013422A5918CD099C63c8CC35283EACa99a705d'
const TEST_MNEMONIC = 'test test test test test test test test test test test junk'
async function main() {
console.log('📊 所有账户 ETH 余额:\n')
// 主账户
const mainBalance = await client.getBalance({ address: MAIN_ADDRESS })
console.log(`主账户: ${MAIN_ADDRESS}`)
console.log(` ETH: ${formatEther(mainBalance)}\n`)
// 测试账户
console.log('测试账户:')
for (let i = 0; i < 5; i++) {
const account = mnemonicToAccount(TEST_MNEMONIC, { addressIndex: i })
const balance = await client.getBalance({ address: account.address })
console.log(` ${i + 1}. ${account.address}: ${formatEther(balance)} ETH`)
}
}
main()

View File

@@ -0,0 +1,23 @@
import { createPublicClient, http, formatEther } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://sepolia-rollup.arbitrum.io/rpc'),
})
async function main() {
const balance = await client.getBalance({ address: '0xa013422A5918CD099C63c8CC35283EACa99a705d' })
console.log('主账户 ETH 余额:', formatEther(balance), 'ETH')
// 检查是否足够 (10 个账户 × 0.01 ETH = 0.1 ETH)
const needed = 0.1
if (parseFloat(formatEther(balance)) < needed) {
console.log(`\n⚠ ETH 不足! 需要至少 ${needed} ETH 来分发 gas 费`)
console.log('请先获取测试网 ETH: https://www.alchemy.com/faucets/arbitrum-sepolia')
} else {
console.log('\n✅ ETH 足够运行模拟脚本')
}
}
main()

View File

@@ -0,0 +1,171 @@
import { createPublicClient, http, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const USDC = getAddress('0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d')
const USER = getAddress('0xa013422A5918CD099C63c8CC35283EACa99a705d')
const TRANSFER_EVENT = {
type: 'event',
name: 'Transfer',
inputs: [
{ indexed: true, name: 'from', type: 'address' },
{ indexed: true, name: 'to', type: 'address' },
{ indexed: false, name: 'value', type: 'uint256' }
]
}
async function main() {
console.log('\n=== 检查借款历史USDC 从 Lending 转出给用户)===\n')
console.log('用户:', USER)
console.log('Lending 合约:', LENDING_PROXY)
console.log('USDC:', USDC)
console.log()
const latestBlock = await client.getBlockNumber()
console.log('最新区块:', latestBlock)
console.log('查询范围: 最近 10000 个区块\n')
try {
// 查找 USDC 从 Lending 转给用户的记录(这才是真正的借款/提取)
const logs = await client.getLogs({
address: USDC,
event: TRANSFER_EVENT,
args: {
from: LENDING_PROXY,
to: USER
},
fromBlock: latestBlock - 10000n,
toBlock: latestBlock
})
if (logs.length > 0) {
console.log(`✓ 找到 ${logs.length} 笔 USDC 转出记录:\n`)
let totalWithdrawn = 0n
for (let i = 0; i < logs.length; i++) {
const log = logs[i]
const { value } = log.args
totalWithdrawn += value
console.log(`${i + 1}. 区块 ${log.blockNumber}`)
console.log(` 交易: ${log.transactionHash}`)
console.log(` 数量: ${Number(value) / 1e6} USDC`)
// 获取交易详情以确认是 withdraw 还是其他操作
try {
const tx = await client.getTransaction({ hash: log.transactionHash })
const selector = tx.input.slice(0, 10)
// withdraw(uint256) = 0x2e1a7d4d
// withdrawFrom(...) = ...
if (selector === '0x2e1a7d4d') {
console.log(` 函数: withdraw (借款/提取)`)
} else {
console.log(` 函数选择器: ${selector}`)
}
} catch (error) {
console.log(` (无法获取交易详情)`)
}
console.log()
}
console.log(`总计: ${Number(totalWithdrawn) / 1e6} USDC`)
} else {
console.log('✗ 未找到任何 USDC 转出记录')
console.log()
console.log('这意味着:')
console.log(' - 用户从未真正从 Lending 提取或借款 USDC')
console.log(' - 如果用户看到"借款成功"的消息,那些都是前端误报')
}
console.log('\n=== 对比USDC 转入 Lending存款===\n')
const supplyLogs = await client.getLogs({
address: USDC,
event: TRANSFER_EVENT,
args: {
from: USER,
to: LENDING_PROXY
},
fromBlock: latestBlock - 10000n,
toBlock: latestBlock
})
if (supplyLogs.length > 0) {
console.log(`找到 ${supplyLogs.length} 笔 USDC 存入记录:\n`)
let totalSupplied = 0n
supplyLogs.forEach((log, i) => {
const { value } = log.args
totalSupplied += value
console.log(`${i + 1}. 区块 ${log.blockNumber}`)
console.log(` 交易: ${log.transactionHash}`)
console.log(` 数量: ${Number(value) / 1e6} USDC`)
console.log()
})
console.log(`总计存入: ${Number(totalSupplied) / 1e6} USDC`)
} else {
console.log('未找到 USDC 存入记录')
}
// 检查当前余额
console.log('\n=== 当前账户状态 ===\n')
const LENDING_ABI = [
{
inputs: [{ name: 'account', type: 'address' }],
name: 'getBalance',
outputs: [{ name: '', type: 'int256' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [{ name: 'account', type: 'address' }],
name: 'borrowBalanceOf',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}
]
const balance = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getBalance',
args: [USER]
})
const borrowBalance = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'borrowBalanceOf',
args: [USER]
})
console.log('USDC 余额(存款):', balance > 0 ? `${Number(balance) / 1e6} USDC` : '0 USDC')
console.log('借款余额:', Number(borrowBalance) / 1e6, 'USDC')
console.log()
if (balance > 0) {
console.log('⚠️ 用户当前有 USDC 存款在 Lending 中')
console.log(` 存款金额: ${Number(balance) / 1e6} USDC`)
console.log()
console.log('根据 Compound V3 设计:')
console.log(' - withdraw() 会先从存款中扣除')
console.log(` - 只有 withdraw 金额 > ${Number(balance) / 1e6} USDC 时,才会产生真正的借款`)
}
} catch (error) {
console.error('查询失败:', error.message)
}
}
main()

View File

@@ -0,0 +1,55 @@
import { createPublicClient, http, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const YT_A = getAddress('0x97204190B35D9895a7a47aa7BaC61ac08De3cF05')
const USER = getAddress('0xa013422A5918CD099C63c8CC35283EACa99a705d')
const LENDING_ABI = [
{
inputs: [
{ internalType: 'address', name: '_user', type: 'address' },
{ internalType: 'address', name: '_collateralAsset', type: 'address' }
],
name: 'getUserCollateral',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}
]
async function main() {
console.log('\n检查抵押品余额...\n')
console.log('用户:', USER)
console.log('抵押品:', YT_A)
try {
const collateralBalance = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getUserCollateral',
args: [USER, YT_A]
})
console.log('\n✓ 抵押品余额:', Number(collateralBalance) / 1e18, 'YT-A')
if (collateralBalance > 0n) {
console.log('\n成功代币已存入合约作为抵押品。')
} else {
console.log('\n警告抵押品余额为 0但钱包代币已被扣除。')
console.log('可能原因:')
console.log('1. 交易失败但代币被锁在某处')
console.log('2. 函数调用错误,需要检查交易哈希')
}
} catch (error) {
console.error('\n✗ 读取失败:', error.message)
}
}
main()

View File

@@ -0,0 +1,119 @@
import { createPublicClient, http, getAddress, keccak256, toHex, pad, concat } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const USER = getAddress('0xa013422A5918CD099C63c8CC35283EACa99a705d')
const YT_A = getAddress('0x97204190B35D9895a7a47aa7BaC61ac08De3cF05')
// Compound V3 使用的公开状态变量
const COMET_ABI = [
{
inputs: [
{ name: 'account', type: 'address' },
{ name: 'asset', type: 'address' }
],
name: 'userCollateral',
outputs: [
{
components: [
{ name: 'balance', type: 'uint128' },
{ name: '_reserved', type: 'uint128' }
],
name: '',
type: 'tuple'
}
],
stateMutability: 'view',
type: 'function'
},
{
inputs: [{ name: 'account', type: 'address' }],
name: 'balanceOf',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [{ name: 'account', type: 'address' }],
name: 'borrowBalanceOf',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [
{ name: 'account', type: 'address' },
{ name: 'asset', type: 'address' }
],
name: 'collateralBalanceOf',
outputs: [{ name: '', type: 'uint128' }],
stateMutability: 'view',
type: 'function'
}
]
async function main() {
console.log('\n检查 Compound V3 存储结构\n')
console.log('合约:', LENDING_PROXY)
console.log('用户:', USER)
console.log('抵押品:', YT_A)
console.log()
// 测试 Compound V3 原生函数
for (const func of COMET_ABI) {
console.log(`--- ${func.name} ---`)
try {
let result
if (func.name === 'userCollateral') {
result = await client.readContract({
address: LENDING_PROXY,
abi: [func],
functionName: func.name,
args: [USER, YT_A]
})
console.log('✓ 成功!')
console.log(' balance:', result.balance.toString(), '(', Number(result.balance) / 1e18, 'YT-A )')
console.log(' _reserved:', result._reserved.toString())
} else if (func.name === 'collateralBalanceOf') {
result = await client.readContract({
address: LENDING_PROXY,
abi: [func],
functionName: func.name,
args: [USER, YT_A]
})
console.log('✓ 成功:', result.toString(), '(', Number(result) / 1e18, 'YT-A )')
} else if (func.name === 'balanceOf') {
result = await client.readContract({
address: LENDING_PROXY,
abi: [func],
functionName: func.name,
args: [USER]
})
console.log('✓ 成功:', result.toString(), '(', Number(result) / 1e6, 'USDC )')
} else {
result = await client.readContract({
address: LENDING_PROXY,
abi: [func],
functionName: func.name,
args: [USER]
})
console.log('✓ 成功:', result.toString())
}
} catch (error) {
console.log('✗ 失败:', error.message.split('\n')[0])
}
console.log()
}
console.log('=== 结论 ===')
console.log('如果上面的 Compound V3 原生函数能工作,')
console.log('说明合约使用了 Compound V3 的数据结构,')
console.log('前端应该调用这些函数而不是自定义的 getUserCollateral 等。\n')
}
main()

View File

@@ -0,0 +1,78 @@
import { createPublicClient, http, parseAbi } from 'viem';
import { arbitrumSepolia } from 'viem/chains';
const CONFIGURATOR_ADDRESS = '0x488409CE9A3Fcd8EbD373dCb7e025cF8AB96fcdc';
const LENDING_PROXY = '0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D';
// 扩展的 ABI包含可能的其他函数
const CONFIGURATOR_ABI = parseAbi([
'function owner() view returns (address)',
'function lending() view returns (address)',
'function lendingContract() view returns (address)',
'function getLending() view returns (address)',
'function setLending(address) external',
'function initialize(address) external',
'function collateralConfigs(address) view returns (bool, uint256, uint256, uint256)'
]);
const publicClient = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://sepolia-rollup.arbitrum.io/rpc')
});
async function checkSetup() {
console.log('🔍 检查 Configurator 设置\n');
// 尝试读取lending地址可能有不同的函数名
const possibleGetters = ['lending', 'lendingContract', 'getLending'];
for (const getter of possibleGetters) {
try {
console.log(`尝试调用 ${getter}()...`);
const lendingAddr = await publicClient.readContract({
address: CONFIGURATOR_ADDRESS,
abi: CONFIGURATOR_ABI,
functionName: getter
});
console.log(`✅ 找到! ${getter}() = ${lendingAddr}`);
console.log(` 期望地址: ${LENDING_PROXY}`);
console.log(` 匹配: ${lendingAddr.toLowerCase() === LENDING_PROXY.toLowerCase() ? '✅' : '❌'}\n`);
if (lendingAddr.toLowerCase() !== LENDING_PROXY.toLowerCase()) {
console.log('⚠️ 警告: Lending 地址不匹配!');
console.log('💡 可能需要调用 setLending() 来设置正确的地址\n');
}
return;
} catch (error) {
console.log(`${getter}() 不存在或调用失败\n`);
}
}
console.log('💡 建议:');
console.log(' 1. Configurator 可能需要先通过 initialize() 或 setLending() 设置 Lending 合约地址');
console.log(' 2. 检查合约源码或部署脚本中的初始化步骤');
console.log(' 3. 可能需要 Lending owner 先在 Configurator 中注册\n');
// 检查是否有其他状态变量
console.log('🔍 尝试读取其他可能的状态...\n');
// 尝试直接读取存储槽
try {
// Slot 0 通常是第一个状态变量
const slot0 = await publicClient.getStorageAt({
address: CONFIGURATOR_ADDRESS,
slot: '0x0'
});
console.log('Storage Slot 0:', slot0);
if (slot0 && slot0 !== '0x' + '0'.repeat(64)) {
const addr = '0x' + slot0.slice(-40);
console.log('可能的地址值:', addr);
}
} catch (error) {
console.log('无法读取存储槽');
}
}
checkSetup();

View File

@@ -0,0 +1,28 @@
import { keccak256, toHex } from 'viem'
// 计算函数选择器
function getFunctionSelector(signature) {
const hash = keccak256(toHex(signature))
return hash.slice(0, 10) // 前4字节
}
console.log('\n计算函数选择器...\n')
const signatures = [
'deposit(address,uint256)',
'supplyCollateral(address,uint256)',
'supply(address,uint256)',
'supplyTo(address,address,uint256)'
]
signatures.forEach(sig => {
const selector = getFunctionSelector(sig)
console.log(`${sig}`)
console.log(` 选择器: ${selector}`)
if (selector === '0x47e7ef24') {
console.log(' ✓ 匹配!')
}
console.log()
})
console.log('错误消息中的选择器: 0x47e7ef24')

View File

@@ -0,0 +1,122 @@
import { createPublicClient, http, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const LENDING_ABI = [
{
inputs: [],
name: 'getBorrowRate',
outputs: [{ internalType: 'uint64', name: '', type: 'uint64' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [],
name: 'getSupplyRate',
outputs: [{ internalType: 'uint64', name: '', type: 'uint64' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [],
name: 'getUtilization',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}
]
async function main() {
console.log('\n检查利率和使用率...\n')
try {
// 1. 获取借款利率
const borrowRate = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getBorrowRate'
})
console.log('=== getBorrowRate ===')
console.log('原始值:', borrowRate.toString())
console.log('类型: uint64')
console.log()
// Compound V3 利率通常是每秒利率,精度 1e18
// APY = (1 + ratePerSecond)^(365*24*60*60) - 1
// 简化近似APY ≈ ratePerSecond * secondsPerYear * 100%
const secondsPerYear = 365 * 24 * 60 * 60
// 如果是 1e18 精度的每秒利率
if (borrowRate > 0n) {
const apyE18 = Number(borrowRate) * secondsPerYear / 1e18
console.log('假设精度 1e18 (每秒利率):')
console.log(' 每秒利率:', Number(borrowRate) / 1e18)
console.log(' 年化利率 (APY):', apyE18.toFixed(2), '%')
console.log()
// 如果是 1e16 精度(百分比形式)
const apyE16 = Number(borrowRate) * secondsPerYear / 1e16
console.log('假设精度 1e16:')
console.log(' 年化利率 (APY):', apyE16.toFixed(2), '%')
console.log()
// 如果是 1e4 精度basis points
const apyE4 = Number(borrowRate) / 1e4
console.log('假设精度 1e4 (basis points):')
console.log(' 年化利率 (APY):', apyE4.toFixed(2), '%')
console.log()
}
// 2. 获取存款利率
const supplyRate = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getSupplyRate'
})
console.log('=== getSupplyRate ===')
console.log('原始值:', supplyRate.toString())
console.log()
if (supplyRate > 0n) {
const apyE18 = Number(supplyRate) * secondsPerYear / 1e18
console.log('假设精度 1e18 (每秒利率):')
console.log(' 年化利率 (APY):', apyE18.toFixed(2), '%')
console.log()
}
// 3. 获取使用率
const utilization = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getUtilization'
})
console.log('=== getUtilization ===')
console.log('原始值:', utilization.toString())
console.log()
if (utilization > 0n) {
// Compound V3 使用率通常是 1e18 精度
const utilizationPercent = Number(utilization) / 1e18 * 100
console.log('假设精度 1e18:')
console.log(' 使用率:', utilizationPercent.toFixed(2), '%')
console.log()
// 如果是 1e4 精度
const utilizationE4 = Number(utilization) / 1e4
console.log('假设精度 1e4:')
console.log(' 使用率:', utilizationE4.toFixed(2), '%')
console.log()
}
} catch (error) {
console.error('查询失败:', error.message)
}
}
main()

View File

@@ -0,0 +1,153 @@
import { createPublicClient, http } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const CONFIGURATOR = '0x488409CE9A3Fcd8EbD373dCb7e025cF8AB96fcdc'
const LENDING_PROXY = '0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D'
const YT_A = '0x97204190B35D9895a7a47aa7BaC61ac08De3cF05'
const USDC = '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d'
const CONFIGURATOR_ABI = [
{
inputs: [
{ internalType: 'address', name: 'lendingProxy', type: 'address' }
],
name: 'getConfiguration',
outputs: [
{
components: [
{ internalType: 'address', name: 'baseToken', type: 'address' },
{ internalType: 'address', name: 'lendingPriceSource', type: 'address' },
{ internalType: 'uint64', name: 'supplyKink', type: 'uint64' },
{ internalType: 'uint64', name: 'supplyPerYearInterestRateSlopeLow', type: 'uint64' },
{ internalType: 'uint64', name: 'supplyPerYearInterestRateSlopeHigh', type: 'uint64' },
{ internalType: 'uint64', name: 'supplyPerYearInterestRateBase', type: 'uint64' },
{ internalType: 'uint64', name: 'borrowKink', type: 'uint64' },
{ internalType: 'uint64', name: 'borrowPerYearInterestRateSlopeLow', type: 'uint64' },
{ internalType: 'uint64', name: 'borrowPerYearInterestRateSlopeHigh', type: 'uint64' },
{ internalType: 'uint64', name: 'borrowPerYearInterestRateBase', type: 'uint64' },
{ internalType: 'uint64', name: 'storeFrontPriceFactor', type: 'uint64' },
{ internalType: 'uint64', name: 'trackingIndexScale', type: 'uint64' },
{ internalType: 'uint104', name: 'baseBorrowMin', type: 'uint104' },
{ internalType: 'uint104', name: 'targetReserves', type: 'uint104' },
{
components: [
{ internalType: 'address', name: 'asset', type: 'address' },
{ internalType: 'uint8', name: 'decimals', type: 'uint8' },
{ internalType: 'uint64', name: 'borrowCollateralFactor', type: 'uint64' },
{ internalType: 'uint64', name: 'liquidateCollateralFactor', type: 'uint64' },
{ internalType: 'uint64', name: 'liquidationFactor', type: 'uint64' },
{ internalType: 'uint128', name: 'supplyCap', type: 'uint128' }
],
internalType: 'struct LendingConfiguration.AssetConfig[]',
name: 'assetConfigs',
type: 'tuple[]'
}
],
internalType: 'struct LendingConfiguration.Configuration',
name: '',
type: 'tuple'
}
],
stateMutability: 'view',
type: 'function'
}
]
const LENDING_ABI = [
{
inputs: [],
name: 'paused',
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
stateMutability: 'view',
type: 'function'
}
]
async function main() {
console.log('\n检查 Lending 合约配置状态...\n')
try {
// 读取配置
const config = await client.readContract({
address: CONFIGURATOR,
abi: CONFIGURATOR_ABI,
functionName: 'getConfiguration',
args: [LENDING_PROXY]
})
// 检查暂停状态
const isPaused = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'paused'
})
console.log('=== 基础配置 ===')
console.log('baseToken:', config.baseToken)
console.log('是否为 USDC:', config.baseToken.toLowerCase() === USDC.toLowerCase() ? '✓ 是' : '✗ 否')
console.log('lendingPriceSource:', config.lendingPriceSource)
console.log('是否为零地址:', config.lendingPriceSource === '0x0000000000000000000000000000000000000000' ? '✗ 是(错误)' : '✓ 否')
console.log('\n=== 利率参数 ===')
console.log('supplyKink:', config.supplyKink.toString())
console.log('borrowKink:', config.borrowKink.toString())
console.log('baseBorrowMin:', config.baseBorrowMin.toString())
console.log('targetReserves:', config.targetReserves.toString())
console.log('\n=== 系统状态 ===')
console.log('是否暂停:', isPaused ? '✗ 是(无法操作)' : '✓ 否')
console.log('\n=== 抵押品配置 ===')
console.log('配置的抵押品数量:', config.assetConfigs.length)
const ytAConfig = config.assetConfigs.find(
cfg => cfg.asset.toLowerCase() === YT_A.toLowerCase()
)
if (ytAConfig) {
console.log('\nYT-A 配置:')
console.log(' 地址:', ytAConfig.asset)
console.log(' 精度:', ytAConfig.decimals)
console.log(' 借款抵押率:', Number(ytAConfig.borrowCollateralFactor) / 1e16, '%')
console.log(' 清算抵押率:', Number(ytAConfig.liquidateCollateralFactor) / 1e16, '%')
console.log(' 清算奖励:', Number(ytAConfig.liquidationFactor) / 1e16, '%')
console.log(' 供应上限:', Number(ytAConfig.supplyCap) / 1e18, 'tokens')
} else {
console.log('\n✗ YT-A 未配置!')
}
// 诊断
console.log('\n=== 诊断结果 ===')
const issues = []
if (config.baseToken === '0x0000000000000000000000000000000000000000') {
issues.push('✗ baseToken 未设置(零地址)')
}
if (config.lendingPriceSource === '0x0000000000000000000000000000000000000000') {
issues.push('✗ lendingPriceSource 未设置(零地址)')
}
if (isPaused) {
issues.push('✗ 系统已暂停')
}
if (!ytAConfig) {
issues.push('✗ YT-A 未配置')
}
if (issues.length > 0) {
console.log('发现问题:')
issues.forEach(issue => console.log(' ' + issue))
} else {
console.log('✓ 所有配置正常')
}
} catch (error) {
console.error('✗ 读取配置失败:', error.message)
}
}
main()

View File

@@ -0,0 +1,237 @@
import { createPublicClient, http, parseAbi } from 'viem';
import { arbitrumSepolia } from 'viem/chains';
const LENDING_PROXY = '0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D';
const CONFIGURATOR_ADDRESS = '0x488409CE9A3Fcd8EbD373dCb7e025cF8AB96fcdc';
const LENDING_ABI = parseAbi([
'function owner() view returns (address)',
'function configurator() view returns (address)',
'function getConfigurator() view returns (address)',
'function setConfigurator(address) external',
'function paused() view returns (bool)',
'function getTotalSupply() view returns (uint256)',
'function getTotalBorrow() view returns (uint256)',
'function getUtilization() view returns (uint256)',
'function getBorrowRate() view returns (uint256)',
'function getSupplyRate(uint256) view returns (uint256)',
'function initialize(address,address,address) external'
]);
const CONFIGURATOR_ABI = [
{
"inputs": [
{ "internalType": "address", "name": "lendingProxy", "type": "address" }
],
"name": "getConfiguration",
"outputs": [
{
"components": [
{ "internalType": "address", "name": "baseToken", "type": "address" },
{ "internalType": "address", "name": "lendingPriceSource", "type": "address" },
{ "internalType": "uint64", "name": "supplyKink", "type": "uint64" },
{ "internalType": "uint64", "name": "supplyPerYearInterestRateSlopeLow", "type": "uint64" },
{ "internalType": "uint64", "name": "supplyPerYearInterestRateSlopeHigh", "type": "uint64" },
{ "internalType": "uint64", "name": "supplyPerYearInterestRateBase", "type": "uint64" },
{ "internalType": "uint64", "name": "borrowKink", "type": "uint64" },
{ "internalType": "uint64", "name": "borrowPerYearInterestRateSlopeLow", "type": "uint64" },
{ "internalType": "uint64", "name": "borrowPerYearInterestRateSlopeHigh", "type": "uint64" },
{ "internalType": "uint64", "name": "borrowPerYearInterestRateBase", "type": "uint64" },
{ "internalType": "uint64", "name": "storeFrontPriceFactor", "type": "uint64" },
{ "internalType": "uint64", "name": "trackingIndexScale", "type": "uint64" },
{ "internalType": "uint104", "name": "baseBorrowMin", "type": "uint104" },
{ "internalType": "uint104", "name": "targetReserves", "type": "uint104" },
{
"components": [
{ "internalType": "address", "name": "asset", "type": "address" },
{ "internalType": "uint8", "name": "decimals", "type": "uint8" },
{ "internalType": "uint64", "name": "borrowCollateralFactor", "type": "uint64" },
{ "internalType": "uint64", "name": "liquidateCollateralFactor", "type": "uint64" },
{ "internalType": "uint64", "name": "liquidationFactor", "type": "uint64" },
{ "internalType": "uint128", "name": "supplyCap", "type": "uint128" }
],
"internalType": "struct LendingConfiguration.AssetConfig[]",
"name": "assetConfigs",
"type": "tuple[]"
}
],
"internalType": "struct LendingConfiguration.Configuration",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
}
];
const publicClient = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://sepolia-rollup.arbitrum.io/rpc')
});
async function checkLending() {
console.log('🔍 检查 Lending 合约设置\n');
try {
// 检查 owner
const owner = await publicClient.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'owner'
});
console.log('1⃣ Lending Owner:', owner);
} catch (error) {
console.log('1⃣ ❌ 读取 owner 失败');
}
// 尝试读取configurator地址
const getters = ['configurator', 'getConfigurator'];
let foundConfigurator = null;
for (const getter of getters) {
try {
console.log(`\n2⃣ 尝试调用 ${getter}()...`);
const configuratorAddr = await publicClient.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: getter
});
console.log(`${getter}() = ${configuratorAddr}`);
console.log(` 期望地址: ${CONFIGURATOR_ADDRESS}`);
console.log(` 匹配: ${configuratorAddr.toLowerCase() === CONFIGURATOR_ADDRESS.toLowerCase() ? '✅' : '❌'}`);
foundConfigurator = configuratorAddr;
if (configuratorAddr.toLowerCase() !== CONFIGURATOR_ADDRESS.toLowerCase()) {
console.log('\n⚠ 警告: Configurator 地址不匹配!');
console.log('💡 Lending 合约指向了不同的 Configurator');
console.log(` Lending 中的: ${configuratorAddr}`);
console.log(` 前端使用的: ${CONFIGURATOR_ADDRESS}`);
}
break;
} catch (error) {
console.log(`${getter}() 不存在或调用失败`);
}
}
if (!foundConfigurator) {
console.log('\n⚠ 未找到 Configurator 地址!');
console.log('💡 Lending 合约可能需要先通过 setConfigurator() 设置 Configurator 地址\n');
}
// 检查是否暂停
try {
const isPaused = await publicClient.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'paused'
});
console.log('\n3⃣ 系统暂停状态:', isPaused ? '已暂停' : '运行中');
} catch (error) {
console.log('\n3⃣ ❌ 读取暂停状态失败');
}
// 检查流动性
try {
const liquidity = await publicClient.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getTotalSupply'
});
console.log('4⃣ 总供应量:', (Number(liquidity) / 1e6).toFixed(2), 'USDC');
} catch (error) {
console.log('4⃣ ❌ 读取总供应量失败');
}
// 检查系统数据查询函数
console.log('\n📊 系统数据查询:');
try {
const totalBorrow = await publicClient.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getTotalBorrow'
});
console.log(' 总借款:', (Number(totalBorrow) / 1e6).toFixed(2), 'USDC');
} catch (error) {
console.log(' ❌ getTotalBorrow() 调用失败');
}
try {
const utilization = await publicClient.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getUtilization'
});
console.log(' 资金利用率:', (Number(utilization) / 1e18 * 100).toFixed(2), '%');
} catch (error) {
console.log(' ❌ getUtilization() 调用失败');
}
try {
const borrowRate = await publicClient.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getBorrowRate'
});
const borrowAPR = (Number(borrowRate) / 1e18 * 100).toFixed(2);
console.log(' 借款年利率:', borrowAPR, '%');
} catch (error) {
console.log(' ❌ getBorrowRate() 调用失败');
}
try {
const utilization = await publicClient.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getUtilization'
});
const supplyRate = await publicClient.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getSupplyRate',
args: [utilization]
});
const supplyAPR = (Number(supplyRate) / 1e18 * 100).toFixed(2);
console.log(' 存款年利率:', supplyAPR, '%');
} catch (error) {
console.log(' ❌ getSupplyRate() 调用失败');
}
// 检查 Configurator 配置
console.log('\n⚙ Configurator 配置:');
try {
const config = await publicClient.readContract({
address: CONFIGURATOR_ADDRESS,
abi: CONFIGURATOR_ABI,
functionName: 'getConfiguration',
args: [LENDING_PROXY]
});
console.log(' 基础资产:', config.baseToken);
console.log(' 价格源:', config.lendingPriceSource);
console.log(' 抵押品资产数量:', config.assetConfigs.length);
config.assetConfigs.forEach((asset, index) => {
console.log(`\n 抵押品 ${index + 1}:`);
console.log(` 地址: ${asset.asset}`);
console.log(` 精度: ${asset.decimals}`);
console.log(` 借款抵押率: ${(Number(asset.borrowCollateralFactor) / 1e18 * 100).toFixed(2)}%`);
console.log(` 清算抵押率: ${(Number(asset.liquidateCollateralFactor) / 1e18 * 100).toFixed(2)}%`);
console.log(` 清算奖励: ${(Number(asset.liquidationFactor) / 1e18 * 100).toFixed(2)}%`);
console.log(` 供应上限: ${(Number(asset.supplyCap) / (10 ** asset.decimals)).toFixed(2)}`);
});
} catch (error) {
console.log(' ❌ 读取 Configurator 配置失败:', error.message);
}
console.log('\n💡 总结:');
console.log(' - 检查 Lending 合约是否正确设置了 Configurator 地址');
console.log(' - 检查 Configurator 合约是否正确设置了 Lending 地址');
console.log(' - 这两个合约需要相互关联才能正常工作');
}
checkLending();

View File

@@ -0,0 +1,46 @@
import { createPublicClient, http } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const CONFIGURATOR = '0x488409CE9A3Fcd8EbD373dCb7e025cF8AB96fcdc'
const LENDING_PROXY = '0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D'
const OWNER_ABI = [
{
inputs: [],
name: 'owner',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function'
}
]
async function main() {
console.log('🔍 检查合约 owner...\n')
try {
const configuratorOwner = await client.readContract({
address: CONFIGURATOR,
abi: OWNER_ABI,
functionName: 'owner'
})
const lendingOwner = await client.readContract({
address: LENDING_PROXY,
abi: OWNER_ABI,
functionName: 'owner'
})
console.log('Configurator Owner:', configuratorOwner)
console.log('Lending Proxy Owner:', lendingOwner)
} catch (error) {
console.error('❌ 读取 owner 失败:', error.message)
}
}
main()

View File

@@ -0,0 +1,110 @@
import { createPublicClient, http, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PRICE_FEED = getAddress('0xE82c7cB9CfA42D6eb7e443956b78f8290249c316')
const YT_A = getAddress('0x97204190B35D9895a7a47aa7BaC61ac08De3cF05')
async function main() {
console.log('\n检查价格精度\n')
try {
// 检查 decimals
const decimals = await client.readContract({
address: LENDING_PRICE_FEED,
abi: [{
inputs: [],
name: 'decimals',
outputs: [{ name: '', type: 'uint8' }],
stateMutability: 'view',
type: 'function'
}],
functionName: 'decimals'
})
console.log('✓ decimals:', decimals)
const price = await client.readContract({
address: LENDING_PRICE_FEED,
abi: [{
inputs: [{ name: 'asset', type: 'address' }],
name: 'getPrice',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}],
functionName: 'getPrice',
args: [YT_A]
})
console.log('\nYT-A 价格:')
console.log(' 原始值:', price.toString())
console.log(' ÷ 1e8:', Number(price) / 1e8)
console.log(' ÷ 1e18:', Number(price) / 1e18)
console.log(' ÷ 1e', decimals, ':', Number(price) / (10 ** Number(decimals)))
// 如果价格是 1e22可能是
// - 错误地设置为 1e30 的固定价格Compound V3 使用 1e30 作为基准)
// - 或者配置了错误的精度
if (price > 1e20) {
console.log('\n⚠ 警告:价格异常高!')
console.log('可能原因:')
console.log('1. 价格设置错误(使用了 1e30 而不是合适的精度)')
console.log('2. 精度配置错误')
console.log('\n建议')
console.log('如果 YT-A 应该价值 $1价格应该设置为:')
console.log(` - 如果精度是 8: ${1e8}`)
console.log(` - 如果精度是 18: ${1e18}`)
console.log(` - 如果使用 Compound V3 格式: ${1e18} (价格) * ${1e18} (精度) / (资产精度)`)
}
} catch (error) {
console.error('✗ 查询失败:', error.message)
console.log('\n尝试读取价格无 decimals:')
try {
const price = await client.readContract({
address: LENDING_PRICE_FEED,
abi: [{
inputs: [{ name: 'asset', type: 'address' }],
name: 'getPrice',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}],
functionName: 'getPrice',
args: [YT_A]
})
console.log('价格:', price.toString())
console.log('\n如果这个值是 1e30 数量级,说明使用了 Compound V3 的价格格式')
console.log('Compound V3 价格 = (USD价格 * 1e', await getBaseScale(), ') / (10^资产精度)')
} catch (e2) {
console.error('仍然失败:', e2.message)
}
}
}
async function getBaseScale() {
try {
const scale = await client.readContract({
address: LENDING_PROXY,
abi: [{
inputs: [],
name: 'baseScale',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}],
functionName: 'baseScale'
})
return scale
} catch {
return '?'
}
}
main()

View File

@@ -0,0 +1,75 @@
import { createPublicClient, http, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PRICE_FEED = getAddress('0xE82c7cB9CfA42D6eb7e443956b78f8290249c316')
const YT_A = getAddress('0x97204190B35D9895a7a47aa7BaC61ac08De3cF05')
const USDC = getAddress('0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d')
const PRICE_FEED_ABI = [
{
inputs: [{ name: 'asset', type: 'address' }],
name: 'getPrice',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [{ name: 'asset', type: 'address' }],
name: 'getAssetPrice',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}
]
async function testPrice(asset, name) {
console.log(`\n=== ${name} (${asset}) ===`)
// 尝试 getPrice
try {
const price1 = await client.readContract({
address: LENDING_PRICE_FEED,
abi: PRICE_FEED_ABI,
functionName: 'getPrice',
args: [asset]
})
console.log('✓ getPrice:', price1.toString(), '(', Number(price1) / 1e8, 'USD )')
} catch (error) {
console.log('✗ getPrice 失败:', error.message.split('\n')[0])
}
// 尝试 getAssetPrice
try {
const price2 = await client.readContract({
address: LENDING_PRICE_FEED,
abi: PRICE_FEED_ABI,
functionName: 'getAssetPrice',
args: [asset]
})
console.log('✓ getAssetPrice:', price2.toString(), '(', Number(price2) / 1e8, 'USD )')
} catch (error) {
console.log('✗ getAssetPrice 失败:', error.message.split('\n')[0])
}
}
async function main() {
console.log('检查价格预言机\n')
console.log('Price Feed 地址:', LENDING_PRICE_FEED)
await testPrice(YT_A, 'YT-A')
await testPrice(USDC, 'USDC')
console.log('\n=== 诊断 ===')
console.log('如果价格查询失败,这就是为什么 getUserAccountData 会 revert')
console.log('需要:')
console.log('1. 检查价格预言机合约是否正确部署')
console.log('2. 检查是否为 YT-A 设置了价格')
console.log('3. 可能需要手动调用 setPrice() 来设置价格')
}
main()

View File

@@ -0,0 +1,55 @@
import { createPublicClient, http } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const PRICE_FEED = '0xE82c7cB9CfA42D6eb7e443956b78f8290249c316'
const YT_A = '0x97204190B35D9895a7a47aa7BaC61ac08De3cF05'
const PRICE_FEED_ABI = [
{
inputs: [
{ internalType: 'address', name: 'asset', type: 'address' }
],
name: 'getPrice',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}
]
async function main() {
console.log('\n检查价格源...\n')
try {
const price = await client.readContract({
address: PRICE_FEED,
abi: PRICE_FEED_ABI,
functionName: 'getPrice',
args: [YT_A]
})
console.log('YT-A 地址:', YT_A)
console.log('价格 (原始值):', price.toString())
console.log('价格 (格式化):', Number(price) / 1e8, 'USD')
if (price === 0n) {
console.log('\n✗ 错误: 价格为 0')
console.log(' 这会导致存入失败,因为合约无法计算抵押品价值')
} else {
console.log('\n✓ 价格正常')
}
} catch (error) {
console.error('\n✗ 读取价格失败:', error.message)
console.log('\n可能的原因:')
console.log(' 1. 价格源合约没有设置 YT-A 的价格')
console.log(' 2. getPrice 函数不存在或签名不匹配')
console.log(' 3. YT-A 地址在价格源中未注册')
}
}
main()

View File

@@ -0,0 +1,76 @@
import { createPublicClient, http, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const USER = getAddress('0xa013422A5918CD099C63c8CC35283EACa99a705d')
const SUPPLY_COLLATERAL_EVENT = {
type: 'event',
name: 'SupplyCollateral',
inputs: [
{ indexed: true, name: 'from', type: 'address' },
{ indexed: true, name: 'dst', type: 'address' },
{ indexed: true, name: 'asset', type: 'address' },
{ indexed: false, name: 'amount', type: 'uint256' }
]
}
async function main() {
console.log('\n检查最近的 SupplyCollateral 事件...\n')
const latestBlock = await client.getBlockNumber()
console.log('最新区块:', latestBlock)
console.log('查询范围: 最近 1000 个区块\n')
try {
const logs = await client.getLogs({
address: LENDING_PROXY,
event: SUPPLY_COLLATERAL_EVENT,
fromBlock: latestBlock - 1000n,
toBlock: latestBlock
})
if (logs.length === 0) {
console.log('未找到任何 SupplyCollateral 事件')
return
}
console.log('找到', logs.length, '个事件:\n')
logs.forEach((log, i) => {
const { from, dst, asset, amount } = log.args
const isCurrentUser = from.toLowerCase() === USER.toLowerCase()
console.log((i + 1) + '. 区块', log.blockNumber)
console.log(' 交易:', log.transactionHash)
console.log(' From:', from, isCurrentUser ? '<- 你的地址' : '')
console.log(' To:', dst)
console.log(' Asset:', asset)
console.log(' Amount:', Number(amount) / 1e18)
console.log()
})
const userLogs = logs.filter(log =>
log.args.from.toLowerCase() === USER.toLowerCase()
)
if (userLogs.length > 0) {
console.log('\n找到你的', userLogs.length, '笔存入记录!')
console.log('最近一笔:')
const latest = userLogs[userLogs.length - 1]
console.log(' 交易哈希:', latest.transactionHash)
console.log(' 数量:', Number(latest.args.amount) / 1e18, 'YT-A')
console.log(' 区块:', latest.blockNumber)
}
} catch (error) {
console.error('查询失败:', error.message)
}
}
main()

View File

@@ -0,0 +1,108 @@
import { createPublicClient, http, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const YT_A = getAddress('0x97204190B35D9895a7a47aa7BaC61ac08De3cF05')
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const USER = getAddress('0xa013422A5918CD099C63c8CC35283EACa99a705d')
const TRANSFER_EVENT = {
type: 'event',
name: 'Transfer',
inputs: [
{ indexed: true, name: 'from', type: 'address' },
{ indexed: true, name: 'to', type: 'address' },
{ indexed: false, name: 'value', type: 'uint256' }
]
}
async function main() {
console.log('\n检查 YT-A 代币转账... \n')
const latestBlock = await client.getBlockNumber()
console.log('最新区块:', latestBlock)
console.log('查询范围: 最近 1000 个区块\n')
try {
// 检查转入 Lending 合约的 Transfer
console.log('--- 1. 查找转入 Lending 合约的转账 ---')
const logsTo = await client.getLogs({
address: YT_A,
event: TRANSFER_EVENT,
args: {
to: LENDING_PROXY
},
fromBlock: latestBlock - 1000n,
toBlock: latestBlock
})
if (logsTo.length === 0) {
console.log('✗ 未找到任何转入 Lending 合约的转账\n')
} else {
console.log('✓ 找到', logsTo.length, '笔转入记录:\n')
logsTo.forEach((log, i) => {
const { from, to, value } = log.args
const isFromUser = from.toLowerCase() === USER.toLowerCase()
console.log((i + 1) + '. 区块', log.blockNumber)
console.log(' 交易:', log.transactionHash)
console.log(' From:', from, isFromUser ? '<- 你的地址' : '')
console.log(' To:', to)
console.log(' Amount:', Number(value) / 1e18, 'YT-A')
console.log()
})
}
// 检查从用户发出的 Transfer
console.log('--- 2. 查找从你的地址发出的转账 ---')
const logsFrom = await client.getLogs({
address: YT_A,
event: TRANSFER_EVENT,
args: {
from: USER
},
fromBlock: latestBlock - 1000n,
toBlock: latestBlock
})
if (logsFrom.length === 0) {
console.log('✗ 未找到任何从你地址发出的转账\n')
} else {
console.log('✓ 找到', logsFrom.length, '笔转出记录:\n')
logsFrom.forEach((log, i) => {
const { from, to, value } = log.args
const isToLending = to.toLowerCase() === LENDING_PROXY.toLowerCase()
console.log((i + 1) + '. 区块', log.blockNumber)
console.log(' 交易:', log.transactionHash)
console.log(' From:', from)
console.log(' To:', to, isToLending ? '<- Lending 合约' : '')
console.log(' Amount:', Number(value) / 1e18, 'YT-A')
console.log()
})
}
// 检查用户的 YT-A 余额
console.log('--- 3. 检查当前 YT-A 余额 ---')
const balance = await client.readContract({
address: YT_A,
abi: [{
inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
name: 'balanceOf',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}],
functionName: 'balanceOf',
args: [USER]
})
console.log('你的 YT-A 余额:', Number(balance) / 1e18, '\n')
} catch (error) {
console.error('查询失败:', error.message)
}
}
main()

View File

@@ -0,0 +1,75 @@
import { createPublicClient, http, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const TX_HASH = '0xf38e4201b397b2e267402e2b355c811fb0d99ce2c9f67b3b0fff26028a7a4df4'
async function main() {
console.log('\n检查交易详情...\n')
console.log('交易哈希:', TX_HASH, '\n')
try {
// 获取交易详情
const tx = await client.getTransaction({ hash: TX_HASH })
console.log('=== 交易基本信息 ===')
console.log('From:', tx.from)
console.log('To:', tx.to)
console.log('区块:', tx.blockNumber)
console.log('Gas Used:', tx.gas.toString())
console.log('\n函数调用 (input):', tx.input.slice(0, 200) + '...')
console.log('函数选择器:', tx.input.slice(0, 10))
// 获取交易回执
const receipt = await client.getTransactionReceipt({ hash: TX_HASH })
console.log('\n=== 交易回执 ===')
console.log('状态:', receipt.status === 'success' ? '✓ 成功' : '✗ 失败')
console.log('Gas 实际使用:', receipt.gasUsed.toString())
console.log('事件数量:', receipt.logs.length)
console.log('\n=== 事件日志 ===')
receipt.logs.forEach((log, i) => {
console.log(`\n事件 ${i + 1}:`)
console.log(' 合约:', log.address)
console.log(' Topics[0]:', log.topics[0])
// 识别 Transfer 事件 (keccak256("Transfer(address,address,uint256)"))
if (log.topics[0] === '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef') {
console.log(' 类型: Transfer 事件')
if (log.topics[1]) console.log(' From:', '0x' + log.topics[1].slice(26))
if (log.topics[2]) console.log(' To:', '0x' + log.topics[2].slice(26))
}
// 识别 SupplyCollateral 事件
else if (log.topics[0] === '0x...') { // 需要知道实际的事件签名
console.log(' 类型: SupplyCollateral 事件')
}
else {
console.log(' 类型: 未知事件')
}
console.log(' Data:', log.data.slice(0, 66) + (log.data.length > 66 ? '...' : ''))
})
// 解析函数选择器
const selector = tx.input.slice(0, 10)
console.log('\n=== 函数识别 ===')
const functionMap = {
'0x47e7ef24': 'deposit(address,uint256)',
'0xe8eda9df': 'supplyCollateral(address,uint256)',
'0x23b872dd': 'transferFrom(address,address,uint256)',
'0xf213159c': 'supply(uint256)',
'0x2e1a7d4d': 'withdraw(uint256)'
}
console.log('调用函数:', functionMap[selector] || '未知: ' + selector)
} catch (error) {
console.error('查询失败:', error.message)
}
}
main()

View File

@@ -0,0 +1,158 @@
import { createPublicClient, http, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const USDC = getAddress('0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d')
const USER = getAddress('0xa013422A5918CD099C63c8CC35283EACa99a705d')
const TRANSFER_EVENT = {
type: 'event',
name: 'Transfer',
inputs: [
{ indexed: true, name: 'from', type: 'address' },
{ indexed: true, name: 'to', type: 'address' },
{ indexed: false, name: 'value', type: 'uint256' }
]
}
async function main() {
console.log('\n检查 USDC 供应/提取历史...\n')
console.log('用户:', USER)
console.log('Lending 合约:', LENDING_PROXY)
console.log()
const latestBlock = await client.getBlockNumber()
console.log('最新区块:', latestBlock)
console.log('查询范围: 最近 10000 个区块\n')
try {
// 1. 检查用户转入 Lending 的 USDCsupply 操作)
console.log('=== 1. USDC 转入 Lending 合约Supply===')
const logsToLending = await client.getLogs({
address: USDC,
event: TRANSFER_EVENT,
args: {
from: USER,
to: LENDING_PROXY
},
fromBlock: latestBlock - 10000n,
toBlock: latestBlock
})
if (logsToLending.length > 0) {
console.log(`找到 ${logsToLending.length} 笔转入记录:\n`)
let totalSupplied = 0n
logsToLending.forEach((log, i) => {
const { value } = log.args
totalSupplied += value
console.log(`${i + 1}. 区块 ${log.blockNumber}`)
console.log(` 交易: ${log.transactionHash}`)
console.log(` 数量: ${Number(value) / 1e6} USDC`)
console.log()
})
console.log(`总存入: ${Number(totalSupplied) / 1e6} USDC\n`)
} else {
console.log('未找到转入记录\n')
}
// 2. 检查 Lending 转给用户的 USDCwithdraw 操作)
console.log('=== 2. USDC 从 Lending 转出Withdraw===')
const logsFromLending = await client.getLogs({
address: USDC,
event: TRANSFER_EVENT,
args: {
from: LENDING_PROXY,
to: USER
},
fromBlock: latestBlock - 10000n,
toBlock: latestBlock
})
if (logsFromLending.length > 0) {
console.log(`找到 ${logsFromLending.length} 笔转出记录:\n`)
let totalWithdrawn = 0n
logsFromLending.forEach((log, i) => {
const { value } = log.args
totalWithdrawn += value
console.log(`${i + 1}. 区块 ${log.blockNumber}`)
console.log(` 交易: ${log.transactionHash}`)
console.log(` 数量: ${Number(value) / 1e6} USDC`)
console.log()
})
console.log(`总提取: ${Number(totalWithdrawn) / 1e6} USDC\n`)
} else {
console.log('未找到转出记录\n')
}
// 3. 获取当前余额
console.log('=== 3. 当前状态 ===')
const LENDING_ABI = [
{
inputs: [{ name: 'account', type: 'address' }],
name: 'getBalance',
outputs: [{ name: '', type: 'int256' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [{ name: 'account', type: 'address' }],
name: 'borrowBalanceOf',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}
]
const balance = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getBalance',
args: [USER]
})
const borrowBalance = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'borrowBalanceOf',
args: [USER]
})
console.log('USDC 余额(在 Lending 中):', Number(balance) / 1e6, 'USDC')
console.log(' 原始值:', balance.toString())
console.log(' 正数 = 存款,负数 = 借款')
console.log()
console.log('借款余额:', Number(borrowBalance) / 1e6, 'USDC')
console.log()
// 4. 分析
console.log('=== 分析 ===')
if (balance > 0) {
console.log('⚠️ 你当前有存款!')
console.log(` 存款金额: ${Number(balance) / 1e6} USDC`)
console.log()
console.log('这意味着:')
console.log(` - 如果你借款 ≤ ${Number(balance) / 1e6} USDC只是提取存款`)
console.log(` - 只有借款 > ${Number(balance) / 1e6} USDC才会产生债务`)
console.log()
console.log('建议:')
console.log(' 1. 如果要真正借款,请借款金额大于当前存款')
console.log(` 例如:借款 ${Number(balance) / 1e6 + 100} USDC`)
console.log(' 2. 或者先提取所有存款,再借款')
} else if (balance < 0) {
console.log('✓ 你有借款!')
console.log(` 借款金额: ${-Number(balance) / 1e6} USDC`)
} else {
console.log('✓ 你没有存款也没有借款')
}
} catch (error) {
console.error('查询失败:', error.message)
}
}
main()

View File

@@ -0,0 +1,100 @@
import { createPublicClient, http, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const USER = getAddress('0xa013422A5918CD099C63c8CC35283EACa99a705d')
const YT_A = getAddress('0x97204190B35D9895a7a47aa7BaC61ac08De3cF05')
const LENDING_ABI = [
{
inputs: [{ internalType: 'address', name: '_user', type: 'address' }],
name: 'getUserAccountData',
outputs: [
{ internalType: 'uint256', name: 'totalCollateralValue', type: 'uint256' },
{ internalType: 'uint256', name: 'totalBorrowValue', type: 'uint256' },
{ internalType: 'uint256', name: 'availableToBorrow', type: 'uint256' },
{ internalType: 'uint256', name: 'healthFactor', type: 'uint256' }
],
stateMutability: 'view',
type: 'function'
},
{
inputs: [
{ internalType: 'address', name: '_user', type: 'address' },
{ internalType: 'address', name: '_collateralAsset', type: 'address' }
],
name: 'getUserCollateral',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}
]
async function main() {
console.log('\n检查用户账户数据...\n')
console.log('用户地址:', USER)
console.log('Lending 合约:', LENDING_PROXY)
try {
// 1. 检查抵押品余额
console.log('\n--- 1. 检查 getUserCollateral (YT-A) ---')
const collateral = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getUserCollateral',
args: [USER, YT_A]
})
console.log('✓ getUserCollateral:', Number(collateral) / 1e18, 'YT-A')
console.log(' 原始值:', collateral.toString())
// 2. 检查账户数据
console.log('\n--- 2. 检查 getUserAccountData ---')
const accountData = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getUserAccountData',
args: [USER]
})
console.log('✓ getUserAccountData 返回:')
console.log(' totalCollateralValue:', Number(accountData[0]) / 1e6, 'USD')
console.log(' totalBorrowValue:', Number(accountData[1]) / 1e6, 'USD')
console.log(' availableToBorrow:', Number(accountData[2]) / 1e6, 'USD')
console.log(' healthFactor:', Number(accountData[3]) / 1e4, '%')
console.log('\n 原始值:')
console.log(' [0]:', accountData[0].toString())
console.log(' [1]:', accountData[1].toString())
console.log(' [2]:', accountData[2].toString())
console.log(' [3]:', accountData[3].toString())
// 3. 诊断
console.log('\n--- 诊断 ---')
if (collateral > 0n && accountData[0] === 0n) {
console.log('⚠️ 问题发现:')
console.log(' - getUserCollateral 返回有值(', Number(collateral) / 1e18, 'YT-A')
console.log(' - 但 getUserAccountData 返回总抵押价值为 0')
console.log(' 可能原因:')
console.log(' 1. 价格预言机返回 0YT-A 价格未设置)')
console.log(' 2. getUserAccountData 函数逻辑错误')
console.log(' 3. 抵押品资产未在配置中激活')
} else if (collateral === 0n) {
console.log('✓ 抵押品余额为 0这是正常的如果之前交易失败')
} else if (accountData[0] > 0n) {
console.log('✓ 一切正常,抵押品价值已正确计算')
}
} catch (error) {
console.error('\n✗ 调用失败:', error.message)
if (error.message.includes('Contract function')) {
console.log('\n可能原因getUserAccountData 函数不存在或签名不匹配')
}
}
}
main()

View File

@@ -0,0 +1,106 @@
import { createPublicClient, http, getAddress, keccak256, toBytes } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const USER = getAddress('0xa013422A5918CD099C63c8CC35283EACa99a705d')
// 计算函数选择器
const withdrawSelector = keccak256(toBytes('withdraw(uint256)')).slice(0, 10)
const supplyCollateralSelector = keccak256(toBytes('supplyCollateral(address,uint256)')).slice(0, 10)
console.log('\n=== 查找用户的交易记录 ===\n')
console.log('用户地址:', USER)
console.log('Lending 合约:', LENDING_PROXY)
console.log('\n函数选择器:')
console.log(' withdraw:', withdrawSelector)
console.log(' supplyCollateral:', supplyCollateralSelector)
console.log()
async function main() {
const latestBlock = await client.getBlockNumber()
console.log('最新区块:', latestBlock)
console.log('查询范围: 最近 10000 个区块\n')
// 获取用户发送到 Lending 合约的所有交易
const fromBlock = latestBlock - 10000n
const toBlock = latestBlock
console.log('=== 查找所有用户 → Lending 的交易 ===\n')
let blockNum = fromBlock
const transactions = []
while (blockNum <= toBlock) {
const endBlock = blockNum + 1000n > toBlock ? toBlock : blockNum + 1000n
try {
const block = await client.getBlock({
blockNumber: blockNum,
includeTransactions: true
})
for (const tx of block.transactions) {
if (typeof tx === 'object' &&
tx.from.toLowerCase() === USER.toLowerCase() &&
tx.to?.toLowerCase() === LENDING_PROXY.toLowerCase()) {
transactions.push({
hash: tx.hash,
blockNumber: block.number,
input: tx.input
})
}
}
} catch (error) {
// Skip blocks without transactions
}
blockNum += 1000n
}
console.log(`找到 ${transactions.length} 笔交易\n`)
if (transactions.length === 0) {
console.log('没有找到任何交易')
return
}
// 分析每笔交易
for (const tx of transactions) {
console.log('---')
console.log('交易哈希:', tx.hash)
console.log('区块:', tx.blockNumber.toString())
const selector = tx.input.slice(0, 10)
console.log('函数选择器:', selector)
let functionName = '未知'
if (selector === withdrawSelector) {
functionName = 'withdraw (借款/提取)'
// 解析参数 (uint256)
const amountHex = '0x' + tx.input.slice(10)
const amount = BigInt(amountHex)
console.log('调用函数:', functionName)
console.log('金额:', Number(amount) / 1e6, 'USDC')
} else if (selector === supplyCollateralSelector) {
functionName = 'supplyCollateral (存入抵押品)'
// 解析参数 (address, uint256)
const assetAddress = '0x' + tx.input.slice(34, 74)
const amountHex = '0x' + tx.input.slice(74)
const amount = BigInt(amountHex)
console.log('调用函数:', functionName)
console.log('资产:', getAddress(assetAddress))
console.log('金额:', Number(amount) / 1e18)
} else {
console.log('调用函数:', functionName, '-', selector)
}
console.log()
}
}
main().catch(console.error)

View File

@@ -0,0 +1,169 @@
import { createWalletClient, http, createPublicClient } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { arbitrumSepolia } from 'viem/chains'
// 从环境变量或命令行参数获取私钥
const PRIVATE_KEY = process.env.PRIVATE_KEY || process.argv[2]
if (!PRIVATE_KEY || !PRIVATE_KEY.startsWith('0x')) {
console.error('❌ 请提供私钥:')
console.error(' 方式1: export PRIVATE_KEY=0x...')
console.error(' 方式2: node configure-collateral.js 0x...')
process.exit(1)
}
const account = privateKeyToAccount(PRIVATE_KEY)
const publicClient = createPublicClient({
chain: arbitrumSepolia,
transport: http()
})
const walletClient = createWalletClient({
account,
chain: arbitrumSepolia,
transport: http()
})
const CONFIGURATOR = '0x488409CE9A3Fcd8EbD373dCb7e025cF8AB96fcdc'
const LENDING_PROXY = '0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D'
const COLLATERAL_ASSETS = [
{
name: 'YT-A',
address: '0x97204190B35D9895a7a47aa7BaC61ac08De3cF05',
collateralFactor: 7500, // 75%
liquidationThreshold: 8500, // 85%
liquidationBonus: 1000 // 10%
},
{
name: 'YT-B',
address: '0x181ef4011c35C4a2Fda08eBC5Cf509Ef58E553fF',
collateralFactor: 7500,
liquidationThreshold: 8500,
liquidationBonus: 1000
},
{
name: 'YT-C',
address: '0xE9A5b9f3a2Eda4358f81d4E2eF4f3280A664e5B0',
collateralFactor: 7500,
liquidationThreshold: 8500,
liquidationBonus: 1000
}
]
// Configurator ABI
const CONFIGURATOR_ABI = [
{
inputs: [
{ internalType: 'address', name: '_asset', type: 'address' },
{ internalType: 'uint256', name: '_collateralFactor', type: 'uint256' },
{ internalType: 'uint256', name: '_liquidationThreshold', type: 'uint256' },
{ internalType: 'uint256', name: '_liquidationBonus', type: 'uint256' }
],
name: 'setCollateralConfig',
outputs: [],
stateMutability: 'nonpayable',
type: 'function'
},
{
inputs: [
{ internalType: 'address', name: '_asset', type: 'address' },
{ internalType: 'bool', name: '_isActive', type: 'bool' }
],
name: 'setCollateralActive',
outputs: [],
stateMutability: 'nonpayable',
type: 'function'
},
{
inputs: [],
name: 'owner',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function'
}
]
async function configureCollateral() {
console.log('\n🔧 配置借贷抵押品\n')
console.log('Configurator:', CONFIGURATOR)
console.log('操作者:', account.address)
console.log('')
// 检查权限
try {
const owner = await publicClient.readContract({
address: CONFIGURATOR,
abi: CONFIGURATOR_ABI,
functionName: 'owner'
})
console.log('Configurator Owner:', owner)
if (owner.toLowerCase() !== account.address.toLowerCase()) {
console.error('\n❌ 错误: 当前账户不是 Configurator 的 owner')
console.error(' 需要使用 owner 账户的私钥')
process.exit(1)
}
} catch (error) {
console.warn('⚠️ 无法检查owner继续尝试配置...\n')
}
// 配置每个抵押品
for (const asset of COLLATERAL_ASSETS) {
console.log(`\n📝 配置 ${asset.name} (${asset.address})`)
console.log(` - 抵押率: ${asset.collateralFactor / 100}%`)
console.log(` - 清算阈值: ${asset.liquidationThreshold / 100}%`)
console.log(` - 清算奖励: ${asset.liquidationBonus / 100}%`)
try {
// 1. 设置抵押品参数
console.log(' → 设置参数...')
const hash1 = await walletClient.writeContract({
address: CONFIGURATOR,
abi: CONFIGURATOR_ABI,
functionName: 'setCollateralConfig',
args: [
asset.address,
asset.collateralFactor,
asset.liquidationThreshold,
asset.liquidationBonus
]
})
console.log(' ✅ 参数设置交易:', hash1)
// 等待确认
await publicClient.waitForTransactionReceipt({ hash: hash1 })
console.log(' ✅ 交易已确认')
// 2. 激活抵押品
console.log(' → 激活抵押品...')
const hash2 = await walletClient.writeContract({
address: CONFIGURATOR,
abi: CONFIGURATOR_ABI,
functionName: 'setCollateralActive',
args: [asset.address, true]
})
console.log(' ✅ 激活交易:', hash2)
// 等待确认
await publicClient.waitForTransactionReceipt({ hash: hash2 })
console.log(' ✅ 交易已确认')
console.log(`${asset.name} 配置完成!`)
} catch (error) {
console.error(` ❌ 配置失败:`, error.message.split('\n')[0])
// 继续处理下一个
continue
}
}
console.log('\n✅ 所有抵押品配置完成!\n')
console.log('现在可以尝试存入抵押品了。')
}
configureCollateral().catch((error) => {
console.error('\n❌ 配置过程出错:', error.message)
process.exit(1)
})

View File

@@ -0,0 +1,107 @@
import { createPublicClient, http, getAddress, decodeErrorResult } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const USER = getAddress('0xa013422A5918CD099C63c8CC35283EACa99a705d')
const YT_A = getAddress('0x97204190B35D9895a7a47aa7BaC61ac08De3cF05')
async function testWithDetails(name, abi, args) {
console.log(`\n=== 测试 ${name} ===`)
try {
const result = await client.readContract({
address: LENDING_PROXY,
abi: [abi],
functionName: name,
args: args
})
console.log('✓ 成功:', result)
return result
} catch (error) {
console.log('✗ 失败')
console.log('错误类型:', error.name)
console.log('错误消息:', error.message.split('\n')[0])
// 尝试解码错误
if (error.data) {
console.log('错误数据:', error.data)
}
// 打印完整堆栈
console.log('\n完整错误:')
console.log(error)
}
}
async function main() {
console.log('诊断 Lending 合约 View 函数\n')
console.log('合约:', LENDING_PROXY)
console.log('用户:', USER)
console.log('抵押品:', YT_A)
// 测试 1: borrowBalanceOf (已知能工作)
await testWithDetails('borrowBalanceOf', {
inputs: [{ name: 'account', type: 'address' }],
name: 'borrowBalanceOf',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}, [USER])
// 测试 2: getUserAccountData
await testWithDetails('getUserAccountData', {
inputs: [{ name: '_user', type: 'address' }],
name: 'getUserAccountData',
outputs: [
{ name: 'totalCollateralValue', type: 'uint256' },
{ name: 'totalBorrowValue', type: 'uint256' },
{ name: 'availableToBorrow', type: 'uint256' },
{ name: 'healthFactor', type: 'uint256' }
],
stateMutability: 'view',
type: 'function'
}, [USER])
// 测试 3: getUserCollateral
await testWithDetails('getUserCollateral', {
inputs: [
{ name: '_user', type: 'address' },
{ name: '_collateralAsset', type: 'address' }
],
name: 'getUserCollateral',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}, [USER, YT_A])
// 测试 4: getCollateralConfig (检查资产是否配置)
await testWithDetails('getCollateralConfig', {
inputs: [{ name: '_asset', type: 'address' }],
name: 'getCollateralConfig',
outputs: [
{ name: 'isActive', type: 'bool' },
{ name: 'decimals', type: 'uint8' },
{ name: 'borrowCollateralFactor', type: 'uint64' },
{ name: 'liquidateCollateralFactor', type: 'uint64' },
{ name: 'liquidationFactor', type: 'uint64' },
{ name: 'supplyCap', type: 'uint128' }
],
stateMutability: 'view',
type: 'function'
}, [YT_A])
// 测试 5: paused (检查是否暂停)
await testWithDetails('paused', {
inputs: [],
name: 'paused',
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'view',
type: 'function'
}, [])
}
main()

View File

@@ -0,0 +1,60 @@
import { createPublicClient, http, getAddress, parseAbiItem } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://sepolia-rollup.arbitrum.io/rpc')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
async function main() {
const latestBlock = await client.getBlockNumber()
console.log('Latest Block:', latestBlock)
const fromBlock = latestBlock - 10000n
console.log(`Checking events from block ${fromBlock} to ${latestBlock}..\n`)
// Check SupplyCollateral
const supplyEvent = parseAbiItem('event SupplyCollateral(address indexed from, address indexed dst, address indexed asset, uint256 amount)')
const supplyLogs = await client.getLogs({
address: LENDING_PROXY,
event: supplyEvent,
fromBlock,
toBlock: latestBlock
})
console.log(`SupplyCollateral Events found: ${supplyLogs.length}`)
// Check Deposit
const depositEvent = parseAbiItem('event Deposit(address indexed user, address indexed collateralAsset, uint256 amount)')
const depositLogs = await client.getLogs({
address: LENDING_PROXY,
event: depositEvent,
fromBlock,
toBlock: latestBlock
})
console.log(`Deposit Events found: ${depositLogs.length}`)
// Check WithdrawCollateral
const withdrawColEvent = parseAbiItem('event WithdrawCollateral(address indexed src, address indexed to, address indexed asset, uint256 amount)')
const withdrawColLogs = await client.getLogs({
address: LENDING_PROXY,
event: withdrawColEvent,
fromBlock,
toBlock: latestBlock
})
console.log(`WithdrawCollateral Events found: ${withdrawColLogs.length}`)
// Check Withdraw
const withdrawEvent = parseAbiItem('event Withdraw(address indexed user, address indexed collateralAsset, uint256 amount)')
const withdrawLogs = await client.getLogs({
address: LENDING_PROXY,
event: withdrawEvent,
fromBlock,
toBlock: latestBlock
})
console.log(`Withdraw Events (custom) found: ${withdrawLogs.length}`)
}
main().catch(console.error)

View File

@@ -0,0 +1,82 @@
import { createPublicClient, http, getAddress, parseAbi, encodeFunctionData } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://sepolia-rollup.arbitrum.io/rpc')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
// A random user address or the deployer to test view functions
const USER = getAddress('0xa013422A5918CD099C63c8CC35283EACa99a705d')
async function main() {
console.log('Checking Lending Proxy View Functions...')
// Check borrowBalanceOf
try {
const data = encodeFunctionData({
abi: parseAbi(['function borrowBalanceOf(address) view returns (uint256)']),
functionName: 'borrowBalanceOf',
args: [USER]
})
const result = await client.call({
to: LENDING_PROXY,
data
})
console.log(`✅ borrowBalanceOf exists. Result: ${result.data}`)
} catch (e) {
console.log(`❌ borrowBalanceOf failed: ${e.message.slice(0, 100)}...`)
}
// Check getBorrowBalance
try {
const data = encodeFunctionData({
abi: parseAbi(['function getBorrowBalance(address) view returns (uint256)']),
functionName: 'getBorrowBalance',
args: [USER]
})
const result = await client.call({
to: LENDING_PROXY,
data
})
console.log(`✅ getBorrowBalance exists. Result: ${result.data}`)
} catch (e) {
console.log(`❌ getBorrowBalance failed: ${e.message.slice(0, 100)}...`)
}
// Check getTotalSupply
try {
const data = encodeFunctionData({
abi: parseAbi(['function getTotalSupply() view returns (uint256)']),
functionName: 'getTotalSupply',
args: []
})
const result = await client.call({
to: LENDING_PROXY,
data
})
console.log(`✅ getTotalSupply exists. Result: ${result.data}`)
} catch (e) {
console.log(`❌ getTotalSupply failed: ${e.message.slice(0, 100)}...`)
}
// Check getTotalLiquidity
try {
const data = encodeFunctionData({
abi: parseAbi(['function getTotalLiquidity() view returns (uint256)']),
functionName: 'getTotalLiquidity',
args: []
})
const result = await client.call({
to: LENDING_PROXY,
data
})
console.log(`✅ getTotalLiquidity exists. Result: ${result.data}`)
} catch (e) {
console.log(`❌ getTotalLiquidity failed: ${e.message.slice(0, 100)}...`)
}
}
main()

View File

@@ -0,0 +1,33 @@
import { decodeAbiParameters, parseAbiParameters } from 'viem';
// 从错误日志中的 data 字段
const txData = '0x5fcbde4700000000000000000000000097204190b35d9895a7a47aa7bac61ac08de3cf050000000000000000000000000000000000000000000000000000000000001d4c000000000000000000000000000000000000000000000000000000000000213400000000000000000000000000000000000000000000000000000000000003e8';
// 函数选择器 (前4字节)
const selector = txData.slice(0, 10);
console.log('函数选择器:', selector);
// 参数数据 (从第10个字符开始)
const paramData = '0x' + txData.slice(10);
try {
// 解码参数
const decoded = decodeAbiParameters(
parseAbiParameters('address, uint256, uint256, uint256'),
paramData
);
console.log('\n解码的参数:');
console.log(' _asset (YT-A地址):', decoded[0]);
console.log(' _collateralFactor:', decoded[1].toString(), '(即', Number(decoded[1]) / 100, '%)');
console.log(' _liquidationThreshold:', decoded[2].toString(), '(即', Number(decoded[2]) / 100, '%)');
console.log(' _liquidationBonus:', decoded[3].toString(), '(即', Number(decoded[3]) / 100, '%)');
console.log('\n✅ 参数解码成功,看起来都是正常的值');
console.log('💡 问题可能在于:');
console.log(' 1. 合约内部的require条件未满足');
console.log(' 2. 可能需要先调用其他初始化函数');
console.log(' 3. 可能Lending合约需要先设置到Configurator中');
} catch (error) {
console.error('解码失败:', error);
}

View File

@@ -0,0 +1,107 @@
import { createPublicClient, http, parseAbi } from 'viem';
import { arbitrumSepolia } from 'viem/chains';
// 合约地址
const CONFIGURATOR_ADDRESS = '0x488409CE9A3Fcd8EbD373dCb7e025cF8AB96fcdc';
const YT_A_ADDRESS = '0x97204190B35D9895a7a47aa7BaC61ac08De3cF05';
const USER_ADDRESS = '0xa013422A5918CD099C63c8CC35283EACa99a705d';
const CONFIGURATOR_ABI = parseAbi([
'function owner() view returns (address)',
'function collateralConfigs(address) view returns (bool isActive, uint256 collateralFactor, uint256 liquidationThreshold, uint256 liquidationBonus)',
'function setCollateralConfig(address _asset, uint256 _collateralFactor, uint256 _liquidationThreshold, uint256 _liquidationBonus)',
'function setCollateralActive(address _asset, bool _isActive)'
]);
const publicClient = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://sepolia-rollup.arbitrum.io/rpc')
});
async function diagnose() {
console.log('🔍 Configurator 诊断工具\n');
try {
// 1. 检查 owner
console.log('1⃣ 检查合约 Owner:');
const owner = await publicClient.readContract({
address: CONFIGURATOR_ADDRESS,
abi: CONFIGURATOR_ABI,
functionName: 'owner'
});
console.log(` Owner: ${owner}`);
console.log(` 你的地址: ${USER_ADDRESS}`);
const isOwner = owner.toLowerCase() === USER_ADDRESS.toLowerCase();
console.log(` 匹配: ${isOwner ? '✅' : '❌'}\n`);
if (!isOwner) {
console.log(' ⚠️ 你不是 owner这就是交易失败的原因。\n');
return;
}
// 2. 检查当前配置
console.log('2⃣ 检查 YT-A 当前配置:');
try {
const config = await publicClient.readContract({
address: CONFIGURATOR_ADDRESS,
abi: CONFIGURATOR_ABI,
functionName: 'collateralConfigs',
args: [YT_A_ADDRESS]
});
console.log(` isActive: ${config[0]}`);
console.log(` collateralFactor: ${config[1]}`);
console.log(` liquidationThreshold: ${config[2]}`);
console.log(` liquidationBonus: ${config[3]}\n`);
} catch (error) {
console.log(` ❌ 读取失败: ${error.message}\n`);
}
// 3. 尝试模拟调用 setCollateralConfig
console.log('3⃣ 模拟调用 setCollateralConfig(YT-A, 7500, 8500, 1000):');
try {
const result = await publicClient.simulateContract({
address: CONFIGURATOR_ADDRESS,
abi: CONFIGURATOR_ABI,
functionName: 'setCollateralConfig',
args: [YT_A_ADDRESS, 7500n, 8500n, 1000n],
account: USER_ADDRESS
});
console.log(' ✅ 模拟成功!交易应该可以执行\n');
console.log(' 模拟结果:', result);
} catch (error) {
console.log(' ❌ 模拟失败!');
console.log(` 错误类型: ${error.name}`);
console.log(` 错误信息: ${error.shortMessage || error.message}`);
if (error.cause) {
console.log(` 底层原因: ${JSON.stringify(error.cause, null, 2)}`);
}
if (error.details) {
console.log(` 详情: ${error.details}`);
}
if (error.metaMessages) {
console.log(` 元信息: ${error.metaMessages.join(', ')}`);
}
console.log('\n');
}
// 4. 检查合约代码
console.log('4⃣ 检查合约代码:');
const bytecode = await publicClient.getBytecode({ address: CONFIGURATOR_ADDRESS });
console.log(` 代码大小: ${bytecode ? bytecode.length : 0} bytes`);
console.log(` 合约已部署: ${bytecode && bytecode.length > 2 ? '✅' : '❌'}\n`);
// 5. 建议
console.log('💡 诊断建议:');
console.log(' 1. 检查合约是否有访问控制(如 Ownable, AccessControl');
console.log(' 2. 参数可能有范围限制(如 collateralFactor 必须 <= 10000');
console.log(' 3. 可能需要先调用其他初始化函数');
console.log(' 4. 查看合约源码了解具体的 require 条件');
console.log('\n📊 在 Arbiscan 查看合约:');
console.log(` https://sepolia.arbiscan.io/address/${CONFIGURATOR_ADDRESS}#code`);
} catch (error) {
console.error('❌ 诊断过程出错:', error);
}
}
diagnose();

View File

@@ -0,0 +1,130 @@
import { createPublicClient, http, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const YT_A = getAddress('0x97204190B35D9895a7a47aa7BaC61ac08De3cF05')
const USER = getAddress('0xa013422A5918CD099C63c8CC35283EACa99a705d')
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const TRANSFER_EVENT = {
type: 'event',
name: 'Transfer',
inputs: [
{ indexed: true, name: 'from', type: 'address' },
{ indexed: true, name: 'to', type: 'address' },
{ indexed: false, name: 'value', type: 'uint256' }
]
}
async function main() {
console.log('\n追踪丢失的 YT-A 代币...\n')
console.log('你的地址:', USER)
console.log('当前余额: 10 YT-A')
console.log('之前余额: 400 YT-A')
console.log('丢失数量: 390 YT-A\n')
const latestBlock = await client.getBlockNumber()
console.log('最新区块:', latestBlock)
console.log('查询范围: 最近 10000 个区块\n')
try {
// 查找所有从用户地址发出的转账
console.log('=== 查找所有转出记录 ===\n')
const logsOut = await client.getLogs({
address: YT_A,
event: TRANSFER_EVENT,
args: { from: USER },
fromBlock: latestBlock - 10000n,
toBlock: latestBlock
})
if (logsOut.length === 0) {
console.log('✗ 未找到任何转出记录(最近 10000 个区块)\n')
} else {
console.log(`✓ 找到 ${logsOut.length} 笔转出记录:\n`)
let totalOut = 0n
logsOut.forEach((log, i) => {
const { from, to, value } = log.args
const isToLending = to.toLowerCase() === LENDING_PROXY.toLowerCase()
totalOut += value
console.log(`${i + 1}. 区块 ${log.blockNumber}`)
console.log(` 交易: ${log.transactionHash}`)
console.log(` To: ${to} ${isToLending ? '<- Lending 合约' : ''}`)
console.log(` 数量: ${Number(value) / 1e18} YT-A`)
console.log()
})
console.log(`总转出: ${Number(totalOut) / 1e18} YT-A\n`)
}
// 查找所有转入用户地址的转账
console.log('=== 查找所有转入记录 ===\n')
const logsIn = await client.getLogs({
address: YT_A,
event: TRANSFER_EVENT,
args: { to: USER },
fromBlock: latestBlock - 10000n,
toBlock: latestBlock
})
if (logsIn.length === 0) {
console.log('✗ 未找到任何转入记录\n')
} else {
console.log(`✓ 找到 ${logsIn.length} 笔转入记录:\n`)
let totalIn = 0n
logsIn.forEach((log, i) => {
const { from, to, value } = log.args
totalIn += value
console.log(`${i + 1}. 区块 ${log.blockNumber}`)
console.log(` 交易: ${log.transactionHash}`)
console.log(` From: ${from}`)
console.log(` 数量: ${Number(value) / 1e18} YT-A`)
console.log()
})
console.log(`总转入: ${Number(totalIn) / 1e18} YT-A\n`)
}
// 计算净流出
if (logsOut.length > 0 || logsIn.length > 0) {
const totalOut = logsOut.reduce((sum, log) => sum + log.args.value, 0n)
const totalIn = logsIn.reduce((sum, log) => sum + log.args.value, 0n)
const netFlow = Number(totalIn - totalOut) / 1e18
console.log('=== 汇总 ===')
console.log(`总转入: ${Number(totalIn) / 1e18} YT-A`)
console.log(`总转出: ${Number(totalOut) / 1e18} YT-A`)
console.log(`净变化: ${netFlow > 0 ? '+' : ''}${netFlow} YT-A`)
console.log(`当前余额: 10 YT-A\n`)
// 检查代币是否在 Lending 合约中
console.log('=== 检查 Lending 合约余额 ===')
const lendingBalance = await client.readContract({
address: YT_A,
abi: [{
inputs: [{ name: 'account', type: 'address' }],
name: 'balanceOf',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}],
functionName: 'balanceOf',
args: [LENDING_PROXY]
})
console.log(`Lending 合约持有的 YT-A: ${Number(lendingBalance) / 1e18}\n`)
}
} catch (error) {
console.error('查询失败:', error.message)
}
}
main()

View File

@@ -0,0 +1,41 @@
import { createPublicClient, http } from 'viem';
import { arbitrumSepolia } from 'viem/chains';
const CONFIGURATOR_ADDRESS = '0x488409CE9A3Fcd8EbD373dCb7e025cF8AB96fcdc';
// ERC-1967 标准存储槽
const IMPLEMENTATION_SLOT = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc';
const publicClient = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://sepolia-rollup.arbitrum.io/rpc')
});
async function getImplementation() {
console.log('🔍 查找代理合约的实现地址\n');
try {
// 读取 ERC-1967 实现槽
const slot = await publicClient.getStorageAt({
address: CONFIGURATOR_ADDRESS,
slot: IMPLEMENTATION_SLOT
});
if (slot && slot !== '0x' + '0'.repeat(64)) {
// 从存储槽中提取地址去掉前面的0
const implementationAddress = '0x' + slot.slice(-40);
console.log('✅ 找到实现合约:');
console.log(` 代理合约 (Configurator): ${CONFIGURATOR_ADDRESS}`);
console.log(` 实现合约 (Implementation): ${implementationAddress}`);
console.log(`\n📊 查看实现合约源码:`);
console.log(` https://sepolia.arbiscan.io/address/${implementationAddress}#code`);
console.log(`\n💡 提示: 需要使用实现合约的 ABI但调用代理合约的地址`);
} else {
console.log('❌ 未找到实现地址');
}
} catch (error) {
console.error('❌ 错误:', error.message);
}
}
getImplementation();

View File

@@ -0,0 +1,60 @@
/**
* 初始化 Lending 合约基础配置
*
* ⚠️ 需要使用 Configurator Owner 钱包执行
* Owner: 0xa013422A5918CD099C63c8CC35283EACa99a705d
*
* 使用方法:
* 1. 确保钱包连接到 Arbitrum Sepolia
* 2. 在管理员面板中手动调用 Configurator 的配置函数
*/
console.log(`
╔════════════════════════════════════════════════════════════╗
║ Lending 合约初始化配置说明 ║
╚════════════════════════════════════════════════════════════╝
❌ 问题: Lending 合约基础配置未初始化
- USDC 地址: 0x0000...0000 ❌
- 价格源地址: 0x0000...0000 ❌
✅ 解决方案: 需要调用 Configurator 初始化函数
📝 合约信息:
- Configurator: 0x488409CE9A3Fcd8EbD373dCb7e025cF8AB96fcdc
- Lending Proxy: 0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D
- Owner: 0xa013422A5918CD099C63c8CC35283EACa99a705d
🔧 需要设置的参数:
1. baseToken (USDC): 0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d
2. lendingPriceSource: 0xE82c7cB9CfA42D6eb7e443956b78f8290249c316
3. 利率参数 (borrowKink, supplyKink 等)
4. baseBorrowMin, targetReserves 等
⚠️ 这需要合约开发者或管理员操作!
💡 可能的原因:
1. 合约刚部署,还没有初始化
2. 初始化函数调用失败
3. 配置被重置了
📞 建议:
联系合约部署者 (0xa013422A5918CD099C63c8CC35283EACa99a705d)
使用 Configurator 合约的初始化函数设置基础配置
`)
// 显示需要调用的函数签名
console.log(`
🔍 需要调用的 Configurator 函数示例:
function setConfiguration(
address lendingProxy,
Configuration memory configuration
) external onlyOwner
其中 Configuration 结构包括:
- baseToken: 0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d (USDC)
- lendingPriceSource: 0xE82c7cB9CfA42D6eb7e443956b78f8290249c316
- 利率参数(根据文档配置)
- assetConfigs: 已经配置了 YT-A ✅
`)

View File

@@ -0,0 +1,134 @@
import { createPublicClient, http, encodeFunctionData, formatUnits } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http()
})
const LENDING_PROXY = '0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D'
const YT_A = '0x97204190B35D9895a7a47aa7BaC61ac08De3cF05'
const USER = '0xa013422a5918cd099c63c8cc35283eaca99a705d'
const CONFIGURATOR = '0x488409CE9A3Fcd8EbD373dCb7e025cF8AB96fcdc'
const LENDING_ABI = [
{
inputs: [
{ internalType: 'address', name: '_collateralAsset', type: 'address' },
{ internalType: 'uint256', name: '_amount', type: 'uint256' }
],
name: 'deposit',
outputs: [],
stateMutability: 'nonpayable',
type: 'function'
}
]
const CONFIGURATOR_ABI = [
{
inputs: [{ internalType: 'address', name: '_asset', type: 'address' }],
name: 'getCollateralConfig',
outputs: [
{ internalType: 'bool', name: 'isActive', type: 'bool' },
{ internalType: 'uint256', name: 'collateralFactor', type: 'uint256' },
{ internalType: 'uint256', name: 'liquidationThreshold', type: 'uint256' },
{ internalType: 'uint256', name: 'liquidationBonus', type: 'uint256' }
],
stateMutability: 'view',
type: 'function'
},
{
inputs: [{ internalType: 'address', name: '', type: 'address' }],
name: 'collateralConfigs',
outputs: [
{ internalType: 'bool', name: 'isActive', type: 'bool' },
{ internalType: 'uint256', name: 'collateralFactor', type: 'uint256' },
{ internalType: 'uint256', name: 'liquidationThreshold', type: 'uint256' },
{ internalType: 'uint256', name: 'liquidationBonus', type: 'uint256' }
],
stateMutability: 'view',
type: 'function'
}
]
async function simulateDeposit() {
console.log('🔍 模拟存款操作...\n')
console.log('合约地址:', LENDING_PROXY)
console.log('抵押品地址:', YT_A)
console.log('用户地址:', USER)
console.log('存款金额: 10 YT-A\n')
// 1. 检查 Configurator 中的配置
console.log('=== 检查 Configurator 配置 ===')
try {
const config = await client.readContract({
address: CONFIGURATOR,
abi: CONFIGURATOR_ABI,
functionName: 'getCollateralConfig',
args: [YT_A]
})
console.log('✅ 通过 getCollateralConfig 读取:')
console.log(' - 是否激活:', config[0])
console.log(' - 抵押率:', Number(config[1]) / 100 + '%')
console.log(' - 清算阈值:', Number(config[2]) / 100 + '%')
console.log(' - 清算奖励:', Number(config[3]) / 100 + '%')
} catch (error) {
console.log('❌ getCollateralConfig 失败:', error.message.split('\n')[0])
// 尝试直接读取mapping
try {
const config = await client.readContract({
address: CONFIGURATOR,
abi: CONFIGURATOR_ABI,
functionName: 'collateralConfigs',
args: [YT_A]
})
console.log('✅ 通过 collateralConfigs mapping 读取:')
console.log(' - 是否激活:', config[0])
console.log(' - 抵押率:', Number(config[1]) / 100 + '%')
console.log(' - 清算阈值:', Number(config[2]) / 100 + '%')
console.log(' - 清算奖励:', Number(config[3]) / 100 + '%')
if (!config[0]) {
console.log('\n⚠ 警告: 抵押品未激活!这就是存款失败的原因。')
}
} catch (error2) {
console.log('❌ collateralConfigs mapping 也失败:', error2.message.split('\n')[0])
}
}
// 2. 模拟调用 deposit
console.log('\n=== 模拟存款调用 ===')
const depositAmount = 10n * 10n ** 18n // 10 YT-A
try {
await client.simulateContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'deposit',
args: [YT_A, depositAmount],
account: USER
})
console.log('✅ 模拟存款成功!交易应该可以执行。')
} catch (error) {
console.log('❌ 模拟存款失败')
console.log('\n详细错误信息:')
console.log(error.message)
// 尝试解析错误原因
if (error.message.includes('Collateral not active')) {
console.log('\n💡 原因: 抵押品未激活')
console.log(' 需要管理员通过 Configurator 激活此抵押品')
} else if (error.message.includes('insufficient')) {
console.log('\n💡 原因: 余额或授权不足')
} else if (error.message.includes('paused')) {
console.log('\n💡 原因: 合约已暂停')
} else {
console.log('\n💡 这可能是由于抵押品未在借贷合约中配置')
}
}
console.log('\n=== 诊断完成 ===\n')
}
simulateDeposit().catch(console.error)

View File

@@ -0,0 +1,117 @@
import { createPublicClient, http, parseEther } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = '0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D'
const YT_A = '0x97204190B35D9895a7a47aa7BaC61ac08De3cF05'
const USDC = '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d'
const USER = '0xa013422A5918CD099C63c8CC35283EACa99a705d'
const LENDING_ABI = [
{
inputs: [
{ internalType: 'address', name: 'asset', type: 'address' },
{ internalType: 'uint256', name: 'amount', type: 'uint256' }
],
name: 'supplyCollateral',
outputs: [],
stateMutability: 'nonpayable',
type: 'function'
},
{
inputs: [
{ internalType: 'address', name: 'asset', type: 'address' }
],
name: 'getAssetInfo',
outputs: [
{ internalType: 'uint8', name: 'offset', type: 'uint8' },
{ internalType: 'address', name: 'priceFeed', type: 'address' },
{ internalType: 'uint64', name: 'scale', type: 'uint64' },
{ internalType: 'uint64', name: 'borrowCollateralFactor', type: 'uint64' },
{ internalType: 'uint64', name: 'liquidateCollateralFactor', type: 'uint64' },
{ internalType: 'uint64', name: 'liquidationFactor', type: 'uint64' },
{ internalType: 'uint128', name: 'supplyCap', type: 'uint128' }
],
stateMutability: 'view',
type: 'function'
},
{
inputs: [],
name: 'baseToken',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function'
}
]
async function main() {
console.log('\n模拟 supplyCollateral 调用...\n')
try {
// 先检查 Lending 合约能否读取到配置
console.log('=== 检查 Lending 合约状态 ===')
const baseToken = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'baseToken'
})
console.log('Lending.baseToken:', baseToken)
console.log('是否为 USDC:', baseToken.toLowerCase() === USDC.toLowerCase() ? '✓' : '✗')
// 检查资产信息
try {
const assetInfo = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getAssetInfo',
args: [YT_A]
})
console.log('\nLending.getAssetInfo(YT-A):')
console.log(' borrowCollateralFactor:', assetInfo[3].toString())
console.log(' liquidateCollateralFactor:', assetInfo[4].toString())
console.log(' supplyCap:', assetInfo[6].toString())
} catch (e) {
console.log('\n✗ getAssetInfo 失败:', e.shortMessage || e.message)
}
// 尝试模拟调用
console.log('\n=== 模拟 supplyCollateral 调用 ===')
const amount = parseEther('10')
try {
await client.simulateContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'supplyCollateral',
args: [YT_A, amount],
account: USER
})
console.log('✓ 模拟成功!理论上应该可以执行')
} catch (error) {
console.log('✗ 模拟失败:')
console.log(' 错误类型:', error.name)
console.log(' 错误信息:', error.shortMessage || error.message)
if (error.cause) {
console.log('\n详细错误:')
console.log(' ', error.cause.message || error.cause)
}
if (error.data) {
console.log('\n错误数据:', error.data)
}
}
} catch (error) {
console.error('\n意外错误:', error.message)
}
}
main()

View File

@@ -0,0 +1,118 @@
/**
* 单账户买入测试脚本
* 使用主账户直接执行买入,不需要分发 ETH
*/
import {
createPublicClient,
createWalletClient,
http,
parseUnits,
formatUnits,
type Address,
type Hex
} from 'viem'
import { arbitrumSepolia } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
const CONTRACTS = {
WUSD: '0x6d2bf81a631dFE19B2f348aE92cF6Ef41ca2DF98' as Address,
VAULT_YT_A: '0x0cA35994F033685E7a57ef9bc5d00dd3cf927330' as Address,
}
const MAIN_PRIVATE_KEY = '0xa082a7037105ebd606bee80906687e400d89899bbb6ba0273a61528c2f5fab89' as Hex
const WUSD_ABI = [
{ inputs: [{ name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }], name: 'approve', outputs: [{ type: 'bool' }], stateMutability: 'nonpayable', type: 'function' },
{ inputs: [{ name: 'account', type: 'address' }], name: 'balanceOf', outputs: [{ type: 'uint256' }], stateMutability: 'view', type: 'function' },
] as const
const VAULT_ABI = [
{ inputs: [{ name: '_wusdAmount', type: 'uint256' }], name: 'depositYT', outputs: [], stateMutability: 'nonpayable', type: 'function' },
{ inputs: [{ name: '_wusdAmount', type: 'uint256' }], name: 'previewBuy', outputs: [{ type: 'uint256' }], stateMutability: 'view', type: 'function' },
{ inputs: [{ name: 'account', type: 'address' }], name: 'balanceOf', outputs: [{ type: 'uint256' }], stateMutability: 'view', type: 'function' },
{ inputs: [], name: 'symbol', outputs: [{ type: 'string' }], stateMutability: 'view', type: 'function' },
] as const
async function main() {
console.log('🚀 单账户买入测试\n')
const publicClient = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://sepolia-rollup.arbitrum.io/rpc'),
})
const account = privateKeyToAccount(MAIN_PRIVATE_KEY)
const walletClient = createWalletClient({
account,
chain: arbitrumSepolia,
transport: http('https://sepolia-rollup.arbitrum.io/rpc'),
})
console.log(`📍 账户: ${account.address}`)
// 检查余额
const ethBalance = await publicClient.getBalance({ address: account.address })
const wusdBalance = await publicClient.readContract({
address: CONTRACTS.WUSD,
abi: WUSD_ABI,
functionName: 'balanceOf',
args: [account.address],
})
console.log(`⛽ ETH 余额: ${formatUnits(ethBalance, 18)} ETH`)
console.log(`💰 WUSD 余额: ${formatUnits(wusdBalance, 18)} WUSD`)
// 获取金库 symbol
const vaultSymbol = await publicClient.readContract({
address: CONTRACTS.VAULT_YT_A,
abi: VAULT_ABI,
functionName: 'symbol',
})
console.log(`🏦 目标金库: ${vaultSymbol}`)
// 买入金额
const buyAmount = parseUnits('50', 18) // 50 WUSD
console.log(`\n📝 买入金额: ${formatUnits(buyAmount, 18)} WUSD`)
// 预览
const previewYT = await publicClient.readContract({
address: CONTRACTS.VAULT_YT_A,
abi: VAULT_ABI,
functionName: 'previewBuy',
args: [buyAmount],
})
console.log(`📊 预计获得: ${formatUnits(previewYT, 18)} ${vaultSymbol}`)
// 执行买入
console.log('\n1⃣ 授权 WUSD...')
const approveHash = await walletClient.writeContract({
address: CONTRACTS.WUSD,
abi: WUSD_ABI,
functionName: 'approve',
args: [CONTRACTS.VAULT_YT_A, buyAmount],
})
await publicClient.waitForTransactionReceipt({ hash: approveHash })
console.log(` ✓ 授权成功: ${approveHash}`)
console.log('\n2⃣ 执行买入...')
const buyHash = await walletClient.writeContract({
address: CONTRACTS.VAULT_YT_A,
abi: VAULT_ABI,
functionName: 'depositYT',
args: [buyAmount],
})
await publicClient.waitForTransactionReceipt({ hash: buyHash })
console.log(` ✓ 买入成功: ${buyHash}`)
// 检查结果
const ytBalance = await publicClient.readContract({
address: CONTRACTS.VAULT_YT_A,
abi: VAULT_ABI,
functionName: 'balanceOf',
args: [account.address],
})
console.log(`\n✅ ${vaultSymbol} 余额: ${formatUnits(ytBalance, 18)}`)
}
main().catch(console.error)

View File

@@ -0,0 +1,182 @@
import { createPublicClient, http, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const LENDING_PRICE_FEED = getAddress('0xE82c7cB9CfA42D6eb7e443956b78f8290249c316')
const CONFIGURATOR = getAddress('0x488409CE9A3Fcd8EbD373dCb7e025cF8AB96fcdc')
const USER = getAddress('0xa013422A5918CD099C63c8CC35283EACa99a705d')
const YT_A = getAddress('0x97204190B35D9895a7a47aa7BaC61ac08De3cF05')
const LENDING_ABI = [
{
inputs: [
{ internalType: 'address', name: 'account', type: 'address' },
{ internalType: 'address', name: 'asset', type: 'address' }
],
name: 'getCollateral',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
name: 'borrowBalanceOf',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}
]
const PRICE_FEED_ABI = [
{
inputs: [{ internalType: 'address', name: 'asset', type: 'address' }],
name: 'getPrice',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}
]
const CONFIGURATOR_ABI = [
{
inputs: [{ internalType: 'address', name: 'lendingProxy', type: 'address' }],
name: 'getConfiguration',
outputs: [
{
components: [
{ internalType: 'address', name: 'baseToken', type: 'address' },
{ internalType: 'address', name: 'lendingPriceSource', type: 'address' },
{ internalType: 'uint64', name: 'supplyKink', type: 'uint64' },
{ internalType: 'uint64', name: 'supplyPerYearInterestRateSlopeLow', type: 'uint64' },
{ internalType: 'uint64', name: 'supplyPerYearInterestRateSlopeHigh', type: 'uint64' },
{ internalType: 'uint64', name: 'supplyPerYearInterestRateBase', type: 'uint64' },
{ internalType: 'uint64', name: 'borrowKink', type: 'uint64' },
{ internalType: 'uint64', name: 'borrowPerYearInterestRateSlopeLow', type: 'uint64' },
{ internalType: 'uint64', name: 'borrowPerYearInterestRateSlopeHigh', type: 'uint64' },
{ internalType: 'uint64', name: 'borrowPerYearInterestRateBase', type: 'uint64' },
{ internalType: 'uint64', name: 'storeFrontPriceFactor', type: 'uint64' },
{ internalType: 'uint64', name: 'trackingIndexScale', type: 'uint64' },
{ internalType: 'uint104', name: 'baseBorrowMin', type: 'uint104' },
{ internalType: 'uint104', name: 'targetReserves', type: 'uint104' },
{
components: [
{ internalType: 'address', name: 'asset', type: 'address' },
{ internalType: 'uint8', name: 'decimals', type: 'uint8' },
{ internalType: 'uint64', name: 'borrowCollateralFactor', type: 'uint64' },
{ internalType: 'uint64', name: 'liquidateCollateralFactor', type: 'uint64' },
{ internalType: 'uint64', name: 'liquidationFactor', type: 'uint64' },
{ internalType: 'uint128', name: 'supplyCap', type: 'uint128' }
],
internalType: 'struct LendingConfiguration.AssetConfig[]',
name: 'assetConfigs',
type: 'tuple[]'
}
],
internalType: 'struct LendingConfiguration.Configuration',
name: '',
type: 'tuple'
}
],
stateMutability: 'view',
type: 'function'
}
]
async function main() {
console.log('\n=== 测试抵押价值计算逻辑 ===\n')
console.log('用户:', USER)
// 1. 获取抵押品余额
console.log('\n--- 1. 获取抵押品余额 ---')
const collateral = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getCollateral',
args: [USER, YT_A]
})
console.log('YT-A 抵押品:', Number(collateral) / 1e18, 'YT-A')
console.log('原始值:', collateral.toString())
// 2. 获取价格
console.log('\n--- 2. 获取 YT-A 价格 ---')
const price = await client.readContract({
address: LENDING_PRICE_FEED,
abi: PRICE_FEED_ABI,
functionName: 'getPrice',
args: [YT_A]
})
console.log('YT-A 价格Compound V3 格式):', price.toString())
console.log(' = 1e30 规模')
// 3. 获取配置
console.log('\n--- 3. 获取配置 ---')
const config = await client.readContract({
address: CONFIGURATOR,
abi: CONFIGURATOR_ABI,
functionName: 'getConfiguration',
args: [LENDING_PROXY]
})
const ytAConfig = config.assetConfigs.find(
cfg => cfg.asset.toLowerCase() === YT_A.toLowerCase()
)
if (ytAConfig) {
console.log('YT-A 配置:')
console.log(' 借款抵押率 (borrowCollateralFactor):', ytAConfig.borrowCollateralFactor.toString())
console.log(' = ', Number(ytAConfig.borrowCollateralFactor) / 1e18 * 100, '%')
console.log(' 清算阈值 (liquidateCollateralFactor):', ytAConfig.liquidateCollateralFactor.toString())
console.log(' = ', Number(ytAConfig.liquidateCollateralFactor) / 1e18 * 100, '%')
}
// 4. 获取借款余额
console.log('\n--- 4. 获取借款余额 ---')
const borrowBalance = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'borrowBalanceOf',
args: [USER]
})
console.log('借款余额:', Number(borrowBalance) / 1e6, 'USDC')
// 5. 计算总抵押价值
console.log('\n--- 5. 计算总抵押价值 ---')
// 价值USDC 6位精度= (balance * price) / 1e42
const valueInUSD = (collateral * price) / BigInt(10 ** 42)
console.log('总抵押价值:', Number(valueInUSD) / 1e6, 'USDC')
console.log('原始值 (6位精度):', valueInUSD.toString())
// 6. 计算可借额度
console.log('\n--- 6. 计算可借额度 ---')
if (ytAConfig) {
const borrowCollateralFactor = BigInt(ytAConfig.borrowCollateralFactor)
const maxBorrow = (valueInUSD * borrowCollateralFactor) / BigInt(10 ** 18)
const availableToBorrow = maxBorrow > borrowBalance ? maxBorrow - borrowBalance : 0n
console.log('最大可借:', Number(maxBorrow) / 1e6, 'USDC')
console.log('已借:', Number(borrowBalance) / 1e6, 'USDC')
console.log('剩余可借:', Number(availableToBorrow) / 1e6, 'USDC')
}
// 7. 计算健康因子
console.log('\n--- 7. 计算健康因子 ---')
if (borrowBalance > 0n && ytAConfig) {
const liquidationFactor = BigInt(ytAConfig.liquidateCollateralFactor)
const liquidationThreshold = (valueInUSD * liquidationFactor) / BigInt(10 ** 18)
// healthFactor 以 10000 为基数
const healthFactor = (liquidationThreshold * BigInt(10000)) / borrowBalance
console.log('清算阈值价值:', Number(liquidationThreshold) / 1e6, 'USDC')
console.log('健康因子 (10000=100%):', Number(healthFactor))
console.log(' = ', Number(healthFactor) / 100, '%')
} else if (borrowBalance === 0n) {
console.log('无借款,健康因子: ∞(无限大,非常安全)')
}
console.log('\n=== 计算完成 ===\n')
}
main()

View File

@@ -0,0 +1,111 @@
import { createPublicClient, http, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const USER = getAddress('0xa013422A5918CD099C63c8CC35283EACa99a705d')
const YT_A = getAddress('0x97204190B35D9895a7a47aa7BaC61ac08De3cF05')
async function testFunction(name, abi, args) {
try {
const result = await client.readContract({
address: LENDING_PROXY,
abi: [abi],
functionName: name,
args: args
})
console.log(`${name} 成功:`, result)
return { success: true, result }
} catch (error) {
console.log(`${name} 失败:`, error.message.split('\n')[0])
return { success: false, error: error.message }
}
}
async function main() {
console.log('\n测试不同的抵押品查询函数...\n')
console.log('合约:', LENDING_PROXY)
console.log('用户:', USER)
console.log('抵押品:', YT_A, '\n')
// 测试 1: userCollateral (Compound V3 标准)
console.log('--- 测试 1: userCollateral(address, address) ---')
await testFunction('userCollateral', {
inputs: [
{ name: 'account', type: 'address' },
{ name: 'asset', type: 'address' }
],
name: 'userCollateral',
outputs: [
{ name: 'balance', type: 'uint128' },
{ name: '_reserved', type: 'uint128' }
],
stateMutability: 'view',
type: 'function'
}, [USER, YT_A])
// 测试 2: getUserCollateral
console.log('\n--- 测试 2: getUserCollateral(address, address) ---')
await testFunction('getUserCollateral', {
inputs: [
{ name: '_user', type: 'address' },
{ name: '_collateralAsset', type: 'address' }
],
name: 'getUserCollateral',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}, [USER, YT_A])
// 测试 3: collateralBalanceOf
console.log('\n--- 测试 3: collateralBalanceOf(address, address) ---')
await testFunction('collateralBalanceOf', {
inputs: [
{ name: 'account', type: 'address' },
{ name: 'asset', type: 'address' }
],
name: 'collateralBalanceOf',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}, [USER, YT_A])
// 测试 4: getUserAccountData
console.log('\n--- 测试 4: getUserAccountData(address) ---')
const result = await testFunction('getUserAccountData', {
inputs: [{ name: '_user', type: 'address' }],
name: 'getUserAccountData',
outputs: [
{ name: 'totalCollateralValue', type: 'uint256' },
{ name: 'totalBorrowValue', type: 'uint256' },
{ name: 'availableToBorrow', type: 'uint256' },
{ name: 'healthFactor', type: 'uint256' }
],
stateMutability: 'view',
type: 'function'
}, [USER])
if (result.success) {
console.log('\n详细结果:')
console.log(' totalCollateralValue:', result.result[0].toString())
console.log(' totalBorrowValue:', result.result[1].toString())
console.log(' availableToBorrow:', result.result[2].toString())
console.log(' healthFactor:', result.result[3].toString())
}
// 测试 5: borrowBalanceOf (检查借款)
console.log('\n--- 测试 5: borrowBalanceOf(address) ---')
await testFunction('borrowBalanceOf', {
inputs: [{ name: 'account', type: 'address' }],
name: 'borrowBalanceOf',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}, [USER])
}
main()

View File

@@ -0,0 +1,94 @@
import { createPublicClient, http } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const CONFIGURATOR = '0x488409CE9A3Fcd8EbD373dCb7e025cF8AB96fcdc'
const LENDING_PROXY = '0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D'
const YT_A = '0x97204190B35D9895a7a47aa7BaC61ac08De3cF05'
const CONFIGURATOR_ABI = [
{
inputs: [
{ internalType: 'address', name: 'lendingProxy', type: 'address' }
],
name: 'getConfiguration',
outputs: [
{
components: [
{ internalType: 'address', name: 'baseToken', type: 'address' },
{ internalType: 'address', name: 'lendingPriceSource', type: 'address' },
{ internalType: 'uint64', name: 'supplyKink', type: 'uint64' },
{ internalType: 'uint64', name: 'supplyPerYearInterestRateSlopeLow', type: 'uint64' },
{ internalType: 'uint64', name: 'supplyPerYearInterestRateSlopeHigh', type: 'uint64' },
{ internalType: 'uint64', name: 'supplyPerYearInterestRateBase', type: 'uint64' },
{ internalType: 'uint64', name: 'borrowKink', type: 'uint64' },
{ internalType: 'uint64', name: 'borrowPerYearInterestRateSlopeLow', type: 'uint64' },
{ internalType: 'uint64', name: 'borrowPerYearInterestRateSlopeHigh', type: 'uint64' },
{ internalType: 'uint64', name: 'borrowPerYearInterestRateBase', type: 'uint64' },
{ internalType: 'uint64', name: 'storeFrontPriceFactor', type: 'uint64' },
{ internalType: 'uint64', name: 'trackingIndexScale', type: 'uint64' },
{ internalType: 'uint104', name: 'baseBorrowMin', type: 'uint104' },
{ internalType: 'uint104', name: 'targetReserves', type: 'uint104' },
{
components: [
{ internalType: 'address', name: 'asset', type: 'address' },
{ internalType: 'uint8', name: 'decimals', type: 'uint8' },
{ internalType: 'uint64', name: 'borrowCollateralFactor', type: 'uint64' },
{ internalType: 'uint64', name: 'liquidateCollateralFactor', type: 'uint64' },
{ internalType: 'uint64', name: 'liquidationFactor', type: 'uint64' },
{ internalType: 'uint128', name: 'supplyCap', type: 'uint128' }
],
internalType: 'struct LendingConfiguration.AssetConfig[]',
name: 'assetConfigs',
type: 'tuple[]'
}
],
internalType: 'struct LendingConfiguration.Configuration',
name: '',
type: 'tuple'
}
],
stateMutability: 'view',
type: 'function'
}
]
async function main() {
console.log('🔍 测试从 Configurator 读取配置...\n')
try {
const config = await client.readContract({
address: CONFIGURATOR,
abi: CONFIGURATOR_ABI,
functionName: 'getConfiguration',
args: [LENDING_PROXY]
})
console.log('✅ 成功读取配置!\n')
console.log('基础代币 (USDC):', config.baseToken)
console.log('价格源:', config.lendingPriceSource)
console.log('\n抵押品配置数量:', config.assetConfigs.length)
config.assetConfigs.forEach((asset, index) => {
console.log(`\n${index + 1}. 抵押品:`, asset.asset)
console.log(' 精度:', asset.decimals)
console.log(' 借款抵押率:', Number(asset.borrowCollateralFactor) / 1e16, '%')
console.log(' 清算抵押率:', Number(asset.liquidateCollateralFactor) / 1e16, '%')
console.log(' 清算奖励:', Number(asset.liquidationFactor) / 1e16, '%')
console.log(' 供应上限:', Number(asset.supplyCap) / 1e18)
if (asset.asset.toLowerCase() === YT_A.toLowerCase()) {
console.log(' ✅ 这是 YT-A')
}
})
} catch (error) {
console.error('❌ 读取配置失败:', error.message)
}
}
main()

View File

@@ -0,0 +1,134 @@
import { createPublicClient, http, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const USER = getAddress('0xa013422A5918CD099C63c8CC35283EACa99a705d')
const YT_A = getAddress('0x97204190B35D9895a7a47aa7BaC61ac08De3cF05')
async function main() {
console.log('\n测试合约文档中的正确函数名\n')
console.log('合约:', LENDING_PROXY)
console.log('用户:', USER)
console.log('抵押品:', YT_A)
console.log()
// 1. 测试 getCollateral正确的函数名
console.log('=== 1. getCollateral(account, asset) ===')
try {
const collateral = await client.readContract({
address: LENDING_PROXY,
abi: [{
inputs: [
{ internalType: 'address', name: 'account', type: 'address' },
{ internalType: 'address', name: 'asset', type: 'address' }
],
name: 'getCollateral',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}],
functionName: 'getCollateral',
args: [USER, YT_A]
})
console.log('✓ 成功!')
console.log(' 抵押品余额:', Number(collateral) / 1e18, 'YT-A')
console.log(' 原始值:', collateral.toString())
} catch (error) {
console.log('✗ 失败:', error.message.split('\n')[0])
}
// 2. 测试 getBalance用户USDC余额
console.log('\n=== 2. getBalance(account) ===')
try {
const balance = await client.readContract({
address: LENDING_PROXY,
abi: [{
inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
name: 'getBalance',
outputs: [{ internalType: 'int256', name: '', type: 'int256' }],
stateMutability: 'view',
type: 'function'
}],
functionName: 'getBalance',
args: [USER]
})
console.log('✓ 成功!')
console.log(' USDC 余额:', Number(balance) / 1e6, 'USDC')
console.log(' 原始值:', balance.toString())
} catch (error) {
console.log('✗ 失败:', error.message.split('\n')[0])
}
// 3. 测试 borrowBalanceOf
console.log('\n=== 3. borrowBalanceOf(account) ===')
try {
const borrowBalance = await client.readContract({
address: LENDING_PROXY,
abi: [{
inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
name: 'borrowBalanceOf',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}],
functionName: 'borrowBalanceOf',
args: [USER]
})
console.log('✓ 成功!')
console.log(' 借款余额:', Number(borrowBalance) / 1e6, 'USDC')
} catch (error) {
console.log('✗ 失败:', error.message.split('\n')[0])
}
// 4. 测试系统函数
console.log('\n=== 4. getTotalBorrow() ===')
try {
const totalBorrow = await client.readContract({
address: LENDING_PROXY,
abi: [{
inputs: [],
name: 'getTotalBorrow',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}],
functionName: 'getTotalBorrow'
})
console.log('✓ 成功!')
console.log(' 系统总借款:', Number(totalBorrow) / 1e6, 'USDC')
} catch (error) {
console.log('✗ 失败:', error.message.split('\n')[0])
}
console.log('\n=== 5. getTotalSupply() ===')
try {
const totalSupply = await client.readContract({
address: LENDING_PROXY,
abi: [{
inputs: [],
name: 'getTotalSupply',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}],
functionName: 'getTotalSupply'
})
console.log('✓ 成功!')
console.log(' 系统总供应:', Number(totalSupply) / 1e6, 'USDC')
} catch (error) {
console.log('✗ 失败:', error.message.split('\n')[0])
}
console.log('\n=== 结论 ===')
console.log('如果上面的函数都能工作,说明:')
console.log('1. 合约使用的是文档中的函数名getCollateral 等)')
console.log('2. 前端 ABI 需要更新为文档中的正确函数名')
console.log('3. getUserCollateral 和 getUserAccountData 不存在于合约中\n')
}
main()

View File

@@ -0,0 +1,67 @@
import { createPublicClient, createWalletClient, http, parseEther } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
const publicClient = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = '0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D'
const YT_A = '0x97204190B35D9895a7a47aa7BaC61ac08De3cF05'
const LENDING_ABI = [
{
inputs: [
{ internalType: 'address', name: 'asset', type: 'address' },
{ internalType: 'uint256', name: 'amount', type: 'uint256' }
],
name: 'supplyCollateral',
outputs: [],
stateMutability: 'nonpayable',
type: 'function'
}
]
async function main() {
console.log('\n尝试捕获详细错误...\n')
try {
// 尝试估算 gas
const gasEstimate = await publicClient.estimateContractGas({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'supplyCollateral',
args: [YT_A, parseEther('10')],
account: '0xa013422A5918CD099C63c8CC35283EACa99a705d'
})
console.log('✓ Gas 估算成功:', gasEstimate.toString())
} catch (error) {
console.log('✗ Gas 估算失败\n')
console.log('错误类型:', error.name)
console.log('错误消息:', error.shortMessage || error.message)
if (error.cause) {
console.log('\n详细原因:')
console.log(' Reason:', error.cause.reason)
console.log(' Details:', error.cause.details)
// 尝试解析 revert 原因
if (error.cause.data) {
console.log(' Raw Data:', error.cause.data)
}
}
if (error.details) {
console.log('\n更多细节:', error.details)
}
// 打印完整错误用于调试
console.log('\n=== 完整错误对象 ===')
console.log(JSON.stringify(error, null, 2))
}
}
main()

View File

@@ -0,0 +1,104 @@
import { createPublicClient, http, formatUnits } from 'viem';
import { arbitrumSepolia } from 'viem/chains';
const RPC_URL = 'https://sepolia-rollup.arbitrum.io/rpc';
const LENDING_PROXY = '0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D';
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http(RPC_URL)
});
async function testBothFunctions() {
console.log('\n=== 测试 getTotalSupply vs getTotalLiquidity ===\n');
// 测试 getTotalSupply
console.log('1⃣ 测试 getTotalSupply():');
try {
const totalSupply = await client.readContract({
address: LENDING_PROXY,
abi: [{
inputs: [],
name: 'getTotalSupply',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}],
functionName: 'getTotalSupply'
});
console.log(' ✅ 调用成功!');
console.log(' 值:', totalSupply.toString());
console.log(' 格式化:', formatUnits(totalSupply, 6), 'USDC');
} catch (error) {
console.log(' ❌ 调用失败!');
console.log(' 错误:', error.message.split('\n')[0]);
}
console.log('\n2⃣ 测试 getTotalLiquidity():');
try {
const totalLiquidity = await client.readContract({
address: LENDING_PROXY,
abi: [{
inputs: [],
name: 'getTotalLiquidity',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}],
functionName: 'getTotalLiquidity'
});
console.log(' ✅ 调用成功!');
console.log(' 值:', totalLiquidity.toString());
console.log(' 格式化:', formatUnits(totalLiquidity, 6), 'USDC');
} catch (error) {
console.log(' ❌ 调用失败!');
console.log(' 错误:', error.message.split('\n')[0]);
}
// 测试其他可能的函数名
console.log('\n3⃣ 测试 totalSupply() (Compound V3 标准):');
try {
const totalSupply = await client.readContract({
address: LENDING_PROXY,
abi: [{
inputs: [],
name: 'totalSupply',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}],
functionName: 'totalSupply'
});
console.log(' ✅ 调用成功!');
console.log(' 值:', totalSupply.toString());
console.log(' 格式化:', formatUnits(totalSupply, 6), 'USDC');
} catch (error) {
console.log(' ❌ 调用失败!');
console.log(' 错误:', error.message.split('\n')[0]);
}
console.log('\n4⃣ 测试 totalBorrow():');
try {
const totalBorrow = await client.readContract({
address: LENDING_PROXY,
abi: [{
inputs: [],
name: 'totalBorrow',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}],
functionName: 'totalBorrow'
});
console.log(' ✅ 调用成功!');
console.log(' 值:', totalBorrow.toString());
console.log(' 格式化:', formatUnits(totalBorrow, 6), 'USDC');
} catch (error) {
console.log(' ❌ 调用失败!');
console.log(' 错误:', error.message.split('\n')[0]);
}
console.log('\n');
}
testBothFunctions().catch(console.error);

View File

@@ -0,0 +1,169 @@
import { createPublicClient, http, formatUnits } from 'viem';
import { arbitrumSepolia } from 'viem/chains';
const RPC_URL = 'https://sepolia-rollup.arbitrum.io/rpc';
const LENDING_PROXY = '0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D';
const TEST_USER = '0xa013422A5918CD099C63c8CC35283EACa99a705d'; // 有抵押品的用户
const YT_A = '0x97204190B35D9895a7a47aa7BaC61ac08De3cF05';
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http(RPC_URL)
});
const testResults = {
success: [],
failed: []
};
async function testFunction(name, abi, params = []) {
try {
const result = await client.readContract({
address: LENDING_PROXY,
abi: [abi],
functionName: name,
args: params
});
testResults.success.push(name);
return { success: true, value: result };
} catch (error) {
testResults.failed.push(name);
return { success: false, error: error.message.split('\n')[0] };
}
}
async function runAllTests() {
console.log('\n=== 验证所有前端 LENDING ABI 函数 ===\n');
// 1. getCollateral - 用户查询
console.log('1⃣ getCollateral(account, asset):');
const r1 = await testFunction('getCollateral', {
inputs: [
{ internalType: 'address', name: 'account', type: 'address' },
{ internalType: 'address', name: 'asset', type: 'address' }
],
name: 'getCollateral',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}, [TEST_USER, YT_A]);
console.log(r1.success ? ` ✅ 成功 - 抵押量: ${formatUnits(r1.value, 18)}` : ` ❌ 失败 - ${r1.error}`);
// 2. getBalance - 用户余额
console.log('\n2⃣ getBalance(account):');
const r2 = await testFunction('getBalance', {
inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
name: 'getBalance',
outputs: [{ internalType: 'int256', name: '', type: 'int256' }],
stateMutability: 'view',
type: 'function'
}, [TEST_USER]);
console.log(r2.success ? ` ✅ 成功 - 余额: ${r2.value.toString()}` : ` ❌ 失败 - ${r2.error}`);
// 3. borrowBalanceOf - 借款余额
console.log('\n3⃣ borrowBalanceOf(account):');
const r3 = await testFunction('borrowBalanceOf', {
inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
name: 'borrowBalanceOf',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}, [TEST_USER]);
console.log(r3.success ? ` ✅ 成功 - 借款: ${formatUnits(r3.value, 6)} USDC` : ` ❌ 失败 - ${r3.error}`);
// 4. getTotalBorrow - 系统总借款
console.log('\n4⃣ getTotalBorrow():');
const r4 = await testFunction('getTotalBorrow', {
inputs: [],
name: 'getTotalBorrow',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
});
console.log(r4.success ? ` ✅ 成功 - 总借款: ${formatUnits(r4.value, 6)} USDC` : ` ❌ 失败 - ${r4.error}`);
// 5. getTotalSupply - 系统总供应
console.log('\n5⃣ getTotalSupply():');
const r5 = await testFunction('getTotalSupply', {
inputs: [],
name: 'getTotalSupply',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
});
console.log(r5.success ? ` ✅ 成功 - 总供应: ${formatUnits(r5.value, 6)} USDC` : ` ❌ 失败 - ${r5.error}`);
// 6. getUtilization - 资金利用率
console.log('\n6⃣ getUtilization():');
const r6 = await testFunction('getUtilization', {
inputs: [],
name: 'getUtilization',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
});
console.log(r6.success ? ` ✅ 成功 - 利用率: ${Number(r6.value) / 1e16}%` : ` ❌ 失败 - ${r6.error}`);
// 7. getBorrowRate - 借款利率
console.log('\n7⃣ getBorrowRate():');
const r7 = await testFunction('getBorrowRate', {
inputs: [],
name: 'getBorrowRate',
outputs: [{ internalType: 'uint64', name: '', type: 'uint64' }],
stateMutability: 'view',
type: 'function'
});
console.log(r7.success ? ` ✅ 成功 - 借款利率: ${Number(r7.value) / 1e16}%` : ` ❌ 失败 - ${r7.error}`);
// 8. getSupplyRate - 存款利率
console.log('\n8⃣ getSupplyRate():');
const r8 = await testFunction('getSupplyRate', {
inputs: [],
name: 'getSupplyRate',
outputs: [{ internalType: 'uint64', name: '', type: 'uint64' }],
stateMutability: 'view',
type: 'function'
});
console.log(r8.success ? ` ✅ 成功 - 存款利率: ${Number(r8.value) / 1e16}%` : ` ❌ 失败 - ${r8.error}`);
// 9. owner - 管理员
console.log('\n9⃣ owner():');
const r9 = await testFunction('owner', {
inputs: [],
name: 'owner',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function'
});
console.log(r9.success ? ` ✅ 成功 - Owner: ${r9.value}` : ` ❌ 失败 - ${r9.error}`);
// 10. paused - 暂停状态
console.log('\n🔟 paused():');
const r10 = await testFunction('paused', {
inputs: [],
name: 'paused',
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
stateMutability: 'view',
type: 'function'
});
console.log(r10.success ? ` ✅ 成功 - Paused: ${r10.value}` : ` ❌ 失败 - ${r10.error}`);
// 汇总
console.log('\n' + '='.repeat(60));
console.log('📊 测试结果汇总:');
console.log('='.repeat(60));
console.log(`✅ 成功: ${testResults.success.length}/10`);
console.log(` ${testResults.success.join(', ')}`);
if (testResults.failed.length > 0) {
console.log(`❌ 失败: ${testResults.failed.length}/10`);
console.log(` ${testResults.failed.join(', ')}`);
}
console.log('='.repeat(60) + '\n');
// 对比测试脚本中的错误函数
console.log('⚠️ check-lending-setup.js 中使用的错误函数:');
console.log(' getTotalLiquidity() - 不存在,应改为 getTotalSupply()');
console.log('');
}
runAllTests().catch(console.error);

View File

@@ -0,0 +1,53 @@
// 验证 APY 计算逻辑
const borrowRatePerSecond = 14999999976144000n
const secondsPerYear = BigInt(365 * 24 * 60 * 60) // 31536000
console.log('\n=== APY 计算验证 ===\n')
console.log('原始数据:')
console.log(' borrowRatePerSecond:', borrowRatePerSecond.toString())
console.log(' secondsPerYear:', secondsPerYear.toString())
console.log()
// 方法1错误的计算之前的方式
const wrongAPY = Number(borrowRatePerSecond) / 10000
console.log('❌ 错误计算除以10000:')
console.log(' APY:', wrongAPY.toFixed(2), '%')
console.log(' = 1,499,999,997,614.40 %(明显错误)')
console.log()
// 方法2正确的计算
// APY% = (ratePerSecond / 1e18) × secondsPerYear × 100
const borrowAPY = (borrowRatePerSecond * secondsPerYear * BigInt(100)) / BigInt(10 ** 18)
console.log('✅ 正确计算Compound V3 格式):')
console.log(' 每秒利率:', Number(borrowRatePerSecond) / 1e18)
console.log(' 年化倍数 (1 + rate)^seconds ≈ rate × seconds:')
console.log(' ', Number(borrowRatePerSecond) / 1e18, '× 31,536,000 =', Number(borrowRatePerSecond) * 31536000 / 1e18)
console.log(' APY:', Number(borrowAPY).toFixed(2), '%')
console.log()
// 详细计算步骤
console.log('计算步骤:')
console.log(' 1. borrowRatePerSecond × secondsPerYear × 100')
console.log(' =', borrowRatePerSecond.toString(), '×', secondsPerYear.toString(), '× 100')
const step1 = borrowRatePerSecond * secondsPerYear * BigInt(100)
console.log(' =', step1.toString())
console.log()
console.log(' 2. 结果 ÷ 10^18')
console.log(' =', step1.toString(), '÷ 1000000000000000000')
console.log(' =', borrowAPY.toString())
console.log()
console.log('最终结果: ', Number(borrowAPY).toFixed(2), '%')
console.log()
// 验证使用率计算
console.log('=== 使用率计算验证 ===\n')
console.log('如果 utilizationRate = 0当前没有借款')
const utilizationRate = 0n
const utilizationPercent = utilizationRate > 0n
? (utilizationRate * BigInt(100)) / BigInt(10 ** 18)
: 0n
console.log(' 使用率:', Number(utilizationPercent).toFixed(2), '%')
console.log()

View File

@@ -0,0 +1,116 @@
import { createPublicClient, http, getAddress } from 'viem'
import { arbitrumSepolia } from 'viem/chains'
const client = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://api.zan.top/node/v1/arb/sepolia/baf84c429d284bb5b676cb8c9ca21c07')
})
const LENDING_PROXY = getAddress('0xCb4E7B1069F6C26A1c27523ce4c8dfD884552d1D')
const USER = getAddress('0xa013422A5918CD099C63c8CC35283EACa99a705d')
const LENDING_ABI = [
{
inputs: [{ name: 'account', type: 'address' }],
name: 'getBalance',
outputs: [{ name: '', type: 'int256' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [{ name: 'account', type: 'address' }],
name: 'borrowBalanceOf',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [],
name: 'getBorrowRate',
outputs: [{ name: '', type: 'uint64' }],
stateMutability: 'view',
type: 'function'
}
]
async function main() {
console.log('\n=== 验证当前借款状态 ===\n')
console.log('用户:', USER)
console.log('Lending 合约:', LENDING_PROXY)
console.log()
// 获取账户余额
const balance = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getBalance',
args: [USER]
})
// 获取借款余额
const borrowBalance = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'borrowBalanceOf',
args: [USER]
})
// 获取借款利率
const borrowRate = await client.readContract({
address: LENDING_PROXY,
abi: LENDING_ABI,
functionName: 'getBorrowRate'
})
console.log('=== 账户状态 ===')
console.log()
console.log('getBalance() 返回值:', balance.toString())
if (balance > 0) {
console.log(' → 用户有存款:', Number(balance) / 1e6, 'USDC')
} else if (balance < 0) {
console.log(' → 用户有负余额(债务):', Number(-balance) / 1e6, 'USDC')
} else {
console.log(' → 用户余额为 0')
}
console.log()
console.log('borrowBalanceOf() 返回值:', borrowBalance.toString())
console.log(' → 借款金额:', Number(borrowBalance) / 1e6, 'USDC')
console.log()
console.log('=== 利息信息 ===')
console.log()
console.log('借款年化利率 (APY):', (Number(borrowRate) / 1e18 * 100).toFixed(2), '%')
console.log()
// 分析结果
console.log('=== 分析 ===')
console.log()
if (borrowBalance > 0n) {
console.log('✓ 用户确实有借款!')
console.log()
console.log('借款金额:', Number(borrowBalance) / 1e6, 'USDC')
console.log()
if (balance === 0n) {
console.log('余额状态: balance = 0这意味着')
console.log(' - 用户之前可能有存款')
console.log(' - withdraw 金额超过了存款')
console.log(' - 超出部分形成了债务')
console.log()
console.log('这正是 Compound V3 的设计:')
console.log(' withdraw(amount) 会:')
console.log(' 1. 优先从存款中扣除')
console.log(' 2. 存款不足时,自动创建债务')
}
console.log()
console.log('用户需要归还的总额:', Number(borrowBalance) / 1e6, 'USDC')
console.log('(注意:每秒都在计息,实际金额会略高)')
} else {
console.log('✗ 用户当前没有借款')
}
}
main()