@hypurrquant/defi-cli 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +129 -17
- package/dist/index.js.map +1 -1
- package/dist/main.js +129 -17
- package/dist/main.js.map +1 -1
- package/dist/mcp-server.js +129 -17
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
- package/skills/defi-cli/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -5130,6 +5130,7 @@ var init_dist2 = __esm({
|
|
|
5130
5130
|
"function repay(address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf) external returns (uint256)",
|
|
5131
5131
|
"function withdraw(address asset, uint256 amount, address to) external returns (uint256)",
|
|
5132
5132
|
"function getUserAccountData(address user) external view returns (uint256 totalCollateralBase, uint256 totalDebtBase, uint256 availableBorrowsBase, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor)",
|
|
5133
|
+
"function getReservesList() external view returns (address[])",
|
|
5133
5134
|
"function getReserveData(address asset) external view returns (uint256 configuration, uint128 liquidityIndex, uint128 currentLiquidityRate, uint128 variableBorrowIndex, uint128 currentVariableBorrowRate, uint128 currentStableBorrowRate, uint40 lastUpdateTimestamp, uint16 id, address aTokenAddress, address stableDebtTokenAddress, address variableDebtTokenAddress, address interestRateStrategyAddress, uint128 accruedToTreasury, uint128 unbacked, uint128 isolationModeTotalDebt)"
|
|
5134
5135
|
]);
|
|
5135
5136
|
ERC20_ABI2 = parseAbi14([
|
|
@@ -5216,13 +5217,37 @@ var init_dist2 = __esm({
|
|
|
5216
5217
|
};
|
|
5217
5218
|
}
|
|
5218
5219
|
async buildWithdraw(params) {
|
|
5220
|
+
let withdrawAmount = params.amount;
|
|
5221
|
+
if (this.rpcUrl) {
|
|
5222
|
+
try {
|
|
5223
|
+
const client = createPublicClient10({ transport: http10(this.rpcUrl) });
|
|
5224
|
+
const reserveData = await client.readContract({
|
|
5225
|
+
address: this.pool,
|
|
5226
|
+
abi: POOL_ABI,
|
|
5227
|
+
functionName: "getReserveData",
|
|
5228
|
+
args: [params.asset]
|
|
5229
|
+
});
|
|
5230
|
+
const aToken = reserveData[8];
|
|
5231
|
+
const aBal = await client.readContract({
|
|
5232
|
+
address: aToken,
|
|
5233
|
+
abi: parseAbi14(["function balanceOf(address) view returns (uint256)"]),
|
|
5234
|
+
functionName: "balanceOf",
|
|
5235
|
+
args: [params.to]
|
|
5236
|
+
});
|
|
5237
|
+
if (aBal > 0n && params.amount >= aBal) {
|
|
5238
|
+
withdrawAmount = (1n << 256n) - 1n;
|
|
5239
|
+
}
|
|
5240
|
+
} catch {
|
|
5241
|
+
}
|
|
5242
|
+
}
|
|
5219
5243
|
const data = encodeFunctionData14({
|
|
5220
5244
|
abi: POOL_ABI,
|
|
5221
5245
|
functionName: "withdraw",
|
|
5222
|
-
args: [params.asset,
|
|
5246
|
+
args: [params.asset, withdrawAmount, params.to]
|
|
5223
5247
|
});
|
|
5248
|
+
const isMax = withdrawAmount === (1n << 256n) - 1n;
|
|
5224
5249
|
return {
|
|
5225
|
-
description: `[${this.protocolName}] Withdraw ${
|
|
5250
|
+
description: `[${this.protocolName}] Withdraw ${isMax ? "all (auto-max)" : withdrawAmount} from pool`,
|
|
5226
5251
|
to: this.pool,
|
|
5227
5252
|
data,
|
|
5228
5253
|
value: 0n,
|
|
@@ -5416,7 +5441,7 @@ var init_dist2 = __esm({
|
|
|
5416
5441
|
async getUserPosition(user) {
|
|
5417
5442
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
5418
5443
|
const client = createPublicClient10({ transport: http10(this.rpcUrl) });
|
|
5419
|
-
const
|
|
5444
|
+
const accountData = await client.readContract({
|
|
5420
5445
|
address: this.pool,
|
|
5421
5446
|
abi: POOL_ABI,
|
|
5422
5447
|
functionName: "getUserAccountData",
|
|
@@ -5424,21 +5449,63 @@ var init_dist2 = __esm({
|
|
|
5424
5449
|
}).catch((e) => {
|
|
5425
5450
|
throw DefiError.rpcError(`[${this.protocolName}] getUserAccountData failed: ${e}`);
|
|
5426
5451
|
});
|
|
5427
|
-
const [totalCollateralBase, totalDebtBase, , , ltv, healthFactor] =
|
|
5452
|
+
const [totalCollateralBase, totalDebtBase, , , ltv, healthFactor] = accountData;
|
|
5428
5453
|
const MAX_UINT2562 = 2n ** 256n - 1n;
|
|
5429
|
-
const hf = healthFactor >= MAX_UINT2562 ?
|
|
5454
|
+
const hf = healthFactor >= MAX_UINT2562 ? null : Number(healthFactor) / 1e18;
|
|
5430
5455
|
const collateralUsd = u256ToF64(totalCollateralBase) / 1e8;
|
|
5431
5456
|
const debtUsd = u256ToF64(totalDebtBase) / 1e8;
|
|
5432
|
-
const
|
|
5433
|
-
const supplies =
|
|
5434
|
-
const borrows =
|
|
5457
|
+
const ltvPct = u256ToF64(ltv) / 100;
|
|
5458
|
+
const supplies = [];
|
|
5459
|
+
const borrows = [];
|
|
5460
|
+
try {
|
|
5461
|
+
const reserves = await client.readContract({
|
|
5462
|
+
address: this.pool,
|
|
5463
|
+
abi: POOL_ABI,
|
|
5464
|
+
functionName: "getReservesList"
|
|
5465
|
+
});
|
|
5466
|
+
const reserveData = await Promise.allSettled(reserves.map(async (asset) => {
|
|
5467
|
+
const data = await client.readContract({
|
|
5468
|
+
address: this.pool,
|
|
5469
|
+
abi: POOL_ABI,
|
|
5470
|
+
functionName: "getReserveData",
|
|
5471
|
+
args: [asset]
|
|
5472
|
+
});
|
|
5473
|
+
const aToken = data[8];
|
|
5474
|
+
const debtToken = data[10];
|
|
5475
|
+
return { asset, aToken, debtToken };
|
|
5476
|
+
}));
|
|
5477
|
+
const ERC20_ABI42 = parseAbi14([
|
|
5478
|
+
"function balanceOf(address) view returns (uint256)",
|
|
5479
|
+
"function symbol() view returns (string)"
|
|
5480
|
+
]);
|
|
5481
|
+
const positions = await Promise.allSettled(reserveData.map(async (r) => {
|
|
5482
|
+
if (r.status !== "fulfilled") return null;
|
|
5483
|
+
const { asset, aToken, debtToken } = r.value;
|
|
5484
|
+
const [aBal, dBal, sym] = await Promise.all([
|
|
5485
|
+
client.readContract({ address: aToken, abi: ERC20_ABI42, functionName: "balanceOf", args: [user] }),
|
|
5486
|
+
client.readContract({ address: debtToken, abi: ERC20_ABI42, functionName: "balanceOf", args: [user] }),
|
|
5487
|
+
client.readContract({ address: asset, abi: ERC20_ABI42, functionName: "symbol" }).catch(() => "?")
|
|
5488
|
+
]);
|
|
5489
|
+
return { asset, symbol: sym, supply: aBal, borrow: dBal };
|
|
5490
|
+
}));
|
|
5491
|
+
for (const p of positions) {
|
|
5492
|
+
if (p.status !== "fulfilled" || !p.value) continue;
|
|
5493
|
+
if (p.value.supply > 0n) supplies.push({ asset: p.value.asset, symbol: p.value.symbol, amount: p.value.supply });
|
|
5494
|
+
if (p.value.borrow > 0n) borrows.push({ asset: p.value.asset, symbol: p.value.symbol, amount: p.value.borrow });
|
|
5495
|
+
}
|
|
5496
|
+
} catch {
|
|
5497
|
+
if (collateralUsd > 0) supplies.push({ asset: zeroAddress8, symbol: "Total Collateral (per-asset breakdown unavailable)", amount: totalCollateralBase });
|
|
5498
|
+
if (debtUsd > 0) borrows.push({ asset: zeroAddress8, symbol: "Total Debt (per-asset breakdown unavailable)", amount: totalDebtBase });
|
|
5499
|
+
}
|
|
5435
5500
|
return {
|
|
5436
5501
|
protocol: this.protocolName,
|
|
5437
5502
|
user,
|
|
5438
5503
|
supplies,
|
|
5439
5504
|
borrows,
|
|
5440
5505
|
health_factor: hf,
|
|
5441
|
-
|
|
5506
|
+
ltv_pct: ltvPct,
|
|
5507
|
+
total_collateral_usd: collateralUsd,
|
|
5508
|
+
total_debt_usd: debtUsd
|
|
5442
5509
|
};
|
|
5443
5510
|
}
|
|
5444
5511
|
};
|
|
@@ -7320,24 +7387,69 @@ var Executor = class _Executor {
|
|
|
7320
7387
|
`);
|
|
7321
7388
|
if (approveTxUrl) process.stderr.write(` Explorer: ${approveTxUrl}
|
|
7322
7389
|
`);
|
|
7323
|
-
await
|
|
7390
|
+
const approveReceipt = await _Executor.waitForReceiptWithRetry(publicClient, approveTxHash);
|
|
7391
|
+
if (approveReceipt.status !== "success") {
|
|
7392
|
+
throw new Error(`Approve tx ${approveTxHash} reverted on-chain (status=${approveReceipt.status}). Aborting downstream tx.`);
|
|
7393
|
+
}
|
|
7324
7394
|
process.stderr.write(
|
|
7325
7395
|
` Approved ${amount} of ${token} for ${spender}
|
|
7326
7396
|
`
|
|
7327
7397
|
);
|
|
7328
7398
|
}
|
|
7329
|
-
/**
|
|
7399
|
+
/**
|
|
7400
|
+
* Wait for a tx receipt with bounded retries. Some L2 RPCs (notably Mantle)
|
|
7401
|
+
* occasionally fail to surface a receipt even after the tx is mined; viem's
|
|
7402
|
+
* default `waitForTransactionReceipt` errors out instead of polling longer.
|
|
7403
|
+
* We retry up to `attempts` times with exponential backoff before giving up.
|
|
7404
|
+
*/
|
|
7405
|
+
static async waitForReceiptWithRetry(client, hash, attempts = 6) {
|
|
7406
|
+
let lastErr;
|
|
7407
|
+
for (let i = 0; i < attempts; i++) {
|
|
7408
|
+
try {
|
|
7409
|
+
return await client.waitForTransactionReceipt({
|
|
7410
|
+
hash,
|
|
7411
|
+
timeout: 6e4,
|
|
7412
|
+
retryCount: 8
|
|
7413
|
+
});
|
|
7414
|
+
} catch (e) {
|
|
7415
|
+
lastErr = e;
|
|
7416
|
+
const backoffMs = Math.min(2e3 * Math.pow(2, i), 6e4);
|
|
7417
|
+
await new Promise((resolve6) => setTimeout(resolve6, backoffMs));
|
|
7418
|
+
}
|
|
7419
|
+
}
|
|
7420
|
+
throw new Error(`waitForReceiptWithRetry: gave up after ${attempts} attempts. Last error: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
|
|
7421
|
+
}
|
|
7422
|
+
/**
|
|
7423
|
+
* Fetch EIP-1559 fee params. Returns [maxFeePerGas, maxPriorityFeePerGas].
|
|
7424
|
+
*
|
|
7425
|
+
* Strategy: read the latest block's `baseFeePerGas` and use the canonical
|
|
7426
|
+
* EIP-1559 formula `maxFee = baseFee * 2 + priorityFee` (1 block of head-room
|
|
7427
|
+
* after a 12.5% bump). Falls back to `getGasPrice() + priorityFee` only when
|
|
7428
|
+
* the chain doesn't expose `baseFeePerGas` (pre-1559).
|
|
7429
|
+
*
|
|
7430
|
+
* Why not gasPrice * 2: `getGasPrice()` returns `baseFee + priorityFee`, so
|
|
7431
|
+
* doubling double-counts the priority component and produces nonsense on
|
|
7432
|
+
* chains where baseFee is already high (e.g., Mantle 50 gwei → 100 gwei,
|
|
7433
|
+
* which can drain MNT before the actual tx settles).
|
|
7434
|
+
*/
|
|
7330
7435
|
async fetchEip1559Fees(rpcUrl) {
|
|
7331
7436
|
try {
|
|
7332
7437
|
const client = createPublicClient2({ transport: http2(rpcUrl) });
|
|
7333
|
-
const gasPrice = await client.getGasPrice();
|
|
7334
7438
|
let priorityFee = DEFAULT_PRIORITY_FEE_WEI;
|
|
7335
7439
|
try {
|
|
7336
7440
|
priorityFee = await client.estimateMaxPriorityFeePerGas();
|
|
7337
7441
|
} catch {
|
|
7338
7442
|
}
|
|
7339
|
-
|
|
7340
|
-
|
|
7443
|
+
try {
|
|
7444
|
+
const block = await client.getBlock({ blockTag: "latest" });
|
|
7445
|
+
if (block.baseFeePerGas !== null && block.baseFeePerGas !== void 0) {
|
|
7446
|
+
const maxFee = block.baseFeePerGas * 125n / 100n + priorityFee;
|
|
7447
|
+
return [maxFee, priorityFee];
|
|
7448
|
+
}
|
|
7449
|
+
} catch {
|
|
7450
|
+
}
|
|
7451
|
+
const gasPrice = await client.getGasPrice();
|
|
7452
|
+
return [gasPrice + priorityFee, priorityFee];
|
|
7341
7453
|
} catch {
|
|
7342
7454
|
return [0n, 0n];
|
|
7343
7455
|
}
|
|
@@ -7501,8 +7613,8 @@ var Executor = class _Executor {
|
|
|
7501
7613
|
`);
|
|
7502
7614
|
if (preTxUrl) process.stderr.write(` Explorer: ${preTxUrl}
|
|
7503
7615
|
`);
|
|
7504
|
-
const
|
|
7505
|
-
if (
|
|
7616
|
+
const preReceiptResult = await _Executor.waitForReceiptWithRetry(publicClient, preTxHash);
|
|
7617
|
+
if (preReceiptResult.status !== "success") {
|
|
7506
7618
|
throw new DefiError("TX_FAILED", `Pre-transaction failed: ${preTx.description}`);
|
|
7507
7619
|
}
|
|
7508
7620
|
process.stderr.write(` Pre-tx confirmed
|
|
@@ -7544,7 +7656,7 @@ var Executor = class _Executor {
|
|
|
7544
7656
|
if (txUrl) process.stderr.write(`Explorer: ${txUrl}
|
|
7545
7657
|
`);
|
|
7546
7658
|
process.stderr.write("Waiting for confirmation...\n");
|
|
7547
|
-
const receipt = await
|
|
7659
|
+
const receipt = await _Executor.waitForReceiptWithRetry(publicClient, txHash);
|
|
7548
7660
|
const status = receipt.status === "success" ? TxStatus.Confirmed : TxStatus.Failed;
|
|
7549
7661
|
let mintedTokenId;
|
|
7550
7662
|
if (receipt.status === "success" && receipt.logs) {
|