@sherwoodagent/cli 0.59.11 → 0.59.13

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
@@ -933,6 +933,50 @@ function resolveTemplate(key) {
933
933
  }
934
934
  return { def, address };
935
935
  }
936
+ function computeProposalAssetChecks(templateKey, opts) {
937
+ if (templateKey === "moonwell-supply" || templateKey === "mamo-yield") {
938
+ if (!opts.amount) return [];
939
+ const token = opts.token || "USDC";
940
+ const asset = resolveToken(token);
941
+ const decimals = token.toUpperCase() === "USDC" ? 6 : 18;
942
+ return [{ asset, amount: parseUnits(opts.amount, decimals), label: token.toUpperCase() }];
943
+ }
944
+ if (templateKey === "venice-inference") {
945
+ if (!opts.amount) return [];
946
+ const assetSymbol = opts.asset || "USDC";
947
+ const asset = resolveToken(assetSymbol);
948
+ const decimals = assetSymbol.toUpperCase() === "USDC" ? 6 : 18;
949
+ return [{ asset, amount: parseUnits(opts.amount, decimals), label: assetSymbol.toUpperCase() }];
950
+ }
951
+ if (templateKey === "aerodrome-lp") {
952
+ if (!opts.tokenA || !opts.tokenB || !opts.amountA || !opts.amountB) return [];
953
+ return [];
954
+ }
955
+ if (templateKey === "wsteth-moonwell") {
956
+ if (!opts.amount) return [];
957
+ return [{
958
+ asset: TOKENS().WETH,
959
+ amount: parseUnits(opts.amount, 18),
960
+ label: "WETH"
961
+ }];
962
+ }
963
+ if (templateKey === "portfolio") {
964
+ if (!opts.amount) return [];
965
+ const defaultAsset = getNetwork() === "robinhood-testnet" ? "WETH" : "USDC";
966
+ const assetSymbol = opts.asset || defaultAsset;
967
+ const asset = resolveToken(assetSymbol);
968
+ const decimals = assetSymbol.toUpperCase() === "USDC" ? 6 : 18;
969
+ return [{ asset, amount: parseUnits(opts.amount, decimals), label: assetSymbol.toUpperCase() }];
970
+ }
971
+ if (templateKey === "hyperliquid-perp" || templateKey === "hyperliquid-grid") {
972
+ if (!opts.amount) return [];
973
+ const token = opts.token || "USDC";
974
+ const asset = resolveToken(token);
975
+ const decimals = token.toUpperCase() === "USDC" ? 6 : 18;
976
+ return [{ asset, amount: parseUnits(opts.amount, decimals), label: token.toUpperCase() }];
977
+ }
978
+ return [];
979
+ }
936
980
  async function buildInitDataForTemplate(templateKey, opts, vault) {
937
981
  if (templateKey === "moonwell-supply") {
938
982
  if (!opts.amount) {
@@ -1513,6 +1557,37 @@ function registerStrategyTemplateCommands(strategy2) {
1513
1557
  console.error(chalk.red(` Vault ${vault} is paused. Cannot propose.`));
1514
1558
  process.exit(1);
1515
1559
  }
1560
+ const checks = computeProposalAssetChecks(templateKey, opts);
1561
+ for (const check of checks) {
1562
+ if (check.amount === 0n) continue;
1563
+ const [balance, decimals] = await Promise.all([
1564
+ publicClient.readContract({
1565
+ address: check.asset,
1566
+ abi: erc20Abi,
1567
+ functionName: "balanceOf",
1568
+ args: [vault]
1569
+ }),
1570
+ publicClient.readContract({
1571
+ address: check.asset,
1572
+ abi: erc20Abi,
1573
+ functionName: "decimals"
1574
+ })
1575
+ ]);
1576
+ if (balance < check.amount) {
1577
+ preflightSpinner.fail("Preflight failed");
1578
+ console.error(
1579
+ chalk.red(
1580
+ ` Vault ${vault} holds ${formatUnits(balance, decimals)} ${check.label} but proposal requires ${formatUnits(check.amount, decimals)} ${check.label}.`
1581
+ )
1582
+ );
1583
+ console.error(
1584
+ chalk.yellow(
1585
+ ` Hint: --amount is in human units (e.g. "10" = 10 ${check.label}), not raw decimals.`
1586
+ )
1587
+ );
1588
+ process.exit(1);
1589
+ }
1590
+ }
1516
1591
  preflightSpinner.succeed("Preflight OK");
1517
1592
  } catch (err) {
1518
1593
  preflightSpinner.fail("Preflight failed");
@@ -4216,7 +4291,7 @@ function registerProposalCommands(program2) {
4216
4291
  );
4217
4292
  console.error(
4218
4293
  DIM2(
4219
- "\n Other states have their own recovery path:\n Draft/Pending \u2192 sherwood proposal cancel --id <id>\n Approved \u2192 sherwood proposal veto <id>\n Expired \u2192 vault is not locked, nothing to do\n Settled \u2192 already settled\n Cancelled \u2192 governor upgrade required (issue #177)\n"
4294
+ "\n Other states have their own recovery path:\n Draft/Pending/GuardianReview/Approved \u2192 sherwood proposal cancel --id <id> (proposer)\n Draft/Pending (vault owner) \u2192 sherwood proposal cancel --id <id> --emergency\n Pending (vault owner) \u2192 sherwood proposal veto --id <id>\n Expired \u2192 vault is not locked, nothing to do\n Settled \u2192 already settled\n Cancelled \u2192 governor upgrade required (issue #177)\n"
4220
4295
  )
4221
4296
  );
4222
4297
  process.exit(1);
@@ -4331,14 +4406,32 @@ function registerProposalCommands(program2) {
4331
4406
  spinner.fail(`Proposal is already ${PROPOSAL_STATES[state]}`);
4332
4407
  process.exit(1);
4333
4408
  }
4409
+ const PROPOSER_CANCELLABLE = /* @__PURE__ */ new Set([
4410
+ PROPOSAL_STATE.Draft,
4411
+ PROPOSAL_STATE.Pending,
4412
+ PROPOSAL_STATE.GuardianReview,
4413
+ PROPOSAL_STATE.Approved
4414
+ ]);
4415
+ const OWNER_EMERGENCY_CANCELLABLE = /* @__PURE__ */ new Set([
4416
+ PROPOSAL_STATE.Draft,
4417
+ PROPOSAL_STATE.Pending
4418
+ ]);
4334
4419
  let hash;
4335
4420
  if (opts.emergency) {
4421
+ if (!OWNER_EMERGENCY_CANCELLABLE.has(state)) {
4422
+ spinner.fail(
4423
+ `Proposal is ${PROPOSAL_STATES[state] || "Unknown"} \u2014 emergencyCancel only works in Draft or Pending. For GuardianReview / Approved, the proposer can run: sherwood proposal cancel --id ${proposalId}`
4424
+ );
4425
+ process.exit(1);
4426
+ }
4336
4427
  spinner.text = W2("Emergency cancelling...");
4337
4428
  hash = await emergencyCancel(proposalId);
4338
4429
  spinner.succeed(G2("Emergency cancelled"));
4339
4430
  } else {
4340
- if (state !== PROPOSAL_STATE.Draft && state !== PROPOSAL_STATE.Pending) {
4341
- spinner.fail(`Proposal is ${PROPOSAL_STATES[state] || "Unknown"} \u2014 use --emergency for non-pending/approved`);
4431
+ if (!PROPOSER_CANCELLABLE.has(state)) {
4432
+ spinner.fail(
4433
+ `Proposal is ${PROPOSAL_STATES[state] || "Unknown"} \u2014 not cancellable in this state. cancelProposal accepts Draft / Pending / GuardianReview / Approved (proposer only).`
4434
+ );
4342
4435
  process.exit(1);
4343
4436
  }
4344
4437
  spinner.text = W2("Cancelling proposal...");