@hypurrquant/defi-cli 1.0.7 → 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.
@@ -4189,6 +4189,79 @@ var init_dist2 = __esm({
4189
4189
  gas_estimate: 3e5
4190
4190
  };
4191
4191
  }
4192
+ /**
4193
+ * List every LB pair from the factory with basic pair info (no rewarder /
4194
+ * APR enrichment). Useful when the factory has pools but none have hooks
4195
+ * deployed yet (e.g. early-stage Monad TraderJoe).
4196
+ *
4197
+ * Three multicall batches: pair addresses, token addresses, token symbols.
4198
+ */
4199
+ async discoverAllPools() {
4200
+ const rpcUrl = this.requireRpc();
4201
+ const client = createPublicClient8({ transport: http8(rpcUrl) });
4202
+ const pairCount = await client.readContract({
4203
+ address: this.lbFactory,
4204
+ abi: lbFactoryAbi,
4205
+ functionName: "getNumberOfLBPairs"
4206
+ });
4207
+ const count = Number(pairCount);
4208
+ if (count === 0) return [];
4209
+ const indexCalls = Array.from({ length: count }, (_, i) => [
4210
+ this.lbFactory,
4211
+ encodeFunctionData12({ abi: lbFactoryAbi, functionName: "getLBPairAtIndex", args: [BigInt(i)] })
4212
+ ]);
4213
+ const indexResults = await multicallRead(rpcUrl, indexCalls);
4214
+ const pairs = indexResults.map((r) => decodeAddressResult(r)).filter((a) => a !== null);
4215
+ if (pairs.length === 0) return [];
4216
+ const tokenCalls = [];
4217
+ for (const pool of pairs) {
4218
+ tokenCalls.push([pool, encodeFunctionData12({ abi: lbPairAbi, functionName: "getTokenX" })]);
4219
+ tokenCalls.push([pool, encodeFunctionData12({ abi: lbPairAbi, functionName: "getTokenY" })]);
4220
+ }
4221
+ const tokenResults = await multicallRead(rpcUrl, tokenCalls);
4222
+ const tokensX = [];
4223
+ const tokensY = [];
4224
+ for (let i = 0; i < pairs.length; i++) {
4225
+ tokensX.push(decodeAddressResult(tokenResults[i * 2] ?? null));
4226
+ tokensY.push(decodeAddressResult(tokenResults[i * 2 + 1] ?? null));
4227
+ }
4228
+ const uniqueTokens = Array.from(
4229
+ new Set([...tokensX, ...tokensY].filter((a) => a !== null))
4230
+ );
4231
+ const symbolCalls = uniqueTokens.map((t) => [
4232
+ t,
4233
+ encodeFunctionData12({ abi: erc20Abi2, functionName: "symbol" })
4234
+ ]);
4235
+ const symbolResults = await multicallRead(rpcUrl, symbolCalls);
4236
+ const symbolMap = /* @__PURE__ */ new Map();
4237
+ for (let i = 0; i < uniqueTokens.length; i++) {
4238
+ const raw = symbolResults[i];
4239
+ if (!raw) continue;
4240
+ try {
4241
+ const sym = decodeFunctionResult4({
4242
+ abi: parseAbi12(["function f() external view returns (string)"]),
4243
+ functionName: "f",
4244
+ data: raw
4245
+ });
4246
+ symbolMap.set(uniqueTokens[i].toLowerCase(), sym);
4247
+ } catch {
4248
+ }
4249
+ }
4250
+ const out = [];
4251
+ for (let i = 0; i < pairs.length; i++) {
4252
+ const tx = tokensX[i];
4253
+ const ty = tokensY[i];
4254
+ if (!tx || !ty) continue;
4255
+ out.push({
4256
+ pool: pairs[i],
4257
+ tokenX: tx,
4258
+ tokenY: ty,
4259
+ symbolX: symbolMap.get(tx.toLowerCase()) ?? "?",
4260
+ symbolY: symbolMap.get(ty.toLowerCase()) ?? "?"
4261
+ });
4262
+ }
4263
+ return out;
4264
+ }
4192
4265
  /**
4193
4266
  * Discover all active rewarded LB pools by iterating the factory.
4194
4267
  * Uses 7 multicall batches to minimise RPC round-trips and avoid 429s.
@@ -5169,6 +5242,7 @@ var init_dist2 = __esm({
5169
5242
  "function repay(address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf) external returns (uint256)",
5170
5243
  "function withdraw(address asset, uint256 amount, address to) external returns (uint256)",
5171
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[])",
5172
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)"
5173
5247
  ]);
5174
5248
  ERC20_ABI = parseAbi14([
@@ -5255,13 +5329,37 @@ var init_dist2 = __esm({
5255
5329
  };
5256
5330
  }
5257
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
+ }
5258
5355
  const data = encodeFunctionData14({
5259
5356
  abi: POOL_ABI,
5260
5357
  functionName: "withdraw",
5261
- args: [params.asset, params.amount, params.to]
5358
+ args: [params.asset, withdrawAmount, params.to]
5262
5359
  });
5360
+ const isMax = withdrawAmount === (1n << 256n) - 1n;
5263
5361
  return {
5264
- description: `[${this.protocolName}] Withdraw ${params.amount} from pool`,
5362
+ description: `[${this.protocolName}] Withdraw ${isMax ? "all (auto-max)" : withdrawAmount} from pool`,
5265
5363
  to: this.pool,
5266
5364
  data,
5267
5365
  value: 0n,
@@ -5455,7 +5553,7 @@ var init_dist2 = __esm({
5455
5553
  async getUserPosition(user) {
5456
5554
  if (!this.rpcUrl) throw DefiError.rpcError("No RPC URL configured");
5457
5555
  const client = createPublicClient10({ transport: http10(this.rpcUrl) });
5458
- const result = await client.readContract({
5556
+ const accountData = await client.readContract({
5459
5557
  address: this.pool,
5460
5558
  abi: POOL_ABI,
5461
5559
  functionName: "getUserAccountData",
@@ -5463,21 +5561,63 @@ var init_dist2 = __esm({
5463
5561
  }).catch((e) => {
5464
5562
  throw DefiError.rpcError(`[${this.protocolName}] getUserAccountData failed: ${e}`);
5465
5563
  });
5466
- const [totalCollateralBase, totalDebtBase, , , ltv, healthFactor] = result;
5564
+ const [totalCollateralBase, totalDebtBase, , , ltv, healthFactor] = accountData;
5467
5565
  const MAX_UINT2562 = 2n ** 256n - 1n;
5468
- const hf = healthFactor >= MAX_UINT2562 ? Infinity : Number(healthFactor) / 1e18;
5566
+ const hf = healthFactor >= MAX_UINT2562 ? null : Number(healthFactor) / 1e18;
5469
5567
  const collateralUsd = u256ToF64(totalCollateralBase) / 1e8;
5470
5568
  const debtUsd = u256ToF64(totalDebtBase) / 1e8;
5471
- const ltvBps = u256ToF64(ltv);
5472
- const supplies = collateralUsd > 0 ? [{ asset: zeroAddress8, symbol: "Total Collateral", amount: totalCollateralBase, value_usd: collateralUsd }] : [];
5473
- 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
+ }
5474
5612
  return {
5475
5613
  protocol: this.protocolName,
5476
5614
  user,
5477
5615
  supplies,
5478
5616
  borrows,
5479
5617
  health_factor: hf,
5480
- net_apy: ltvBps / 100
5618
+ ltv_pct: ltvPct,
5619
+ total_collateral_usd: collateralUsd,
5620
+ total_debt_usd: debtUsd
5481
5621
  };
5482
5622
  }
5483
5623
  };
@@ -5734,6 +5874,7 @@ var init_dist2 = __esm({
5734
5874
  "function borrowBalanceStored(address account) external view returns (uint256)",
5735
5875
  "function mint(uint256 mintAmount) external returns (uint256)",
5736
5876
  "function redeem(uint256 redeemTokens) external returns (uint256)",
5877
+ "function redeemUnderlying(uint256 redeemAmount) external returns (uint256)",
5737
5878
  "function borrow(uint256 borrowAmount) external returns (uint256)",
5738
5879
  "function repayBorrow(uint256 repayAmount) external returns (uint256)"
5739
5880
  ]);
@@ -5776,57 +5917,55 @@ var init_dist2 = __esm({
5776
5917
  name() {
5777
5918
  return this.protocolName;
5778
5919
  }
5920
+ // Resolve the vToken whose underlying() matches params.asset. Compound V2 has
5921
+ // a separate vToken per asset, so all builders must dispatch on the request
5922
+ // asset. Returns the resolved vToken or throws if no candidate matches.
5923
+ async vtokenFor(asset) {
5924
+ const v = await this.resolveVtoken(asset);
5925
+ if (!v) throw DefiError.contractError(`[${this.protocolName}] no vToken for asset ${asset}`);
5926
+ return v;
5927
+ }
5779
5928
  async buildSupply(params) {
5780
- const data = encodeFunctionData16({
5781
- abi: CTOKEN_ABI,
5782
- functionName: "mint",
5783
- args: [params.amount]
5784
- });
5929
+ const vtoken = await this.vtokenFor(params.asset);
5930
+ const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "mint", args: [params.amount] });
5785
5931
  return {
5786
- description: `[${this.protocolName}] Supply ${params.amount} to Venus`,
5787
- to: this.defaultVtoken,
5932
+ description: `[${this.protocolName}] Supply ${params.amount} of ${params.asset} to Venus`,
5933
+ to: vtoken,
5788
5934
  data,
5789
5935
  value: 0n,
5790
- gas_estimate: 3e5
5936
+ gas_estimate: 3e5,
5937
+ approvals: [{ token: params.asset, spender: vtoken, amount: params.amount }]
5791
5938
  };
5792
5939
  }
5793
5940
  async buildBorrow(params) {
5794
- const data = encodeFunctionData16({
5795
- abi: CTOKEN_ABI,
5796
- functionName: "borrow",
5797
- args: [params.amount]
5798
- });
5941
+ const vtoken = await this.vtokenFor(params.asset);
5942
+ const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "borrow", args: [params.amount] });
5799
5943
  return {
5800
- description: `[${this.protocolName}] Borrow ${params.amount} from Venus`,
5801
- to: this.defaultVtoken,
5944
+ description: `[${this.protocolName}] Borrow ${params.amount} of ${params.asset} from Venus`,
5945
+ to: vtoken,
5802
5946
  data,
5803
5947
  value: 0n,
5804
5948
  gas_estimate: 35e4
5805
5949
  };
5806
5950
  }
5807
5951
  async buildRepay(params) {
5808
- const data = encodeFunctionData16({
5809
- abi: CTOKEN_ABI,
5810
- functionName: "repayBorrow",
5811
- args: [params.amount]
5812
- });
5952
+ const vtoken = await this.vtokenFor(params.asset);
5953
+ const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "repayBorrow", args: [params.amount] });
5813
5954
  return {
5814
- description: `[${this.protocolName}] Repay ${params.amount} to Venus`,
5815
- to: this.defaultVtoken,
5955
+ description: `[${this.protocolName}] Repay ${params.amount} of ${params.asset} to Venus`,
5956
+ to: vtoken,
5816
5957
  data,
5817
5958
  value: 0n,
5818
- gas_estimate: 3e5
5959
+ gas_estimate: 3e5,
5960
+ approvals: [{ token: params.asset, spender: vtoken, amount: params.amount }]
5819
5961
  };
5820
5962
  }
5821
5963
  async buildWithdraw(params) {
5822
- const data = encodeFunctionData16({
5823
- abi: CTOKEN_ABI,
5824
- functionName: "redeem",
5825
- args: [params.amount]
5826
- });
5964
+ const vtoken = await this.vtokenFor(params.asset);
5965
+ const data = encodeFunctionData16({ abi: CTOKEN_ABI, functionName: "redeemUnderlying", args: [params.amount] });
5827
5966
  return {
5828
- description: `[${this.protocolName}] Withdraw from Venus`,
5829
- to: this.defaultVtoken,
5967
+ description: `[${this.protocolName}] Withdraw ${params.amount} of ${params.asset} from Venus`,
5968
+ to: vtoken,
5830
5969
  data,
5831
5970
  value: 0n,
5832
5971
  gas_estimate: 25e4
@@ -7361,24 +7500,69 @@ var Executor = class _Executor {
7361
7500
  `);
7362
7501
  if (approveTxUrl) process.stderr.write(` Explorer: ${approveTxUrl}
7363
7502
  `);
7364
- 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
+ }
7365
7507
  process.stderr.write(
7366
7508
  ` Approved ${amount} of ${token} for ${spender}
7367
7509
  `
7368
7510
  );
7369
7511
  }
7370
- /** 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
+ */
7371
7548
  async fetchEip1559Fees(rpcUrl) {
7372
7549
  try {
7373
7550
  const client = createPublicClient23({ transport: http23(rpcUrl) });
7374
- const gasPrice = await client.getGasPrice();
7375
7551
  let priorityFee = DEFAULT_PRIORITY_FEE_WEI;
7376
7552
  try {
7377
7553
  priorityFee = await client.estimateMaxPriorityFeePerGas();
7378
7554
  } catch {
7379
7555
  }
7380
- const maxFee = gasPrice * 2n + priorityFee;
7381
- 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 * 2n + priorityFee;
7560
+ return [maxFee, priorityFee];
7561
+ }
7562
+ } catch {
7563
+ }
7564
+ const gasPrice = await client.getGasPrice();
7565
+ return [gasPrice + priorityFee, priorityFee];
7382
7566
  } catch {
7383
7567
  return [0n, 0n];
7384
7568
  }
@@ -7542,8 +7726,8 @@ var Executor = class _Executor {
7542
7726
  `);
7543
7727
  if (preTxUrl) process.stderr.write(` Explorer: ${preTxUrl}
7544
7728
  `);
7545
- const preReceipt = await publicClient.waitForTransactionReceipt({ hash: preTxHash });
7546
- if (preReceipt.status !== "success") {
7729
+ const preReceiptResult = await _Executor.waitForReceiptWithRetry(publicClient, preTxHash);
7730
+ if (preReceiptResult.status !== "success") {
7547
7731
  throw new DefiError("TX_FAILED", `Pre-transaction failed: ${preTx.description}`);
7548
7732
  }
7549
7733
  process.stderr.write(` Pre-tx confirmed
@@ -7585,7 +7769,7 @@ var Executor = class _Executor {
7585
7769
  if (txUrl) process.stderr.write(`Explorer: ${txUrl}
7586
7770
  `);
7587
7771
  process.stderr.write("Waiting for confirmation...\n");
7588
- const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
7772
+ const receipt = await _Executor.waitForReceiptWithRetry(publicClient, txHash);
7589
7773
  const status = receipt.status === "success" ? TxStatus.Confirmed : TxStatus.Failed;
7590
7774
  let mintedTokenId;
7591
7775
  if (receipt.status === "success" && receipt.logs) {