@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/main.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) {
@@ -7337,11 +7341,29 @@ var Executor = class _Executor {
7337
7341
  static applyGasBuffer(gas) {
7338
7342
  return gas * GAS_BUFFER_BPS / 10000n;
7339
7343
  }
7344
+ /**
7345
+ * EIP-1559 max-fee formula: `baseFee * 1.25 + priorityFee`.
7346
+ * Extracted as a static so the math is unit-testable without mocking viem.
7347
+ */
7348
+ static computeMaxFee(baseFeePerGas, priorityFee) {
7349
+ return baseFeePerGas * 125n / 100n + priorityFee;
7350
+ }
7340
7351
  /**
7341
7352
  * Check allowance for a single token/spender pair and send an approve tx if needed.
7342
7353
  * Only called in broadcast mode (not dry-run).
7343
7354
  */
7355
+ /**
7356
+ * Recognize the standard native-token sentinels:
7357
+ * - 0x0000…0000 — defi-cli's internal marker for native gas tokens
7358
+ * - 0xeeee…eeee — 1inch / KyberSwap / OpenOcean canonical sentinel
7359
+ * Any address normalised to lowercase matches case-insensitively.
7360
+ */
7361
+ static isNativeSentinel(token) {
7362
+ const t = token.toLowerCase();
7363
+ return t === "0x0000000000000000000000000000000000000000" || t === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
7364
+ }
7344
7365
  async checkAndApprove(token, spender, amount, owner, publicClient, walletClient) {
7366
+ if (_Executor.isNativeSentinel(token)) return;
7345
7367
  const allowance = await publicClient.readContract({
7346
7368
  address: token,
7347
7369
  abi: ERC20_ABI,
@@ -7422,9 +7444,12 @@ var Executor = class _Executor {
7422
7444
  /**
7423
7445
  * Fetch EIP-1559 fee params. Returns [maxFeePerGas, maxPriorityFeePerGas].
7424
7446
  *
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
7447
+ * Strategy: read the latest block's `baseFeePerGas` and apply the conservative
7448
+ * formula `maxFee = baseFee * 1.25 + priorityFee` (one block of head-room — the
7449
+ * 12.5% per-block max bump). The canonical wastes budget on chains where
7450
+ * baseFee is already elevated (e.g., Mantle ~50 gwei → 100 gwei doubled the
7451
+ * MNT requirement and broke multi-step flows). Falls back to
7452
+ * `getGasPrice() + priorityFee` only when
7428
7453
  * the chain doesn't expose `baseFeePerGas` (pre-1559).
7429
7454
  *
7430
7455
  * Why not gasPrice * 2: `getGasPrice()` returns `baseFee + priorityFee`, so
@@ -7443,8 +7468,7 @@ var Executor = class _Executor {
7443
7468
  try {
7444
7469
  const block = await client.getBlock({ blockTag: "latest" });
7445
7470
  if (block.baseFeePerGas !== null && block.baseFeePerGas !== void 0) {
7446
- const maxFee = block.baseFeePerGas * 125n / 100n + priorityFee;
7447
- return [maxFee, priorityFee];
7471
+ return [_Executor.computeMaxFee(block.baseFeePerGas, priorityFee), priorityFee];
7448
7472
  }
7449
7473
  } catch {
7450
7474
  }
@@ -7487,6 +7511,7 @@ var Executor = class _Executor {
7487
7511
  if (tx.approvals && tx.approvals.length > 0) {
7488
7512
  const pendingApprovals = [];
7489
7513
  for (const approval of tx.approvals) {
7514
+ if (_Executor.isNativeSentinel(approval.token)) continue;
7490
7515
  try {
7491
7516
  const allowance = await client.readContract({
7492
7517
  address: approval.token,
@@ -8265,7 +8290,8 @@ function handleSchema(params) {
8265
8290
  function registerSchema(parent, getOpts) {
8266
8291
  parent.command("schema [command]").description("Output JSON schema for a command (agent-friendly)").option("--all", "Show all schemas").action(async (command, opts) => {
8267
8292
  const mode = getOpts();
8268
- const action = opts.all ? "all" : command ?? "all";
8293
+ const raw = opts.all ? "all" : command ?? "all";
8294
+ const action = raw.replace(/-/g, ".");
8269
8295
  const params = { action };
8270
8296
  const schema = handleSchema(params);
8271
8297
  printOutput(schema, mode);
@@ -8456,6 +8482,11 @@ function buildPipelineSteps(p, input = {}) {
8456
8482
  ["--protocol", slug, "slug"],
8457
8483
  ["--gauge", input.gauge, "gauge-from-voter.gaugeForPool"]
8458
8484
  ]);
8485
+ const claimAutoStakeNftGauge = () => `defi ${chainFlag}lp claim ` + buildCmd([
8486
+ ["--protocol", slug, "slug"],
8487
+ ["--gauge", input.gauge, "gauge-from-voter.gaugeForPool"],
8488
+ ["--token-id", input.tokenId, "token-id-from-mint-result"]
8489
+ ]);
8459
8490
  switch (p.reward_strategy) {
8460
8491
  case "lp_fee_only":
8461
8492
  return [
@@ -8480,11 +8511,18 @@ function buildPipelineSteps(p, input = {}) {
8480
8511
  { step: "stake", function: "gauge.deposit(amount)", cli_command: baseFarm },
8481
8512
  { step: "claim", function: "gauge.earned(token, account) \u2192 gauge.getReward(account, tokens[])", cli_command: claimWithGauge() }
8482
8513
  ];
8483
- case "auto_stake":
8514
+ case "auto_stake": {
8515
+ const isNftAutoStake = p.interface === "uniswap_v3";
8484
8516
  return [
8485
8517
  { step: "mint", function: "Router.addLiquidity / NPM.mint", note: "LP automatically receives x(3,3) emissions \u2014 no separate stake step", cli_command: baseAdd },
8486
- { step: "claim", function: "gauge.getReward(account, tokens[])", note: "Multi-token reward (xRAM + WHYPE on Ramses HL)", cli_command: claimWithGauge() }
8518
+ {
8519
+ step: "claim",
8520
+ function: isNftAutoStake ? "NPM.getPeriodReward(currentEpoch, tokenId, tokens[], receiver)" : "gauge.getReward(account, tokens[])",
8521
+ 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)",
8522
+ cli_command: isNftAutoStake ? claimAutoStakeNftGauge() : claimWithGauge()
8523
+ }
8487
8524
  ];
8525
+ }
8488
8526
  case "on_chain_masterchef":
8489
8527
  return [
8490
8528
  { step: "mint", function: "NPM.mint or pool.mint", cli_command: baseAdd },
@@ -9195,7 +9233,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
9195
9233
  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."
9196
9234
  }, getOpts());
9197
9235
  });
9198
- 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) => {
9236
+ 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) => {
9199
9237
  const executor = makeExecutor2();
9200
9238
  const chainName = parent.opts().chain;
9201
9239
  if (!chainName) {
@@ -9211,6 +9249,9 @@ function registerLP(parent, getOpts, makeExecutor2) {
9211
9249
  if (iface === "uniswap_v2" && protocol.contracts?.["lb_factory"]) {
9212
9250
  if (!opts.pool) throw new Error(`--pool is required for ${protocol.name} (Liquidity Book \u2014 pass --pool <addr>)`);
9213
9251
  if (!opts.bins) throw new Error("--bins <id1,id2,...> is required for Merchant Moe LB remove");
9252
+ if (!opts.tokenA || !opts.tokenB) {
9253
+ throw new Error(`--token-a and --token-b are required for ${protocol.name} (Liquidity Book) remove`);
9254
+ }
9214
9255
  const lbAdapter = createMerchantMoeLB(protocol, rpcUrl);
9215
9256
  const tokenA2 = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
9216
9257
  const tokenB2 = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
@@ -9253,8 +9294,22 @@ function registerLP(parent, getOpts, makeExecutor2) {
9253
9294
  printOutput({ step: "lb_remove", ...result }, getOpts());
9254
9295
  return;
9255
9296
  }
9256
- const tokenA = opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address;
9257
- const tokenB = opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address;
9297
+ const NFT_REMOVE_IFACES = /* @__PURE__ */ new Set(["uniswap_v3", "algebra_v3", "thena_cl", "hybra"]);
9298
+ const isNftRemove = !!opts.tokenId && NFT_REMOVE_IFACES.has(iface);
9299
+ if (!isNftRemove) {
9300
+ const missing = [];
9301
+ if (!opts.tokenA) missing.push("--token-a");
9302
+ if (!opts.tokenB) missing.push("--token-b");
9303
+ if (!opts.liquidity) missing.push("--liquidity");
9304
+ if (missing.length > 0) {
9305
+ printOutput({
9306
+ error: `${missing.join(", ")} required for ${protocol.name} remove (or pass --token-id for V3/CL NFT-based remove).`
9307
+ }, getOpts());
9308
+ return;
9309
+ }
9310
+ }
9311
+ const tokenA = opts.tokenA ? opts.tokenA.startsWith("0x") ? opts.tokenA : registry.resolveToken(chainName, opts.tokenA).address : void 0;
9312
+ const tokenB = opts.tokenB ? opts.tokenB.startsWith("0x") ? opts.tokenB : registry.resolveToken(chainName, opts.tokenB).address : void 0;
9258
9313
  const poolAddr = opts.pool ? opts.pool : void 0;
9259
9314
  let didUnstake = false;
9260
9315
  if (iface === "algebra_v3" && protocol.contracts?.["farming_center"] && opts.tokenId && poolAddr) {
@@ -9304,7 +9359,7 @@ function registerLP(parent, getOpts, makeExecutor2) {
9304
9359
  if (iface === "hybra" && (!wOpts || wOpts.redeemType === 1)) {
9305
9360
  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");
9306
9361
  }
9307
- const withdrawTx = await gaugeAdapter.buildWithdraw(gaugeAddr, BigInt(opts.liquidity), tokenId, wOpts);
9362
+ const withdrawTx = await gaugeAdapter.buildWithdraw(gaugeAddr, opts.liquidity ? BigInt(opts.liquidity) : 0n, tokenId, wOpts);
9308
9363
  const withdrawResult = await executor.execute(withdrawTx);
9309
9364
  printOutput({ step: "unstake_gauge", ...withdrawResult }, getOpts());
9310
9365
  if (withdrawResult.status !== "confirmed" && withdrawResult.status !== "simulated") {
@@ -9319,11 +9374,31 @@ function registerLP(parent, getOpts, makeExecutor2) {
9319
9374
  }
9320
9375
  process.stderr.write("Step 2/2: Removing liquidity...\n");
9321
9376
  const dexAdapter = createDex(protocol, rpcUrl);
9377
+ let removeLiquidity = opts.liquidity ? BigInt(opts.liquidity) : 0n;
9378
+ if (isNftRemove && removeLiquidity === 0n) {
9379
+ const npm = protocol.contracts?.["position_manager"];
9380
+ if (npm) {
9381
+ const c = createPublicClient23({ transport: http23(rpcUrl) });
9382
+ const pos = await detectV3Liquidity(c, npm, BigInt(opts.tokenId));
9383
+ if (pos) {
9384
+ removeLiquidity = pos.liquidity;
9385
+ process.stderr.write(` Read live liquidity ${removeLiquidity} from NPM.positions(${opts.tokenId}).
9386
+ `);
9387
+ }
9388
+ }
9389
+ }
9390
+ if (isNftRemove && removeLiquidity === 0n) {
9391
+ printOutput({
9392
+ error: `tokenId ${opts.tokenId} has zero liquidity (already removed?). Pass --liquidity explicitly to override, or pick a different tokenId.`
9393
+ }, getOpts());
9394
+ return;
9395
+ }
9396
+ const ZERO = "0x0000000000000000000000000000000000000000";
9322
9397
  const removeTx = await dexAdapter.buildRemoveLiquidity({
9323
9398
  protocol: protocol.name,
9324
- token_a: tokenA,
9325
- token_b: tokenB,
9326
- liquidity: BigInt(opts.liquidity),
9399
+ token_a: tokenA ?? ZERO,
9400
+ token_b: tokenB ?? ZERO,
9401
+ liquidity: removeLiquidity,
9327
9402
  recipient,
9328
9403
  token_id: opts.tokenId ? BigInt(opts.tokenId) : void 0
9329
9404
  });
@@ -9702,12 +9777,20 @@ function resolveTokenAddress(registry, chainName, tokenOrAddress) {
9702
9777
  return registry.resolveToken(chainName, tokenOrAddress).address;
9703
9778
  }
9704
9779
  var FALLBACK_ADDRESS = "0x0000000000000000000000000000000000000001";
9780
+ var warnedFallback = false;
9705
9781
  function resolveWallet(override) {
9706
9782
  if (override) return override;
9707
9783
  try {
9708
9784
  const { address } = resolveWalletWithSigner();
9709
9785
  return address;
9710
9786
  } catch {
9787
+ if (!warnedFallback) {
9788
+ process.stderr.write(
9789
+ `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.
9790
+ `
9791
+ );
9792
+ warnedFallback = true;
9793
+ }
9711
9794
  return FALLBACK_ADDRESS;
9712
9795
  }
9713
9796
  }
@@ -9810,7 +9893,8 @@ function resolveAsset(registry, chain, asset) {
9810
9893
  }
9811
9894
  async function collectLendingRates(registry, chainName, rpc, assetAddr) {
9812
9895
  const protos = registry.getProtocolsForChain(chainName).filter((p) => p.category === ProtocolCategory.Lending);
9813
- const results = [];
9896
+ const rates = [];
9897
+ const errors = [];
9814
9898
  let first = true;
9815
9899
  for (const proto of protos) {
9816
9900
  if (!first) {
@@ -9819,19 +9903,22 @@ async function collectLendingRates(registry, chainName, rpc, assetAddr) {
9819
9903
  first = false;
9820
9904
  try {
9821
9905
  const lending = createLending(proto, rpc);
9822
- const rates = await lending.getRates(assetAddr);
9823
- results.push(rates);
9906
+ const r = await lending.getRates(assetAddr);
9907
+ rates.push(r);
9824
9908
  } catch (err) {
9825
9909
  process.stderr.write(`Warning: ${proto.name} rates unavailable: ${err}
9826
9910
  `);
9911
+ errors.push({ protocol: proto.name, type: "lending_supply", reason: errMsg(err) });
9827
9912
  }
9828
9913
  }
9829
- return results;
9914
+ return { rates, errors };
9830
9915
  }
9831
9916
  async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
9832
9917
  const opportunities = [];
9833
- const lendingRates = await collectLendingRates(registry, chainName, rpc, assetAddr);
9834
- for (const r of lendingRates) {
9918
+ const errors = [];
9919
+ const lendingResult = await collectLendingRates(registry, chainName, rpc, assetAddr);
9920
+ errors.push(...lendingResult.errors);
9921
+ for (const r of lendingResult.rates) {
9835
9922
  if (r.supply_apy > 0) {
9836
9923
  opportunities.push({
9837
9924
  protocol: r.protocol,
@@ -9857,7 +9944,8 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
9857
9944
  utilization: rates.utilization
9858
9945
  });
9859
9946
  }
9860
- } catch {
9947
+ } catch (e) {
9948
+ errors.push({ protocol: proto.name, type: "morpho_vault", reason: errMsg(e) });
9861
9949
  }
9862
9950
  }
9863
9951
  }
@@ -9873,7 +9961,8 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
9873
9961
  apy: info.apy ?? 0,
9874
9962
  total_assets: info.total_assets.toString()
9875
9963
  });
9876
- } catch {
9964
+ } catch (e) {
9965
+ errors.push({ protocol: proto.name, type: "vault", reason: errMsg(e) });
9877
9966
  }
9878
9967
  }
9879
9968
  }
@@ -9882,7 +9971,7 @@ async function collectAllYields(registry, chainName, rpc, asset, assetAddr) {
9882
9971
  const ba = b["apy"] ?? 0;
9883
9972
  return ba - aa;
9884
9973
  });
9885
- return opportunities;
9974
+ return { opportunities, errors };
9886
9975
  }
9887
9976
  async function runYieldScan(registry, asset, output) {
9888
9977
  const t0 = Date.now();
@@ -10024,10 +10113,13 @@ function registerYield(parent, getOpts, makeExecutor2) {
10024
10113
  const chain = registry.getChain(chainName);
10025
10114
  const rpc = chain.effectiveRpcUrl();
10026
10115
  const assetAddr = resolveAsset(registry, chainName, opts.asset);
10027
- const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
10116
+ const { rates: results, errors: ratesErrors } = await collectLendingRates(registry, chainName, rpc, assetAddr);
10028
10117
  if (results.length === 0) {
10029
10118
  printOutput(
10030
- { error: `No lending rate data available for asset '${opts.asset}'` },
10119
+ ratesErrors.length > 0 ? {
10120
+ error: `Could not collect lending rates for '${opts.asset}': ${ratesErrors.length} probe(s) failed (likely RPC throttling). Retry, or set ${chainName.toUpperCase()}_RPC_URL.`,
10121
+ failed_probes: ratesErrors
10122
+ } : { error: `No lending rate data available for asset '${opts.asset}'` },
10031
10123
  getOpts()
10032
10124
  );
10033
10125
  process.exit(1);
@@ -10269,9 +10361,16 @@ function registerYield(parent, getOpts, makeExecutor2) {
10269
10361
  const assetAddr = resolveAsset(registry, chainName, asset);
10270
10362
  const strategy = opts.strategy ?? "auto";
10271
10363
  if (strategy === "auto") {
10272
- const opportunities = await collectAllYields(registry, chainName, rpc, asset, assetAddr);
10364
+ const { opportunities, errors } = await collectAllYields(registry, chainName, rpc, asset, assetAddr);
10273
10365
  if (opportunities.length === 0) {
10274
- printOutput({ error: `No yield opportunities found for '${asset}'` }, getOpts());
10366
+ if (errors.length > 0) {
10367
+ printOutput({
10368
+ 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.`,
10369
+ failed_probes: errors
10370
+ }, getOpts());
10371
+ } else {
10372
+ printOutput({ error: `No yield opportunities found for '${asset}'` }, getOpts());
10373
+ }
10275
10374
  process.exit(1);
10276
10375
  return;
10277
10376
  }
@@ -10301,9 +10400,12 @@ function registerYield(parent, getOpts, makeExecutor2) {
10301
10400
  getOpts()
10302
10401
  );
10303
10402
  } else if (strategy === "best-supply") {
10304
- const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
10403
+ const { rates: results, errors: rErr } = await collectLendingRates(registry, chainName, rpc, assetAddr);
10305
10404
  if (results.length === 0) {
10306
- printOutput({ error: `No lending rate data available for asset '${asset}'` }, getOpts());
10405
+ printOutput(
10406
+ 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}'` },
10407
+ getOpts()
10408
+ );
10307
10409
  process.exit(1);
10308
10410
  return;
10309
10411
  }
@@ -10326,9 +10428,12 @@ function registerYield(parent, getOpts, makeExecutor2) {
10326
10428
  getOpts()
10327
10429
  );
10328
10430
  } else if (strategy === "leverage-loop") {
10329
- const results = await collectLendingRates(registry, chainName, rpc, assetAddr);
10431
+ const { rates: results, errors: lErr } = await collectLendingRates(registry, chainName, rpc, assetAddr);
10330
10432
  if (results.length === 0) {
10331
- printOutput({ error: `No lending rate data available for asset '${asset}'` }, getOpts());
10433
+ printOutput(
10434
+ 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}'` },
10435
+ getOpts()
10436
+ );
10332
10437
  process.exit(1);
10333
10438
  return;
10334
10439
  }
@@ -10392,14 +10497,14 @@ function registerYield(parent, getOpts, makeExecutor2) {
10392
10497
 
10393
10498
  // src/commands/portfolio.ts
10394
10499
  init_dist();
10395
- import { encodeFunctionData as encodeFunctionData29, parseAbi as parseAbi33 } from "viem";
10500
+ import { createPublicClient as createPublicClient25, encodeFunctionData as encodeFunctionData29, http as http25, parseAbi as parseAbi33 } from "viem";
10396
10501
 
10397
10502
  // src/portfolio-tracker.ts
10398
10503
  init_dist();
10399
10504
  import { mkdirSync, writeFileSync, readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
10400
10505
  import { homedir } from "os";
10401
10506
  import { resolve as resolve3 } from "path";
10402
- import { encodeFunctionData as encodeFunctionData28, parseAbi as parseAbi31 } from "viem";
10507
+ import { createPublicClient as createPublicClient24, encodeFunctionData as encodeFunctionData28, http as http24, parseAbi as parseAbi31 } from "viem";
10403
10508
  var ERC20_ABI4 = parseAbi31([
10404
10509
  "function balanceOf(address owner) external view returns (uint256)"
10405
10510
  ]);
@@ -10450,7 +10555,16 @@ async function takeSnapshot(chainName, wallet, registry) {
10450
10555
  const oracleEntry = registry.getProtocolsForChain(chainName).find((p) => p.interface === "aave_v3" && p.contracts?.["oracle"]);
10451
10556
  const oracleAddr = oracleEntry?.contracts?.["oracle"];
10452
10557
  const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
10558
+ const priceTokens = [];
10453
10559
  if (oracleAddr) {
10560
+ for (const t of tokenEntries) {
10561
+ calls.push([
10562
+ oracleAddr,
10563
+ encodeFunctionData28({ abi: ORACLE_ABI4, functionName: "getAssetPrice", args: [t.address] })
10564
+ ]);
10565
+ callLabels.push(`price:${t.address}`);
10566
+ priceTokens.push(t.address);
10567
+ }
10454
10568
  calls.push([
10455
10569
  oracleAddr,
10456
10570
  encodeFunctionData28({ abi: ORACLE_ABI4, functionName: "getAssetPrice", args: [wrappedNative] })
@@ -10461,10 +10575,19 @@ async function takeSnapshot(chainName, wallet, registry) {
10461
10575
  if (calls.length > 0) {
10462
10576
  results = await multicallRead(rpc, calls);
10463
10577
  }
10578
+ const balanceCount = tokenEntries.length;
10579
+ const lendingCount = lendingProtocols.length;
10580
+ const priceStartIdx = balanceCount + lendingCount;
10464
10581
  let nativePriceUsd = 0;
10582
+ const priceByToken = /* @__PURE__ */ new Map();
10465
10583
  if (oracleAddr) {
10466
- const priceData = results[results.length - 1] ?? null;
10467
- nativePriceUsd = Number(decodeU256Word(priceData)) / 1e8;
10584
+ for (let i = 0; i < priceTokens.length; i++) {
10585
+ const priceData = results[priceStartIdx + i] ?? null;
10586
+ const px = Number(decodeU256Word(priceData)) / 1e8;
10587
+ if (px > 0) priceByToken.set(priceTokens[i].toLowerCase(), px);
10588
+ }
10589
+ const nativePriceData = results[priceStartIdx + priceTokens.length] ?? null;
10590
+ nativePriceUsd = Number(decodeU256Word(nativePriceData)) / 1e8;
10468
10591
  }
10469
10592
  let idx = 0;
10470
10593
  const tokens = [];
@@ -10474,8 +10597,20 @@ async function takeSnapshot(chainName, wallet, registry) {
10474
10597
  const balance = decodeU256Word(results[idx] ?? null);
10475
10598
  const balF64 = Number(balance) / 10 ** entry.decimals;
10476
10599
  const symbolUpper = entry.symbol.toUpperCase();
10477
- const priceUsd = symbolUpper.includes("USD") ? 1 : nativePriceUsd;
10478
- const valueUsd = balF64 * priceUsd;
10600
+ const tokenAddrLower = entry.address.toLowerCase();
10601
+ let priceUsd;
10602
+ let valueUsd;
10603
+ if (symbolUpper.includes("USD")) {
10604
+ priceUsd = 1;
10605
+ valueUsd = balF64;
10606
+ } else if (tokenAddrLower === wrappedNative.toLowerCase()) {
10607
+ priceUsd = nativePriceUsd;
10608
+ valueUsd = balF64 * nativePriceUsd;
10609
+ } else {
10610
+ const px = priceByToken.get(tokenAddrLower);
10611
+ priceUsd = px ?? 0;
10612
+ valueUsd = px && px > 0 ? balF64 * px : 0;
10613
+ }
10479
10614
  totalValueUsd += valueUsd;
10480
10615
  tokens.push({
10481
10616
  token: entry.address,
@@ -10516,6 +10651,23 @@ async function takeSnapshot(chainName, wallet, registry) {
10516
10651
  }
10517
10652
  idx++;
10518
10653
  }
10654
+ try {
10655
+ const client = createPublicClient24({ transport: http24(rpc) });
10656
+ const nativeBalance = await client.getBalance({ address: user });
10657
+ if (nativeBalance > 0n) {
10658
+ const nativeF64 = Number(nativeBalance) / 1e18;
10659
+ const nativeValueUsd = nativeF64 * nativePriceUsd;
10660
+ totalValueUsd += nativeValueUsd;
10661
+ tokens.push({
10662
+ token: wrappedNative,
10663
+ symbol: chain.native_token ?? "NATIVE",
10664
+ balance: nativeBalance,
10665
+ value_usd: nativeValueUsd,
10666
+ price_usd: nativePriceUsd
10667
+ });
10668
+ }
10669
+ } catch {
10670
+ }
10519
10671
  return {
10520
10672
  timestamp: Date.now(),
10521
10673
  chain: chainName,
@@ -10633,6 +10785,10 @@ function registerPortfolio(parent, getOpts) {
10633
10785
  const calls = [];
10634
10786
  const callLabels = [];
10635
10787
  const tokenSymbols = (registry.tokens.get(chainName) ?? []).map((t) => t.symbol);
10788
+ const tokenAddrsByCallIdx = [];
10789
+ const oracleEntry = registry.getProtocolsForChain(chainName).find((p) => p.interface === "aave_v3" && p.contracts?.["oracle"]);
10790
+ const oracleAddr = oracleEntry?.contracts?.["oracle"];
10791
+ const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
10636
10792
  for (const symbol of tokenSymbols) {
10637
10793
  let entry;
10638
10794
  try {
@@ -10646,6 +10802,7 @@ function registerPortfolio(parent, getOpts) {
10646
10802
  encodeFunctionData29({ abi: ERC20_ABI5, functionName: "balanceOf", args: [user] })
10647
10803
  ]);
10648
10804
  callLabels.push(`balance:${symbol}`);
10805
+ tokenAddrsByCallIdx.push(entry.address);
10649
10806
  }
10650
10807
  const lendingProtocols = registry.getProtocolsForChain(chainName).filter((p) => p.category === ProtocolCategory.Lending && p.interface === "aave_v3").filter((p) => p.contracts?.["pool"]);
10651
10808
  for (const p of lendingProtocols) {
@@ -10655,10 +10812,16 @@ function registerPortfolio(parent, getOpts) {
10655
10812
  ]);
10656
10813
  callLabels.push(`lending:${p.name}`);
10657
10814
  }
10658
- const oracleEntry = registry.getProtocolsForChain(chainName).find((p) => p.interface === "aave_v3" && p.contracts?.["oracle"]);
10659
- const oracleAddr = oracleEntry?.contracts?.["oracle"];
10660
- const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
10815
+ const priceTokens = [];
10661
10816
  if (oracleAddr) {
10817
+ for (const tokenAddr of tokenAddrsByCallIdx) {
10818
+ calls.push([
10819
+ oracleAddr,
10820
+ encodeFunctionData29({ abi: ORACLE_ABI5, functionName: "getAssetPrice", args: [tokenAddr] })
10821
+ ]);
10822
+ callLabels.push(`price:${tokenAddr}`);
10823
+ priceTokens.push(tokenAddr);
10824
+ }
10662
10825
  calls.push([
10663
10826
  oracleAddr,
10664
10827
  encodeFunctionData29({ abi: ORACLE_ABI5, functionName: "getAssetPrice", args: [wrappedNative] })
@@ -10683,10 +10846,19 @@ function registerPortfolio(parent, getOpts) {
10683
10846
  printOutput({ error: `Multicall failed: ${errMsg(e)}` }, mode);
10684
10847
  return;
10685
10848
  }
10849
+ const balanceCallCount = tokenAddrsByCallIdx.length;
10850
+ const lendingCallCount = lendingProtocols.length;
10851
+ const priceStartIdx = balanceCallCount + lendingCallCount;
10686
10852
  let nativePriceUsd = 0;
10853
+ const priceByToken = /* @__PURE__ */ new Map();
10687
10854
  if (oracleAddr) {
10688
- const priceData = results[results.length - 1] ?? null;
10689
- nativePriceUsd = Number(decodeU2562(priceData)) / 1e8;
10855
+ for (let i = 0; i < priceTokens.length; i++) {
10856
+ const priceData = results[priceStartIdx + i] ?? null;
10857
+ const px = Number(decodeU2562(priceData)) / 1e8;
10858
+ if (px > 0) priceByToken.set(priceTokens[i].toLowerCase(), px);
10859
+ }
10860
+ const nativePriceData = results[priceStartIdx + priceTokens.length] ?? null;
10861
+ nativePriceUsd = Number(decodeU2562(nativePriceData)) / 1e8;
10690
10862
  }
10691
10863
  let totalValueUsd = 0;
10692
10864
  let idx = 0;
@@ -10705,12 +10877,21 @@ function registerPortfolio(parent, getOpts) {
10705
10877
  const decimals = entry.decimals;
10706
10878
  const balF64 = Number(balance) / 10 ** decimals;
10707
10879
  const symbolUpper = symbol.toUpperCase();
10708
- const valueUsd = symbolUpper.includes("USD") || symbolUpper.includes("usd") ? balF64 : balF64 * nativePriceUsd;
10709
- totalValueUsd += valueUsd;
10880
+ const tokenAddrLower = entry.address.toLowerCase();
10881
+ let valueUsd;
10882
+ if (symbolUpper.includes("USD")) {
10883
+ valueUsd = balF64;
10884
+ } else if (tokenAddrLower === wrappedNative.toLowerCase()) {
10885
+ valueUsd = balF64 * nativePriceUsd;
10886
+ } else {
10887
+ const px = priceByToken.get(tokenAddrLower);
10888
+ valueUsd = px && px > 0 ? balF64 * px : null;
10889
+ }
10890
+ if (valueUsd !== null) totalValueUsd += valueUsd;
10710
10891
  tokenBalances.push({
10711
10892
  symbol,
10712
10893
  balance: balF64.toFixed(4),
10713
- value_usd: valueUsd.toFixed(2)
10894
+ value_usd: valueUsd !== null ? valueUsd.toFixed(2) : null
10714
10895
  });
10715
10896
  }
10716
10897
  idx++;
@@ -10740,11 +10921,23 @@ function registerPortfolio(parent, getOpts) {
10740
10921
  }
10741
10922
  idx++;
10742
10923
  }
10924
+ let nativeBalance = 0n;
10925
+ let nativeValueUsd = 0;
10926
+ try {
10927
+ const client = createPublicClient25({ transport: http25(rpc) });
10928
+ nativeBalance = await client.getBalance({ address: user });
10929
+ const nativeF64 = Number(nativeBalance) / 1e18;
10930
+ nativeValueUsd = nativeF64 * nativePriceUsd;
10931
+ if (nativeBalance > 0n) totalValueUsd += nativeValueUsd;
10932
+ } catch {
10933
+ }
10743
10934
  printOutput(
10744
10935
  {
10745
10936
  address: user,
10746
10937
  chain: chain.name,
10747
10938
  native_price_usd: nativePriceUsd.toFixed(2),
10939
+ native_balance: (Number(nativeBalance) / 1e18).toFixed(6),
10940
+ native_value_usd: nativeValueUsd.toFixed(2),
10748
10941
  total_value_usd: totalValueUsd.toFixed(2),
10749
10942
  token_balances: tokenBalances,
10750
10943
  lending_positions: lendingPositions
@@ -11010,38 +11203,50 @@ function registerPrice(parent, getOpts) {
11010
11203
 
11011
11204
  // src/commands/wallet.ts
11012
11205
  init_dist();
11013
- import { createPublicClient as createPublicClient24, http as http24, formatEther } from "viem";
11206
+ import { createPublicClient as createPublicClient26, http as http26, formatEther } from "viem";
11207
+ function resolveCurrentAddress(override) {
11208
+ if (override) return { address: override, source: "flag" };
11209
+ try {
11210
+ const { address, signer } = resolveWalletWithSigner();
11211
+ return { address, source: signer ? "ows" : process.env["DEFI_PRIVATE_KEY"] ? "private_key" : "env" };
11212
+ } catch {
11213
+ return { address: null, source: "none" };
11214
+ }
11215
+ }
11014
11216
  function registerWallet(parent, getOpts) {
11015
11217
  const wallet = parent.command("wallet").description("Wallet management");
11016
- wallet.command("balance").description("Show native token balance").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
11218
+ 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) => {
11017
11219
  const chainName = requireChain(parent, getOpts);
11018
11220
  if (!chainName) return;
11019
- const addr = opts.address ?? process.env["DEFI_WALLET_ADDRESS"];
11221
+ const { address: addr, source } = resolveCurrentAddress(opts.address);
11020
11222
  if (!addr) {
11021
- printOutput({ error: "--address required (or set DEFI_WALLET_ADDRESS)" }, getOpts());
11223
+ printOutput({
11224
+ error: "No wallet configured. Set DEFI_WALLET_ADDRESS, set DEFI_PRIVATE_KEY, or pass --address."
11225
+ }, getOpts());
11022
11226
  return;
11023
11227
  }
11024
11228
  const registry = Registry.loadEmbedded();
11025
11229
  const chain = registry.getChain(chainName);
11026
- const client = createPublicClient24({ transport: http24(chain.effectiveRpcUrl()) });
11230
+ const client = createPublicClient26({ transport: http26(chain.effectiveRpcUrl()) });
11027
11231
  const balance = await client.getBalance({ address: addr });
11028
11232
  printOutput({
11029
11233
  chain: chain.name,
11030
11234
  address: addr,
11235
+ wallet_source: source,
11031
11236
  native_token: chain.native_token,
11032
11237
  balance_wei: balance,
11033
11238
  balance_formatted: formatEther(balance)
11034
11239
  }, getOpts());
11035
11240
  });
11036
11241
  wallet.command("address").description("Show configured wallet address").action(async () => {
11037
- const addr = process.env.DEFI_WALLET_ADDRESS ?? "(not set)";
11038
- printOutput({ address: addr }, getOpts());
11242
+ const { address, source } = resolveCurrentAddress();
11243
+ printOutput({ address, source }, getOpts());
11039
11244
  });
11040
11245
  }
11041
11246
 
11042
11247
  // src/commands/token.ts
11043
11248
  init_dist();
11044
- import { createPublicClient as createPublicClient25, http as http25, maxUint256 } from "viem";
11249
+ import { createPublicClient as createPublicClient27, http as http27, maxUint256 } from "viem";
11045
11250
  function registerToken(parent, getOpts, makeExecutor2) {
11046
11251
  const token = parent.command("token").description("Token operations: approve, allowance, transfer, balance");
11047
11252
  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) => {
@@ -11054,7 +11259,7 @@ function registerToken(parent, getOpts, makeExecutor2) {
11054
11259
  }
11055
11260
  const registry = Registry.loadEmbedded();
11056
11261
  const chain = registry.getChain(chainName);
11057
- const client = createPublicClient25({ transport: http25(chain.effectiveRpcUrl()) });
11262
+ const client = createPublicClient27({ transport: http27(chain.effectiveRpcUrl()) });
11058
11263
  const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
11059
11264
  const [balance, symbol, decimals] = await Promise.all([
11060
11265
  client.readContract({ address: tokenAddr, abi: erc20Abi, functionName: "balanceOf", args: [owner] }),
@@ -11090,7 +11295,7 @@ function registerToken(parent, getOpts, makeExecutor2) {
11090
11295
  }
11091
11296
  const registry = Registry.loadEmbedded();
11092
11297
  const chain = registry.getChain(chainName);
11093
- const client = createPublicClient25({ transport: http25(chain.effectiveRpcUrl()) });
11298
+ const client = createPublicClient27({ transport: http27(chain.effectiveRpcUrl()) });
11094
11299
  const tokenAddr = resolveTokenAddress(registry, chainName, opts.token);
11095
11300
  const allowance = await client.readContract({
11096
11301
  address: tokenAddr,
@@ -11208,6 +11413,17 @@ var CCTP_USDC_ADDRESSES = {
11208
11413
  base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
11209
11414
  polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
11210
11415
  };
11416
+ function cctpMinFeeGuard(amountWei, maxFeeSubunits, feeUsdc) {
11417
+ if (amountWei <= maxFeeSubunits) {
11418
+ const amountUsdc = Number(amountWei) / 1e6;
11419
+ return {
11420
+ error: `CCTP: amount ${amountWei.toString()} (${amountUsdc} USDC) is below the minimum bridge fee of ${maxFeeSubunits} (${feeUsdc} USDC). Increase --amount.`,
11421
+ minimum_amount_wei: maxFeeSubunits.toString(),
11422
+ minimum_amount_usdc: feeUsdc
11423
+ };
11424
+ }
11425
+ return null;
11426
+ }
11211
11427
  async function getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc) {
11212
11428
  try {
11213
11429
  const res = await fetch(`${CCTP_FEE_API}/${srcDomain}/${dstDomain}`);
@@ -11296,12 +11512,9 @@ function registerBridge(parent, getOpts) {
11296
11512
  }
11297
11513
  const amountUsdc = Number(BigInt(opts.amount)) / 1e6;
11298
11514
  const { fee, maxFeeSubunits } = await getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc);
11299
- if (BigInt(opts.amount) <= maxFeeSubunits) {
11300
- printOutput({
11301
- error: `CCTP: amount ${opts.amount} (${amountUsdc} USDC) is below the minimum bridge fee of ${maxFeeSubunits} (${fee} USDC). Increase --amount.`,
11302
- minimum_amount_wei: maxFeeSubunits.toString(),
11303
- minimum_amount_usdc: fee
11304
- }, getOpts());
11515
+ const guardErr = cctpMinFeeGuard(BigInt(opts.amount), maxFeeSubunits, fee);
11516
+ if (guardErr) {
11517
+ printOutput(guardErr, getOpts());
11305
11518
  return;
11306
11519
  }
11307
11520
  const recipientPadded = `0x${"0".repeat(24)}${recipient.replace("0x", "").toLowerCase()}`;
@@ -11582,12 +11795,14 @@ function registerSwap(parent, getOpts, makeExecutor2) {
11582
11795
  slippagePct,
11583
11796
  wallet
11584
11797
  );
11798
+ const fromLower = fromAddr.toLowerCase();
11799
+ const isNativeInput = fromLower === "0x0000000000000000000000000000000000000000" || fromLower === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
11585
11800
  const tx = {
11586
11801
  description: `OpenOcean: swap ${opts.amount} of ${fromAddr} -> ${toAddr}`,
11587
11802
  to: swap.to,
11588
11803
  data: swap.data,
11589
11804
  value: parseBigIntValue(swap.value),
11590
- approvals: [{ token: fromAddr, spender: swap.to, amount: BigInt(opts.amount) }]
11805
+ ...isNativeInput ? {} : { approvals: [{ token: fromAddr, spender: swap.to, amount: BigInt(opts.amount) }] }
11591
11806
  };
11592
11807
  const result = await executor.execute(tx);
11593
11808
  printOutput({
@@ -11679,7 +11894,8 @@ import pc2 from "picocolors";
11679
11894
  import { createInterface } from "readline";
11680
11895
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
11681
11896
  import { resolve as resolve4 } from "path";
11682
- var DEFI_DIR = resolve4(process.env.HOME || "~", ".defi");
11897
+ import { homedir as homedir2 } from "os";
11898
+ var DEFI_DIR = resolve4(homedir2(), ".defi");
11683
11899
  var ENV_FILE = resolve4(DEFI_DIR, ".env");
11684
11900
  function ensureDefiDir() {
11685
11901
  if (!existsSync3(DEFI_DIR)) mkdirSync2(DEFI_DIR, { recursive: true, mode: 448 });
@@ -11714,6 +11930,44 @@ function writeEnvFile(env) {
11714
11930
  function ask(rl, question) {
11715
11931
  return new Promise((res) => rl.question(question, (answer) => res(answer.trim())));
11716
11932
  }
11933
+ function askSecret(rl, question) {
11934
+ const stdin = process.stdin;
11935
+ if (!stdin.isTTY) return ask(rl, question);
11936
+ process.stdout.write(question);
11937
+ const rlAny = rl;
11938
+ const original = rlAny._writeToOutput;
11939
+ rlAny._writeToOutput = (s) => {
11940
+ if (s.includes("\n") || s.includes("\r")) {
11941
+ original?.call(rl, s);
11942
+ }
11943
+ };
11944
+ return new Promise((res) => {
11945
+ rl.question("", (answer) => {
11946
+ rlAny._writeToOutput = original;
11947
+ process.stdout.write("\n");
11948
+ res(answer.trim());
11949
+ });
11950
+ });
11951
+ }
11952
+ function maskRpcUrl(s) {
11953
+ try {
11954
+ const u = new URL(s);
11955
+ if (u.pathname && u.pathname !== "/" && u.pathname.length > 1) {
11956
+ return `${u.protocol}//${u.host}/***`;
11957
+ }
11958
+ return `${u.protocol}//${u.host}`;
11959
+ } catch {
11960
+ return "***";
11961
+ }
11962
+ }
11963
+ function isValidRpcUrl(s) {
11964
+ try {
11965
+ const u = new URL(s);
11966
+ return u.protocol === "http:" || u.protocol === "https:";
11967
+ } catch {
11968
+ return false;
11969
+ }
11970
+ }
11717
11971
  function isValidAddress(s) {
11718
11972
  return /^0x[0-9a-fA-F]{40}$/.test(s);
11719
11973
  }
@@ -11738,7 +11992,14 @@ function registerSetup(program2) {
11738
11992
  if (Object.keys(existing).length > 0) {
11739
11993
  console.log(pc2.white(" Current configuration:"));
11740
11994
  for (const [key, value] of Object.entries(existing)) {
11741
- const masked = key.toLowerCase().includes("key") ? value.slice(0, 6) + "..." + value.slice(-4) : value;
11995
+ let masked;
11996
+ if (key.toLowerCase().includes("key")) {
11997
+ masked = value.slice(0, 6) + "..." + value.slice(-4);
11998
+ } else if (key.endsWith("RPC_URL")) {
11999
+ masked = maskRpcUrl(value);
12000
+ } else {
12001
+ masked = value;
12002
+ }
11742
12003
  console.log(` ${pc2.cyan(key.padEnd(24))} ${pc2.gray(masked)}`);
11743
12004
  }
11744
12005
  console.log();
@@ -11752,7 +12013,7 @@ function registerSetup(program2) {
11752
12013
  }
11753
12014
  const newEnv = {};
11754
12015
  console.log(pc2.cyan(pc2.bold(" Wallet")));
11755
- const privateKey = await ask(rl, " Private key (optional, for --broadcast, 0x...): ");
12016
+ const privateKey = await askSecret(rl, " Private key (optional, for --broadcast, 0x... \u2014 input hidden): ");
11756
12017
  if (privateKey) {
11757
12018
  const normalized = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
11758
12019
  if (!isValidPrivateKey(normalized)) {
@@ -11788,6 +12049,10 @@ function registerSetup(program2) {
11788
12049
  for (const { env, label } of rpcPrompts) {
11789
12050
  const value = await ask(rl, ` ${label} RPC URL: `);
11790
12051
  if (value) {
12052
+ if (!isValidRpcUrl(value)) {
12053
+ console.log(pc2.yellow(` Invalid URL (${value}). Skipped \u2014 re-run setup to retry.`));
12054
+ continue;
12055
+ }
11791
12056
  newEnv[env] = value;
11792
12057
  console.log(` ${pc2.green("OK")} ${label} RPC set`);
11793
12058
  }
@@ -11811,7 +12076,7 @@ function registerSetup(program2) {
11811
12076
  ];
11812
12077
  for (const [k, label] of rpcSummary) {
11813
12078
  const v = finalEnv[k];
11814
- if (v) console.log(` ${label}: ${pc2.gray(v)}`);
12079
+ if (v) console.log(` ${label}: ${pc2.gray(maskRpcUrl(v))}`);
11815
12080
  }
11816
12081
  console.log(pc2.bold(pc2.white("\n Next steps:")));
11817
12082
  console.log(` ${pc2.green("defi portfolio")} view balances & positions`);
@@ -11830,11 +12095,11 @@ function registerSetup(program2) {
11830
12095
  // src/commands/ows.ts
11831
12096
  import pc3 from "picocolors";
11832
12097
  init_dist();
11833
- import { createPublicClient as createPublicClient26, http as http26, formatEther as formatEther2 } from "viem";
12098
+ import { createPublicClient as createPublicClient28, http as http28, formatEther as formatEther2 } from "viem";
11834
12099
  async function getEvmBalance(address, chainName) {
11835
12100
  const registry = Registry.loadEmbedded();
11836
12101
  const chain = registry.getChain(chainName);
11837
- const client = createPublicClient26({ transport: http26(chain.effectiveRpcUrl()) });
12102
+ const client = createPublicClient28({ transport: http28(chain.effectiveRpcUrl()) });
11838
12103
  const balance = await client.getBalance({ address });
11839
12104
  return {
11840
12105
  native_token: chain.native_token,