@hypurrquant/defi-cli 1.0.10 → 1.0.12

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 CHANGED
@@ -1968,12 +1968,16 @@ var init_dist2 = __esm({
1968
1968
  functionName: "add_liquidity",
1969
1969
  args: [[params.amount_a, params.amount_b], 0n]
1970
1970
  });
1971
+ const approvals = [];
1972
+ if (params.amount_a > 0n) approvals.push({ token: params.token_a, spender: this.router, amount: params.amount_a });
1973
+ if (params.amount_b > 0n) approvals.push({ token: params.token_b, spender: this.router, amount: params.amount_b });
1971
1974
  return {
1972
1975
  description: `[${this.protocolName}] Curve add liquidity`,
1973
1976
  to: this.router,
1974
1977
  data,
1975
1978
  value: 0n,
1976
- gas_estimate: 4e5
1979
+ gas_estimate: 4e5,
1980
+ approvals
1977
1981
  };
1978
1982
  }
1979
1983
  async buildRemoveLiquidity(params) {
@@ -7333,11 +7337,29 @@ var Executor = class _Executor {
7333
7337
  static applyGasBuffer(gas) {
7334
7338
  return gas * GAS_BUFFER_BPS / 10000n;
7335
7339
  }
7340
+ /**
7341
+ * EIP-1559 max-fee formula: `baseFee * 1.25 + priorityFee`.
7342
+ * Extracted as a static so the math is unit-testable without mocking viem.
7343
+ */
7344
+ static computeMaxFee(baseFeePerGas, priorityFee) {
7345
+ return baseFeePerGas * 125n / 100n + priorityFee;
7346
+ }
7336
7347
  /**
7337
7348
  * Check allowance for a single token/spender pair and send an approve tx if needed.
7338
7349
  * Only called in broadcast mode (not dry-run).
7339
7350
  */
7351
+ /**
7352
+ * Recognize the standard native-token sentinels:
7353
+ * - 0x0000…0000 — defi-cli's internal marker for native gas tokens
7354
+ * - 0xeeee…eeee — 1inch / KyberSwap / OpenOcean canonical sentinel
7355
+ * Any address normalised to lowercase matches case-insensitively.
7356
+ */
7357
+ static isNativeSentinel(token) {
7358
+ const t = token.toLowerCase();
7359
+ return t === "0x0000000000000000000000000000000000000000" || t === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
7360
+ }
7340
7361
  async checkAndApprove(token, spender, amount, owner, publicClient, walletClient) {
7362
+ if (_Executor.isNativeSentinel(token)) return;
7341
7363
  const allowance = await publicClient.readContract({
7342
7364
  address: token,
7343
7365
  abi: ERC20_ABI,
@@ -7418,9 +7440,12 @@ var Executor = class _Executor {
7418
7440
  /**
7419
7441
  * Fetch EIP-1559 fee params. Returns [maxFeePerGas, maxPriorityFeePerGas].
7420
7442
  *
7421
- * Strategy: read the latest block's `baseFeePerGas` and use the canonical
7422
- * EIP-1559 formula `maxFee = baseFee * 2 + priorityFee` (1 block of head-room
7423
- * after a 12.5% bump). Falls back to `getGasPrice() + priorityFee` only when
7443
+ * Strategy: read the latest block's `baseFeePerGas` and apply the conservative
7444
+ * formula `maxFee = baseFee * 1.25 + priorityFee` (one block of head-room — the
7445
+ * 12.5% per-block max bump). The canonical wastes budget on chains where
7446
+ * baseFee is already elevated (e.g., Mantle ~50 gwei → 100 gwei doubled the
7447
+ * MNT requirement and broke multi-step flows). Falls back to
7448
+ * `getGasPrice() + priorityFee` only when
7424
7449
  * the chain doesn't expose `baseFeePerGas` (pre-1559).
7425
7450
  *
7426
7451
  * Why not gasPrice * 2: `getGasPrice()` returns `baseFee + priorityFee`, so
@@ -7439,8 +7464,7 @@ var Executor = class _Executor {
7439
7464
  try {
7440
7465
  const block = await client.getBlock({ blockTag: "latest" });
7441
7466
  if (block.baseFeePerGas !== null && block.baseFeePerGas !== void 0) {
7442
- const maxFee = block.baseFeePerGas * 125n / 100n + priorityFee;
7443
- return [maxFee, priorityFee];
7467
+ return [_Executor.computeMaxFee(block.baseFeePerGas, priorityFee), priorityFee];
7444
7468
  }
7445
7469
  } catch {
7446
7470
  }
@@ -7483,6 +7507,7 @@ var Executor = class _Executor {
7483
7507
  if (tx.approvals && tx.approvals.length > 0) {
7484
7508
  const pendingApprovals = [];
7485
7509
  for (const approval of tx.approvals) {
7510
+ if (_Executor.isNativeSentinel(approval.token)) continue;
7486
7511
  try {
7487
7512
  const allowance = await client.readContract({
7488
7513
  address: approval.token,
@@ -8261,7 +8286,8 @@ function handleSchema(params) {
8261
8286
  function registerSchema(parent, getOpts) {
8262
8287
  parent.command("schema [command]").description("Output JSON schema for a command (agent-friendly)").option("--all", "Show all schemas").action(async (command, opts) => {
8263
8288
  const mode = getOpts();
8264
- const action = opts.all ? "all" : command ?? "all";
8289
+ const raw = opts.all ? "all" : command ?? "all";
8290
+ const action = raw.replace(/-/g, ".");
8265
8291
  const params = { action };
8266
8292
  const schema = handleSchema(params);
8267
8293
  printOutput(schema, mode);
@@ -8452,6 +8478,11 @@ function buildPipelineSteps(p, input = {}) {
8452
8478
  ["--protocol", slug, "slug"],
8453
8479
  ["--gauge", input.gauge, "gauge-from-voter.gaugeForPool"]
8454
8480
  ]);
8481
+ const claimAutoStakeNftGauge = () => `defi ${chainFlag}lp claim ` + buildCmd([
8482
+ ["--protocol", slug, "slug"],
8483
+ ["--gauge", input.gauge, "gauge-from-voter.gaugeForPool"],
8484
+ ["--token-id", input.tokenId, "token-id-from-mint-result"]
8485
+ ]);
8455
8486
  switch (p.reward_strategy) {
8456
8487
  case "lp_fee_only":
8457
8488
  return [
@@ -8476,11 +8507,18 @@ function buildPipelineSteps(p, input = {}) {
8476
8507
  { step: "stake", function: "gauge.deposit(amount)", cli_command: baseFarm },
8477
8508
  { step: "claim", function: "gauge.earned(token, account) \u2192 gauge.getReward(account, tokens[])", cli_command: claimWithGauge() }
8478
8509
  ];
8479
- case "auto_stake":
8510
+ case "auto_stake": {
8511
+ const isNftAutoStake = p.interface === "uniswap_v3";
8480
8512
  return [
8481
8513
  { step: "mint", function: "Router.addLiquidity / NPM.mint", note: "LP automatically receives x(3,3) emissions \u2014 no separate stake step", cli_command: baseAdd },
8482
- { step: "claim", function: "gauge.getReward(account, tokens[])", note: "Multi-token reward (xRAM + WHYPE on Ramses HL)", cli_command: claimWithGauge() }
8514
+ {
8515
+ step: "claim",
8516
+ function: isNftAutoStake ? "NPM.getPeriodReward(currentEpoch, tokenId, tokens[], receiver)" : "gauge.getReward(account, tokens[])",
8517
+ note: isNftAutoStake ? "Ramses CL: claim via NPM with --token-id; gauge.getReward* reverts NOT_AUTHORIZED_CLAIMER for EOAs" : "Multi-token reward (xRAM + WHYPE on Ramses HL)",
8518
+ cli_command: isNftAutoStake ? claimAutoStakeNftGauge() : claimWithGauge()
8519
+ }
8483
8520
  ];
8521
+ }
8484
8522
  case "on_chain_masterchef":
8485
8523
  return [
8486
8524
  { step: "mint", function: "NPM.mint or pool.mint", cli_command: baseAdd },
@@ -9191,7 +9229,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
9191
9229
  note: "Plan output. Run each cli_command sequentially. After the mint step, broadcast mode prints `details.minted_token_id` \u2014 feed that into the next step's --token-id."
9192
9230
  }, getOpts());
9193
9231
  });
9194
- lp.command("remove").description("Auto-unstake (if staked) and remove liquidity from a pool").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--liquidity <amount>", "Liquidity amount to remove in wei").option("--pool <address>", "Pool address (needed to resolve gauge)").option("--gauge <address>", "Gauge contract address (for solidly/hybra unstake)").option("--token-id <id>", "NFT tokenId (for CL gauge or farming positions)").option("--recipient <address>", "Recipient address").option("--redeem-type <n>", "Hybra: 0=instant exit (with penalty), 1=lock into 2-year veHYBR (default \u2014 WARNING: long lock)").option("--bins <binIds>", "Merchant Moe LB: comma-separated bin IDs to withdraw").option("--amounts <wei>", "Merchant Moe LB: comma-separated bin amounts (parallel to --bins, default: full balance)").action(async (opts) => {
9232
+ lp.command("remove").description("Auto-unstake (if staked) and remove liquidity from a pool").requiredOption("--protocol <protocol>", "Protocol slug").option("--token-a <token>", "First token symbol or address (required for V2/Curve/LB)").option("--token-b <token>", "Second token symbol or address (required for V2/Curve/LB)").option("--liquidity <amount>", "Liquidity amount to remove in wei (required for V2/Curve/LB)").option("--pool <address>", "Pool address (needed to resolve gauge)").option("--gauge <address>", "Gauge contract address (for solidly/hybra unstake)").option("--token-id <id>", "NFT tokenId (for CL gauge or farming positions)").option("--recipient <address>", "Recipient address").option("--redeem-type <n>", "Hybra: 0=instant exit (with penalty), 1=lock into 2-year veHYBR (default \u2014 WARNING: long lock)").option("--bins <binIds>", "Merchant Moe LB: comma-separated bin IDs to withdraw").option("--amounts <wei>", "Merchant Moe LB: comma-separated bin amounts (parallel to --bins, default: full balance)").action(async (opts) => {
9195
9233
  const executor = makeExecutor2();
9196
9234
  const chainName = parent.opts().chain;
9197
9235
  if (!chainName) {
@@ -9207,6 +9245,9 @@ function registerLP(parent, getOpts, makeExecutor2) {
9207
9245
  if (iface === "uniswap_v2" && protocol.contracts?.["lb_factory"]) {
9208
9246
  if (!opts.pool) throw new Error(`--pool is required for ${protocol.name} (Liquidity Book \u2014 pass --pool <addr>)`);
9209
9247
  if (!opts.bins) throw new Error("--bins <id1,id2,...> is required for Merchant Moe LB remove");
9248
+ if (!opts.tokenA || !opts.tokenB) {
9249
+ throw new Error(`--token-a and --token-b are required for ${protocol.name} (Liquidity Book) remove`);
9250
+ }
9210
9251
  const lbAdapter = createMerchantMoeLB(protocol, rpcUrl);
9211
9252
  const tokenA2 = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
9212
9253
  const tokenB2 = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
@@ -9249,8 +9290,22 @@ function registerLP(parent, getOpts, makeExecutor2) {
9249
9290
  printOutput({ step: "lb_remove", ...result }, getOpts());
9250
9291
  return;
9251
9292
  }
9252
- const tokenA = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
9253
- const tokenB = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
9293
+ const NFT_REMOVE_IFACES = /* @__PURE__ */ new Set(["uniswap_v3", "algebra_v3", "thena_cl", "hybra"]);
9294
+ const isNftRemove = !!opts.tokenId && NFT_REMOVE_IFACES.has(iface);
9295
+ if (!isNftRemove) {
9296
+ const missing = [];
9297
+ if (!opts.tokenA) missing.push("--token-a");
9298
+ if (!opts.tokenB) missing.push("--token-b");
9299
+ if (!opts.liquidity) missing.push("--liquidity");
9300
+ if (missing.length > 0) {
9301
+ printOutput({
9302
+ error: `${missing.join(", ")} required for ${protocol.name} remove (or pass --token-id for V3/CL NFT-based remove).`
9303
+ }, getOpts());
9304
+ return;
9305
+ }
9306
+ }
9307
+ const tokenA = opts.tokenA ? opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address : void 0;
9308
+ const tokenB = opts.tokenB ? opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address : void 0;
9254
9309
  const poolAddr = opts.pool ? opts.pool : void 0;
9255
9310
  let didUnstake = false;
9256
9311
  if (iface === "algebra_v3" && protocol.contracts?.["farming_center"] && opts.tokenId && poolAddr) {
@@ -9300,7 +9355,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
9300
9355
  if (iface === "hybra" && (!wOpts || wOpts.redeemType === 1)) {
9301
9356
  process.stderr.write("WARNING: Hybra default redeemType=1 locks rewards into 2-year veHYBR NFT. Pass --redeem-type 0 for instant exit (with penalty).\n");
9302
9357
  }
9303
- const withdrawTx = await gaugeAdapter.buildWithdraw(gaugeAddr, BigInt(opts.liquidity), tokenId, wOpts);
9358
+ const withdrawTx = await gaugeAdapter.buildWithdraw(gaugeAddr, opts.liquidity ? BigInt(opts.liquidity) : 0n, tokenId, wOpts);
9304
9359
  const withdrawResult = await executor.execute(withdrawTx);
9305
9360
  printOutput({ step: "unstake_gauge", ...withdrawResult }, getOpts());
9306
9361
  if (withdrawResult.status !== "confirmed" && withdrawResult.status !== "simulated") {
@@ -9315,11 +9370,31 @@ function registerLP(parent, getOpts, makeExecutor2) {
9315
9370
  }
9316
9371
  process.stderr.write("Step 2/2: Removing liquidity...\n");
9317
9372
  const dexAdapter = createDex(protocol, rpcUrl);
9373
+ let removeLiquidity = opts.liquidity ? BigInt(opts.liquidity) : 0n;
9374
+ if (isNftRemove && removeLiquidity === 0n) {
9375
+ const npm = protocol.contracts?.["position_manager"];
9376
+ if (npm) {
9377
+ const c = createPublicClient23({ transport: http23(rpcUrl) });
9378
+ const pos = await detectV3Liquidity(c, npm, BigInt(opts.tokenId));
9379
+ if (pos) {
9380
+ removeLiquidity = pos.liquidity;
9381
+ process.stderr.write(` Read live liquidity ${removeLiquidity} from NPM.positions(${opts.tokenId}).
9382
+ `);
9383
+ }
9384
+ }
9385
+ }
9386
+ if (isNftRemove && removeLiquidity === 0n) {
9387
+ printOutput({
9388
+ error: `tokenId ${opts.tokenId} has zero liquidity (already removed?). Pass --liquidity explicitly to override, or pick a different tokenId.`
9389
+ }, getOpts());
9390
+ return;
9391
+ }
9392
+ const ZERO = "0x0000000000000000000000000000000000000000";
9318
9393
  const removeTx = await dexAdapter.buildRemoveLiquidity({
9319
9394
  protocol: protocol.name,
9320
- token_a: tokenA,
9321
- token_b: tokenB,
9322
- liquidity: BigInt(opts.liquidity),
9395
+ token_a: tokenA ?? ZERO,
9396
+ token_b: tokenB ?? ZERO,
9397
+ liquidity: removeLiquidity,
9323
9398
  recipient,
9324
9399
  token_id: opts.tokenId ? BigInt(opts.tokenId) : void 0
9325
9400
  });
@@ -9698,12 +9773,20 @@ function resolveTokenAddress(registry, chainName, tokenOrAddress) {
9698
9773
  return registry.resolveToken(chainName, tokenOrAddress).address;
9699
9774
  }
9700
9775
  var FALLBACK_ADDRESS = "0x0000000000000000000000000000000000000001";
9776
+ var warnedFallback = false;
9701
9777
  function resolveWallet(override) {
9702
9778
  if (override) return override;
9703
9779
  try {
9704
9780
  const { address } = resolveWalletWithSigner();
9705
9781
  return address;
9706
9782
  } catch {
9783
+ if (!warnedFallback) {
9784
+ process.stderr.write(
9785
+ `WARNING: no wallet configured (set DEFI_WALLET_ADDRESS or DEFI_PRIVATE_KEY, or use --wallet <name>). Using placeholder ${FALLBACK_ADDRESS} for dry-run preview ONLY \u2014 do NOT pass --broadcast with this address.
9786
+ `
9787
+ );
9788
+ warnedFallback = true;
9789
+ }
9707
9790
  return FALLBACK_ADDRESS;
9708
9791
  }
9709
9792
  }
@@ -9806,7 +9889,8 @@ function resolveAsset(registry, chain, asset) {
9806
9889
  }
9807
9890
  async function collectLendingRates(registry, chainName, rpc, assetAddr) {
9808
9891
  const protos = registry.getProtocolsForChain(chainName).filter((p) => p.category === ProtocolCategory.Lending);
9809
- const results = [];
9892
+ const rates = [];
9893
+ const errors = [];
9810
9894
  let first = true;
9811
9895
  for (const proto of protos) {
9812
9896
  if (!first) {
@@ -9815,19 +9899,22 @@ async function collectLendingRates(registry, chainName, rpc, assetAddr) {
9815
9899
  first = false;
9816
9900
  try {
9817
9901
  const lending = createLending(proto, rpc);
9818
- const rates = await lending.getRates(assetAddr);
9819
- results.push(rates);
9902
+ const r = await lending.getRates(assetAddr);
9903
+ rates.push(r);
9820
9904
  } catch (err) {
9821
9905
  process.stderr.write(`Warning: ${proto.name} rates unavailable: ${err}
9822
9906
  `);
9907
+ errors.push({ protocol: proto.name, type: "lending_supply", reason: errMsg(err) });
9823
9908
  }
9824
9909
  }
9825
- return results;
9910
+ return { rates, errors };
9826
9911
  }
9827
9912
  async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
9828
9913
  const opportunities = [];
9829
- const lendingRates = await collectLendingRates(registry, chainName, rpc, assetAddr);
9830
- for (const r of lendingRates) {
9914
+ const errors = [];
9915
+ const lendingResult = await collectLendingRates(registry, chainName, rpc, assetAddr);
9916
+ errors.push(...lendingResult.errors);
9917
+ for (const r of lendingResult.rates) {
9831
9918
  if (r.supply_apy > 0) {
9832
9919
  opportunities.push({
9833
9920
  protocol: r.protocol,
@@ -9853,7 +9940,8 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
9853
9940
  utilization: rates.utilization
9854
9941
  });
9855
9942
  }
9856
- } catch {
9943
+ } catch (e) {
9944
+ errors.push({ protocol: proto.name, type: "morpho_vault", reason: errMsg(e) });
9857
9945
  }
9858
9946
  }
9859
9947
  }
@@ -9869,7 +9957,8 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
9869
9957
  apy: info.apy ?? 0,
9870
9958
  total_assets: info.total_assets.toString()
9871
9959
  });
9872
- } catch {
9960
+ } catch (e) {
9961
+ errors.push({ protocol: proto.name, type: "vault", reason: errMsg(e) });
9873
9962
  }
9874
9963
  }
9875
9964
  }
@@ -9878,7 +9967,7 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
9878
9967
  const ba = b["apy"] ?? 0;
9879
9968
  return ba - aa;
9880
9969
  });
9881
- return opportunities;
9970
+ return { opportunities, errors };
9882
9971
  }
9883
9972
  async function runYieldScan(registry, asset, output) {
9884
9973
  const t0 = Date.now();
@@ -10020,10 +10109,13 @@ function registerYield(parent, getOpts, makeExecutor2) {
10020
10109
  const chain = registry.getChain(chainName);
10021
10110
  const rpc = chain.effectiveRpcUrl();
10022
10111
  const assetAddr = resolveAsset(registry, chainName, opts.asset);
10023
- const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
10112
+ const { rates: results, errors: ratesErrors } = await collectLendingRates(registry, chainName, rpc, assetAddr);
10024
10113
  if (results.length === 0) {
10025
10114
  printOutput(
10026
- { error: `No lending rate data available for asset '${opts.asset}'` },
10115
+ ratesErrors.length > 0 ? {
10116
+ error: `Could not collect lending rates for '${opts.asset}': ${ratesErrors.length} probe(s) failed (likely RPC throttling). Retry, or set ${chainName.toUpperCase()}_RPC_URL.`,
10117
+ failed_probes: ratesErrors
10118
+ } : { error: `No lending rate data available for asset '${opts.asset}'` },
10027
10119
  getOpts()
10028
10120
  );
10029
10121
  process.exit(1);
@@ -10265,9 +10357,16 @@ function registerYield(parent, getOpts, makeExecutor2) {
10265
10357
  const assetAddr = resolveAsset(registry, chainName, asset);
10266
10358
  const strategy = opts.strategy ?? "auto";
10267
10359
  if (strategy === "auto") {
10268
- const opportunities = await collectAllYields(registry, chainName, rpc, asset, assetAddr);
10360
+ const { opportunities, errors } = await collectAllYields(registry, chainName, rpc, asset, assetAddr);
10269
10361
  if (opportunities.length === 0) {
10270
- printOutput({ error: `No yield opportunities found for '${asset}'` }, getOpts());
10362
+ if (errors.length > 0) {
10363
+ printOutput({
10364
+ error: `Could not collect yield data for '${asset}': ${errors.length} probe(s) failed (likely RPC throttling or transport error). Retry, or set a private RPC URL via ${chainName.toUpperCase()}_RPC_URL.`,
10365
+ failed_probes: errors
10366
+ }, getOpts());
10367
+ } else {
10368
+ printOutput({ error: `No yield opportunities found for '${asset}'` }, getOpts());
10369
+ }
10271
10370
  process.exit(1);
10272
10371
  return;
10273
10372
  }
@@ -10297,9 +10396,12 @@ function registerYield(parent, getOpts, makeExecutor2) {
10297
10396
  getOpts()
10298
10397
  );
10299
10398
  } else if (strategy === "best-supply") {
10300
- const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
10399
+ const { rates: results, errors: rErr } = await collectLendingRates(registry, chainName, rpc, assetAddr);
10301
10400
  if (results.length === 0) {
10302
- printOutput({ error: `No lending rate data available for asset '${asset}'` }, getOpts());
10401
+ printOutput(
10402
+ rErr.length > 0 ? { error: `Could not collect lending rates for '${asset}': ${rErr.length} probe(s) failed (RPC throttling likely).`, failed_probes: rErr } : { error: `No lending rate data available for asset '${asset}'` },
10403
+ getOpts()
10404
+ );
10303
10405
  process.exit(1);
10304
10406
  return;
10305
10407
  }
@@ -10322,9 +10424,12 @@ function registerYield(parent, getOpts, makeExecutor2) {
10322
10424
  getOpts()
10323
10425
  );
10324
10426
  } else if (strategy === "leverage-loop") {
10325
- const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
10427
+ const { rates: results, errors: lErr } = await collectLendingRates(registry, chainName, rpc, assetAddr);
10326
10428
  if (results.length === 0) {
10327
- printOutput({ error: `No lending rate data available for asset '${asset}'` }, getOpts());
10429
+ printOutput(
10430
+ lErr.length > 0 ? { error: `Could not collect lending rates for '${asset}': ${lErr.length} probe(s) failed (RPC throttling likely).`, failed_probes: lErr } : { error: `No lending rate data available for asset '${asset}'` },
10431
+ getOpts()
10432
+ );
10328
10433
  process.exit(1);
10329
10434
  return;
10330
10435
  }
@@ -10388,14 +10493,14 @@ function registerYield(parent, getOpts, makeExecutor2) {
10388
10493
 
10389
10494
  // src/commands/portfolio.ts
10390
10495
  init_dist();
10391
- import { encodeFunctionData as encodeFunctionData29, parseAbi as parseAbi33 } from "viem";
10496
+ import { createPublicClient as createPublicClient25, encodeFunctionData as encodeFunctionData29, http as http25, parseAbi as parseAbi33 } from "viem";
10392
10497
 
10393
10498
  // src/portfolio-tracker.ts
10394
10499
  init_dist();
10395
10500
  import { mkdirSync, writeFileSync, readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
10396
10501
  import { homedir } from "os";
10397
10502
  import { resolve as resolve3 } from "path";
10398
- import { encodeFunctionData as encodeFunctionData28, parseAbi as parseAbi31 } from "viem";
10503
+ import { createPublicClient as createPublicClient24, encodeFunctionData as encodeFunctionData28, http as http24, parseAbi as parseAbi31 } from "viem";
10399
10504
  var ERC20_ABI4 = parseAbi31([
10400
10505
  "function balanceOf(address owner) external view returns (uint256)"
10401
10506
  ]);
@@ -10446,7 +10551,16 @@ async function takeSnapshot(chainName, wallet, registry) {
10446
10551
  const oracleEntry = registry.getProtocolsForChain(chainName).find((p) => p.interface === "aave_v3" && p.contracts?.["oracle"]);
10447
10552
  const oracleAddr = oracleEntry?.contracts?.["oracle"];
10448
10553
  const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
10554
+ const priceTokens = [];
10449
10555
  if (oracleAddr) {
10556
+ for (const t of tokenEntries) {
10557
+ calls.push([
10558
+ oracleAddr,
10559
+ encodeFunctionData28({ abi: ORACLE_ABI4, functionName: "getAssetPrice", args: [t.address] })
10560
+ ]);
10561
+ callLabels.push(`price:${t.address}`);
10562
+ priceTokens.push(t.address);
10563
+ }
10450
10564
  calls.push([
10451
10565
  oracleAddr,
10452
10566
  encodeFunctionData28({ abi: ORACLE_ABI4, functionName: "getAssetPrice", args: [wrappedNative] })
@@ -10457,10 +10571,19 @@ async function takeSnapshot(chainName, wallet, registry) {
10457
10571
  if (calls.length > 0) {
10458
10572
  results = await multicallRead(rpc, calls);
10459
10573
  }
10574
+ const balanceCount = tokenEntries.length;
10575
+ const lendingCount = lendingProtocols.length;
10576
+ const priceStartIdx = balanceCount + lendingCount;
10460
10577
  let nativePriceUsd = 0;
10578
+ const priceByToken = /* @__PURE__ */ new Map();
10461
10579
  if (oracleAddr) {
10462
- const priceData = results[results.length - 1] ?? null;
10463
- nativePriceUsd = Number(decodeU256Word(priceData)) / 1e8;
10580
+ for (let i = 0; i < priceTokens.length; i++) {
10581
+ const priceData = results[priceStartIdx + i] ?? null;
10582
+ const px = Number(decodeU256Word(priceData)) / 1e8;
10583
+ if (px > 0) priceByToken.set(priceTokens[i].toLowerCase(), px);
10584
+ }
10585
+ const nativePriceData = results[priceStartIdx + priceTokens.length] ?? null;
10586
+ nativePriceUsd = Number(decodeU256Word(nativePriceData)) / 1e8;
10464
10587
  }
10465
10588
  let idx = 0;
10466
10589
  const tokens = [];
@@ -10470,8 +10593,20 @@ async function takeSnapshot(chainName, wallet, registry) {
10470
10593
  const balance = decodeU256Word(results[idx] ?? null);
10471
10594
  const balF64 = Number(balance) / 10 ** entry.decimals;
10472
10595
  const symbolUpper = entry.symbol.toUpperCase();
10473
- const priceUsd = symbolUpper.includes("USD") ? 1 : nativePriceUsd;
10474
- const valueUsd = balF64 * priceUsd;
10596
+ const tokenAddrLower = entry.address.toLowerCase();
10597
+ let priceUsd;
10598
+ let valueUsd;
10599
+ if (symbolUpper.includes("USD")) {
10600
+ priceUsd = 1;
10601
+ valueUsd = balF64;
10602
+ } else if (tokenAddrLower === wrappedNative.toLowerCase()) {
10603
+ priceUsd = nativePriceUsd;
10604
+ valueUsd = balF64 * nativePriceUsd;
10605
+ } else {
10606
+ const px = priceByToken.get(tokenAddrLower);
10607
+ priceUsd = px ?? 0;
10608
+ valueUsd = px && px > 0 ? balF64 * px : 0;
10609
+ }
10475
10610
  totalValueUsd += valueUsd;
10476
10611
  tokens.push({
10477
10612
  token: entry.address,
@@ -10512,6 +10647,23 @@ async function takeSnapshot(chainName, wallet, registry) {
10512
10647
  }
10513
10648
  idx++;
10514
10649
  }
10650
+ try {
10651
+ const client = createPublicClient24({ transport: http24(rpc) });
10652
+ const nativeBalance = await client.getBalance({ address: user });
10653
+ if (nativeBalance > 0n) {
10654
+ const nativeF64 = Number(nativeBalance) / 1e18;
10655
+ const nativeValueUsd = nativeF64 * nativePriceUsd;
10656
+ totalValueUsd += nativeValueUsd;
10657
+ tokens.push({
10658
+ token: wrappedNative,
10659
+ symbol: chain.native_token ?? "NATIVE",
10660
+ balance: nativeBalance,
10661
+ value_usd: nativeValueUsd,
10662
+ price_usd: nativePriceUsd
10663
+ });
10664
+ }
10665
+ } catch {
10666
+ }
10515
10667
  return {
10516
10668
  timestamp: Date.now(),
10517
10669
  chain: chainName,
@@ -10629,6 +10781,10 @@ function registerPortfolio(parent, getOpts) {
10629
10781
  const calls = [];
10630
10782
  const callLabels = [];
10631
10783
  const tokenSymbols = (registry.tokens.get(chainName) ?? []).map((t) => t.symbol);
10784
+ const tokenAddrsByCallIdx = [];
10785
+ const oracleEntry = registry.getProtocolsForChain(chainName).find((p) => p.interface === "aave_v3" && p.contracts?.["oracle"]);
10786
+ const oracleAddr = oracleEntry?.contracts?.["oracle"];
10787
+ const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
10632
10788
  for (const symbol of tokenSymbols) {
10633
10789
  let entry;
10634
10790
  try {
@@ -10642,6 +10798,7 @@ function registerPortfolio(parent, getOpts) {
10642
10798
  encodeFunctionData29({ abi: ERC20_ABI5, functionName: "balanceOf", args: [user] })
10643
10799
  ]);
10644
10800
  callLabels.push(`balance:${symbol}`);
10801
+ tokenAddrsByCallIdx.push(entry.address);
10645
10802
  }
10646
10803
  const lendingProtocols = registry.getProtocolsForChain(chainName).filter((p) => p.category === ProtocolCategory.Lending && p.interface === "aave_v3").filter((p) => p.contracts?.["pool"]);
10647
10804
  for (const p of lendingProtocols) {
@@ -10651,10 +10808,16 @@ function registerPortfolio(parent, getOpts) {
10651
10808
  ]);
10652
10809
  callLabels.push(`lending:${p.name}`);
10653
10810
  }
10654
- const oracleEntry = registry.getProtocolsForChain(chainName).find((p) => p.interface === "aave_v3" && p.contracts?.["oracle"]);
10655
- const oracleAddr = oracleEntry?.contracts?.["oracle"];
10656
- const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
10811
+ const priceTokens = [];
10657
10812
  if (oracleAddr) {
10813
+ for (const tokenAddr of tokenAddrsByCallIdx) {
10814
+ calls.push([
10815
+ oracleAddr,
10816
+ encodeFunctionData29({ abi: ORACLE_ABI5, functionName: "getAssetPrice", args: [tokenAddr] })
10817
+ ]);
10818
+ callLabels.push(`price:${tokenAddr}`);
10819
+ priceTokens.push(tokenAddr);
10820
+ }
10658
10821
  calls.push([
10659
10822
  oracleAddr,
10660
10823
  encodeFunctionData29({ abi: ORACLE_ABI5, functionName: "getAssetPrice", args: [wrappedNative] })
@@ -10679,10 +10842,19 @@ function registerPortfolio(parent, getOpts) {
10679
10842
  printOutput({ error: `Multicall failed: ${errMsg(e)}` }, mode);
10680
10843
  return;
10681
10844
  }
10845
+ const balanceCallCount = tokenAddrsByCallIdx.length;
10846
+ const lendingCallCount = lendingProtocols.length;
10847
+ const priceStartIdx = balanceCallCount + lendingCallCount;
10682
10848
  let nativePriceUsd = 0;
10849
+ const priceByToken = /* @__PURE__ */ new Map();
10683
10850
  if (oracleAddr) {
10684
- const priceData = results[results.length - 1] ?? null;
10685
- nativePriceUsd = Number(decodeU2562(priceData)) / 1e8;
10851
+ for (let i = 0; i < priceTokens.length; i++) {
10852
+ const priceData = results[priceStartIdx + i] ?? null;
10853
+ const px = Number(decodeU2562(priceData)) / 1e8;
10854
+ if (px > 0) priceByToken.set(priceTokens[i].toLowerCase(), px);
10855
+ }
10856
+ const nativePriceData = results[priceStartIdx + priceTokens.length] ?? null;
10857
+ nativePriceUsd = Number(decodeU2562(nativePriceData)) / 1e8;
10686
10858
  }
10687
10859
  let totalValueUsd = 0;
10688
10860
  let idx = 0;
@@ -10701,12 +10873,21 @@ function registerPortfolio(parent, getOpts) {
10701
10873
  const decimals = entry.decimals;
10702
10874
  const balF64 = Number(balance) / 10 ** decimals;
10703
10875
  const symbolUpper = symbol.toUpperCase();
10704
- const valueUsd = symbolUpper.includes("USD") || symbolUpper.includes("usd") ? balF64 : balF64 * nativePriceUsd;
10705
- totalValueUsd += valueUsd;
10876
+ const tokenAddrLower = entry.address.toLowerCase();
10877
+ let valueUsd;
10878
+ if (symbolUpper.includes("USD")) {
10879
+ valueUsd = balF64;
10880
+ } else if (tokenAddrLower === wrappedNative.toLowerCase()) {
10881
+ valueUsd = balF64 * nativePriceUsd;
10882
+ } else {
10883
+ const px = priceByToken.get(tokenAddrLower);
10884
+ valueUsd = px && px > 0 ? balF64 * px : null;
10885
+ }
10886
+ if (valueUsd !== null) totalValueUsd += valueUsd;
10706
10887
  tokenBalances.push({
10707
10888
  symbol,
10708
10889
  balance: balF64.toFixed(4),
10709
- value_usd: valueUsd.toFixed(2)
10890
+ value_usd: valueUsd !== null ? valueUsd.toFixed(2) : null
10710
10891
  });
10711
10892
  }
10712
10893
  idx++;
@@ -10736,11 +10917,23 @@ function registerPortfolio(parent, getOpts) {
10736
10917
  }
10737
10918
  idx++;
10738
10919
  }
10920
+ let nativeBalance = 0n;
10921
+ let nativeValueUsd = 0;
10922
+ try {
10923
+ const client = createPublicClient25({ transport: http25(rpc) });
10924
+ nativeBalance = await client.getBalance({ address: user });
10925
+ const nativeF64 = Number(nativeBalance) / 1e18;
10926
+ nativeValueUsd = nativeF64 * nativePriceUsd;
10927
+ if (nativeBalance > 0n) totalValueUsd += nativeValueUsd;
10928
+ } catch {
10929
+ }
10739
10930
  printOutput(
10740
10931
  {
10741
10932
  address: user,
10742
10933
  chain: chain.name,
10743
10934
  native_price_usd: nativePriceUsd.toFixed(2),
10935
+ native_balance: (Number(nativeBalance) / 1e18).toFixed(6),
10936
+ native_value_usd: nativeValueUsd.toFixed(2),
10744
10937
  total_value_usd: totalValueUsd.toFixed(2),
10745
10938
  token_balances: tokenBalances,
10746
10939
  lending_positions: lendingPositions
@@ -11006,38 +11199,50 @@ function registerPrice(parent, getOpts) {
11006
11199
 
11007
11200
  // src/commands/wallet.ts
11008
11201
  init_dist();
11009
- import { createPublicClient as createPublicClient24, http as http24, formatEther } from "viem";
11202
+ import { createPublicClient as createPublicClient26, http as http26, formatEther } from "viem";
11203
+ function resolveCurrentAddress(override) {
11204
+ if (override) return { address: override, source: "flag" };
11205
+ try {
11206
+ const { address, signer } = resolveWalletWithSigner();
11207
+ return { address, source: signer ? "ows" : process.env["DEFI_PRIVATE_KEY"] ? "private_key" : "env" };
11208
+ } catch {
11209
+ return { address: null, source: "none" };
11210
+ }
11211
+ }
11010
11212
  function registerWallet(parent, getOpts) {
11011
11213
  const wallet = parent.command("wallet").description("Wallet management");
11012
- wallet.command("balance").description("Show native token balance").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
11214
+ wallet.command("balance").description("Show native token balance").option("--address <address>", "Wallet address (defaults to OWS vault, DEFI_PRIVATE_KEY, or DEFI_WALLET_ADDRESS)").action(async (opts) => {
11013
11215
  const chainName = requireChain(parent, getOpts);
11014
11216
  if (!chainName) return;
11015
- const addr = opts.address ?? process.env["DEFI_WALLET_ADDRESS"];
11217
+ const { address: addr, source } = resolveCurrentAddress(opts.address);
11016
11218
  if (!addr) {
11017
- printOutput({ error: "--address required (or set DEFI_WALLET_ADDRESS)" }, getOpts());
11219
+ printOutput({
11220
+ error: "No wallet configured. Set DEFI_WALLET_ADDRESS, set DEFI_PRIVATE_KEY, or pass --address."
11221
+ }, getOpts());
11018
11222
  return;
11019
11223
  }
11020
11224
  const registry = Registry.loadEmbedded();
11021
11225
  const chain = registry.getChain(chainName);
11022
- const client = createPublicClient24({ transport: http24(chain.effectiveRpcUrl()) });
11226
+ const client = createPublicClient26({ transport: http26(chain.effectiveRpcUrl()) });
11023
11227
  const balance = await client.getBalance({ address: addr });
11024
11228
  printOutput({
11025
11229
  chain: chain.name,
11026
11230
  address: addr,
11231
+ wallet_source: source,
11027
11232
  native_token: chain.native_token,
11028
11233
  balance_wei: balance,
11029
11234
  balance_formatted: formatEther(balance)
11030
11235
  }, getOpts());
11031
11236
  });
11032
11237
  wallet.command("address").description("Show configured wallet address").action(async () => {
11033
- const addr = process.env.DEFI_WALLET_ADDRESS ?? "(not set)";
11034
- printOutput({ address: addr }, getOpts());
11238
+ const { address, source } = resolveCurrentAddress();
11239
+ printOutput({ address, source }, getOpts());
11035
11240
  });
11036
11241
  }
11037
11242
 
11038
11243
  // src/commands/token.ts
11039
11244
  init_dist();
11040
- import { createPublicClient as createPublicClient25, http as http25, maxUint256 } from "viem";
11245
+ import { createPublicClient as createPublicClient27, http as http27, maxUint256 } from "viem";
11041
11246
  function registerToken(parent, getOpts, makeExecutor2) {
11042
11247
  const token = parent.command("token").description("Token operations: approve, allowance, transfer, balance");
11043
11248
  token.command("balance").description("Query token balance for an address").requiredOption("--token <token>", "Token symbol or address").option("--owner <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
@@ -11050,7 +11255,7 @@ function registerToken(parent, getOpts, makeExecutor2) {
11050
11255
  }
11051
11256
  const registry = Registry.loadEmbedded();
11052
11257
  const chain = registry.getChain(chainName);
11053
- const client = createPublicClient25({ transport: http25(chain.effectiveRpcUrl()) });
11258
+ const client = createPublicClient27({ transport: http27(chain.effectiveRpcUrl()) });
11054
11259
  const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
11055
11260
  const [balance, symbol, decimals] = await Promise.all([
11056
11261
  client.readContract({ address: tokenAddr, abi: erc20Abi, functionName: "balanceOf", args: [owner] }),
@@ -11086,7 +11291,7 @@ function registerToken(parent, getOpts, makeExecutor2) {
11086
11291
  }
11087
11292
  const registry = Registry.loadEmbedded();
11088
11293
  const chain = registry.getChain(chainName);
11089
- const client = createPublicClient25({ transport: http25(chain.effectiveRpcUrl()) });
11294
+ const client = createPublicClient27({ transport: http27(chain.effectiveRpcUrl()) });
11090
11295
  const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
11091
11296
  const allowance = await client.readContract({
11092
11297
  address: tokenAddr,
@@ -11204,6 +11409,17 @@ var CCTP_USDC_ADDRESSES = {
11204
11409
  base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
11205
11410
  polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
11206
11411
  };
11412
+ function cctpMinFeeGuard(amountWei, maxFeeSubunits, feeUsdc) {
11413
+ if (amountWei <= maxFeeSubunits) {
11414
+ const amountUsdc = Number(amountWei) / 1e6;
11415
+ return {
11416
+ error: `CCTP: amount ${amountWei.toString()} (${amountUsdc} USDC) is below the minimum bridge fee of ${maxFeeSubunits} (${feeUsdc} USDC). Increase --amount.`,
11417
+ minimum_amount_wei: maxFeeSubunits.toString(),
11418
+ minimum_amount_usdc: feeUsdc
11419
+ };
11420
+ }
11421
+ return null;
11422
+ }
11207
11423
  async function getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc) {
11208
11424
  try {
11209
11425
  const res = await fetch(`${CCTP_FEE_API}/${srcDomain}/${dstDomain}`);
@@ -11292,12 +11508,9 @@ function registerBridge(parent, getOpts) {
11292
11508
  }
11293
11509
  const amountUsdc = Number(BigInt(opts.amount)) / 1e6;
11294
11510
  const { fee, maxFeeSubunits } = await getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc);
11295
- if (BigInt(opts.amount) <= maxFeeSubunits) {
11296
- printOutput({
11297
- error: `CCTP: amount ${opts.amount} (${amountUsdc} USDC) is below the minimum bridge fee of ${maxFeeSubunits} (${fee} USDC). Increase --amount.`,
11298
- minimum_amount_wei: maxFeeSubunits.toString(),
11299
- minimum_amount_usdc: fee
11300
- }, getOpts());
11511
+ const guardErr = cctpMinFeeGuard(BigInt(opts.amount), maxFeeSubunits, fee);
11512
+ if (guardErr) {
11513
+ printOutput(guardErr, getOpts());
11301
11514
  return;
11302
11515
  }
11303
11516
  const recipientPadded = `0x${"0".repeat(24)}${recipient.replace("0x", "").toLowerCase()}`;
@@ -11578,12 +11791,14 @@ function registerSwap(parent, getOpts, makeExecutor2) {
11578
11791
  slippagePct,
11579
11792
  wallet
11580
11793
  );
11794
+ const fromLower = fromAddr.toLowerCase();
11795
+ const isNativeInput = fromLower === "0x0000000000000000000000000000000000000000" || fromLower === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
11581
11796
  const tx = {
11582
11797
  description: `OpenOcean: swap ${opts.amount} of ${fromAddr} -> ${toAddr}`,
11583
11798
  to: swap.to,
11584
11799
  data: swap.data,
11585
11800
  value: parseBigIntValue(swap.value),
11586
- approvals: [{ token: fromAddr, spender: swap.to, amount: BigInt(opts.amount) }]
11801
+ ...isNativeInput ? {} : { approvals: [{ token: fromAddr, spender: swap.to, amount: BigInt(opts.amount) }] }
11587
11802
  };
11588
11803
  const result = await executor.execute(tx);
11589
11804
  printOutput({
@@ -11675,7 +11890,8 @@ import pc2 from "picocolors";
11675
11890
  import { createInterface } from "readline";
11676
11891
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
11677
11892
  import { resolve as resolve4 } from "path";
11678
- var DEFI_DIR = resolve4(process.env.HOME || "~", ".defi");
11893
+ import { homedir as homedir2 } from "os";
11894
+ var DEFI_DIR = resolve4(homedir2(), ".defi");
11679
11895
  var ENV_FILE = resolve4(DEFI_DIR, ".env");
11680
11896
  function ensureDefiDir() {
11681
11897
  if (!existsSync3(DEFI_DIR)) mkdirSync2(DEFI_DIR, { recursive: true, mode: 448 });
@@ -11710,6 +11926,44 @@ function writeEnvFile(env) {
11710
11926
  function ask(rl, question) {
11711
11927
  return new Promise((res) => rl.question(question, (answer) => res(answer.trim())));
11712
11928
  }
11929
+ function askSecret(rl, question) {
11930
+ const stdin = process.stdin;
11931
+ if (!stdin.isTTY) return ask(rl, question);
11932
+ process.stdout.write(question);
11933
+ const rlAny = rl;
11934
+ const original = rlAny._writeToOutput;
11935
+ rlAny._writeToOutput = (s) => {
11936
+ if (s.includes("\n") || s.includes("\r")) {
11937
+ original?.call(rl, s);
11938
+ }
11939
+ };
11940
+ return new Promise((res) => {
11941
+ rl.question("", (answer) => {
11942
+ rlAny._writeToOutput = original;
11943
+ process.stdout.write("\n");
11944
+ res(answer.trim());
11945
+ });
11946
+ });
11947
+ }
11948
+ function maskRpcUrl(s) {
11949
+ try {
11950
+ const u = new URL(s);
11951
+ if (u.pathname && u.pathname !== "/" && u.pathname.length > 1) {
11952
+ return `${u.protocol}//${u.host}/***`;
11953
+ }
11954
+ return `${u.protocol}//${u.host}`;
11955
+ } catch {
11956
+ return "***";
11957
+ }
11958
+ }
11959
+ function isValidRpcUrl(s) {
11960
+ try {
11961
+ const u = new URL(s);
11962
+ return u.protocol === "http:" || u.protocol === "https:";
11963
+ } catch {
11964
+ return false;
11965
+ }
11966
+ }
11713
11967
  function isValidAddress(s) {
11714
11968
  return /^0x[0-9a-fA-F]{40}$/.test(s);
11715
11969
  }
@@ -11734,7 +11988,14 @@ function registerSetup(program2) {
11734
11988
  if (Object.keys(existing).length > 0) {
11735
11989
  console.log(pc2.white(" Current configuration:"));
11736
11990
  for (const [key, value] of Object.entries(existing)) {
11737
- const masked = key.toLowerCase().includes("key") ? value.slice(0, 6) + "..." + value.slice(-4) : value;
11991
+ let masked;
11992
+ if (key.toLowerCase().includes("key")) {
11993
+ masked = value.slice(0, 6) + "..." + value.slice(-4);
11994
+ } else if (key.endsWith("RPC_URL")) {
11995
+ masked = maskRpcUrl(value);
11996
+ } else {
11997
+ masked = value;
11998
+ }
11738
11999
  console.log(` ${pc2.cyan(key.padEnd(24))} ${pc2.gray(masked)}`);
11739
12000
  }
11740
12001
  console.log();
@@ -11748,7 +12009,7 @@ function registerSetup(program2) {
11748
12009
  }
11749
12010
  const newEnv = {};
11750
12011
  console.log(pc2.cyan(pc2.bold(" Wallet")));
11751
- const privateKey = await ask(rl, " Private key (optional, for --broadcast, 0x...): ");
12012
+ const privateKey = await askSecret(rl, " Private key (optional, for --broadcast, 0x... \u2014 input hidden): ");
11752
12013
  if (privateKey) {
11753
12014
  const normalized = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
11754
12015
  if (!isValidPrivateKey(normalized)) {
@@ -11784,6 +12045,10 @@ function registerSetup(program2) {
11784
12045
  for (const { env, label } of rpcPrompts) {
11785
12046
  const value = await ask(rl, ` ${label} RPC URL: `);
11786
12047
  if (value) {
12048
+ if (!isValidRpcUrl(value)) {
12049
+ console.log(pc2.yellow(` Invalid URL (${value}). Skipped \u2014 re-run setup to retry.`));
12050
+ continue;
12051
+ }
11787
12052
  newEnv[env] = value;
11788
12053
  console.log(` ${pc2.green("OK")} ${label} RPC set`);
11789
12054
  }
@@ -11807,7 +12072,7 @@ function registerSetup(program2) {
11807
12072
  ];
11808
12073
  for (const [k, label] of rpcSummary) {
11809
12074
  const v = finalEnv[k];
11810
- if (v) console.log(` ${label}: ${pc2.gray(v)}`);
12075
+ if (v) console.log(` ${label}: ${pc2.gray(maskRpcUrl(v))}`);
11811
12076
  }
11812
12077
  console.log(pc2.bold(pc2.white("\n Next steps:")));
11813
12078
  console.log(` ${pc2.green("defi portfolio")} view balances & positions`);
@@ -11826,11 +12091,11 @@ function registerSetup(program2) {
11826
12091
  // src/commands/ows.ts
11827
12092
  import pc3 from "picocolors";
11828
12093
  init_dist();
11829
- import { createPublicClient as createPublicClient26, http as http26, formatEther as formatEther2 } from "viem";
12094
+ import { createPublicClient as createPublicClient28, http as http28, formatEther as formatEther2 } from "viem";
11830
12095
  async function getEvmBalance(address, chainName) {
11831
12096
  const registry = Registry.loadEmbedded();
11832
12097
  const chain = registry.getChain(chainName);
11833
- const client = createPublicClient26({ transport: http26(chain.effectiveRpcUrl()) });
12098
+ const client = createPublicClient28({ transport: http28(chain.effectiveRpcUrl()) });
11834
12099
  const balance = await client.getBalance({ address });
11835
12100
  return {
11836
12101
  native_token: chain.native_token,