@hypurrquant/defi-cli 1.0.8 → 1.0.9
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/mcp-server.js
CHANGED
|
@@ -5242,6 +5242,7 @@ var init_dist2 = __esm({
|
|
|
5242
5242
|
"function repay(address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf) external returns (uint256)",
|
|
5243
5243
|
"function withdraw(address asset, uint256 amount, address to) external returns (uint256)",
|
|
5244
5244
|
"function getUserAccountData(address user) external view returns (uint256 totalCollateralBase, uint256 totalDebtBase, uint256 availableBorrowsBase, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor)",
|
|
5245
|
+
"function getReservesList() external view returns (address[])",
|
|
5245
5246
|
"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)"
|
|
5246
5247
|
]);
|
|
5247
5248
|
ERC20_ABI = parseAbi14([
|
|
@@ -5328,13 +5329,37 @@ var init_dist2 = __esm({
|
|
|
5328
5329
|
};
|
|
5329
5330
|
}
|
|
5330
5331
|
async buildWithdraw(params) {
|
|
5332
|
+
let withdrawAmount = params.amount;
|
|
5333
|
+
if (this.rpcUrl) {
|
|
5334
|
+
try {
|
|
5335
|
+
const client = createPublicClient10({ transport: http10(this.rpcUrl) });
|
|
5336
|
+
const reserveData = await client.readContract({
|
|
5337
|
+
address: this.pool,
|
|
5338
|
+
abi: POOL_ABI,
|
|
5339
|
+
functionName: "getReserveData",
|
|
5340
|
+
args: [params.asset]
|
|
5341
|
+
});
|
|
5342
|
+
const aToken = reserveData[8];
|
|
5343
|
+
const aBal = await client.readContract({
|
|
5344
|
+
address: aToken,
|
|
5345
|
+
abi: parseAbi14(["function balanceOf(address) view returns (uint256)"]),
|
|
5346
|
+
functionName: "balanceOf",
|
|
5347
|
+
args: [params.to]
|
|
5348
|
+
});
|
|
5349
|
+
if (aBal > 0n && params.amount >= aBal) {
|
|
5350
|
+
withdrawAmount = (1n << 256n) - 1n;
|
|
5351
|
+
}
|
|
5352
|
+
} catch {
|
|
5353
|
+
}
|
|
5354
|
+
}
|
|
5331
5355
|
const data = encodeFunctionData14({
|
|
5332
5356
|
abi: POOL_ABI,
|
|
5333
5357
|
functionName: "withdraw",
|
|
5334
|
-
args: [params.asset,
|
|
5358
|
+
args: [params.asset, withdrawAmount, params.to]
|
|
5335
5359
|
});
|
|
5360
|
+
const isMax = withdrawAmount === (1n << 256n) - 1n;
|
|
5336
5361
|
return {
|
|
5337
|
-
description: `[${this.protocolName}] Withdraw ${
|
|
5362
|
+
description: `[${this.protocolName}] Withdraw ${isMax ? "all (auto-max)" : withdrawAmount} from pool`,
|
|
5338
5363
|
to: this.pool,
|
|
5339
5364
|
data,
|
|
5340
5365
|
value: 0n,
|
|
@@ -5528,7 +5553,7 @@ var init_dist2 = __esm({
|
|
|
5528
5553
|
async getUserPosition(user) {
|
|
5529
5554
|
if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
|
|
5530
5555
|
const client = createPublicClient10({ transport: http10(this.rpcUrl) });
|
|
5531
|
-
const
|
|
5556
|
+
const accountData = await client.readContract({
|
|
5532
5557
|
address: this.pool,
|
|
5533
5558
|
abi: POOL_ABI,
|
|
5534
5559
|
functionName: "getUserAccountData",
|
|
@@ -5536,21 +5561,63 @@ var init_dist2 = __esm({
|
|
|
5536
5561
|
}).catch((e) => {
|
|
5537
5562
|
throw DefiError.rpcError(`[${this.protocolName}] getUserAccountData failed: ${e}`);
|
|
5538
5563
|
});
|
|
5539
|
-
const [totalCollateralBase, totalDebtBase, , , ltv, healthFactor] =
|
|
5564
|
+
const [totalCollateralBase, totalDebtBase, , , ltv, healthFactor] = accountData;
|
|
5540
5565
|
const MAX_UINT2562 = 2n ** 256n - 1n;
|
|
5541
|
-
const hf = healthFactor >= MAX_UINT2562 ?
|
|
5566
|
+
const hf = healthFactor >= MAX_UINT2562 ? null : Number(healthFactor) / 1e18;
|
|
5542
5567
|
const collateralUsd = u256ToF64(totalCollateralBase) / 1e8;
|
|
5543
5568
|
const debtUsd = u256ToF64(totalDebtBase) / 1e8;
|
|
5544
|
-
const
|
|
5545
|
-
const supplies =
|
|
5546
|
-
const borrows =
|
|
5569
|
+
const ltvPct = u256ToF64(ltv) / 100;
|
|
5570
|
+
const supplies = [];
|
|
5571
|
+
const borrows = [];
|
|
5572
|
+
try {
|
|
5573
|
+
const reserves = await client.readContract({
|
|
5574
|
+
address: this.pool,
|
|
5575
|
+
abi: POOL_ABI,
|
|
5576
|
+
functionName: "getReservesList"
|
|
5577
|
+
});
|
|
5578
|
+
const reserveData = await Promise.allSettled(reserves.map(async (asset) => {
|
|
5579
|
+
const data = await client.readContract({
|
|
5580
|
+
address: this.pool,
|
|
5581
|
+
abi: POOL_ABI,
|
|
5582
|
+
functionName: "getReserveData",
|
|
5583
|
+
args: [asset]
|
|
5584
|
+
});
|
|
5585
|
+
const aToken = data[8];
|
|
5586
|
+
const debtToken = data[10];
|
|
5587
|
+
return { asset, aToken, debtToken };
|
|
5588
|
+
}));
|
|
5589
|
+
const ERC20_ABI42 = parseAbi14([
|
|
5590
|
+
"function balanceOf(address) view returns (uint256)",
|
|
5591
|
+
"function symbol() view returns (string)"
|
|
5592
|
+
]);
|
|
5593
|
+
const positions = await Promise.allSettled(reserveData.map(async (r) => {
|
|
5594
|
+
if (r.status !== "fulfilled") return null;
|
|
5595
|
+
const { asset, aToken, debtToken } = r.value;
|
|
5596
|
+
const [aBal, dBal, sym] = await Promise.all([
|
|
5597
|
+
client.readContract({ address: aToken, abi: ERC20_ABI42, functionName: "balanceOf", args: [user] }),
|
|
5598
|
+
client.readContract({ address: debtToken, abi: ERC20_ABI42, functionName: "balanceOf", args: [user] }),
|
|
5599
|
+
client.readContract({ address: asset, abi: ERC20_ABI42, functionName: "symbol" }).catch(() => "?")
|
|
5600
|
+
]);
|
|
5601
|
+
return { asset, symbol: sym, supply: aBal, borrow: dBal };
|
|
5602
|
+
}));
|
|
5603
|
+
for (const p of positions) {
|
|
5604
|
+
if (p.status !== "fulfilled" || !p.value) continue;
|
|
5605
|
+
if (p.value.supply > 0n) supplies.push({ asset: p.value.asset, symbol: p.value.symbol, amount: p.value.supply });
|
|
5606
|
+
if (p.value.borrow > 0n) borrows.push({ asset: p.value.asset, symbol: p.value.symbol, amount: p.value.borrow });
|
|
5607
|
+
}
|
|
5608
|
+
} catch {
|
|
5609
|
+
if (collateralUsd > 0) supplies.push({ asset: zeroAddress8, symbol: "Total Collateral (per-asset breakdown unavailable)", amount: totalCollateralBase });
|
|
5610
|
+
if (debtUsd > 0) borrows.push({ asset: zeroAddress8, symbol: "Total Debt (per-asset breakdown unavailable)", amount: totalDebtBase });
|
|
5611
|
+
}
|
|
5547
5612
|
return {
|
|
5548
5613
|
protocol: this.protocolName,
|
|
5549
5614
|
user,
|
|
5550
5615
|
supplies,
|
|
5551
5616
|
borrows,
|
|
5552
5617
|
health_factor: hf,
|
|
5553
|
-
|
|
5618
|
+
ltv_pct: ltvPct,
|
|
5619
|
+
total_collateral_usd: collateralUsd,
|
|
5620
|
+
total_debt_usd: debtUsd
|
|
5554
5621
|
};
|
|
5555
5622
|
}
|
|
5556
5623
|
};
|
|
@@ -7433,24 +7500,69 @@ var Executor = class _Executor {
|
|
|
7433
7500
|
`);
|
|
7434
7501
|
if (approveTxUrl) process.stderr.write(` Explorer: ${approveTxUrl}
|
|
7435
7502
|
`);
|
|
7436
|
-
await
|
|
7503
|
+
const approveReceipt = await _Executor.waitForReceiptWithRetry(publicClient, approveTxHash);
|
|
7504
|
+
if (approveReceipt.status !== "success") {
|
|
7505
|
+
throw new Error(`Approve tx ${approveTxHash} reverted on-chain (status=${approveReceipt.status}). Aborting downstream tx.`);
|
|
7506
|
+
}
|
|
7437
7507
|
process.stderr.write(
|
|
7438
7508
|
` Approved ${amount} of ${token} for ${spender}
|
|
7439
7509
|
`
|
|
7440
7510
|
);
|
|
7441
7511
|
}
|
|
7442
|
-
/**
|
|
7512
|
+
/**
|
|
7513
|
+
* Wait for a tx receipt with bounded retries. Some L2 RPCs (notably Mantle)
|
|
7514
|
+
* occasionally fail to surface a receipt even after the tx is mined; viem's
|
|
7515
|
+
* default `waitForTransactionReceipt` errors out instead of polling longer.
|
|
7516
|
+
* We retry up to `attempts` times with exponential backoff before giving up.
|
|
7517
|
+
*/
|
|
7518
|
+
static async waitForReceiptWithRetry(client, hash, attempts = 6) {
|
|
7519
|
+
let lastErr;
|
|
7520
|
+
for (let i = 0; i < attempts; i++) {
|
|
7521
|
+
try {
|
|
7522
|
+
return await client.waitForTransactionReceipt({
|
|
7523
|
+
hash,
|
|
7524
|
+
timeout: 6e4,
|
|
7525
|
+
retryCount: 8
|
|
7526
|
+
});
|
|
7527
|
+
} catch (e) {
|
|
7528
|
+
lastErr = e;
|
|
7529
|
+
const backoffMs = Math.min(2e3 * Math.pow(2, i), 6e4);
|
|
7530
|
+
await new Promise((resolve2) => setTimeout(resolve2, backoffMs));
|
|
7531
|
+
}
|
|
7532
|
+
}
|
|
7533
|
+
throw new Error(`waitForReceiptWithRetry: gave up after ${attempts} attempts. Last error: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
|
|
7534
|
+
}
|
|
7535
|
+
/**
|
|
7536
|
+
* Fetch EIP-1559 fee params. Returns [maxFeePerGas, maxPriorityFeePerGas].
|
|
7537
|
+
*
|
|
7538
|
+
* Strategy: read the latest block's `baseFeePerGas` and use the canonical
|
|
7539
|
+
* EIP-1559 formula `maxFee = baseFee * 2 + priorityFee` (1 block of head-room
|
|
7540
|
+
* after a 12.5% bump). Falls back to `getGasPrice() + priorityFee` only when
|
|
7541
|
+
* the chain doesn't expose `baseFeePerGas` (pre-1559).
|
|
7542
|
+
*
|
|
7543
|
+
* Why not gasPrice * 2: `getGasPrice()` returns `baseFee + priorityFee`, so
|
|
7544
|
+
* doubling double-counts the priority component and produces nonsense on
|
|
7545
|
+
* chains where baseFee is already high (e.g., Mantle 50 gwei → 100 gwei,
|
|
7546
|
+
* which can drain MNT before the actual tx settles).
|
|
7547
|
+
*/
|
|
7443
7548
|
async fetchEip1559Fees(rpcUrl) {
|
|
7444
7549
|
try {
|
|
7445
7550
|
const client = createPublicClient23({ transport: http23(rpcUrl) });
|
|
7446
|
-
const gasPrice = await client.getGasPrice();
|
|
7447
7551
|
let priorityFee = DEFAULT_PRIORITY_FEE_WEI;
|
|
7448
7552
|
try {
|
|
7449
7553
|
priorityFee = await client.estimateMaxPriorityFeePerGas();
|
|
7450
7554
|
} catch {
|
|
7451
7555
|
}
|
|
7452
|
-
|
|
7453
|
-
|
|
7556
|
+
try {
|
|
7557
|
+
const block = await client.getBlock({ blockTag: "latest" });
|
|
7558
|
+
if (block.baseFeePerGas !== null && block.baseFeePerGas !== void 0) {
|
|
7559
|
+
const maxFee = block.baseFeePerGas * 2n + priorityFee;
|
|
7560
|
+
return [maxFee, priorityFee];
|
|
7561
|
+
}
|
|
7562
|
+
} catch {
|
|
7563
|
+
}
|
|
7564
|
+
const gasPrice = await client.getGasPrice();
|
|
7565
|
+
return [gasPrice + priorityFee, priorityFee];
|
|
7454
7566
|
} catch {
|
|
7455
7567
|
return [0n, 0n];
|
|
7456
7568
|
}
|
|
@@ -7614,8 +7726,8 @@ var Executor = class _Executor {
|
|
|
7614
7726
|
`);
|
|
7615
7727
|
if (preTxUrl) process.stderr.write(` Explorer: ${preTxUrl}
|
|
7616
7728
|
`);
|
|
7617
|
-
const
|
|
7618
|
-
if (
|
|
7729
|
+
const preReceiptResult = await _Executor.waitForReceiptWithRetry(publicClient, preTxHash);
|
|
7730
|
+
if (preReceiptResult.status !== "success") {
|
|
7619
7731
|
throw new DefiError("TX_FAILED", `Pre-transaction failed: ${preTx.description}`);
|
|
7620
7732
|
}
|
|
7621
7733
|
process.stderr.write(` Pre-tx confirmed
|
|
@@ -7657,7 +7769,7 @@ var Executor = class _Executor {
|
|
|
7657
7769
|
if (txUrl) process.stderr.write(`Explorer: ${txUrl}
|
|
7658
7770
|
`);
|
|
7659
7771
|
process.stderr.write("Waiting for confirmation...\n");
|
|
7660
|
-
const receipt = await
|
|
7772
|
+
const receipt = await _Executor.waitForReceiptWithRetry(publicClient, txHash);
|
|
7661
7773
|
const status = receipt.status === "success" ? TxStatus.Confirmed : TxStatus.Failed;
|
|
7662
7774
|
let mintedTokenId;
|
|
7663
7775
|
if (receipt.status === "success" && receipt.logs) {
|