@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.
@@ -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, params.amount, params.to]
5358
+ args: [params.asset, withdrawAmount, params.to]
5335
5359
  });
5360
+ const isMax = withdrawAmount === (1n << 256n) - 1n;
5336
5361
  return {
5337
- description: `[${this.protocolName}] Withdraw ${params.amount} from pool`,
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 result = await client.readContract({
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] = result;
5564
+ const [totalCollateralBase, totalDebtBase, , , ltv, healthFactor] = accountData;
5540
5565
  const MAX_UINT2562 = 2n ** 256n - 1n;
5541
- const hf = healthFactor >= MAX_UINT2562 ? Infinity : Number(healthFactor) / 1e18;
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 ltvBps = u256ToF64(ltv);
5545
- const supplies = collateralUsd > 0 ? [{ asset: zeroAddress8, symbol: "Total Collateral", amount: totalCollateralBase, value_usd: collateralUsd }] : [];
5546
- const borrows = debtUsd > 0 ? [{ asset: zeroAddress8, symbol: "Total Debt", amount: totalDebtBase, value_usd: debtUsd }] : [];
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
- net_apy: ltvBps / 100
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 publicClient.waitForTransactionReceipt({ hash: approveTxHash });
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
- /** Fetch EIP-1559 fee params from the network. Returns [maxFeePerGas, maxPriorityFeePerGas]. */
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
- const maxFee = gasPrice * 2n + priorityFee;
7453
- return [maxFee, priorityFee];
7556
+ try {
7557
+ const block = await client.getBlock({ blockTag: "latest" });
7558
+ if (block.baseFeePerGas !== null && block.baseFeePerGas !== void 0) {
7559
+ const maxFee = block.baseFeePerGas * 125n / 100n + 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 preReceipt = await publicClient.waitForTransactionReceipt({ hash: preTxHash });
7618
- if (preReceipt.status !== "success") {
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 publicClient.waitForTransactionReceipt({ hash: txHash });
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) {