@sherwoodagent/cli 0.19.1 → 0.22.7

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.
Files changed (65) hide show
  1. package/dist/{chat-WKOSUZXO.js → chat-JYAJMP4D.js} +42 -16
  2. package/dist/chat-JYAJMP4D.js.map +1 -0
  3. package/dist/{chunk-CTLEZZO7.js → chunk-4Y6Y4SVH.js} +43 -33
  4. package/dist/chunk-4Y6Y4SVH.js.map +1 -0
  5. package/dist/chunk-ASJTFFZH.js +485 -0
  6. package/dist/chunk-ASJTFFZH.js.map +1 -0
  7. package/dist/{chunk-SZ5QQJGA.js → chunk-B42ZCCZ7.js} +54 -2
  8. package/dist/chunk-B42ZCCZ7.js.map +1 -0
  9. package/dist/{chunk-QT7BUKWR.js → chunk-G7JQRLQE.js} +141 -37
  10. package/dist/chunk-G7JQRLQE.js.map +1 -0
  11. package/dist/{chunk-LHDWONBI.js → chunk-JR5K5WVR.js} +45 -6
  12. package/dist/chunk-JR5K5WVR.js.map +1 -0
  13. package/dist/{chunk-TAAEMX3L.js → chunk-LVPBJ7MI.js} +2 -2
  14. package/dist/{chunk-XR2ZUO4R.js → chunk-QGZUMIVO.js} +2 -2
  15. package/dist/{chunk-P4J6FKAP.js → chunk-VO6DCRWV.js} +36 -64
  16. package/dist/chunk-VO6DCRWV.js.map +1 -0
  17. package/dist/chunk-W2ZUPR3W.js +64 -0
  18. package/dist/chunk-W2ZUPR3W.js.map +1 -0
  19. package/dist/{chunk-YJAMY25R.js → chunk-Y7LS6DEF.js} +26 -39
  20. package/dist/chunk-Y7LS6DEF.js.map +1 -0
  21. package/dist/{client-3AAXAX4C.js → client-SCIY6SUP.js} +12 -4
  22. package/dist/{config-IDAHD7S3.js → config-BGS5MX4D.js} +10 -2
  23. package/dist/{cron-SKYKVZ6K.js → cron-EOULYO3R.js} +8 -3
  24. package/dist/cron-EOULYO3R.js.map +1 -0
  25. package/dist/{eas-JE3NQCXE.js → eas-X5UIEM6D.js} +14 -8
  26. package/dist/{governor-J3W67NXA.js → governor-ZKRSAHG4.js} +6 -6
  27. package/dist/index.js +1599 -392
  28. package/dist/index.js.map +1 -1
  29. package/dist/{network-DBUZ7GRF.js → network-UTSOP7EF.js} +3 -3
  30. package/dist/research-SJ6D3L2B.js +14 -0
  31. package/dist/{research-47YMVW3N.js → research-ZW6XVTGZ.js} +20 -15
  32. package/dist/research-ZW6XVTGZ.js.map +1 -0
  33. package/dist/session-XAW5UIXP.js +17 -0
  34. package/dist/session-XAW5UIXP.js.map +1 -0
  35. package/dist/{session-Q2WRYXYF.js → session-XZNKLK3I.js} +17 -66
  36. package/dist/session-XZNKLK3I.js.map +1 -0
  37. package/dist/{trade-D6VT5MLQ.js → trade-OJM5HNHL.js} +139 -68
  38. package/dist/trade-OJM5HNHL.js.map +1 -0
  39. package/dist/xmtp-XKHXATPQ.js +278 -0
  40. package/dist/xmtp-XKHXATPQ.js.map +1 -0
  41. package/package.json +7 -2
  42. package/dist/chat-WKOSUZXO.js.map +0 -1
  43. package/dist/chunk-CTLEZZO7.js.map +0 -1
  44. package/dist/chunk-I7CKBBOG.js +0 -129
  45. package/dist/chunk-I7CKBBOG.js.map +0 -1
  46. package/dist/chunk-LHDWONBI.js.map +0 -1
  47. package/dist/chunk-P4J6FKAP.js.map +0 -1
  48. package/dist/chunk-QT7BUKWR.js.map +0 -1
  49. package/dist/chunk-SZ5QQJGA.js.map +0 -1
  50. package/dist/chunk-YJAMY25R.js.map +0 -1
  51. package/dist/cron-SKYKVZ6K.js.map +0 -1
  52. package/dist/research-47YMVW3N.js.map +0 -1
  53. package/dist/research-DZINC4N4.js +0 -14
  54. package/dist/session-Q2WRYXYF.js.map +0 -1
  55. package/dist/trade-D6VT5MLQ.js.map +0 -1
  56. package/dist/xmtp-7DF7UG66.js +0 -367
  57. package/dist/xmtp-7DF7UG66.js.map +0 -1
  58. /package/dist/{chunk-TAAEMX3L.js.map → chunk-LVPBJ7MI.js.map} +0 -0
  59. /package/dist/{chunk-XR2ZUO4R.js.map → chunk-QGZUMIVO.js.map} +0 -0
  60. /package/dist/{client-3AAXAX4C.js.map → client-SCIY6SUP.js.map} +0 -0
  61. /package/dist/{config-IDAHD7S3.js.map → config-BGS5MX4D.js.map} +0 -0
  62. /package/dist/{eas-JE3NQCXE.js.map → eas-X5UIEM6D.js.map} +0 -0
  63. /package/dist/{governor-J3W67NXA.js.map → governor-ZKRSAHG4.js.map} +0 -0
  64. /package/dist/{network-DBUZ7GRF.js.map → network-UTSOP7EF.js.map} +0 -0
  65. /package/dist/{research-DZINC4N4.js.map → research-SJ6D3L2B.js.map} +0 -0
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  getQuote,
10
10
  listModels,
11
11
  provisionApiKey
12
- } from "./chunk-YJAMY25R.js";
12
+ } from "./chunk-Y7LS6DEF.js";
13
13
  import {
14
14
  PROPOSAL_STATE,
15
15
  PROPOSAL_STATES,
@@ -40,7 +40,7 @@ import {
40
40
  setVotingPeriod,
41
41
  settleProposal,
42
42
  vote
43
- } from "./chunk-P4J6FKAP.js";
43
+ } from "./chunk-VO6DCRWV.js";
44
44
  import {
45
45
  fetchMetadata,
46
46
  uploadMetadata
@@ -49,26 +49,30 @@ import {
49
49
  createApproval,
50
50
  createJoinRequest,
51
51
  getEasScanUrl,
52
+ parseReferrer,
52
53
  queryApprovals,
53
54
  queryJoinRequests,
54
- revokeAttestation
55
- } from "./chunk-CTLEZZO7.js";
55
+ revokeAttestation,
56
+ stripReferrerPrefix
57
+ } from "./chunk-4Y6Y4SVH.js";
56
58
  import {
57
59
  approveDepositor,
58
60
  deposit,
61
+ depositWithEthWrap,
59
62
  executeBatch,
60
63
  getAssetDecimals,
61
64
  getBalance,
62
65
  getVaultAddress,
63
66
  getVaultInfo,
64
67
  isAgent,
68
+ preflightDeposit,
65
69
  registerAgent,
66
70
  removeDepositor,
67
71
  resolveSyndicate,
68
72
  resolveVaultSyndicate,
69
73
  setTextRecord,
70
74
  setVaultAddress
71
- } from "./chunk-QT7BUKWR.js";
75
+ } from "./chunk-G7JQRLQE.js";
72
76
  import {
73
77
  AERODROME,
74
78
  AGENT_REGISTRY,
@@ -86,14 +90,15 @@ import {
86
90
  UNISWAP,
87
91
  VENICE,
88
92
  VENICE_STAKING_ABI
89
- } from "./chunk-LHDWONBI.js";
93
+ } from "./chunk-JR5K5WVR.js";
90
94
  import {
95
+ formatContractError,
91
96
  getAccount,
92
97
  getPublicClient,
93
- getWalletClient,
94
98
  sendTxWithRetry,
99
+ waitForReceipt,
95
100
  writeContractWithRetry
96
- } from "./chunk-I7CKBBOG.js";
101
+ } from "./chunk-ASJTFFZH.js";
97
102
  import {
98
103
  VALID_NETWORKS,
99
104
  getChain,
@@ -102,12 +107,15 @@ import {
102
107
  getRpcUrl,
103
108
  isTestnet,
104
109
  setNetwork
105
- } from "./chunk-TAAEMX3L.js";
110
+ } from "./chunk-LVPBJ7MI.js";
106
111
  import {
112
+ addSyndicate,
107
113
  cacheGroupId,
108
114
  getAgentId,
109
115
  getChainContracts,
110
116
  getNotifyTo,
117
+ getPrimarySyndicate,
118
+ getSyndicates,
111
119
  getUniswapApiKey,
112
120
  getVeniceApiKey,
113
121
  loadConfig,
@@ -115,17 +123,18 @@ import {
115
123
  setChainContract,
116
124
  setConfigRpcUrl,
117
125
  setNotifyTo,
126
+ setPrimarySyndicate,
118
127
  setPrivateKey,
119
128
  setUniswapApiKey,
120
129
  setVeniceApiKey
121
- } from "./chunk-SZ5QQJGA.js";
130
+ } from "./chunk-B42ZCCZ7.js";
122
131
 
123
132
  // src/index.ts
124
133
  import { config as loadDotenv } from "dotenv";
125
134
  import { createRequire } from "module";
126
135
  import { Command, Option } from "commander";
127
136
  import { parseUnits as parseUnits5, isAddress as isAddress5 } from "viem";
128
- import chalk7 from "chalk";
137
+ import chalk8 from "chalk";
129
138
  import ora7 from "ora";
130
139
  import { input, confirm, select } from "@inquirer/prompts";
131
140
 
@@ -186,7 +195,7 @@ async function cloneTemplate(template) {
186
195
  data: creationCode,
187
196
  value: 0n
188
197
  });
189
- const receipt = await getPublicClient().waitForTransactionReceipt({ hash });
198
+ const receipt = await waitForReceipt(hash);
190
199
  if (!receipt.contractAddress) {
191
200
  throw new Error(`Clone deployment failed \u2014 no contract address in receipt (tx: ${hash})`);
192
201
  }
@@ -428,6 +437,52 @@ function buildSettleCalls4(clone) {
428
437
  ];
429
438
  }
430
439
 
440
+ // src/strategies/mamo-yield-template.ts
441
+ import { encodeAbiParameters as encodeAbiParameters5, encodeFunctionData as encodeFunctionData5 } from "viem";
442
+ function buildInitData5(underlying, mamoFactory, minRedeemAmount) {
443
+ return encodeAbiParameters5(
444
+ [
445
+ { type: "address" },
446
+ { type: "address" },
447
+ { type: "uint256" }
448
+ ],
449
+ [underlying, mamoFactory, minRedeemAmount]
450
+ );
451
+ }
452
+ function buildExecuteCalls5(clone, underlying, amount) {
453
+ return [
454
+ {
455
+ target: underlying,
456
+ data: encodeFunctionData5({
457
+ abi: ERC20_ABI,
458
+ functionName: "approve",
459
+ args: [clone, amount]
460
+ }),
461
+ value: 0n
462
+ },
463
+ {
464
+ target: clone,
465
+ data: encodeFunctionData5({
466
+ abi: BASE_STRATEGY_ABI,
467
+ functionName: "execute"
468
+ }),
469
+ value: 0n
470
+ }
471
+ ];
472
+ }
473
+ function buildSettleCalls5(clone) {
474
+ return [
475
+ {
476
+ target: clone,
477
+ data: encodeFunctionData5({
478
+ abi: BASE_STRATEGY_ABI,
479
+ functionName: "settle"
480
+ }),
481
+ value: 0n
482
+ }
483
+ ];
484
+ }
485
+
431
486
  // src/commands/strategy-template.ts
432
487
  var ZERO = "0x0000000000000000000000000000000000000000";
433
488
  var TEMPLATES = [
@@ -454,6 +509,12 @@ var TEMPLATES = [
454
509
  key: "wsteth-moonwell",
455
510
  description: "WETH \u2192 wstETH \u2192 Moonwell \u2014 stack Lido + lending yield",
456
511
  addressKey: "WSTETH_MOONWELL"
512
+ },
513
+ {
514
+ name: "Mamo Yield",
515
+ key: "mamo-yield",
516
+ description: "Deposit into Mamo for optimized yield across Moonwell + Morpho vaults",
517
+ addressKey: "MAMO_YIELD"
457
518
  }
458
519
  ];
459
520
  function resolveTemplate(key) {
@@ -500,6 +561,48 @@ async function buildInitDataForTemplate(templateKey, opts, vault) {
500
561
  const decimals = assetSymbol.toUpperCase() === "USDC" ? 6 : 18;
501
562
  const assetAmount = parseUnits(opts.amount, decimals);
502
563
  const agent = opts.agent || getAccount().address;
564
+ const isSingleHop = !!opts.singleHop;
565
+ let minVVV = 0n;
566
+ if (!isDirect) {
567
+ if (opts.minVvv) {
568
+ minVVV = parseUnits(opts.minVvv, 18);
569
+ } else {
570
+ const publicClient = getPublicClient();
571
+ const aeroRouterAbi = [{
572
+ name: "getAmountsOut",
573
+ type: "function",
574
+ stateMutability: "view",
575
+ inputs: [
576
+ { name: "amountIn", type: "uint256" },
577
+ { name: "routes", type: "tuple[]", components: [
578
+ { name: "from", type: "address" },
579
+ { name: "to", type: "address" },
580
+ { name: "stable", type: "bool" },
581
+ { name: "factory", type: "address" }
582
+ ] }
583
+ ],
584
+ outputs: [{ name: "amounts", type: "uint256[]" }]
585
+ }];
586
+ const routes = isSingleHop ? [{ from: asset, to: vvv, stable: false, factory: AERODROME().FACTORY }] : [
587
+ { from: asset, to: TOKENS().WETH, stable: false, factory: AERODROME().FACTORY },
588
+ { from: TOKENS().WETH, to: vvv, stable: false, factory: AERODROME().FACTORY }
589
+ ];
590
+ try {
591
+ const amounts = await publicClient.readContract({
592
+ address: AERODROME().ROUTER,
593
+ abi: aeroRouterAbi,
594
+ functionName: "getAmountsOut",
595
+ args: [assetAmount, routes]
596
+ });
597
+ const expectedVVV = amounts[amounts.length - 1];
598
+ minVVV = expectedVVV * 95n / 100n;
599
+ console.log(chalk.dim(` Auto-quoted minVVV: ${(Number(minVVV) / 1e18).toFixed(4)} VVV (5% slippage on ~${(Number(expectedVVV) / 1e18).toFixed(4)} VVV)`));
600
+ } catch {
601
+ console.error(chalk.red("Could not quote USDC\u2192VVV price. Pass --min-vvv <amount> manually."));
602
+ process.exit(1);
603
+ }
604
+ }
605
+ }
503
606
  const params = {
504
607
  asset,
505
608
  weth: isDirect ? ZERO : TOKENS().WETH,
@@ -509,9 +612,9 @@ async function buildInitDataForTemplate(templateKey, opts, vault) {
509
612
  aeroFactory: isDirect ? ZERO : AERODROME().FACTORY,
510
613
  agent,
511
614
  assetAmount,
512
- minVVV: isDirect ? 0n : parseUnits(opts.minVvv || "0", 18),
615
+ minVVV,
513
616
  deadlineOffset: 300n,
514
- singleHop: !!opts.singleHop
617
+ singleHop: isSingleHop
515
618
  };
516
619
  return {
517
620
  initData: buildInitData2(params),
@@ -586,6 +689,26 @@ async function buildInitDataForTemplate(templateKey, opts, vault) {
586
689
  assetAmount: supplyAmount
587
690
  };
588
691
  }
692
+ if (templateKey === "mamo-yield") {
693
+ if (!opts.amount) {
694
+ console.error(chalk.red("--amount is required for mamo-yield template"));
695
+ process.exit(1);
696
+ }
697
+ if (!opts.mamoFactory) {
698
+ console.error(chalk.red("--mamo-factory is required for mamo-yield template"));
699
+ process.exit(1);
700
+ }
701
+ const token = opts.token || "USDC";
702
+ const underlying = resolveToken(token);
703
+ const decimals = token.toUpperCase() === "USDC" ? 6 : 18;
704
+ const amount = parseUnits(opts.amount, decimals);
705
+ const minRedeemAmount = parseUnits(opts.minRedeem || opts.amount, decimals);
706
+ return {
707
+ initData: buildInitData5(underlying, opts.mamoFactory, minRedeemAmount),
708
+ asset: underlying,
709
+ assetAmount: amount
710
+ };
711
+ }
589
712
  throw new Error(`No init builder for template: ${templateKey}`);
590
713
  }
591
714
  function buildCallsForTemplate(templateKey, clone, asset, assetAmount, extraApprovals) {
@@ -615,6 +738,12 @@ function buildCallsForTemplate(templateKey, clone, asset, assetAmount, extraAppr
615
738
  settleCalls: buildSettleCalls4(clone)
616
739
  };
617
740
  }
741
+ if (templateKey === "mamo-yield") {
742
+ return {
743
+ executeCalls: buildExecuteCalls5(clone, asset, assetAmount),
744
+ settleCalls: buildSettleCalls5(clone)
745
+ };
746
+ }
618
747
  throw new Error(`No call builder for template: ${templateKey}`);
619
748
  }
620
749
  function resolveToken(symbolOrAddress) {
@@ -679,7 +808,7 @@ function registerStrategyTemplateCommands(strategy2) {
679
808
  console.log(chalk.dim("Full proposal: sherwood strategy propose <template> --vault <addr> ..."));
680
809
  console.log();
681
810
  });
682
- strategy2.command("clone").description("Clone a strategy template and initialize it").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell").requiredOption("--vault <address>", "Vault address").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").action(async (templateKey, opts) => {
811
+ strategy2.command("clone").description("Clone a strategy template and initialize it").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell, mamo-yield").requiredOption("--vault <address>", "Vault address").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").option("--mamo-factory <address>", "Mamo StrategyFactory address (Mamo)").action(async (templateKey, opts) => {
683
812
  const vault = opts.vault;
684
813
  if (!isAddress(vault)) {
685
814
  console.error(chalk.red("Invalid vault address"));
@@ -697,7 +826,7 @@ function registerStrategyTemplateCommands(strategy2) {
697
826
  console.log(chalk.dim(` Tx: ${getExplorerUrl(cloneHash)}`));
698
827
  } catch (err) {
699
828
  cloneSpinner.fail("Clone failed");
700
- console.error(chalk.red(err instanceof Error ? err.message : String(err)));
829
+ console.error(chalk.red(formatContractError(err)));
701
830
  process.exit(1);
702
831
  }
703
832
  const initSpinner = ora("Initializing strategy...").start();
@@ -712,14 +841,14 @@ function registerStrategyTemplateCommands(strategy2) {
712
841
  functionName: "initialize",
713
842
  args: [vault, account.address, initData]
714
843
  });
715
- const receipt = await getPublicClient().waitForTransactionReceipt({ hash: initHash });
844
+ const receipt = await waitForReceipt(initHash);
716
845
  if (receipt.status === "reverted") {
717
846
  throw new Error("Initialize transaction reverted on-chain");
718
847
  }
719
848
  initSpinner.succeed("Initialized");
720
849
  } catch (err) {
721
850
  initSpinner.fail("Initialize failed");
722
- console.error(chalk.red(err instanceof Error ? err.message : String(err)));
851
+ console.error(chalk.red(formatContractError(err)));
723
852
  process.exit(1);
724
853
  }
725
854
  console.log();
@@ -727,7 +856,7 @@ function registerStrategyTemplateCommands(strategy2) {
727
856
  console.log(chalk.dim("Use this address in your proposal batch calls."));
728
857
  console.log();
729
858
  });
730
- strategy2.command("init").description("Initialize an already-deployed but uninitialized strategy clone").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell").requiredOption("--clone <address>", "Clone address to initialize").requiredOption("--vault <address>", "Vault address").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").action(async (templateKey, opts) => {
859
+ strategy2.command("init").description("Initialize an already-deployed but uninitialized strategy clone").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell, mamo-yield").requiredOption("--clone <address>", "Clone address to initialize").requiredOption("--vault <address>", "Vault address").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").option("--mamo-factory <address>", "Mamo StrategyFactory address (Mamo)").action(async (templateKey, opts) => {
731
860
  const clone = opts.clone;
732
861
  const vault = opts.vault;
733
862
  if (!isAddress(clone)) {
@@ -761,7 +890,7 @@ function registerStrategyTemplateCommands(strategy2) {
761
890
  functionName: "initialize",
762
891
  args: [vault, account.address, initData]
763
892
  });
764
- const receipt = await publicClient.waitForTransactionReceipt({ hash: initHash });
893
+ const receipt = await waitForReceipt(initHash);
765
894
  if (receipt.status === "reverted") {
766
895
  throw new Error("Initialize transaction reverted on-chain");
767
896
  }
@@ -769,7 +898,7 @@ function registerStrategyTemplateCommands(strategy2) {
769
898
  console.log(chalk.dim(` Tx: ${getExplorerUrl(initHash)}`));
770
899
  } catch (err) {
771
900
  initSpinner.fail("Initialize failed");
772
- console.error(chalk.red(err instanceof Error ? err.message : String(err)));
901
+ console.error(chalk.red(formatContractError(err)));
773
902
  process.exit(1);
774
903
  }
775
904
  const verifiedVault = await publicClient.readContract({
@@ -783,7 +912,7 @@ function registerStrategyTemplateCommands(strategy2) {
783
912
  console.log(` Proposer: ${chalk.green(getAccount().address)}`);
784
913
  console.log();
785
914
  });
786
- strategy2.command("propose").description("Clone + init + build calls + submit governance proposal (all-in-one)").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell").requiredOption("--vault <address>", "Vault address").option("--write-calls <dir>", "Write execute/settle JSON to directory (skip proposal submission)").option("--name <name>", "Proposal name").option("--description <text>", "Proposal description").option("--performance-fee <bps>", "Agent fee in bps").option("--duration <duration>", "Strategy duration (7d, 24h, etc.)").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").action(async (templateKey, opts) => {
915
+ strategy2.command("propose").description("Clone + init + build calls + submit governance proposal (all-in-one)").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell, mamo-yield").requiredOption("--vault <address>", "Vault address").option("--write-calls <dir>", "Write execute/settle JSON to directory (skip proposal submission)").option("--name <name>", "Proposal name").option("--description <text>", "Proposal description").option("--performance-fee <bps>", "Agent fee in bps").option("--duration <duration>", "Strategy duration (7d, 24h, etc.)").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").option("--mamo-factory <address>", "Mamo StrategyFactory address (Mamo)").action(async (templateKey, opts) => {
787
916
  const vault = opts.vault;
788
917
  if (!isAddress(vault)) {
789
918
  console.error(chalk.red("Invalid vault address"));
@@ -799,7 +928,7 @@ function registerStrategyTemplateCommands(strategy2) {
799
928
  console.log(chalk.dim(` Tx: ${getExplorerUrl(result.hash)}`));
800
929
  } catch (err) {
801
930
  cloneSpinner.fail("Clone failed");
802
- console.error(chalk.red(err instanceof Error ? err.message : String(err)));
931
+ console.error(chalk.red(formatContractError(err)));
803
932
  process.exit(1);
804
933
  }
805
934
  const initSpinner = ora("Initializing strategy...").start();
@@ -820,14 +949,14 @@ function registerStrategyTemplateCommands(strategy2) {
820
949
  functionName: "initialize",
821
950
  args: [vault, account2.address, built.initData]
822
951
  });
823
- const initReceipt = await getPublicClient().waitForTransactionReceipt({ hash: initHash });
952
+ const initReceipt = await waitForReceipt(initHash);
824
953
  if (initReceipt.status === "reverted") {
825
954
  throw new Error("Initialize transaction reverted on-chain");
826
955
  }
827
956
  initSpinner.succeed("Initialized");
828
957
  } catch (err) {
829
958
  initSpinner.fail("Initialize failed");
830
- console.error(chalk.red(err instanceof Error ? err.message : String(err)));
959
+ console.error(chalk.red(formatContractError(err)));
831
960
  process.exit(1);
832
961
  }
833
962
  const { executeCalls, settleCalls } = buildCallsForTemplate(
@@ -874,9 +1003,9 @@ function registerStrategyTemplateCommands(strategy2) {
874
1003
  console.error(chalk.red("Missing --name, --performance-fee, or --duration. Use --write-calls to skip proposal submission."));
875
1004
  process.exit(1);
876
1005
  }
877
- const { propose: propose2 } = await import("./governor-J3W67NXA.js");
1006
+ const { propose: propose2 } = await import("./governor-ZKRSAHG4.js");
878
1007
  const { pinJSON } = await import("./ipfs-22YLNQ2C.js");
879
- const { parseDuration: parseDuration2 } = await import("./governor-J3W67NXA.js");
1008
+ const { parseDuration: parseDuration2 } = await import("./governor-ZKRSAHG4.js");
880
1009
  const performanceFeeBps = BigInt(opts.performanceFee);
881
1010
  if (performanceFeeBps < 0n || performanceFeeBps > 10000n) {
882
1011
  console.error(chalk.red("--performance-fee must be 0-10000 (basis points)"));
@@ -902,7 +1031,7 @@ function registerStrategyTemplateCommands(strategy2) {
902
1031
  metaSpinner.succeed(`Metadata pinned: ${metadataURI}`);
903
1032
  } catch (err) {
904
1033
  metaSpinner.fail("IPFS pin failed");
905
- console.error(chalk.red(err instanceof Error ? err.message : String(err)));
1034
+ console.error(chalk.red(formatContractError(err)));
906
1035
  process.exit(1);
907
1036
  }
908
1037
  const proposeSpinner = ora("Submitting proposal...").start();
@@ -916,11 +1045,12 @@ function registerStrategyTemplateCommands(strategy2) {
916
1045
  settleCalls
917
1046
  );
918
1047
  proposeSpinner.succeed(`Proposal #${proposalId} created`);
1048
+ console.log(`Proposal #${proposalId}`);
919
1049
  console.log(chalk.dim(` Tx: ${getExplorerUrl(hash)}`));
920
1050
  console.log(chalk.dim(` Clone: ${clone}`));
921
1051
  } catch (err) {
922
1052
  proposeSpinner.fail("Proposal failed");
923
- console.error(chalk.red(err instanceof Error ? err.message : String(err)));
1053
+ console.error(chalk.red(formatContractError(err)));
924
1054
  process.exit(1);
925
1055
  }
926
1056
  if (templateKey === "venice-inference") {
@@ -941,9 +1071,8 @@ function getFactoryAddress() {
941
1071
  return SHERWOOD().FACTORY;
942
1072
  }
943
1073
  async function createSyndicate(params) {
944
- const wallet = getWalletClient();
945
1074
  const client = getPublicClient();
946
- const hash = await wallet.writeContract({
1075
+ const hash = await writeContractWithRetry({
947
1076
  account: getAccount(),
948
1077
  chain: getChain(),
949
1078
  address: getFactoryAddress(),
@@ -961,7 +1090,7 @@ async function createSyndicate(params) {
961
1090
  }
962
1091
  ]
963
1092
  });
964
- const receipt = await client.waitForTransactionReceipt({ hash });
1093
+ const receipt = await waitForReceipt(hash);
965
1094
  for (const log of receipt.logs) {
966
1095
  try {
967
1096
  const event = decodeEventLog({
@@ -988,6 +1117,16 @@ async function createSyndicate(params) {
988
1117
  vault: info.vault
989
1118
  };
990
1119
  }
1120
+ async function subdomainExists(subdomain) {
1121
+ const client = getPublicClient();
1122
+ const id = await client.readContract({
1123
+ address: getFactoryAddress(),
1124
+ abi: SYNDICATE_FACTORY_ABI,
1125
+ functionName: "subdomainToSyndicate",
1126
+ args: [subdomain]
1127
+ });
1128
+ return id > 0n ? id : null;
1129
+ }
991
1130
  async function getSyndicate(id) {
992
1131
  const client = getPublicClient();
993
1132
  const result = await client.readContract({
@@ -1032,9 +1171,7 @@ async function getActiveSyndicates() {
1032
1171
  }));
1033
1172
  }
1034
1173
  async function updateMetadata(syndicateId, metadataURI) {
1035
- const wallet = getWalletClient();
1036
- const client = getPublicClient();
1037
- const hash = await wallet.writeContract({
1174
+ const hash = await writeContractWithRetry({
1038
1175
  account: getAccount(),
1039
1176
  chain: getChain(),
1040
1177
  address: getFactoryAddress(),
@@ -1042,7 +1179,7 @@ async function updateMetadata(syndicateId, metadataURI) {
1042
1179
  functionName: "updateMetadata",
1043
1180
  args: [syndicateId, metadataURI]
1044
1181
  });
1045
- await client.waitForTransactionReceipt({ hash });
1182
+ await waitForReceipt(hash);
1046
1183
  return hash;
1047
1184
  }
1048
1185
 
@@ -1139,7 +1276,7 @@ function registerVeniceCommands(program2) {
1139
1276
  checkSpinner.succeed(`sVVV balance: ${formatUnits2(sVvvBalance, 18)}`);
1140
1277
  } catch (err) {
1141
1278
  checkSpinner.fail("Failed to check sVVV balance");
1142
- console.error(chalk2.red(err instanceof Error ? err.message : String(err)));
1279
+ console.error(chalk2.red(formatContractError(err)));
1143
1280
  process.exit(1);
1144
1281
  }
1145
1282
  const keySpinner = ora2("Provisioning Venice API key...").start();
@@ -1149,7 +1286,7 @@ function registerVeniceCommands(program2) {
1149
1286
  console.log(chalk2.dim(` Key: ${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`));
1150
1287
  console.log(chalk2.dim(" Saved to ~/.sherwood/config.json"));
1151
1288
  try {
1152
- const { createVeniceProvisionAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-JE3NQCXE.js");
1289
+ const { createVeniceProvisionAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-X5UIEM6D.js");
1153
1290
  const { uid } = await createVeniceProvisionAttestation(account.address);
1154
1291
  if (uid !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
1155
1292
  console.log(chalk2.dim(` Attested: ${getEasScanUrl2(uid)}`));
@@ -1158,7 +1295,7 @@ function registerVeniceCommands(program2) {
1158
1295
  }
1159
1296
  } catch (err) {
1160
1297
  keySpinner.fail("Failed to provision API key");
1161
- console.error(chalk2.red(err instanceof Error ? err.message : String(err)));
1298
+ console.error(chalk2.red(formatContractError(err)));
1162
1299
  process.exit(1);
1163
1300
  }
1164
1301
  });
@@ -1238,7 +1375,7 @@ function registerVeniceCommands(program2) {
1238
1375
  console.log();
1239
1376
  } catch (err) {
1240
1377
  spinner.fail("Failed to load status");
1241
- console.error(chalk2.red(err instanceof Error ? err.message : String(err)));
1378
+ console.error(chalk2.red(formatContractError(err)));
1242
1379
  process.exit(1);
1243
1380
  }
1244
1381
  });
@@ -1254,11 +1391,11 @@ function registerVeniceCommands(program2) {
1254
1391
  console.log();
1255
1392
  } catch (err) {
1256
1393
  spinner.fail("Failed to list models");
1257
- console.error(chalk2.red(err instanceof Error ? err.message : String(err)));
1394
+ console.error(chalk2.red(formatContractError(err)));
1258
1395
  process.exit(1);
1259
1396
  }
1260
1397
  });
1261
- venice.command("infer").description("Run private inference via Venice chat completions").requiredOption("--prompt <text>", "User prompt").requiredOption("--model <id>", "Venice model ID (use 'venice models' to list)").option("--system <text>", "System prompt").option("--data <path>", "Path to data file \u2014 contents prepended to prompt as context").option("--web-search", "Enable Venice web search", false).option("--no-thinking", "Disable chain-of-thought reasoning").option("--temperature <n>", "Sampling temperature (0-2)").option("--max-tokens <n>", "Maximum completion tokens").option("--json", "Output raw JSON response", false).action(async (opts) => {
1398
+ venice.command("infer").description("Run private inference via Venice chat completions").requiredOption("--prompt <text>", "User prompt").requiredOption("--model <id>", "Venice model ID (use 'venice models' to list)").option("--system <text>", "System prompt").option("--data <path>", "Path to data file \u2014 contents prepended to prompt as context").option("--web-search", "Enable Venice web search", false).option("--no-thinking", "Disable chain-of-thought reasoning").option("--temperature <n>", "Sampling temperature (0-2)").option("--max-tokens <n>", "Maximum completion tokens").option("--json", "Output raw JSON response", false).option("--vault <address>", "Vault address \u2014 attestation recipient (defaults to config vault)").action(async (opts) => {
1262
1399
  const messages = [];
1263
1400
  if (opts.system) {
1264
1401
  messages.push({ role: "system", content: opts.system });
@@ -1275,7 +1412,7 @@ ${data}
1275
1412
  ${opts.prompt}`;
1276
1413
  } catch (err) {
1277
1414
  console.error(chalk2.red(`Failed to read data file: ${opts.data}`));
1278
- console.error(chalk2.red(err instanceof Error ? err.message : String(err)));
1415
+ console.error(chalk2.red(formatContractError(err)));
1279
1416
  process.exit(1);
1280
1417
  }
1281
1418
  }
@@ -1300,14 +1437,18 @@ ${opts.prompt}`;
1300
1437
  console.log(chalk2.dim(`Model: ${result.model} | Tokens: ${result.usage.promptTokens} in, ${result.usage.completionTokens} out, ${result.usage.totalTokens} total`));
1301
1438
  }
1302
1439
  try {
1303
- const { createVeniceInferenceAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-JE3NQCXE.js");
1304
- const { keccak256, toHex } = await import("viem");
1440
+ const { createVeniceInferenceAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-X5UIEM6D.js");
1441
+ const { keccak256, toHex, isAddress: isAddr } = await import("viem");
1442
+ const { getChainContracts: getChainContracts2 } = await import("./config-BGS5MX4D.js");
1443
+ const { getChain: getActiveChain } = await import("./network-UTSOP7EF.js");
1444
+ const vaultRecipient = opts.vault && isAddr(opts.vault) ? opts.vault : getChainContracts2(getActiveChain().id).vault;
1305
1445
  const promptHash = keccak256(toHex(userContent)).slice(0, 18);
1306
1446
  const { uid } = await createVeniceInferenceAttestation(
1307
1447
  result.model,
1308
1448
  result.usage.promptTokens,
1309
1449
  result.usage.completionTokens,
1310
- promptHash
1450
+ promptHash,
1451
+ vaultRecipient
1311
1452
  );
1312
1453
  if (uid !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
1313
1454
  console.log(chalk2.dim(`Attested: ${getEasScanUrl2(uid)}`));
@@ -1316,7 +1457,7 @@ ${opts.prompt}`;
1316
1457
  }
1317
1458
  } catch (err) {
1318
1459
  spinner.fail("Inference failed");
1319
- console.error(chalk2.red(err instanceof Error ? err.message : String(err)));
1460
+ console.error(chalk2.red(formatContractError(err)));
1320
1461
  process.exit(1);
1321
1462
  }
1322
1463
  });
@@ -1328,7 +1469,7 @@ import chalk3 from "chalk";
1328
1469
  import ora3 from "ora";
1329
1470
 
1330
1471
  // src/strategies/allowance-disburse.ts
1331
- import { encodeFunctionData as encodeFunctionData5, parseUnits as parseUnits3 } from "viem";
1472
+ import { encodeFunctionData as encodeFunctionData6, parseUnits as parseUnits3 } from "viem";
1332
1473
  function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDecimals, minUsdc, swapPath) {
1333
1474
  const assetAmount = parseUnits3(config.amount, assetDecimals);
1334
1475
  const isUsdc = assetAddress.toLowerCase() === TOKENS().USDC.toLowerCase();
@@ -1337,7 +1478,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
1337
1478
  if (!isUsdc) {
1338
1479
  calls.push({
1339
1480
  target: assetAddress,
1340
- data: encodeFunctionData5({
1481
+ data: encodeFunctionData6({
1341
1482
  abi: ERC20_ABI,
1342
1483
  functionName: "approve",
1343
1484
  args: [UNISWAP().SWAP_ROUTER, assetAmount]
@@ -1347,7 +1488,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
1347
1488
  if (isWeth) {
1348
1489
  calls.push({
1349
1490
  target: UNISWAP().SWAP_ROUTER,
1350
- data: encodeFunctionData5({
1491
+ data: encodeFunctionData6({
1351
1492
  abi: SWAP_ROUTER_EXACT_INPUT_SINGLE_ABI,
1352
1493
  functionName: "exactInputSingle",
1353
1494
  args: [
@@ -1367,7 +1508,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
1367
1508
  } else {
1368
1509
  calls.push({
1369
1510
  target: UNISWAP().SWAP_ROUTER,
1370
- data: encodeFunctionData5({
1511
+ data: encodeFunctionData6({
1371
1512
  abi: SWAP_ROUTER_ABI,
1372
1513
  functionName: "exactInput",
1373
1514
  args: [
@@ -1387,7 +1528,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
1387
1528
  for (const agent of agents) {
1388
1529
  calls.push({
1389
1530
  target: TOKENS().USDC,
1390
- data: encodeFunctionData5({
1531
+ data: encodeFunctionData6({
1391
1532
  abi: ERC20_ABI,
1392
1533
  functionName: "transfer",
1393
1534
  args: [agent, perAgent]
@@ -1436,7 +1577,7 @@ function registerAllowanceCommands(program2) {
1436
1577
  spinner.stop();
1437
1578
  } catch (err) {
1438
1579
  spinner.fail("Failed to read vault state");
1439
- console.error(chalk3.red(err instanceof Error ? err.message : String(err)));
1580
+ console.error(chalk3.red(formatContractError(err)));
1440
1581
  process.exit(1);
1441
1582
  }
1442
1583
  if (agents.length === 0) {
@@ -1501,7 +1642,7 @@ function registerAllowanceCommands(program2) {
1501
1642
  );
1502
1643
  } catch (err) {
1503
1644
  quoteSpinner.fail("Failed to fetch quote");
1504
- console.error(chalk3.red(err instanceof Error ? err.message : String(err)));
1645
+ console.error(chalk3.red(formatContractError(err)));
1505
1646
  process.exit(1);
1506
1647
  }
1507
1648
  }
@@ -1532,7 +1673,7 @@ function registerAllowanceCommands(program2) {
1532
1673
  console.log(chalk3.dim(` ${getExplorerUrl(txHash)}`));
1533
1674
  } catch (err) {
1534
1675
  execSpinner.fail("Execution failed");
1535
- console.error(chalk3.red(err instanceof Error ? err.message : String(err)));
1676
+ console.error(chalk3.red(formatContractError(err)));
1536
1677
  process.exit(1);
1537
1678
  }
1538
1679
  });
@@ -1586,7 +1727,7 @@ function registerAllowanceCommands(program2) {
1586
1727
  console.log();
1587
1728
  } catch (err) {
1588
1729
  spinner.fail("Failed to load status");
1589
- console.error(chalk3.red(err instanceof Error ? err.message : String(err)));
1730
+ console.error(chalk3.red(formatContractError(err)));
1590
1731
  process.exit(1);
1591
1732
  }
1592
1733
  });
@@ -1663,7 +1804,7 @@ function registerIdentityCommands(program2) {
1663
1804
  console.log(chalk4.dim(` sherwood syndicate create --agent-id ${tokenId} --subdomain <name> --name <name>`));
1664
1805
  } catch (err) {
1665
1806
  spinner.fail("Failed to register identity");
1666
- console.error(chalk4.red(err instanceof Error ? err.message : String(err)));
1807
+ console.error(chalk4.red(formatContractError(err)));
1667
1808
  process.exit(1);
1668
1809
  }
1669
1810
  });
@@ -1690,7 +1831,7 @@ function registerIdentityCommands(program2) {
1690
1831
  console.log(chalk4.dim(` Saved to ~/.sherwood/config.json`));
1691
1832
  } catch (err) {
1692
1833
  spinner.fail("Failed to load identity");
1693
- console.error(chalk4.red(err instanceof Error ? err.message : String(err)));
1834
+ console.error(chalk4.red(formatContractError(err)));
1694
1835
  process.exit(1);
1695
1836
  }
1696
1837
  });
@@ -1743,7 +1884,7 @@ function registerIdentityCommands(program2) {
1743
1884
  console.log();
1744
1885
  } catch (err) {
1745
1886
  spinner.fail("Failed to check identity");
1746
- console.error(chalk4.red(err instanceof Error ? err.message : String(err)));
1887
+ console.error(chalk4.red(formatContractError(err)));
1747
1888
  process.exit(1);
1748
1889
  }
1749
1890
  });
@@ -1751,7 +1892,7 @@ function registerIdentityCommands(program2) {
1751
1892
 
1752
1893
  // src/commands/proposal.ts
1753
1894
  import { isAddress as isAddress4 } from "viem";
1754
- import chalk5 from "chalk";
1895
+ import chalk6 from "chalk";
1755
1896
  import ora5 from "ora";
1756
1897
  import { readFileSync as readFileSync2 } from "fs";
1757
1898
 
@@ -1785,13 +1926,737 @@ function parseBigIntArg(value, name) {
1785
1926
  return BigInt(value);
1786
1927
  }
1787
1928
 
1788
- // src/commands/proposal.ts
1929
+ // src/lib/simulate.ts
1930
+ import { decodeFunctionData, encodeFunctionData as encodeFunctionData7 } from "viem";
1931
+ import chalk5 from "chalk";
1789
1932
  var G = chalk5.green;
1790
1933
  var W = chalk5.white;
1791
1934
  var DIM = chalk5.gray;
1792
1935
  var BOLD = chalk5.white.bold;
1793
1936
  var LABEL = chalk5.green.bold;
1794
1937
  var SEP = () => console.log(DIM("\u2500".repeat(60)));
1938
+ var MOONWELL_CTOKEN_ABI = [
1939
+ {
1940
+ name: "mint",
1941
+ type: "function",
1942
+ stateMutability: "nonpayable",
1943
+ inputs: [{ name: "mintAmount", type: "uint256" }],
1944
+ outputs: [{ name: "", type: "uint256" }]
1945
+ },
1946
+ {
1947
+ name: "redeem",
1948
+ type: "function",
1949
+ stateMutability: "nonpayable",
1950
+ inputs: [{ name: "redeemTokens", type: "uint256" }],
1951
+ outputs: [{ name: "", type: "uint256" }]
1952
+ },
1953
+ {
1954
+ name: "redeemUnderlying",
1955
+ type: "function",
1956
+ stateMutability: "nonpayable",
1957
+ inputs: [{ name: "redeemAmount", type: "uint256" }],
1958
+ outputs: [{ name: "", type: "uint256" }]
1959
+ },
1960
+ {
1961
+ name: "borrow",
1962
+ type: "function",
1963
+ stateMutability: "nonpayable",
1964
+ inputs: [{ name: "borrowAmount", type: "uint256" }],
1965
+ outputs: [{ name: "", type: "uint256" }]
1966
+ },
1967
+ {
1968
+ name: "repayBorrow",
1969
+ type: "function",
1970
+ stateMutability: "nonpayable",
1971
+ inputs: [{ name: "repayAmount", type: "uint256" }],
1972
+ outputs: [{ name: "", type: "uint256" }]
1973
+ }
1974
+ ];
1975
+ var MOONWELL_COMPTROLLER_ABI = [
1976
+ {
1977
+ name: "enterMarkets",
1978
+ type: "function",
1979
+ stateMutability: "nonpayable",
1980
+ inputs: [{ name: "mTokens", type: "address[]" }],
1981
+ outputs: [{ name: "", type: "uint256[]" }]
1982
+ },
1983
+ {
1984
+ name: "exitMarket",
1985
+ type: "function",
1986
+ stateMutability: "nonpayable",
1987
+ inputs: [{ name: "mToken", type: "address" }],
1988
+ outputs: [{ name: "", type: "uint256" }]
1989
+ }
1990
+ ];
1991
+ var SWAP_ROUTER_SINGLE_ABI = [
1992
+ {
1993
+ name: "exactInputSingle",
1994
+ type: "function",
1995
+ stateMutability: "nonpayable",
1996
+ inputs: [
1997
+ {
1998
+ name: "params",
1999
+ type: "tuple",
2000
+ components: [
2001
+ { name: "tokenIn", type: "address" },
2002
+ { name: "tokenOut", type: "address" },
2003
+ { name: "fee", type: "uint24" },
2004
+ { name: "recipient", type: "address" },
2005
+ { name: "amountIn", type: "uint256" },
2006
+ { name: "amountOutMinimum", type: "uint256" },
2007
+ { name: "sqrtPriceLimitX96", type: "uint160" }
2008
+ ]
2009
+ }
2010
+ ],
2011
+ outputs: [{ name: "amountOut", type: "uint256" }]
2012
+ }
2013
+ ];
2014
+ function buildLabelMap() {
2015
+ const map = /* @__PURE__ */ new Map();
2016
+ const add = (addr, label) => {
2017
+ if (addr !== "0x0000000000000000000000000000000000000000") {
2018
+ map.set(addr.toLowerCase(), label);
2019
+ }
2020
+ };
2021
+ try {
2022
+ const tokens = TOKENS();
2023
+ add(tokens.USDC, "USDC");
2024
+ add(tokens.WETH, "WETH");
2025
+ add(tokens.cbETH, "cbETH");
2026
+ add(tokens.wstETH, "wstETH");
2027
+ add(tokens.cbBTC, "cbBTC");
2028
+ add(tokens.DAI, "DAI");
2029
+ add(tokens.AERO, "AERO");
2030
+ const mw = MOONWELL();
2031
+ add(mw.COMPTROLLER, "Moonwell Comptroller");
2032
+ add(mw.mUSDC, "mUSDC");
2033
+ add(mw.mWETH, "mWETH");
2034
+ add(mw.mCbETH, "mCbETH");
2035
+ add(mw.mWstETH, "mWstETH");
2036
+ add(mw.mCbBTC, "mCbBTC");
2037
+ add(mw.mDAI, "mDAI");
2038
+ add(mw.mAERO, "mAERO");
2039
+ const uni = UNISWAP();
2040
+ add(uni.SWAP_ROUTER, "SwapRouter");
2041
+ add(uni.QUOTER_V2, "QuoterV2");
2042
+ const sw = SHERWOOD();
2043
+ add(sw.FACTORY, "SyndicateFactory");
2044
+ add(sw.GOVERNOR, "SyndicateGovernor");
2045
+ const aero = AERODROME();
2046
+ add(aero.ROUTER, "Aerodrome Router");
2047
+ add(aero.FACTORY, "Aerodrome Factory");
2048
+ const venice = VENICE();
2049
+ add(venice.VVV, "VVV");
2050
+ add(venice.STAKING, "sVVV Staking");
2051
+ add(venice.DIEM, "DIEM");
2052
+ const strats = STRATEGY_TEMPLATES();
2053
+ add(strats.MOONWELL_SUPPLY, "MoonwellSupplyStrategy");
2054
+ add(strats.AERODROME_LP, "AerodromeLPStrategy");
2055
+ add(strats.VENICE_INFERENCE, "VeniceInferenceStrategy");
2056
+ add(strats.WSTETH_MOONWELL, "WstETHMoonwellStrategy");
2057
+ } catch {
2058
+ }
2059
+ return map;
2060
+ }
2061
+ function buildDecimalsMap() {
2062
+ const map = /* @__PURE__ */ new Map();
2063
+ try {
2064
+ const tokens = TOKENS();
2065
+ map.set(tokens.USDC.toLowerCase(), { decimals: 6, symbol: "USDC" });
2066
+ map.set(tokens.WETH.toLowerCase(), { decimals: 18, symbol: "WETH" });
2067
+ map.set(tokens.cbETH.toLowerCase(), { decimals: 18, symbol: "cbETH" });
2068
+ map.set(tokens.wstETH.toLowerCase(), { decimals: 18, symbol: "wstETH" });
2069
+ map.set(tokens.cbBTC.toLowerCase(), { decimals: 8, symbol: "cbBTC" });
2070
+ map.set(tokens.DAI.toLowerCase(), { decimals: 18, symbol: "DAI" });
2071
+ map.set(tokens.AERO.toLowerCase(), { decimals: 18, symbol: "AERO" });
2072
+ } catch {
2073
+ }
2074
+ return map;
2075
+ }
2076
+ function buildMTokenToUnderlying() {
2077
+ const map = /* @__PURE__ */ new Map();
2078
+ try {
2079
+ const mw = MOONWELL();
2080
+ const tokens = TOKENS();
2081
+ map.set(mw.mUSDC.toLowerCase(), tokens.USDC.toLowerCase());
2082
+ map.set(mw.mWETH.toLowerCase(), tokens.WETH.toLowerCase());
2083
+ map.set(mw.mCbETH.toLowerCase(), tokens.cbETH.toLowerCase());
2084
+ map.set(mw.mWstETH.toLowerCase(), tokens.wstETH.toLowerCase());
2085
+ map.set(mw.mCbBTC.toLowerCase(), tokens.cbBTC.toLowerCase());
2086
+ map.set(mw.mDAI.toLowerCase(), tokens.DAI.toLowerCase());
2087
+ map.set(mw.mAERO.toLowerCase(), tokens.AERO.toLowerCase());
2088
+ } catch {
2089
+ }
2090
+ return map;
2091
+ }
2092
+ function getAddressLabel(addr) {
2093
+ const labels = buildLabelMap();
2094
+ return labels.get(addr.toLowerCase()) ?? null;
2095
+ }
2096
+ function truncAddr(addr) {
2097
+ return `${addr.slice(0, 6)}...${addr.slice(-4)}`;
2098
+ }
2099
+ function labeledAddr(addr) {
2100
+ const label = buildLabelMap().get(addr.toLowerCase());
2101
+ const short = truncAddr(addr);
2102
+ return label ? `${short} (${label})` : short;
2103
+ }
2104
+ function formatTokenAmount(amount, target) {
2105
+ const decimalsMap = buildDecimalsMap();
2106
+ const tokenInfo = decimalsMap.get(target.toLowerCase());
2107
+ if (tokenInfo) {
2108
+ return formatWithDecimals(amount, tokenInfo.decimals, tokenInfo.symbol);
2109
+ }
2110
+ const mTokenMap = buildMTokenToUnderlying();
2111
+ const underlying = mTokenMap.get(target.toLowerCase());
2112
+ if (underlying) {
2113
+ const underlyingInfo = decimalsMap.get(underlying);
2114
+ if (underlyingInfo) {
2115
+ return formatWithDecimals(amount, underlyingInfo.decimals, underlyingInfo.symbol);
2116
+ }
2117
+ }
2118
+ return amount.toString();
2119
+ }
2120
+ function formatWithDecimals(amount, decimals, symbol) {
2121
+ const num = Number(amount) / 10 ** decimals;
2122
+ const formatted = num.toLocaleString("en-US", {
2123
+ minimumFractionDigits: 2,
2124
+ maximumFractionDigits: decimals <= 8 ? 2 : 4
2125
+ });
2126
+ return `${formatted} ${symbol}`;
2127
+ }
2128
+ function decodeCallData(target, data) {
2129
+ if (data.length < 10) return null;
2130
+ const labels = buildLabelMap();
2131
+ const targetLower = target.toLowerCase();
2132
+ const candidates = [];
2133
+ const mw = safeCall(() => MOONWELL());
2134
+ if (mw) {
2135
+ const isMToken = [mw.mUSDC, mw.mWETH, mw.mCbETH, mw.mWstETH, mw.mCbBTC, mw.mDAI, mw.mAERO].some((a) => a.toLowerCase() === targetLower);
2136
+ if (isMToken) {
2137
+ candidates.push({ abi: MOONWELL_CTOKEN_ABI, context: labels.get(targetLower) || "mToken" });
2138
+ }
2139
+ if (mw.COMPTROLLER.toLowerCase() === targetLower) {
2140
+ candidates.push({ abi: MOONWELL_COMPTROLLER_ABI, context: "Comptroller" });
2141
+ }
2142
+ }
2143
+ const uni = safeCall(() => UNISWAP());
2144
+ if (uni && uni.SWAP_ROUTER.toLowerCase() === targetLower) {
2145
+ candidates.push({ abi: SWAP_ROUTER_ABI, context: "SwapRouter" });
2146
+ candidates.push({ abi: SWAP_ROUTER_SINGLE_ABI, context: "SwapRouter" });
2147
+ }
2148
+ candidates.push({ abi: ERC20_ABI, context: labels.get(targetLower) || "ERC20" });
2149
+ for (const { abi, context } of candidates) {
2150
+ try {
2151
+ const decoded = decodeFunctionData({
2152
+ abi,
2153
+ data
2154
+ });
2155
+ const args = formatArgs(decoded.args, decoded.functionName, target);
2156
+ return `${decoded.functionName}(${args})`;
2157
+ } catch {
2158
+ continue;
2159
+ }
2160
+ }
2161
+ return null;
2162
+ }
2163
+ function formatArgs(args, functionName, target) {
2164
+ if (!args || args.length === 0) return "";
2165
+ const parts = [];
2166
+ for (const arg of args) {
2167
+ if (typeof arg === "bigint") {
2168
+ parts.push(formatTokenAmount(arg, target));
2169
+ } else if (typeof arg === "string" && arg.startsWith("0x") && arg.length === 42) {
2170
+ parts.push(labeledAddr(arg));
2171
+ } else if (typeof arg === "object" && arg !== null && !Array.isArray(arg)) {
2172
+ const entries = Object.entries(arg);
2173
+ const structParts = [];
2174
+ for (const [key, val] of entries) {
2175
+ if (typeof val === "bigint") {
2176
+ structParts.push(`${key}: ${formatTokenAmount(val, target)}`);
2177
+ } else if (typeof val === "string" && val.startsWith("0x") && val.length === 42) {
2178
+ structParts.push(`${key}: ${labeledAddr(val)}`);
2179
+ } else if (typeof val === "string" && val.startsWith("0x") && key === "path") {
2180
+ structParts.push(`path: ${decodeUniswapPath(val)}`);
2181
+ } else {
2182
+ structParts.push(`${key}: ${String(val)}`);
2183
+ }
2184
+ }
2185
+ parts.push(`{${structParts.join(", ")}}`);
2186
+ } else if (Array.isArray(arg)) {
2187
+ const arrParts = arg.map((item) => {
2188
+ if (typeof item === "string" && item.startsWith("0x") && item.length === 42) {
2189
+ return labeledAddr(item);
2190
+ }
2191
+ return String(item);
2192
+ });
2193
+ parts.push(`[${arrParts.join(", ")}]`);
2194
+ } else {
2195
+ parts.push(String(arg));
2196
+ }
2197
+ }
2198
+ return parts.join(", ");
2199
+ }
2200
+ function decodeUniswapPath(pathHex) {
2201
+ const labels = buildLabelMap();
2202
+ const path = pathHex.startsWith("0x") ? pathHex.slice(2) : pathHex;
2203
+ const tokens = [];
2204
+ let offset = 0;
2205
+ while (offset + 40 <= path.length) {
2206
+ const addr = `0x${path.slice(offset, offset + 40)}`;
2207
+ const label = labels.get(addr.toLowerCase());
2208
+ tokens.push(label || truncAddr(addr));
2209
+ offset += 40;
2210
+ if (offset + 6 <= path.length) {
2211
+ offset += 6;
2212
+ } else {
2213
+ break;
2214
+ }
2215
+ }
2216
+ return tokens.join(" \u2192 ");
2217
+ }
2218
+ function safeCall(fn) {
2219
+ try {
2220
+ return fn();
2221
+ } catch {
2222
+ return null;
2223
+ }
2224
+ }
2225
+ function decodeCallArgs(target, data) {
2226
+ if (data.length < 10) return null;
2227
+ const targetLower = target.toLowerCase();
2228
+ const candidates = [];
2229
+ const mw = safeCall(() => MOONWELL());
2230
+ if (mw) {
2231
+ const isMToken = [mw.mUSDC, mw.mWETH, mw.mCbETH, mw.mWstETH, mw.mCbBTC, mw.mDAI, mw.mAERO].some((a) => a.toLowerCase() === targetLower);
2232
+ if (isMToken) candidates.push({ abi: MOONWELL_CTOKEN_ABI });
2233
+ if (mw.COMPTROLLER.toLowerCase() === targetLower) candidates.push({ abi: MOONWELL_COMPTROLLER_ABI });
2234
+ }
2235
+ const uni = safeCall(() => UNISWAP());
2236
+ if (uni && uni.SWAP_ROUTER.toLowerCase() === targetLower) {
2237
+ candidates.push({ abi: SWAP_ROUTER_ABI });
2238
+ candidates.push({ abi: SWAP_ROUTER_SINGLE_ABI });
2239
+ }
2240
+ candidates.push({ abi: ERC20_ABI });
2241
+ for (const { abi } of candidates) {
2242
+ try {
2243
+ const decoded = decodeFunctionData({ abi, data });
2244
+ return { functionName: decoded.functionName, args: decoded.args };
2245
+ } catch {
2246
+ continue;
2247
+ }
2248
+ }
2249
+ return null;
2250
+ }
2251
+ function analyzeRisk(calls, callResults, context) {
2252
+ const risks = [];
2253
+ const labels = buildLabelMap();
2254
+ const vaultLower = context?.vault?.toLowerCase();
2255
+ const isKnown = (addr) => {
2256
+ const lower = addr.toLowerCase();
2257
+ return labels.has(lower) || lower === vaultLower;
2258
+ };
2259
+ let allTargetsVerified = true;
2260
+ let allCallsDecoded = true;
2261
+ for (let i = 0; i < calls.length; i++) {
2262
+ const call = calls[i];
2263
+ const result = callResults[i];
2264
+ const callNum = i + 1;
2265
+ if (result && !result.success) {
2266
+ risks.push({
2267
+ level: "critical",
2268
+ code: "SIMULATION_FAILED",
2269
+ message: `Call #${callNum} to ${labeledAddr(call.target)} reverted during simulation`,
2270
+ callIndex: i
2271
+ });
2272
+ }
2273
+ const targetKnown = isKnown(call.target);
2274
+ if (!targetKnown) {
2275
+ allTargetsVerified = false;
2276
+ risks.push({
2277
+ level: "critical",
2278
+ code: "UNKNOWN_TARGET",
2279
+ message: `Call #${callNum} targets ${labeledAddr(call.target)} which is not in the known address registry`,
2280
+ callIndex: i
2281
+ });
2282
+ }
2283
+ const decoded = decodeCallArgs(call.target, call.data);
2284
+ if (!decoded) {
2285
+ allCallsDecoded = false;
2286
+ if (!targetKnown) {
2287
+ risks.push({
2288
+ level: "critical",
2289
+ code: "UNDECODED_CALLDATA",
2290
+ message: `Call #${callNum} has unrecognized calldata (selector ${call.data.slice(0, 10)}) targeting unknown contract`,
2291
+ callIndex: i
2292
+ });
2293
+ }
2294
+ continue;
2295
+ }
2296
+ if (decoded.functionName === "transfer" && decoded.args.length >= 2) {
2297
+ const to = decoded.args[0];
2298
+ if (typeof to === "string" && to.startsWith("0x") && !isKnown(to)) {
2299
+ risks.push({
2300
+ level: "critical",
2301
+ code: "TRANSFER_TO_UNKNOWN",
2302
+ message: `Call #${callNum} transfer() sends funds to ${truncAddr(to)} which is not a known protocol`,
2303
+ callIndex: i
2304
+ });
2305
+ }
2306
+ }
2307
+ if (decoded.functionName === "transferFrom" && decoded.args.length >= 3) {
2308
+ const to = decoded.args[1];
2309
+ if (typeof to === "string" && to.startsWith("0x") && !isKnown(to)) {
2310
+ risks.push({
2311
+ level: "critical",
2312
+ code: "TRANSFER_FROM_TO_UNKNOWN",
2313
+ message: `Call #${callNum} transferFrom() sends funds to ${truncAddr(to)} which is not a known protocol`,
2314
+ callIndex: i
2315
+ });
2316
+ }
2317
+ }
2318
+ if (decoded.functionName === "approve" && decoded.args.length >= 2) {
2319
+ const spender = decoded.args[0];
2320
+ if (typeof spender === "string" && spender.startsWith("0x") && !isKnown(spender)) {
2321
+ risks.push({
2322
+ level: "critical",
2323
+ code: "APPROVE_TO_UNKNOWN",
2324
+ message: `Call #${callNum} approve() grants allowance to ${truncAddr(spender)} which is not a known protocol`,
2325
+ callIndex: i
2326
+ });
2327
+ }
2328
+ }
2329
+ }
2330
+ if (context?.performanceFeeBps !== void 0) {
2331
+ const fee = context.performanceFeeBps;
2332
+ const maxFee = context.governorParams?.maxPerformanceFeeBps;
2333
+ if (maxFee && fee > maxFee * 80n / 100n) {
2334
+ risks.push({
2335
+ level: "critical",
2336
+ code: "EXCESSIVE_PERFORMANCE_FEE",
2337
+ message: `Performance fee ${Number(fee) / 100}% is within 20% of the hard cap (${Number(maxFee) / 100}%)`
2338
+ });
2339
+ } else if (fee > 2000n) {
2340
+ risks.push({
2341
+ level: "warning",
2342
+ code: "HIGH_PERFORMANCE_FEE",
2343
+ message: `Performance fee ${Number(fee) / 100}% exceeds 20% threshold`
2344
+ });
2345
+ }
2346
+ }
2347
+ if (context?.strategyDuration !== void 0) {
2348
+ const dur = context.strategyDuration;
2349
+ if (dur < 3600n) {
2350
+ risks.push({
2351
+ level: "warning",
2352
+ code: "SHORT_STRATEGY_DURATION",
2353
+ message: `Strategy duration ${Number(dur)}s is under 1 hour \u2014 flash-loan-style risk`
2354
+ });
2355
+ }
2356
+ if (dur > 2592000n) {
2357
+ risks.push({
2358
+ level: "warning",
2359
+ code: "LONG_STRATEGY_DURATION",
2360
+ message: `Strategy duration ${Number(dur) / 86400} days exceeds 30-day threshold`
2361
+ });
2362
+ }
2363
+ }
2364
+ const hasCritical = risks.some((r) => r.level === "critical");
2365
+ const hasWarning = risks.some((r) => r.level === "warning");
2366
+ if (!hasCritical && !hasWarning) {
2367
+ if (allTargetsVerified) {
2368
+ risks.push({
2369
+ level: "info",
2370
+ code: "ALL_TARGETS_VERIFIED",
2371
+ message: "All call targets are verified protocol addresses"
2372
+ });
2373
+ }
2374
+ if (allCallsDecoded) {
2375
+ risks.push({
2376
+ level: "info",
2377
+ code: "ALL_CALLS_DECODED",
2378
+ message: "All calldata successfully decoded"
2379
+ });
2380
+ }
2381
+ }
2382
+ return risks;
2383
+ }
2384
+ var API_URLS = {
2385
+ "base": "https://app.sherwood.sh",
2386
+ "base-sepolia": "https://testnet.app.sherwood.sh"
2387
+ };
2388
+ function getApiBaseUrl() {
2389
+ if (process.env.SHERWOOD_API_URL) return process.env.SHERWOOD_API_URL;
2390
+ const chain = getChain();
2391
+ return API_URLS[chain.testnet ? "base-sepolia" : "base"] || API_URLS["base"];
2392
+ }
2393
+ async function simulateViaApi(vault, calls) {
2394
+ const baseUrl = getApiBaseUrl();
2395
+ const chainId = getChain().id;
2396
+ const response = await fetch(`${baseUrl}/api/simulate`, {
2397
+ method: "POST",
2398
+ headers: { "Content-Type": "application/json" },
2399
+ body: JSON.stringify({
2400
+ vault,
2401
+ chainId,
2402
+ calls: calls.map((c) => ({
2403
+ target: c.target,
2404
+ data: c.data,
2405
+ value: c.value.toString()
2406
+ }))
2407
+ })
2408
+ });
2409
+ if (!response.ok) {
2410
+ const body = await response.json().catch(() => ({ error: "Unknown error" }));
2411
+ throw new Error(body.error || `Simulation API returned ${response.status}`);
2412
+ }
2413
+ const result = await response.json();
2414
+ const callResults = result.calls.map((c, i) => ({
2415
+ index: c.index,
2416
+ target: calls[i]?.target || c.target,
2417
+ decodedFunction: calls[i] ? decodeCallData(calls[i].target, calls[i].data) : null,
2418
+ selector: calls[i]?.data.slice(0, 10) || "0x",
2419
+ success: c.success,
2420
+ gasUsed: c.gasUsed
2421
+ }));
2422
+ for (const cr of callResults) {
2423
+ if (!cr.decodedFunction) {
2424
+ const matchingCall = calls[cr.index];
2425
+ if (matchingCall) {
2426
+ cr.decodedFunction = decodeCallData(matchingCall.target, matchingCall.data);
2427
+ }
2428
+ }
2429
+ }
2430
+ const warnings = [];
2431
+ for (const cr of callResults) {
2432
+ if (!cr.success) {
2433
+ warnings.push(`Call #${cr.index + 1} to ${labeledAddr(cr.target)} failed`);
2434
+ }
2435
+ }
2436
+ return {
2437
+ success: result.success,
2438
+ totalGasUsed: result.totalGasUsed,
2439
+ callResults,
2440
+ warnings,
2441
+ risks: [],
2442
+ source: "api",
2443
+ errorMessage: result.errorMessage ?? void 0
2444
+ };
2445
+ }
2446
+ async function simulateViaEthCall(vault, calls) {
2447
+ const governor = getGovernorAddress();
2448
+ const client = getPublicClient();
2449
+ const input2 = encodeFunctionData7({
2450
+ abi: SYNDICATE_VAULT_ABI,
2451
+ functionName: "executeGovernorBatch",
2452
+ args: [calls.map((c) => ({ target: c.target, data: c.data, value: c.value }))]
2453
+ });
2454
+ const callResults = calls.map((c, i) => ({
2455
+ index: i,
2456
+ target: c.target,
2457
+ decodedFunction: decodeCallData(c.target, c.data),
2458
+ selector: c.data.slice(0, 10),
2459
+ success: true,
2460
+ // will be set after simulation
2461
+ gasUsed: 0
2462
+ }));
2463
+ try {
2464
+ await client.call({
2465
+ account: governor,
2466
+ to: vault,
2467
+ data: input2
2468
+ });
2469
+ return {
2470
+ success: true,
2471
+ totalGasUsed: 0,
2472
+ callResults,
2473
+ warnings: [],
2474
+ risks: [],
2475
+ source: "eth_call"
2476
+ };
2477
+ } catch (err) {
2478
+ const message = err instanceof Error ? err.message : String(err);
2479
+ for (const cr of callResults) {
2480
+ cr.success = false;
2481
+ }
2482
+ return {
2483
+ success: false,
2484
+ totalGasUsed: 0,
2485
+ callResults,
2486
+ warnings: [`Batch reverted: ${message.slice(0, 200)}`],
2487
+ risks: [],
2488
+ source: "eth_call",
2489
+ errorMessage: message
2490
+ };
2491
+ }
2492
+ }
2493
+ async function simulateBatchCalls(vault, calls, callType, riskContext) {
2494
+ let result;
2495
+ try {
2496
+ result = await simulateViaApi(vault, calls);
2497
+ } catch (err) {
2498
+ const message = err instanceof Error ? err.message : String(err);
2499
+ console.log(DIM(` Simulation API unavailable (${message}), falling back to eth_call...`));
2500
+ result = await simulateViaEthCall(vault, calls);
2501
+ }
2502
+ const ctx = { vault, ...riskContext };
2503
+ result.risks = analyzeRisk(calls, result.callResults, ctx);
2504
+ return result;
2505
+ }
2506
+ function printSimulationResult(result, callType) {
2507
+ console.log();
2508
+ console.log(LABEL(` \u25C6 Simulation \u2014 ${callType} calls`));
2509
+ SEP();
2510
+ const sourceLabel = result.source === "api" ? "Tenderly (via API)" : `eth_call ${DIM("(configure Tenderly for detailed results)")}`;
2511
+ console.log(W(` Source: ${sourceLabel}`));
2512
+ const statusIcon = result.success ? G("\u2713 PASS") : chalk5.red("\u2716 FAIL");
2513
+ console.log(W(` Status: ${statusIcon}`));
2514
+ if (result.totalGasUsed > 0) {
2515
+ console.log(W(` Total Gas: ${result.totalGasUsed.toLocaleString()}`));
2516
+ }
2517
+ if (result.errorMessage && !result.success) {
2518
+ console.log(chalk5.red(` Error: ${result.errorMessage.slice(0, 120)}`));
2519
+ }
2520
+ console.log();
2521
+ for (const cr of result.callResults) {
2522
+ const icon = cr.success ? G("\u2713") : chalk5.red("\u2716");
2523
+ const funcName = cr.decodedFunction || `${cr.selector}... ${DIM("(unknown)")}`;
2524
+ console.log(W(` [${cr.index + 1}] ${icon} ${funcName}`));
2525
+ const label = getAddressLabel(cr.target);
2526
+ const targetDisplay = label ? `${cr.target} (${label})` : cr.target;
2527
+ console.log(DIM(` Target: ${targetDisplay}`));
2528
+ if (cr.gasUsed > 0) {
2529
+ console.log(DIM(` Gas: ${cr.gasUsed.toLocaleString()}`));
2530
+ }
2531
+ console.log();
2532
+ }
2533
+ if (result.risks.length > 0) {
2534
+ const criticals = result.risks.filter((r) => r.level === "critical");
2535
+ const warnings = result.risks.filter((r) => r.level === "warning");
2536
+ const infos = result.risks.filter((r) => r.level === "info");
2537
+ if (criticals.length > 0) {
2538
+ console.log(chalk5.red.bold(` \u2716 CRITICAL RISKS (${criticals.length})`));
2539
+ for (const r of criticals) {
2540
+ const ref = r.callIndex !== void 0 ? ` [call #${r.callIndex + 1}]` : "";
2541
+ console.log(chalk5.red(` \u2716${ref} ${r.code} \u2014 ${r.message}`));
2542
+ }
2543
+ console.log();
2544
+ }
2545
+ if (warnings.length > 0) {
2546
+ console.log(chalk5.yellow.bold(` \u26A0 WARNINGS (${warnings.length})`));
2547
+ for (const r of warnings) {
2548
+ const ref = r.callIndex !== void 0 ? ` [call #${r.callIndex + 1}]` : "";
2549
+ console.log(chalk5.yellow(` \u26A0${ref} ${r.code} \u2014 ${r.message}`));
2550
+ }
2551
+ console.log();
2552
+ }
2553
+ if (criticals.length === 0 && warnings.length === 0 && infos.length > 0) {
2554
+ console.log(G.bold(` \u2713 RISK ASSESSMENT: CLEAN`));
2555
+ for (const r of infos) {
2556
+ console.log(G(` \u2713 ${r.message}`));
2557
+ }
2558
+ console.log();
2559
+ }
2560
+ }
2561
+ if (result.warnings.length > 0) {
2562
+ console.log(chalk5.yellow(` \u26A0 Simulation Warnings`));
2563
+ for (const w of result.warnings) {
2564
+ console.log(chalk5.yellow(` - ${w}`));
2565
+ }
2566
+ console.log();
2567
+ }
2568
+ SEP();
2569
+ }
2570
+ async function sendSimulationAlert(subdomain, proposalId, vault, execResult, settleResult) {
2571
+ try {
2572
+ const xmtp = await import("./xmtp-XKHXATPQ.js");
2573
+ const { getAccount: getAccount2 } = await import("./client-SCIY6SUP.js");
2574
+ const group = await xmtp.getGroup("", subdomain);
2575
+ const allRisks = [...execResult.risks, ...settleResult?.risks ?? []];
2576
+ const criticals = allRisks.filter((r) => r.level === "critical");
2577
+ const warnings = allRisks.filter((r) => r.level === "warning");
2578
+ const highestLevel = criticals.length > 0 ? "critical" : warnings.length > 0 ? "warning" : "info";
2579
+ const lines = [];
2580
+ const statusEmoji = highestLevel === "critical" ? "\u{1F6A8}" : highestLevel === "warning" ? "\u26A0\uFE0F" : "\u2705";
2581
+ const statusText = highestLevel === "critical" ? "RISKS DETECTED" : highestLevel === "warning" ? "WARNINGS" : "CLEAN";
2582
+ lines.push(`## ${statusEmoji} Proposal #${proposalId} \u2014 Simulation Report`);
2583
+ lines.push(`**Vault**: \`${truncAddr(vault)}\``);
2584
+ lines.push(`**Status**: ${statusText}`);
2585
+ lines.push(`**Source**: ${execResult.source === "api" ? "Tenderly" : "eth_call"}`);
2586
+ lines.push("");
2587
+ lines.push(`### Execute Calls ${execResult.success ? "\u2713" : "\u2716"}`);
2588
+ for (const cr of execResult.callResults) {
2589
+ const icon = cr.success ? "\u2713" : "\u2716";
2590
+ const fn = cr.decodedFunction || `${cr.selector}...`;
2591
+ const flag = allRisks.find((r) => r.callIndex === cr.index);
2592
+ const suffix = flag && flag.level === "critical" ? " \u2190 **" + flag.code + "**" : "";
2593
+ lines.push(`${cr.index + 1}. ${icon} \`${fn}\`${suffix}`);
2594
+ }
2595
+ lines.push("");
2596
+ if (settleResult && settleResult.callResults.length > 0) {
2597
+ lines.push(`### Settlement Calls ${settleResult.success ? "\u2713" : "\u2716"}`);
2598
+ for (const cr of settleResult.callResults) {
2599
+ const icon = cr.success ? "\u2713" : "\u2716";
2600
+ const fn = cr.decodedFunction || `${cr.selector}...`;
2601
+ lines.push(`${cr.index + 1}. ${icon} \`${fn}\``);
2602
+ }
2603
+ lines.push("");
2604
+ }
2605
+ if (criticals.length > 0) {
2606
+ lines.push(`### \u{1F6A8} Critical Risks`);
2607
+ for (const r of criticals) {
2608
+ const ref = r.callIndex !== void 0 ? ` [call #${r.callIndex + 1}]` : "";
2609
+ lines.push(`- **${r.code}**${ref}: ${r.message}`);
2610
+ }
2611
+ lines.push("");
2612
+ }
2613
+ if (warnings.length > 0) {
2614
+ lines.push(`### \u26A0\uFE0F Warnings`);
2615
+ for (const r of warnings) {
2616
+ const ref = r.callIndex !== void 0 ? ` [call #${r.callIndex + 1}]` : "";
2617
+ lines.push(`- **${r.code}**${ref}: ${r.message}`);
2618
+ }
2619
+ lines.push("");
2620
+ }
2621
+ if (highestLevel === "critical") {
2622
+ lines.push(`### Action Required`);
2623
+ lines.push(`Recommend **VETO** \u2014 critical risks detected.`);
2624
+ } else if (highestLevel === "warning") {
2625
+ lines.push(`### Review Required`);
2626
+ lines.push(`Warnings detected \u2014 manual review recommended before execution.`);
2627
+ } else {
2628
+ lines.push(`### \u2705 No Risks Detected`);
2629
+ lines.push(`All call targets verified. All calldata decoded. Safe to proceed.`);
2630
+ }
2631
+ const markdown = lines.join("\n");
2632
+ const messageType = highestLevel === "critical" ? "RISK_ALERT" : "POSITION_UPDATE";
2633
+ await xmtp.sendEnvelope(group, {
2634
+ type: messageType,
2635
+ from: getAccount2().address,
2636
+ text: markdown,
2637
+ data: {
2638
+ format: "markdown",
2639
+ proposalId: Number(proposalId),
2640
+ vault,
2641
+ riskLevel: highestLevel,
2642
+ riskCodes: allRisks.filter((r) => r.level !== "info").map((r) => r.code),
2643
+ simulationSuccess: execResult.success && (settleResult?.success ?? true)
2644
+ },
2645
+ timestamp: Math.floor(Date.now() / 1e3)
2646
+ });
2647
+ } catch (err) {
2648
+ const message = err instanceof Error ? err.message : String(err);
2649
+ console.log(chalk5.yellow(` \u26A0 Failed to send XMTP alert: ${message.slice(0, 100)}`));
2650
+ }
2651
+ }
2652
+
2653
+ // src/commands/proposal.ts
2654
+ var G2 = chalk6.green;
2655
+ var W2 = chalk6.white;
2656
+ var DIM2 = chalk6.gray;
2657
+ var BOLD2 = chalk6.white.bold;
2658
+ var LABEL2 = chalk6.green.bold;
2659
+ var SEP2 = () => console.log(DIM2("\u2500".repeat(60)));
1795
2660
  function formatTimestamp(ts) {
1796
2661
  if (ts === 0n) return "\u2014";
1797
2662
  return new Date(Number(ts) * 1e3).toLocaleString();
@@ -1807,11 +2672,11 @@ function parseCallsFile(path) {
1807
2672
  }
1808
2673
  function registerProposalCommands(program2) {
1809
2674
  const proposal = program2.command("proposal").description("Governance proposals \u2014 create, vote, execute, settle");
1810
- proposal.command("create").description("Submit a strategy proposal").requiredOption("--vault <address>", "Vault address the proposal targets").requiredOption("--name <name>", "Strategy name").requiredOption("--description <text>", "Strategy rationale and risk summary").requiredOption("--performance-fee <bps>", "Agent fee in bps (e.g. 1500 = 15%)").requiredOption("--duration <duration>", "Strategy duration (e.g. 7d, 24h, 3600)").requiredOption("--execute-calls <path>", "Path to JSON file with execute Call[] array").requiredOption("--settle-calls <path>", "Path to JSON file with settlement Call[] array").option("--metadata-uri <uri>", "Override \u2014 skip IPFS upload and use this URI directly").action(async (opts) => {
2675
+ proposal.command("create").description("Submit a strategy proposal").requiredOption("--vault <address>", "Vault address the proposal targets").requiredOption("--name <name>", "Strategy name").requiredOption("--description <text>", "Strategy rationale and risk summary").requiredOption("--performance-fee <bps>", "Agent fee in bps (e.g. 1500 = 15%)").requiredOption("--duration <duration>", "Strategy duration (e.g. 7d, 24h, 3600)").requiredOption("--execute-calls <path>", "Path to JSON file with execute Call[] array").requiredOption("--settle-calls <path>", "Path to JSON file with settlement Call[] array").option("--metadata-uri <uri>", "Override \u2014 skip IPFS upload and use this URI directly").option("--simulate", "Simulate calls via Tenderly before submitting").option("--force", "Submit even if simulation fails (requires --simulate)").action(async (opts) => {
1811
2676
  try {
1812
2677
  const vault = opts.vault;
1813
2678
  if (!isAddress4(vault)) {
1814
- console.error(chalk5.red(`Invalid vault address: ${opts.vault}`));
2679
+ console.error(chalk6.red(`Invalid vault address: ${opts.vault}`));
1815
2680
  process.exit(1);
1816
2681
  }
1817
2682
  const performanceFeeBps = parseBigIntArg(opts.performanceFee, "performance-fee");
@@ -1820,7 +2685,7 @@ function registerProposalCommands(program2) {
1820
2685
  const settleCalls = parseCallsFile(opts.settleCalls);
1821
2686
  let metadataURI = opts.metadataUri || "";
1822
2687
  if (!metadataURI) {
1823
- const spinner2 = ora5({ text: W("Uploading metadata to IPFS..."), color: "green" }).start();
2688
+ const spinner2 = ora5({ text: W2("Uploading metadata to IPFS..."), color: "green" }).start();
1824
2689
  try {
1825
2690
  const account = getAccount();
1826
2691
  const metadata = {
@@ -1841,36 +2706,67 @@ function registerProposalCommands(program2) {
1841
2706
  createdAt: Math.floor(Date.now() / 1e3)
1842
2707
  };
1843
2708
  metadataURI = await uploadMetadata(proposalMeta);
1844
- spinner2.succeed(G(`Metadata pinned: ${DIM(metadataURI)}`));
2709
+ spinner2.succeed(G2(`Metadata pinned: ${DIM2(metadataURI)}`));
1845
2710
  } catch (err) {
1846
- spinner2.warn(chalk5.yellow("IPFS upload failed \u2014 using inline metadata"));
2711
+ spinner2.warn(chalk6.yellow("IPFS upload failed \u2014 using inline metadata"));
1847
2712
  const json = JSON.stringify({ name: opts.name, description: opts.description, vault });
1848
2713
  metadataURI = `data:application/json;base64,${Buffer.from(json).toString("base64")}`;
1849
2714
  }
1850
2715
  }
1851
2716
  console.log();
1852
- console.log(LABEL(" \u25C6 Proposal Summary"));
1853
- SEP();
1854
- console.log(W(` Name: ${BOLD(opts.name)}`));
1855
- console.log(W(` Vault: ${G(vault)}`));
1856
- console.log(W(` Performance Fee: ${Number(performanceFeeBps) / 100}%`));
1857
- console.log(W(` Duration: ${formatDurationShort(strategyDuration)}`));
1858
- console.log(W(` Calls: ${executeCalls.length} execute + ${settleCalls.length} settle`));
1859
- console.log(W(` Metadata: ${DIM(metadataURI.length > 50 ? metadataURI.slice(0, 50) + "..." : metadataURI)}`));
1860
- SEP();
1861
- const spinner = ora5({ text: W("Submitting proposal..."), color: "green" }).start();
2717
+ console.log(LABEL2(" \u25C6 Proposal Summary"));
2718
+ SEP2();
2719
+ console.log(W2(` Name: ${BOLD2(opts.name)}`));
2720
+ console.log(W2(` Vault: ${G2(vault)}`));
2721
+ console.log(W2(` Performance Fee: ${Number(performanceFeeBps) / 100}%`));
2722
+ console.log(W2(` Duration: ${formatDurationShort(strategyDuration)}`));
2723
+ console.log(W2(` Calls: ${executeCalls.length} execute + ${settleCalls.length} settle`));
2724
+ console.log(W2(` Metadata: ${DIM2(metadataURI.length > 50 ? metadataURI.slice(0, 50) + "..." : metadataURI)}`));
2725
+ SEP2();
2726
+ if (opts.simulate) {
2727
+ let createRiskContext = { vault, performanceFeeBps, strategyDuration };
2728
+ try {
2729
+ const govParams = await getGovernorParams();
2730
+ createRiskContext = { ...createRiskContext, governorParams: govParams };
2731
+ } catch {
2732
+ }
2733
+ const simSpinner = ora5({ text: W2("Simulating execute calls..."), color: "green" }).start();
2734
+ const execResult = await simulateBatchCalls(vault, executeCalls, "execute", createRiskContext);
2735
+ simSpinner.stop();
2736
+ printSimulationResult(execResult, "execute");
2737
+ if (settleCalls.length > 0) {
2738
+ const settleSpinner = ora5({ text: W2("Simulating settlement calls..."), color: "green" }).start();
2739
+ const settleResult = await simulateBatchCalls(vault, settleCalls, "settlement", createRiskContext);
2740
+ settleSpinner.stop();
2741
+ printSimulationResult(settleResult, "settlement");
2742
+ if (!settleResult.success && !opts.force) {
2743
+ console.error(chalk6.red("\n \u2716 Settlement simulation failed. Use --force to submit anyway."));
2744
+ process.exit(1);
2745
+ }
2746
+ }
2747
+ if (!execResult.success && !opts.force) {
2748
+ console.error(chalk6.red("\n \u2716 Execution simulation failed. Use --force to submit anyway."));
2749
+ process.exit(1);
2750
+ }
2751
+ const hasCriticalRisks = execResult.risks.some((r) => r.level === "critical");
2752
+ if (hasCriticalRisks && !opts.force) {
2753
+ console.error(chalk6.red("\n \u2716 Critical risks detected. Use --force to submit anyway."));
2754
+ process.exit(1);
2755
+ }
2756
+ }
2757
+ const spinner = ora5({ text: W2("Submitting proposal..."), color: "green" }).start();
1862
2758
  const result = await propose(vault, metadataURI, performanceFeeBps, strategyDuration, executeCalls, settleCalls);
1863
- spinner.succeed(G("Proposal submitted"));
2759
+ spinner.succeed(G2("Proposal submitted"));
1864
2760
  console.log();
1865
- console.log(LABEL(" \u25C6 Proposal Created"));
1866
- SEP();
1867
- console.log(W(` Proposal ID: ${G(`#${result.proposalId}`)}`));
1868
- console.log(W(` Tx: ${DIM(getExplorerUrl(result.hash))}`));
1869
- SEP();
2761
+ console.log(LABEL2(" \u25C6 Proposal Created"));
2762
+ SEP2();
2763
+ console.log(W2(` Proposal ID: ${G2(`#${result.proposalId}`)}`));
2764
+ console.log(W2(` Tx: ${DIM2(getExplorerUrl(result.hash))}`));
2765
+ SEP2();
1870
2766
  console.log();
1871
2767
  } catch (err) {
1872
- console.error(chalk5.red(`
1873
- \u2716 ${err instanceof Error ? err.message : String(err)}`));
2768
+ console.error(chalk6.red(`
2769
+ \u2716 ${formatContractError(err)}`));
1874
2770
  process.exit(1);
1875
2771
  }
1876
2772
  });
@@ -1880,7 +2776,7 @@ function registerProposalCommands(program2) {
1880
2776
  const count = await proposalCount();
1881
2777
  if (count === 0n) {
1882
2778
  spinner.stop();
1883
- console.log(DIM("\n No proposals found.\n"));
2779
+ console.log(DIM2("\n No proposals found.\n"));
1884
2780
  return;
1885
2781
  }
1886
2782
  const vaultFilter = opts.vault ? opts.vault.toLowerCase() : null;
@@ -1900,16 +2796,16 @@ function registerProposalCommands(program2) {
1900
2796
  });
1901
2797
  spinner.stop();
1902
2798
  if (proposals.length === 0) {
1903
- console.log(DIM("\n No matching proposals.\n"));
2799
+ console.log(DIM2("\n No matching proposals.\n"));
1904
2800
  return;
1905
2801
  }
1906
2802
  console.log();
1907
- console.log(BOLD(`Proposals (${proposals.length})`));
1908
- console.log(DIM("\u2500".repeat(90)));
2803
+ console.log(BOLD2(`Proposals (${proposals.length})`));
2804
+ console.log(DIM2("\u2500".repeat(90)));
1909
2805
  console.log(
1910
- DIM(" ID ") + DIM("Agent".padEnd(14)) + DIM("State".padEnd(12)) + DIM("Votes (For/Against)".padEnd(22)) + DIM("Fee".padEnd(8)) + DIM("Duration".padEnd(10)) + DIM("Created")
2806
+ DIM2(" ID ") + DIM2("Agent".padEnd(14)) + DIM2("State".padEnd(12)) + DIM2("Votes (For/Against)".padEnd(22)) + DIM2("Fee".padEnd(8)) + DIM2("Duration".padEnd(10)) + DIM2("Created")
1911
2807
  );
1912
- console.log(DIM("\u2500".repeat(90)));
2808
+ console.log(DIM2("\u2500".repeat(90)));
1913
2809
  for (const p of proposals) {
1914
2810
  const state = PROPOSAL_STATES[p.computedState] || "Unknown";
1915
2811
  const created = p.snapshotTimestamp > 0n ? new Date(Number(p.snapshotTimestamp) * 1e3).toLocaleDateString() : "\u2014";
@@ -1924,7 +2820,7 @@ function registerProposalCommands(program2) {
1924
2820
  console.log();
1925
2821
  } catch (err) {
1926
2822
  spinner.fail("Failed to load proposals");
1927
- console.error(chalk5.red(err instanceof Error ? err.message : String(err)));
2823
+ console.error(chalk6.red(formatContractError(err)));
1928
2824
  process.exit(1);
1929
2825
  }
1930
2826
  });
@@ -1940,63 +2836,63 @@ function registerProposalCommands(program2) {
1940
2836
  const totalVotes = p.votesFor + p.votesAgainst;
1941
2837
  const quorumNeeded = totalVotes > 0n ? `${Number(params.vetoThresholdBps) / 100}%` : "\u2014";
1942
2838
  console.log();
1943
- console.log(LABEL(` \u25C6 Proposal #${p.id}`));
1944
- SEP();
2839
+ console.log(LABEL2(` \u25C6 Proposal #${p.id}`));
2840
+ SEP2();
1945
2841
  if (p.metadataURI && p.metadataURI.startsWith("ipfs://")) {
1946
2842
  try {
1947
2843
  const meta = await fetchMetadata(p.metadataURI);
1948
- console.log(W(` Name: ${BOLD(meta.name)}`));
1949
- console.log(W(` Description: ${DIM(meta.description)}`));
2844
+ console.log(W2(` Name: ${BOLD2(meta.name)}`));
2845
+ console.log(W2(` Description: ${DIM2(meta.description)}`));
1950
2846
  } catch {
1951
- console.log(W(` Metadata: ${DIM(p.metadataURI)}`));
2847
+ console.log(W2(` Metadata: ${DIM2(p.metadataURI)}`));
1952
2848
  }
1953
2849
  } else if (p.metadataURI) {
1954
- console.log(W(` Metadata: ${DIM(p.metadataURI)}`));
2850
+ console.log(W2(` Metadata: ${DIM2(p.metadataURI)}`));
1955
2851
  }
1956
- console.log(W(` State: ${BOLD(stateLabel)}`));
1957
- console.log(W(` Proposer: ${G(p.proposer)}`));
1958
- console.log(W(` Vault: ${G(p.vault)}`));
1959
- console.log(W(` Performance Fee: ${Number(p.performanceFeeBps) / 100}%`));
1960
- console.log(W(` Duration: ${formatDurationShort(p.strategyDuration)}`));
2852
+ console.log(W2(` State: ${BOLD2(stateLabel)}`));
2853
+ console.log(W2(` Proposer: ${G2(p.proposer)}`));
2854
+ console.log(W2(` Vault: ${G2(p.vault)}`));
2855
+ console.log(W2(` Performance Fee: ${Number(p.performanceFeeBps) / 100}%`));
2856
+ console.log(W2(` Duration: ${formatDurationShort(p.strategyDuration)}`));
1961
2857
  console.log();
1962
- console.log(LABEL(" Timestamps"));
1963
- console.log(W(` Snapshot: ${formatTimestamp(p.snapshotTimestamp)}`));
1964
- console.log(W(` Vote End: ${formatTimestamp(p.voteEnd)}`));
1965
- console.log(W(` Execute By: ${formatTimestamp(p.executeBy)}`));
1966
- console.log(W(` Executed At: ${formatTimestamp(p.executedAt)}`));
2858
+ console.log(LABEL2(" Timestamps"));
2859
+ console.log(W2(` Snapshot: ${formatTimestamp(p.snapshotTimestamp)}`));
2860
+ console.log(W2(` Vote End: ${formatTimestamp(p.voteEnd)}`));
2861
+ console.log(W2(` Execute By: ${formatTimestamp(p.executeBy)}`));
2862
+ console.log(W2(` Executed At: ${formatTimestamp(p.executedAt)}`));
1967
2863
  console.log();
1968
- console.log(LABEL(" Votes"));
1969
- console.log(W(` For: ${formatShares(p.votesFor)}`));
1970
- console.log(W(` Against: ${formatShares(p.votesAgainst)}`));
1971
- console.log(W(` Abstain: ${formatShares(p.votesAbstain)}`));
1972
- console.log(W(` Veto Threshold: ${quorumNeeded}`));
2864
+ console.log(LABEL2(" Votes"));
2865
+ console.log(W2(` For: ${formatShares(p.votesFor)}`));
2866
+ console.log(W2(` Against: ${formatShares(p.votesAgainst)}`));
2867
+ console.log(W2(` Abstain: ${formatShares(p.votesAbstain)}`));
2868
+ console.log(W2(` Veto Threshold: ${quorumNeeded}`));
1973
2869
  if (state === PROPOSAL_STATE.Executed || state === PROPOSAL_STATE.Settled) {
1974
2870
  try {
1975
2871
  const cap = await getCapitalSnapshot(id);
1976
2872
  console.log();
1977
- console.log(LABEL(" Capital"));
1978
- console.log(W(` Snapshot: ${formatUSDC(cap)}`));
2873
+ console.log(LABEL2(" Capital"));
2874
+ console.log(W2(` Snapshot: ${formatUSDC(cap)}`));
1979
2875
  } catch {
1980
2876
  }
1981
2877
  }
1982
2878
  const execCalls = await getExecuteCalls(id);
1983
2879
  const settlCalls = await getSettlementCalls(id);
1984
2880
  console.log();
1985
- console.log(LABEL(` Execute Calls (${execCalls.length})`));
2881
+ console.log(LABEL2(` Execute Calls (${execCalls.length})`));
1986
2882
  for (let i = 0; i < execCalls.length; i++) {
1987
- console.log(DIM(` [${i}] target=${execCalls[i].target}`));
1988
- console.log(DIM(` data=${execCalls[i].data.slice(0, 20)}... value=${execCalls[i].value}`));
2883
+ console.log(DIM2(` [${i}] target=${execCalls[i].target}`));
2884
+ console.log(DIM2(` data=${execCalls[i].data.slice(0, 20)}... value=${execCalls[i].value}`));
1989
2885
  }
1990
- console.log(LABEL(` Settlement Calls (${settlCalls.length})`));
2886
+ console.log(LABEL2(` Settlement Calls (${settlCalls.length})`));
1991
2887
  for (let i = 0; i < settlCalls.length; i++) {
1992
- console.log(DIM(` [${i}] target=${settlCalls[i].target}`));
1993
- console.log(DIM(` data=${settlCalls[i].data.slice(0, 20)}... value=${settlCalls[i].value}`));
2888
+ console.log(DIM2(` [${i}] target=${settlCalls[i].target}`));
2889
+ console.log(DIM2(` data=${settlCalls[i].data.slice(0, 20)}... value=${settlCalls[i].value}`));
1994
2890
  }
1995
- SEP();
2891
+ SEP2();
1996
2892
  console.log();
1997
2893
  } catch (err) {
1998
2894
  spinner.fail("Failed to load proposal");
1999
- console.error(chalk5.red(err instanceof Error ? err.message : String(err)));
2895
+ console.error(chalk6.red(formatContractError(err)));
2000
2896
  process.exit(1);
2001
2897
  }
2002
2898
  });
@@ -2006,7 +2902,7 @@ function registerProposalCommands(program2) {
2006
2902
  const supportRaw = String(opts.support).toLowerCase();
2007
2903
  const support = supportRaw === "yes" || supportRaw === "for" ? VOTE_TYPE.For : supportRaw === "no" || supportRaw === "against" ? VOTE_TYPE.Against : supportRaw === "abstain" ? VOTE_TYPE.Abstain : null;
2008
2904
  if (support === null) {
2009
- console.error(chalk5.red(`Invalid support value "${opts.support}". Use for|against|abstain.`));
2905
+ console.error(chalk6.red(`Invalid support value "${opts.support}". Use for|against|abstain.`));
2010
2906
  process.exit(1);
2011
2907
  }
2012
2908
  const account = getAccount();
@@ -2025,27 +2921,27 @@ function registerProposalCommands(program2) {
2025
2921
  const weight = await getVoteWeight(proposalId, account.address);
2026
2922
  spinner.stop();
2027
2923
  console.log();
2028
- console.log(LABEL(" \u25C6 Cast Vote"));
2029
- SEP();
2030
- console.log(W(` Proposal: #${proposalId}`));
2031
- console.log(W(` Vault: ${G(p.vault)}`));
2032
- console.log(W(
2033
- ` Support: ${support === VOTE_TYPE.For ? G("FOR") : support === VOTE_TYPE.Against ? chalk5.red("AGAINST") : DIM("ABSTAIN")}`
2924
+ console.log(LABEL2(" \u25C6 Cast Vote"));
2925
+ SEP2();
2926
+ console.log(W2(` Proposal: #${proposalId}`));
2927
+ console.log(W2(` Vault: ${G2(p.vault)}`));
2928
+ console.log(W2(
2929
+ ` Support: ${support === VOTE_TYPE.For ? G2("FOR") : support === VOTE_TYPE.Against ? chalk6.red("AGAINST") : DIM2("ABSTAIN")}`
2034
2930
  ));
2035
- console.log(W(` Weight: ${formatShares(weight)} shares`));
2036
- SEP();
2037
- const voteSpinner = ora5({ text: W("Submitting vote..."), color: "green" }).start();
2931
+ console.log(W2(` Weight: ${formatShares(weight)} shares`));
2932
+ SEP2();
2933
+ const voteSpinner = ora5({ text: W2("Submitting vote..."), color: "green" }).start();
2038
2934
  const hash = await vote(proposalId, support);
2039
- voteSpinner.succeed(G("Vote cast"));
2040
- console.log(DIM(` ${getExplorerUrl(hash)}`));
2935
+ voteSpinner.succeed(G2("Vote cast"));
2936
+ console.log(DIM2(` ${getExplorerUrl(hash)}`));
2041
2937
  console.log();
2042
2938
  } catch (err) {
2043
- console.error(chalk5.red(`
2044
- \u2716 ${err instanceof Error ? err.message : String(err)}`));
2939
+ console.error(chalk6.red(`
2940
+ \u2716 ${formatContractError(err)}`));
2045
2941
  process.exit(1);
2046
2942
  }
2047
2943
  });
2048
- proposal.command("execute").description("Execute an approved proposal").requiredOption("--id <proposalId>", "Proposal ID").action(async (opts) => {
2944
+ proposal.command("execute").description("Execute an approved proposal").requiredOption("--id <proposalId>", "Proposal ID").option("--dry-run", "Simulate execution without sending a transaction").action(async (opts) => {
2049
2945
  try {
2050
2946
  const proposalId = parseBigIntArg(opts.id, "proposal ID");
2051
2947
  const spinner = ora5("Loading proposal...").start();
@@ -2054,19 +2950,34 @@ function registerProposalCommands(program2) {
2054
2950
  spinner.fail(`Proposal is ${PROPOSAL_STATES[state] || "Unknown"}, not Approved`);
2055
2951
  process.exit(1);
2056
2952
  }
2057
- spinner.text = W("Executing proposal...");
2953
+ if (opts.dryRun) {
2954
+ const p = await getProposal(proposalId);
2955
+ const execCalls = await getExecuteCalls(proposalId);
2956
+ const dryRunContext = {
2957
+ vault: p.vault,
2958
+ performanceFeeBps: p.performanceFeeBps,
2959
+ strategyDuration: p.strategyDuration
2960
+ };
2961
+ spinner.text = W2("Simulating execution...");
2962
+ const result = await simulateBatchCalls(p.vault, execCalls, "execute", dryRunContext);
2963
+ spinner.stop();
2964
+ printSimulationResult(result, "execute");
2965
+ const hasCritical = result.risks.some((r) => r.level === "critical");
2966
+ process.exit(result.success && !hasCritical ? 0 : 1);
2967
+ }
2968
+ spinner.text = W2("Executing proposal...");
2058
2969
  const hash = await executeProposal(proposalId);
2059
- spinner.succeed(G("Proposal executed"));
2060
- console.log(DIM(` ${getExplorerUrl(hash)}`));
2970
+ spinner.succeed(G2("Proposal executed"));
2971
+ console.log(DIM2(` ${getExplorerUrl(hash)}`));
2061
2972
  try {
2062
2973
  const cap = await getCapitalSnapshot(proposalId);
2063
- console.log(DIM(` Capital snapshot: ${formatUSDC(cap)}`));
2974
+ console.log(DIM2(` Capital snapshot: ${formatUSDC(cap)}`));
2064
2975
  } catch {
2065
2976
  }
2066
2977
  console.log();
2067
2978
  } catch (err) {
2068
- console.error(chalk5.red(`
2069
- \u2716 ${err instanceof Error ? err.message : String(err)}`));
2979
+ console.error(chalk6.red(`
2980
+ \u2716 ${formatContractError(err)}`));
2070
2981
  process.exit(1);
2071
2982
  }
2072
2983
  });
@@ -2086,27 +2997,27 @@ function registerProposalCommands(program2) {
2086
2997
  const durationElapsed = p.executedAt > 0n && now >= p.executedAt + p.strategyDuration;
2087
2998
  let hash;
2088
2999
  if (isProposer && !durationElapsed) {
2089
- spinner.text = W("Settling (proposer)...");
3000
+ spinner.text = W2("Settling (proposer)...");
2090
3001
  hash = await settleProposal(proposalId);
2091
- spinner.succeed(G("Settled by proposer"));
3002
+ spinner.succeed(G2("Settled by proposer"));
2092
3003
  } else if (durationElapsed && !opts.calls) {
2093
- spinner.text = W("Settling (permissionless)...");
3004
+ spinner.text = W2("Settling (permissionless)...");
2094
3005
  hash = await settleProposal(proposalId);
2095
- spinner.succeed(G("Settled (permissionless)"));
3006
+ spinner.succeed(G2("Settled (permissionless)"));
2096
3007
  } else if (durationElapsed && opts.calls) {
2097
- spinner.text = W("Emergency settling...");
3008
+ spinner.text = W2("Emergency settling...");
2098
3009
  const calls = parseCallsFile(opts.calls);
2099
3010
  hash = await emergencySettle(proposalId, calls);
2100
- spinner.succeed(G("Emergency settled"));
3011
+ spinner.succeed(G2("Emergency settled"));
2101
3012
  } else {
2102
3013
  spinner.fail("Cannot settle: duration not elapsed and you are not the proposer.");
2103
3014
  process.exit(1);
2104
3015
  }
2105
- console.log(DIM(` ${getExplorerUrl(hash)}`));
3016
+ console.log(DIM2(` ${getExplorerUrl(hash)}`));
2106
3017
  console.log();
2107
3018
  } catch (err) {
2108
- console.error(chalk5.red(`
2109
- \u2716 ${err instanceof Error ? err.message : String(err)}`));
3019
+ console.error(chalk6.red(`
3020
+ \u2716 ${formatContractError(err)}`));
2110
3021
  process.exit(1);
2111
3022
  }
2112
3023
  });
@@ -2121,22 +3032,90 @@ function registerProposalCommands(program2) {
2121
3032
  }
2122
3033
  let hash;
2123
3034
  if (opts.emergency) {
2124
- spinner.text = W("Emergency cancelling...");
3035
+ spinner.text = W2("Emergency cancelling...");
2125
3036
  hash = await emergencyCancel(proposalId);
2126
- spinner.succeed(G("Emergency cancelled"));
3037
+ spinner.succeed(G2("Emergency cancelled"));
2127
3038
  } else {
2128
3039
  if (state !== PROPOSAL_STATE.Draft && state !== PROPOSAL_STATE.Pending) {
2129
3040
  spinner.fail(`Proposal is ${PROPOSAL_STATES[state] || "Unknown"} \u2014 use --emergency for non-pending/approved`);
2130
3041
  process.exit(1);
2131
3042
  }
2132
- spinner.text = W("Cancelling proposal...");
3043
+ spinner.text = W2("Cancelling proposal...");
2133
3044
  hash = await cancelProposal(proposalId);
2134
- spinner.succeed(G("Proposal cancelled"));
3045
+ spinner.succeed(G2("Proposal cancelled"));
2135
3046
  }
2136
- console.log(DIM(` ${getExplorerUrl(hash)}`));
3047
+ console.log(DIM2(` ${getExplorerUrl(hash)}`));
2137
3048
  console.log();
2138
3049
  } catch (err) {
2139
- console.error(chalk5.red(`
3050
+ console.error(chalk6.red(`
3051
+ \u2716 ${formatContractError(err)}`));
3052
+ process.exit(1);
3053
+ }
3054
+ });
3055
+ proposal.command("simulate").description("Simulate proposal calls via Tenderly (or eth_call fallback)").option("--id <proposalId>", "Proposal ID to simulate").option("--vault <address>", "Vault address (required with --execute-calls)").option("--execute-calls <path>", "Path to JSON execute calls file").option("--settle-calls <path>", "Path to JSON settlement calls file").option("--notify <subdomain>", "Send results to syndicate XMTP chat").action(async (opts) => {
3056
+ try {
3057
+ let vault;
3058
+ let executeCalls;
3059
+ let settlementCalls = [];
3060
+ let riskContext = {};
3061
+ let proposalId;
3062
+ if (opts.id) {
3063
+ proposalId = parseBigIntArg(opts.id, "proposal ID");
3064
+ const spinner2 = ora5("Loading proposal...").start();
3065
+ const p = await getProposal(proposalId);
3066
+ vault = p.vault;
3067
+ executeCalls = await getExecuteCalls(proposalId);
3068
+ settlementCalls = await getSettlementCalls(proposalId);
3069
+ riskContext = {
3070
+ vault,
3071
+ performanceFeeBps: p.performanceFeeBps,
3072
+ strategyDuration: p.strategyDuration
3073
+ };
3074
+ try {
3075
+ const govParams = await getGovernorParams();
3076
+ riskContext.governorParams = govParams;
3077
+ } catch {
3078
+ }
3079
+ spinner2.stop();
3080
+ console.log(DIM2(`
3081
+ Simulating proposal #${proposalId} on vault ${vault.slice(0, 10)}...`));
3082
+ } else if (opts.executeCalls) {
3083
+ if (!opts.vault || !isAddress4(opts.vault)) {
3084
+ console.error(chalk6.red("\n \u2716 --vault is required when using --execute-calls"));
3085
+ process.exit(1);
3086
+ }
3087
+ vault = opts.vault;
3088
+ executeCalls = parseCallsFile(opts.executeCalls);
3089
+ if (opts.settleCalls) {
3090
+ settlementCalls = parseCallsFile(opts.settleCalls);
3091
+ }
3092
+ riskContext = { vault };
3093
+ } else {
3094
+ console.error(chalk6.red("\n \u2716 Provide --id <proposalId> or --execute-calls <path>"));
3095
+ process.exit(1);
3096
+ }
3097
+ const spinner = ora5({ text: W2("Simulating execute calls..."), color: "green" }).start();
3098
+ const execResult = await simulateBatchCalls(vault, executeCalls, "execute", riskContext);
3099
+ spinner.stop();
3100
+ printSimulationResult(execResult, "execute");
3101
+ let settleResult;
3102
+ if (settlementCalls.length > 0) {
3103
+ const settleSpinner = ora5({ text: W2("Simulating settlement calls..."), color: "green" }).start();
3104
+ settleResult = await simulateBatchCalls(vault, settlementCalls, "settlement", riskContext);
3105
+ settleSpinner.stop();
3106
+ printSimulationResult(settleResult, "settlement");
3107
+ }
3108
+ if (opts.notify && proposalId !== void 0) {
3109
+ const notifySpinner = ora5({ text: W2("Sending XMTP alert..."), color: "green" }).start();
3110
+ await sendSimulationAlert(opts.notify, proposalId, vault, execResult, settleResult);
3111
+ notifySpinner.succeed(G2("Risk report sent to chat"));
3112
+ }
3113
+ const hasCritical = execResult.risks.some((r) => r.level === "critical") || (settleResult?.risks.some((r) => r.level === "critical") ?? false);
3114
+ if (!execResult.success || settleResult && !settleResult.success || hasCritical) {
3115
+ process.exit(1);
3116
+ }
3117
+ } catch (err) {
3118
+ console.error(chalk6.red(`
2140
3119
  \u2716 ${err instanceof Error ? err.message : String(err)}`));
2141
3120
  process.exit(1);
2142
3121
  }
@@ -2144,14 +3123,14 @@ function registerProposalCommands(program2) {
2144
3123
  }
2145
3124
 
2146
3125
  // src/commands/governor.ts
2147
- import chalk6 from "chalk";
3126
+ import chalk7 from "chalk";
2148
3127
  import ora6 from "ora";
2149
- var G2 = chalk6.green;
2150
- var W2 = chalk6.white;
2151
- var DIM2 = chalk6.gray;
2152
- var BOLD2 = chalk6.white.bold;
2153
- var LABEL2 = chalk6.green.bold;
2154
- var SEP2 = () => console.log(DIM2("\u2500".repeat(60)));
3128
+ var G3 = chalk7.green;
3129
+ var W3 = chalk7.white;
3130
+ var DIM3 = chalk7.gray;
3131
+ var BOLD3 = chalk7.white.bold;
3132
+ var LABEL3 = chalk7.green.bold;
3133
+ var SEP3 = () => console.log(DIM3("\u2500".repeat(60)));
2155
3134
  function registerGovernorCommands(program2) {
2156
3135
  const governor = program2.command("governor").description("Governor parameters and vault management");
2157
3136
  governor.command("info").description("Display current governor parameters and registered vaults").action(async () => {
@@ -2163,29 +3142,29 @@ function registerGovernorCommands(program2) {
2163
3142
  ]);
2164
3143
  spinner.stop();
2165
3144
  console.log();
2166
- console.log(LABEL2(" \u25C6 Governor Parameters"));
2167
- SEP2();
2168
- console.log(W2(` Address: ${G2(getGovernorAddress())}`));
2169
- console.log(W2(` Voting Period: ${BOLD2(formatDurationLong(params.votingPeriod))}`));
2170
- console.log(W2(` Execution Window: ${BOLD2(formatDurationLong(params.executionWindow))}`));
2171
- console.log(W2(` Veto Threshold: ${BOLD2(`${Number(params.vetoThresholdBps) / 100}%`)}`));
2172
- console.log(W2(` Max Performance Fee: ${BOLD2(`${Number(params.maxPerformanceFeeBps) / 100}%`)}`));
2173
- console.log(W2(` Max Strategy Duration:${BOLD2(` ${formatDurationLong(params.maxStrategyDuration)}`)}`));
2174
- console.log(W2(` Cooldown Period: ${BOLD2(formatDurationLong(params.cooldownPeriod))}`));
3145
+ console.log(LABEL3(" \u25C6 Governor Parameters"));
3146
+ SEP3();
3147
+ console.log(W3(` Address: ${G3(getGovernorAddress())}`));
3148
+ console.log(W3(` Voting Period: ${BOLD3(formatDurationLong(params.votingPeriod))}`));
3149
+ console.log(W3(` Execution Window: ${BOLD3(formatDurationLong(params.executionWindow))}`));
3150
+ console.log(W3(` Veto Threshold: ${BOLD3(`${Number(params.vetoThresholdBps) / 100}%`)}`));
3151
+ console.log(W3(` Max Performance Fee: ${BOLD3(`${Number(params.maxPerformanceFeeBps) / 100}%`)}`));
3152
+ console.log(W3(` Max Strategy Duration:${BOLD3(` ${formatDurationLong(params.maxStrategyDuration)}`)}`));
3153
+ console.log(W3(` Cooldown Period: ${BOLD3(formatDurationLong(params.cooldownPeriod))}`));
2175
3154
  console.log();
2176
- console.log(LABEL2(` Registered Vaults (${vaults.length})`));
3155
+ console.log(LABEL3(` Registered Vaults (${vaults.length})`));
2177
3156
  if (vaults.length === 0) {
2178
- console.log(DIM2(" (none)"));
3157
+ console.log(DIM3(" (none)"));
2179
3158
  } else {
2180
3159
  for (const v of vaults) {
2181
- console.log(W2(` ${G2(v)}`));
3160
+ console.log(W3(` ${G3(v)}`));
2182
3161
  }
2183
3162
  }
2184
- SEP2();
3163
+ SEP3();
2185
3164
  console.log();
2186
3165
  } catch (err) {
2187
3166
  spinner.fail("Failed to load governor info");
2188
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3167
+ console.error(chalk7.red(formatContractError(err)));
2189
3168
  process.exit(1);
2190
3169
  }
2191
3170
  });
@@ -2193,11 +3172,11 @@ function registerGovernorCommands(program2) {
2193
3172
  const spinner = ora6("Setting voting period...").start();
2194
3173
  try {
2195
3174
  const hash = await setVotingPeriod(parseBigIntArg(opts.seconds, "seconds"));
2196
- spinner.succeed(G2(`Voting period change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
2197
- console.log(DIM2(` ${getExplorerUrl(hash)}`));
3175
+ spinner.succeed(G3(`Voting period change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
3176
+ console.log(DIM3(` ${getExplorerUrl(hash)}`));
2198
3177
  } catch (err) {
2199
3178
  spinner.fail("Failed to set voting period");
2200
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3179
+ console.error(chalk7.red(formatContractError(err)));
2201
3180
  process.exit(1);
2202
3181
  }
2203
3182
  });
@@ -2205,11 +3184,11 @@ function registerGovernorCommands(program2) {
2205
3184
  const spinner = ora6("Setting execution window...").start();
2206
3185
  try {
2207
3186
  const hash = await setExecutionWindow(parseBigIntArg(opts.seconds, "seconds"));
2208
- spinner.succeed(G2(`Execution window change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
2209
- console.log(DIM2(` ${getExplorerUrl(hash)}`));
3187
+ spinner.succeed(G3(`Execution window change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
3188
+ console.log(DIM3(` ${getExplorerUrl(hash)}`));
2210
3189
  } catch (err) {
2211
3190
  spinner.fail("Failed to set execution window");
2212
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3191
+ console.error(chalk7.red(formatContractError(err)));
2213
3192
  process.exit(1);
2214
3193
  }
2215
3194
  });
@@ -2217,11 +3196,11 @@ function registerGovernorCommands(program2) {
2217
3196
  const spinner = ora6("Setting veto threshold...").start();
2218
3197
  try {
2219
3198
  const hash = await setVetoThresholdBps(parseBigIntArg(opts.bps, "bps"));
2220
- spinner.succeed(G2(`Veto threshold change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
2221
- console.log(DIM2(` ${getExplorerUrl(hash)}`));
3199
+ spinner.succeed(G3(`Veto threshold change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
3200
+ console.log(DIM3(` ${getExplorerUrl(hash)}`));
2222
3201
  } catch (err) {
2223
3202
  spinner.fail("Failed to set veto threshold");
2224
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3203
+ console.error(chalk7.red(formatContractError(err)));
2225
3204
  process.exit(1);
2226
3205
  }
2227
3206
  });
@@ -2229,11 +3208,11 @@ function registerGovernorCommands(program2) {
2229
3208
  const spinner = ora6("Setting max fee...").start();
2230
3209
  try {
2231
3210
  const hash = await setMaxPerformanceFeeBps(parseBigIntArg(opts.bps, "bps"));
2232
- spinner.succeed(G2(`Max performance fee change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
2233
- console.log(DIM2(` ${getExplorerUrl(hash)}`));
3211
+ spinner.succeed(G3(`Max performance fee change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
3212
+ console.log(DIM3(` ${getExplorerUrl(hash)}`));
2234
3213
  } catch (err) {
2235
3214
  spinner.fail("Failed to set max fee");
2236
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3215
+ console.error(chalk7.red(formatContractError(err)));
2237
3216
  process.exit(1);
2238
3217
  }
2239
3218
  });
@@ -2241,11 +3220,11 @@ function registerGovernorCommands(program2) {
2241
3220
  const spinner = ora6("Setting max duration...").start();
2242
3221
  try {
2243
3222
  const hash = await setMaxStrategyDuration(parseBigIntArg(opts.seconds, "seconds"));
2244
- spinner.succeed(G2(`Max strategy duration change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
2245
- console.log(DIM2(` ${getExplorerUrl(hash)}`));
3223
+ spinner.succeed(G3(`Max strategy duration change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
3224
+ console.log(DIM3(` ${getExplorerUrl(hash)}`));
2246
3225
  } catch (err) {
2247
3226
  spinner.fail("Failed to set max duration");
2248
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3227
+ console.error(chalk7.red(formatContractError(err)));
2249
3228
  process.exit(1);
2250
3229
  }
2251
3230
  });
@@ -2253,11 +3232,11 @@ function registerGovernorCommands(program2) {
2253
3232
  const spinner = ora6("Setting cooldown...").start();
2254
3233
  try {
2255
3234
  const hash = await setCooldownPeriod(parseBigIntArg(opts.seconds, "seconds"));
2256
- spinner.succeed(G2(`Cooldown period change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
2257
- console.log(DIM2(` ${getExplorerUrl(hash)}`));
3235
+ spinner.succeed(G3(`Cooldown period change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
3236
+ console.log(DIM3(` ${getExplorerUrl(hash)}`));
2258
3237
  } catch (err) {
2259
3238
  spinner.fail("Failed to set cooldown");
2260
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3239
+ console.error(chalk7.red(formatContractError(err)));
2261
3240
  process.exit(1);
2262
3241
  }
2263
3242
  });
@@ -2265,11 +3244,11 @@ function registerGovernorCommands(program2) {
2265
3244
  const spinner = ora6("Setting protocol fee...").start();
2266
3245
  try {
2267
3246
  const hash = await setProtocolFeeBps(parseBigIntArg(opts.bps, "bps"));
2268
- spinner.succeed(G2(`Protocol fee change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
2269
- console.log(DIM2(` ${getExplorerUrl(hash)}`));
3247
+ spinner.succeed(G3(`Protocol fee change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
3248
+ console.log(DIM3(` ${getExplorerUrl(hash)}`));
2270
3249
  } catch (err) {
2271
3250
  spinner.fail("Failed to set protocol fee");
2272
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3251
+ console.error(chalk7.red(formatContractError(err)));
2273
3252
  process.exit(1);
2274
3253
  }
2275
3254
  });
@@ -2283,20 +3262,20 @@ try {
2283
3262
  var require2 = createRequire(import.meta.url);
2284
3263
  var { version: CLI_VERSION } = require2("../package.json");
2285
3264
  async function loadXmtp() {
2286
- return import("./xmtp-7DF7UG66.js");
3265
+ return import("./xmtp-XKHXATPQ.js");
2287
3266
  }
2288
3267
  async function loadCron() {
2289
- return import("./cron-SKYKVZ6K.js");
3268
+ return import("./cron-EOULYO3R.js");
2290
3269
  }
2291
- var G3 = chalk7.green;
2292
- var W3 = chalk7.white;
2293
- var DIM3 = chalk7.gray;
2294
- var BOLD3 = chalk7.white.bold;
2295
- var LABEL3 = chalk7.green.bold;
2296
- var SEP3 = () => console.log(DIM3("\u2500".repeat(60)));
3270
+ var G4 = chalk8.green;
3271
+ var W4 = chalk8.white;
3272
+ var DIM4 = chalk8.gray;
3273
+ var BOLD4 = chalk8.white.bold;
3274
+ var LABEL4 = chalk8.green.bold;
3275
+ var SEP4 = () => console.log(DIM4("\u2500".repeat(60)));
2297
3276
  function validateAddress(value, name) {
2298
3277
  if (!isAddress5(value)) {
2299
- console.error(chalk7.red(`Invalid ${name} address: ${value}`));
3278
+ console.error(chalk8.red(`Invalid ${name} address: ${value}`));
2300
3279
  process.exit(1);
2301
3280
  }
2302
3281
  return value;
@@ -2316,7 +3295,7 @@ program.name("sherwood").description("CLI for agent-managed investment syndicate
2316
3295
  process.env.ENABLE_TESTNET = "true";
2317
3296
  if (network !== "base") {
2318
3297
  console.warn(
2319
- chalk7.yellow("[warn] --testnet ignored, --chain takes precedence")
3298
+ chalk8.yellow("[warn] --testnet ignored, --chain takes precedence")
2320
3299
  );
2321
3300
  } else {
2322
3301
  network = "base-sepolia";
@@ -2324,39 +3303,39 @@ program.name("sherwood").description("CLI for agent-managed investment syndicate
2324
3303
  }
2325
3304
  setNetwork(network);
2326
3305
  if (getNetwork() !== "base") {
2327
- console.log(chalk7.yellow(`[${getNetwork()}]`));
3306
+ console.log(chalk8.yellow(`[${getNetwork()}]`));
2328
3307
  }
2329
3308
  });
2330
3309
  var syndicate = program.command("syndicate");
2331
3310
  syndicate.command("create").description("Create a new syndicate via the factory (interactive)").option("--subdomain <name>", "ENS subdomain (skip prompt)").option("--name <name>", "Syndicate name (skip prompt)").option("--agent-id <id>", "ERC-8004 agent identity token ID (skip prompt)").option("--asset <symbol-or-address>", "Vault asset: USDC, WETH, or a token address").option("--description <text>", "Short description").option("--metadata-uri <uri>", "Override metadata URI (skip IPFS upload)").option("--open-deposits", "Allow anyone to deposit (no whitelist)").option("--public-chat", "Enable dashboard spectator mode", false).option("-y, --yes", "Skip confirmation prompt (non-interactive mode)", false).action(async (opts) => {
2332
3311
  try {
2333
3312
  console.log();
2334
- console.log(LABEL3(" \u25C6 Create Syndicate"));
2335
- SEP3();
3313
+ console.log(LABEL4(" \u25C6 Create Syndicate"));
3314
+ SEP4();
2336
3315
  const wallet = getAccount();
2337
- console.log(DIM3(` Wallet: ${wallet.address}`));
2338
- console.log(DIM3(` Network: ${getChain().name}`));
2339
- SEP3();
3316
+ console.log(DIM4(` Wallet: ${wallet.address}`));
3317
+ console.log(DIM4(` Network: ${getChain().name}`));
3318
+ SEP4();
2340
3319
  const savedAgentId = getAgentId();
2341
3320
  const nonInteractive = opts.yes;
2342
3321
  const name = opts.name || (nonInteractive ? (() => {
2343
3322
  throw new Error("--name is required in non-interactive mode (-y)");
2344
3323
  })() : await input({
2345
- message: G3("Syndicate name"),
3324
+ message: G4("Syndicate name"),
2346
3325
  validate: (v) => v.length > 0 || "Name is required"
2347
3326
  }));
2348
3327
  const subdomain = opts.subdomain || (nonInteractive ? name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "") : await input({
2349
- message: G3("ENS subdomain"),
3328
+ message: G4("ENS subdomain"),
2350
3329
  default: name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, ""),
2351
3330
  validate: (v) => v.length >= 3 || "Must be at least 3 characters"
2352
3331
  }));
2353
3332
  const description = opts.description || (nonInteractive ? `${name} \u2014 a Sherwood syndicate` : await input({
2354
- message: G3("Description"),
3333
+ message: G4("Description"),
2355
3334
  default: `${name} \u2014 a Sherwood syndicate`
2356
3335
  }));
2357
- const agentIdStr = opts.agentId || (savedAgentId ? nonInteractive ? String(savedAgentId) : await input({ message: G3("Agent ID (ERC-8004)"), default: String(savedAgentId) }) : nonInteractive ? "0" : await input({ message: G3("Agent ID (ERC-8004)"), validate: (v) => /^\d+$/.test(v) || "Must be a number" }));
3336
+ const agentIdStr = opts.agentId || (savedAgentId ? nonInteractive ? String(savedAgentId) : await input({ message: G4("Agent ID (ERC-8004)"), default: String(savedAgentId) }) : nonInteractive ? "0" : await input({ message: G4("Agent ID (ERC-8004)"), validate: (v) => /^\d+$/.test(v) || "Must be a number" }));
2358
3337
  const openDeposits = opts.openDeposits !== void 0 ? opts.openDeposits : nonInteractive ? true : await confirm({
2359
- message: G3("Open deposits? (anyone can deposit)"),
3338
+ message: G4("Open deposits? (anyone can deposit)"),
2360
3339
  default: true
2361
3340
  });
2362
3341
  const ASSET_SYMBOLS = {
@@ -2372,14 +3351,14 @@ syndicate.command("create").description("Create a new syndicate via the factory
2372
3351
  asset = opts.asset;
2373
3352
  } else {
2374
3353
  const supported = Object.keys(ASSET_SYMBOLS).join(", ");
2375
- console.error(chalk7.red(` Unknown asset "${opts.asset}". Use a symbol (${supported}) or a 0x address.`));
3354
+ console.error(chalk8.red(` Unknown asset "${opts.asset}". Use a symbol (${supported}) or a 0x address.`));
2376
3355
  process.exit(1);
2377
3356
  }
2378
3357
  } else if (nonInteractive) {
2379
3358
  asset = TOKENS().USDC !== "0x0000000000000000000000000000000000000000" ? ASSET_SYMBOLS.USDC : ASSET_SYMBOLS.WETH;
2380
3359
  } else {
2381
3360
  const assetChoice = await select({
2382
- message: G3("Vault asset (what token do depositors provide?)"),
3361
+ message: G4("Vault asset (what token do depositors provide?)"),
2383
3362
  choices: [
2384
3363
  { name: "USDC", value: "USDC", description: "USD Coin (6 decimals)" },
2385
3364
  { name: "WETH", value: "WETH", description: "Wrapped Ether (18 decimals)" }
@@ -2394,26 +3373,26 @@ syndicate.command("create").description("Create a new syndicate via the factory
2394
3373
  ]);
2395
3374
  const symbol = `sw${assetSymbol}`;
2396
3375
  console.log();
2397
- console.log(LABEL3(" \u25C6 Review"));
2398
- SEP3();
2399
- console.log(W3(` Name: ${BOLD3(name)}`));
2400
- console.log(W3(` ENS: ${G3(`${subdomain}.sherwoodagent.eth`)}`));
2401
- console.log(W3(` Description: ${DIM3(description)}`));
2402
- console.log(W3(` Agent ID: #${agentIdStr}`));
2403
- console.log(W3(` Asset: ${assetSymbol} (${asset.slice(0, 10)}...)`));
2404
- console.log(W3(` Share token: ${symbol}`));
2405
- console.log(W3(` Open deposits: ${openDeposits ? G3("yes") : chalk7.red("no (whitelist)")}`));
2406
- SEP3();
3376
+ console.log(LABEL4(" \u25C6 Review"));
3377
+ SEP4();
3378
+ console.log(W4(` Name: ${BOLD4(name)}`));
3379
+ console.log(W4(` ENS: ${G4(`${subdomain}.sherwoodagent.eth`)}`));
3380
+ console.log(W4(` Description: ${DIM4(description)}`));
3381
+ console.log(W4(` Agent ID: #${agentIdStr}`));
3382
+ console.log(W4(` Asset: ${assetSymbol} (${asset.slice(0, 10)}...)`));
3383
+ console.log(W4(` Share token: ${symbol}`));
3384
+ console.log(W4(` Open deposits: ${openDeposits ? G4("yes") : chalk8.red("no (whitelist)")}`));
3385
+ SEP4();
2407
3386
  if (!nonInteractive) {
2408
- const go = await confirm({ message: G3("Deploy syndicate?"), default: true });
3387
+ const go = await confirm({ message: G4("Deploy syndicate?"), default: true });
2409
3388
  if (!go) {
2410
- console.log(DIM3(" Cancelled."));
3389
+ console.log(DIM4(" Cancelled."));
2411
3390
  return;
2412
3391
  }
2413
3392
  }
2414
3393
  let metadataURI = opts.metadataUri || "";
2415
3394
  if (!metadataURI) {
2416
- const spinner2 = ora7({ text: W3("Uploading metadata to IPFS..."), color: "green" }).start();
3395
+ const spinner2 = ora7({ text: W4("Uploading metadata to IPFS..."), color: "green" }).start();
2417
3396
  try {
2418
3397
  const metadata = {
2419
3398
  schema: "sherwood/syndicate/v1",
@@ -2427,14 +3406,77 @@ syndicate.command("create").description("Create a new syndicate via the factory
2427
3406
  links: {}
2428
3407
  };
2429
3408
  metadataURI = await uploadMetadata(metadata);
2430
- spinner2.succeed(G3(`Metadata pinned: ${DIM3(metadataURI)}`));
3409
+ spinner2.succeed(G4(`Metadata pinned: ${DIM4(metadataURI)}`));
2431
3410
  } catch (err) {
2432
- spinner2.warn(chalk7.yellow(`IPFS upload failed \u2014 using inline metadata`));
3411
+ spinner2.warn(chalk8.yellow(`IPFS upload failed \u2014 using inline metadata`));
2433
3412
  const json = JSON.stringify({ name, description, subdomain, asset: assetSymbol, openDeposits, createdBy: "@sherwoodagent/cli" });
2434
3413
  metadataURI = `data:application/json;base64,${Buffer.from(json).toString("base64")}`;
2435
3414
  }
2436
3415
  }
2437
- const spinner = ora7({ text: W3("Deploying vault via factory..."), color: "green" }).start();
3416
+ const existingId = await subdomainExists(subdomain);
3417
+ if (existingId !== null) {
3418
+ const existingInfo = await getSyndicate(existingId);
3419
+ const callerAddress = getAccount().address.toLowerCase();
3420
+ if (existingInfo.creator.toLowerCase() !== callerAddress) {
3421
+ console.error(chalk8.red(`
3422
+ \u2716 Subdomain '${subdomain}' is already taken by another syndicate.
3423
+ `));
3424
+ process.exit(1);
3425
+ }
3426
+ console.log(chalk8.yellow(`
3427
+ \u26A0 Syndicate '${subdomain}' already exists \u2014 resuming setup...
3428
+ `));
3429
+ const spinner2 = ora7({ text: W4("Resuming setup..."), color: "green" }).start();
3430
+ const chainId2 = getChain().id;
3431
+ setChainContract(chainId2, "vault", existingInfo.vault);
3432
+ addSyndicate(chainId2, { subdomain, vault: existingInfo.vault, role: "creator" });
3433
+ setPrimarySyndicate(chainId2, subdomain);
3434
+ spinner2.text = W4("Checking agent registration...");
3435
+ try {
3436
+ setVaultAddress(existingInfo.vault);
3437
+ const isAlreadyAgent = await isAgent(getAccount().address);
3438
+ if (!isAlreadyAgent) {
3439
+ spinner2.text = W4("Registering creator as agent...");
3440
+ await registerAgent(BigInt(agentIdStr), getAccount().address);
3441
+ }
3442
+ } catch {
3443
+ }
3444
+ spinner2.text = W4("Checking chat group...");
3445
+ try {
3446
+ const xmtp = await loadXmtp();
3447
+ const xmtpClient = await xmtp.getXmtpClient();
3448
+ try {
3449
+ await xmtp.getGroup(xmtpClient, subdomain);
3450
+ } catch {
3451
+ spinner2.text = W4("Creating chat group...");
3452
+ const groupId = await xmtp.createSyndicateGroup(xmtpClient, subdomain, opts.publicChat);
3453
+ await setTextRecord(subdomain, "xmtpGroupId", groupId, existingInfo.vault);
3454
+ cacheGroupId(subdomain, groupId);
3455
+ }
3456
+ } catch {
3457
+ console.warn(chalk8.yellow("\n \u26A0 Could not set up XMTP chat group"));
3458
+ }
3459
+ try {
3460
+ const cron = await loadCron();
3461
+ cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
3462
+ } catch {
3463
+ }
3464
+ spinner2.stop();
3465
+ console.log();
3466
+ console.log(LABEL4(" \u25C6 Syndicate Setup Resumed"));
3467
+ SEP4();
3468
+ console.log(W4(` ID: ${G4(`#${existingId}`)}`));
3469
+ console.log(W4(` Vault: ${G4(existingInfo.vault)}`));
3470
+ console.log(W4(` ENS: ${G4(`${subdomain}.sherwoodagent.eth`)}`));
3471
+ console.log(W4(` Chat: ${DIM4(`sherwood chat ${subdomain}`)}`));
3472
+ const refParam2 = getAgentId() ? `?ref=${getAgentId()}` : "";
3473
+ console.log(W4(` Share: ${G4(`https://sherwood.sh/syndicate/${subdomain}${refParam2}`)}`));
3474
+ SEP4();
3475
+ console.log(G4(" \u2713 Syndicate saved to ~/.sherwood/config.json (set as primary)"));
3476
+ console.log();
3477
+ return;
3478
+ }
3479
+ const spinner = ora7({ text: W4("Deploying vault via factory..."), color: "green" }).start();
2438
3480
  const result = await createSyndicate({
2439
3481
  creatorAgentId: BigInt(agentIdStr),
2440
3482
  metadataURI,
@@ -2444,9 +3486,12 @@ syndicate.command("create").description("Create a new syndicate via the factory
2444
3486
  openDeposits,
2445
3487
  subdomain
2446
3488
  });
2447
- setChainContract(getChain().id, "vault", result.vault);
3489
+ const chainId = getChain().id;
3490
+ setChainContract(chainId, "vault", result.vault);
3491
+ addSyndicate(chainId, { subdomain, vault: result.vault, role: "creator" });
3492
+ setPrimarySyndicate(chainId, subdomain);
2448
3493
  await new Promise((r) => setTimeout(r, 2e3));
2449
- spinner.text = W3("Registering creator as agent...");
3494
+ spinner.text = W4("Registering creator as agent...");
2450
3495
  try {
2451
3496
  setVaultAddress(result.vault);
2452
3497
  const creatorAddress = getAccount().address;
@@ -2456,9 +3501,9 @@ syndicate.command("create").description("Create a new syndicate via the factory
2456
3501
  // agentAddress = creator EOA (direct execution)
2457
3502
  );
2458
3503
  } catch (regErr) {
2459
- console.warn(chalk7.yellow("\n \u26A0 Could not auto-register creator as agent \u2014 register manually with `syndicate add`"));
3504
+ console.warn(chalk8.yellow("\n \u26A0 Could not auto-register creator as agent \u2014 register manually with `syndicate add`"));
2460
3505
  }
2461
- spinner.text = W3("Setting up chat...");
3506
+ spinner.text = W4("Setting up chat...");
2462
3507
  try {
2463
3508
  const xmtp = await loadXmtp();
2464
3509
  const xmtpClient = await xmtp.getXmtpClient();
@@ -2466,35 +3511,37 @@ syndicate.command("create").description("Create a new syndicate via the factory
2466
3511
  await setTextRecord(subdomain, "xmtpGroupId", groupId, result.vault);
2467
3512
  cacheGroupId(subdomain, groupId);
2468
3513
  } catch {
2469
- console.warn(chalk7.yellow("\n \u26A0 Could not create XMTP chat group"));
2470
- console.warn(chalk7.dim(` Recover later with: sherwood chat ${subdomain} init`));
3514
+ console.warn(chalk8.yellow("\n \u26A0 Could not create XMTP chat group"));
3515
+ console.warn(chalk8.dim(` Recover later with: sherwood chat ${subdomain} init`));
2471
3516
  }
2472
3517
  try {
2473
3518
  const cron = await loadCron();
2474
3519
  const cronResult = cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
2475
3520
  if (cronResult.isOpenClaw && cronResult.registered) {
2476
- console.log(G3(" \u2713 Participation crons registered (15m check + hourly summary)"));
3521
+ console.log(G4(" \u2713 Participation crons registered (15m check + hourly summary)"));
2477
3522
  } else if (!cronResult.isOpenClaw) {
2478
- console.log(DIM3(" Tip: Set up a scheduled process to run `sherwood session check " + subdomain + "` periodically"));
3523
+ console.log(DIM4(" Tip: Set up a scheduled process to run `sherwood session check " + subdomain + "` periodically"));
2479
3524
  }
2480
3525
  } catch {
2481
3526
  }
2482
3527
  spinner.stop();
2483
3528
  console.log();
2484
- console.log(LABEL3(" \u25C6 Syndicate Created"));
2485
- SEP3();
2486
- console.log(W3(` ID: ${G3(`#${result.syndicateId}`)}`));
2487
- console.log(W3(` Vault: ${G3(result.vault)}`));
2488
- console.log(W3(` ENS: ${G3(`${subdomain}.sherwoodagent.eth`)}`));
2489
- console.log(W3(` Metadata: ${DIM3(metadataURI.length > 50 ? metadataURI.slice(0, 50) + "..." : metadataURI)}`));
2490
- console.log(W3(` Explorer: ${DIM3(getExplorerUrl(result.hash))}`));
2491
- console.log(W3(` Chat: ${DIM3(`sherwood chat ${subdomain}`)}`));
2492
- SEP3();
2493
- console.log(G3(" \u2713 Vault saved to ~/.sherwood/config.json"));
3529
+ console.log(LABEL4(" \u25C6 Syndicate Created"));
3530
+ SEP4();
3531
+ console.log(W4(` ID: ${G4(`#${result.syndicateId}`)}`));
3532
+ console.log(W4(` Vault: ${G4(result.vault)}`));
3533
+ console.log(W4(` ENS: ${G4(`${subdomain}.sherwoodagent.eth`)}`));
3534
+ console.log(W4(` Metadata: ${DIM4(metadataURI.length > 50 ? metadataURI.slice(0, 50) + "..." : metadataURI)}`));
3535
+ console.log(W4(` Explorer: ${DIM4(getExplorerUrl(result.hash))}`));
3536
+ console.log(W4(` Chat: ${DIM4(`sherwood chat ${subdomain}`)}`));
3537
+ const refParam = getAgentId() ? `?ref=${getAgentId()}` : "";
3538
+ console.log(W4(` Share: ${G4(`https://sherwood.sh/syndicate/${subdomain}${refParam}`)}`));
3539
+ SEP4();
3540
+ console.log(G4(" \u2713 Syndicate saved to ~/.sherwood/config.json (set as primary)"));
2494
3541
  console.log();
2495
3542
  } catch (err) {
2496
- console.error(chalk7.red(`
2497
- \u2716 ${err instanceof Error ? err.message : String(err)}`));
3543
+ console.error(chalk8.red(`
3544
+ \u2716 ${formatContractError(err)}`));
2498
3545
  process.exit(1);
2499
3546
  }
2500
3547
  });
@@ -2518,34 +3565,34 @@ syndicate.command("list").description("List active syndicates (queries subgraph,
2518
3565
  }
2519
3566
  spinner.stop();
2520
3567
  if (syndicates.length === 0) {
2521
- console.log(chalk7.dim("No active syndicates found."));
3568
+ console.log(chalk8.dim("No active syndicates found."));
2522
3569
  return;
2523
3570
  }
2524
3571
  console.log();
2525
- console.log(chalk7.bold(`Active Syndicates (${syndicates.length})`));
3572
+ console.log(chalk8.bold(`Active Syndicates (${syndicates.length})`));
2526
3573
  if (!process.env.SUBGRAPH_URL) {
2527
- console.log(chalk7.dim(" (Set SUBGRAPH_URL for faster indexed queries)"));
3574
+ console.log(chalk8.dim(" (Set SUBGRAPH_URL for faster indexed queries)"));
2528
3575
  }
2529
- console.log(chalk7.dim("\u2500".repeat(70)));
3576
+ console.log(chalk8.dim("\u2500".repeat(70)));
2530
3577
  for (const s of syndicates) {
2531
3578
  const ts = typeof s.createdAt === "string" ? Number(s.createdAt) : Number(s.createdAt);
2532
3579
  const date = new Date(ts * 1e3).toLocaleDateString();
2533
3580
  const ensName = s.subdomain ? `${s.subdomain}.sherwoodagent.eth` : "";
2534
- console.log(` #${s.id} ${chalk7.bold(ensName || String(s.vault))}`);
2535
- if (ensName) console.log(` Vault: ${chalk7.cyan(String(s.vault))}`);
3581
+ console.log(` #${s.id} ${chalk8.bold(ensName || String(s.vault))}`);
3582
+ if (ensName) console.log(` Vault: ${chalk8.cyan(String(s.vault))}`);
2536
3583
  console.log(` Creator: ${s.creator}`);
2537
3584
  console.log(` Created: ${date}`);
2538
3585
  if (s.totalDeposits) {
2539
3586
  console.log(` Deposits: ${s.totalDeposits} USDC`);
2540
3587
  }
2541
3588
  if (s.metadataURI) {
2542
- console.log(` Metadata: ${chalk7.dim(s.metadataURI)}`);
3589
+ console.log(` Metadata: ${chalk8.dim(s.metadataURI)}`);
2543
3590
  }
2544
3591
  console.log();
2545
3592
  }
2546
3593
  } catch (err) {
2547
3594
  spinner.fail("Failed to load syndicates");
2548
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3595
+ console.error(chalk8.red(formatContractError(err)));
2549
3596
  process.exit(1);
2550
3597
  }
2551
3598
  });
@@ -2556,27 +3603,27 @@ syndicate.command("info").description("Display syndicate details by ID").argumen
2556
3603
  const info = await getSyndicate(id);
2557
3604
  spinner.stop();
2558
3605
  if (!info.vault || info.vault === "0x0000000000000000000000000000000000000000") {
2559
- console.log(chalk7.red(`Syndicate #${id} not found.`));
3606
+ console.log(chalk8.red(`Syndicate #${id} not found.`));
2560
3607
  process.exit(1);
2561
3608
  }
2562
3609
  const date = new Date(Number(info.createdAt) * 1e3).toLocaleDateString();
2563
3610
  console.log();
2564
- console.log(chalk7.bold(`Syndicate #${info.id}`));
2565
- console.log(chalk7.dim("\u2500".repeat(40)));
3611
+ console.log(chalk8.bold(`Syndicate #${info.id}`));
3612
+ console.log(chalk8.dim("\u2500".repeat(40)));
2566
3613
  if (info.subdomain) {
2567
- console.log(` ENS: ${chalk7.bold(`${info.subdomain}.sherwoodagent.eth`)}`);
3614
+ console.log(` ENS: ${chalk8.bold(`${info.subdomain}.sherwoodagent.eth`)}`);
2568
3615
  }
2569
- console.log(` Vault: ${chalk7.cyan(info.vault)}`);
3616
+ console.log(` Vault: ${chalk8.cyan(info.vault)}`);
2570
3617
  console.log(` Creator: ${info.creator}`);
2571
3618
  console.log(` Created: ${date}`);
2572
- console.log(` Active: ${info.active ? chalk7.green("yes") : chalk7.red("no")}`);
3619
+ console.log(` Active: ${info.active ? chalk8.green("yes") : chalk8.red("no")}`);
2573
3620
  if (info.metadataURI) {
2574
- console.log(` Metadata: ${chalk7.dim(info.metadataURI)}`);
3621
+ console.log(` Metadata: ${chalk8.dim(info.metadataURI)}`);
2575
3622
  }
2576
3623
  setVaultAddress(info.vault);
2577
3624
  const vaultInfo = await getVaultInfo();
2578
3625
  console.log();
2579
- console.log(chalk7.bold(" Vault Stats"));
3626
+ console.log(chalk8.bold(" Vault Stats"));
2580
3627
  console.log(` Total Assets: ${vaultInfo.totalAssets}`);
2581
3628
  console.log(` Agent Count: ${vaultInfo.agentCount}`);
2582
3629
  console.log(` Redemptions Locked: ${vaultInfo.redemptionsLocked}`);
@@ -2584,12 +3631,12 @@ syndicate.command("info").description("Display syndicate details by ID").argumen
2584
3631
  console.log();
2585
3632
  } catch (err) {
2586
3633
  spinner.fail("Failed to load syndicate info");
2587
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3634
+ console.error(chalk8.red(formatContractError(err)));
2588
3635
  process.exit(1);
2589
3636
  }
2590
3637
  });
2591
3638
  syndicate.command("update-metadata").description("Update syndicate metadata (creator only)").requiredOption("--id <id>", "Syndicate ID").option("--name <name>", "Syndicate name").option("--description <text>", "Short description").option("--uri <uri>", "Direct metadata URI (skips IPFS upload)").action(async (opts) => {
2592
- const spinner = ora7({ text: W3("Loading syndicate..."), color: "green" }).start();
3639
+ const spinner = ora7({ text: W4("Loading syndicate..."), color: "green" }).start();
2593
3640
  try {
2594
3641
  const syndicateId = BigInt(opts.id);
2595
3642
  let metadataURI = opts.uri;
@@ -2601,7 +3648,7 @@ syndicate.command("update-metadata").description("Update syndicate metadata (cre
2601
3648
  }
2602
3649
  const name = opts.name || info.subdomain;
2603
3650
  const description = opts.description || `${name} \u2014 a Sherwood syndicate on ${info.subdomain}.sherwoodagent.eth`;
2604
- spinner.text = W3("Uploading metadata to IPFS...");
3651
+ spinner.text = W4("Uploading metadata to IPFS...");
2605
3652
  const metadata = {
2606
3653
  schema: "sherwood/syndicate/v1",
2607
3654
  name,
@@ -2612,15 +3659,15 @@ syndicate.command("update-metadata").description("Update syndicate metadata (cre
2612
3659
  links: {}
2613
3660
  };
2614
3661
  metadataURI = await uploadMetadata(metadata);
2615
- spinner.text = W3("Updating on-chain metadata...");
3662
+ spinner.text = W4("Updating on-chain metadata...");
2616
3663
  }
2617
3664
  const hash = await updateMetadata(syndicateId, metadataURI);
2618
- spinner.succeed(G3(`Metadata updated`));
2619
- console.log(DIM3(` IPFS: ${metadataURI}`));
2620
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
3665
+ spinner.succeed(G4(`Metadata updated`));
3666
+ console.log(DIM4(` IPFS: ${metadataURI}`));
3667
+ console.log(DIM4(` ${getExplorerUrl(hash)}`));
2621
3668
  } catch (err) {
2622
3669
  spinner.fail("Metadata update failed");
2623
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3670
+ console.error(chalk8.red(formatContractError(err)));
2624
3671
  process.exit(1);
2625
3672
  }
2626
3673
  });
@@ -2631,10 +3678,10 @@ syndicate.command("approve-depositor").description("Approve an address to deposi
2631
3678
  const depositor = validateAddress(opts.depositor, "depositor");
2632
3679
  const hash = await approveDepositor(depositor);
2633
3680
  spinner.succeed(`Depositor approved: ${hash}`);
2634
- console.log(chalk7.dim(` ${getExplorerUrl(hash)}`));
3681
+ console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
2635
3682
  } catch (err) {
2636
3683
  spinner.fail("Approval failed");
2637
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3684
+ console.error(chalk8.red(formatContractError(err)));
2638
3685
  process.exit(1);
2639
3686
  }
2640
3687
  });
@@ -2645,10 +3692,10 @@ syndicate.command("remove-depositor").description("Remove an address from the de
2645
3692
  const depositor = validateAddress(opts.depositor, "depositor");
2646
3693
  const hash = await removeDepositor(depositor);
2647
3694
  spinner.succeed(`Depositor removed: ${hash}`);
2648
- console.log(chalk7.dim(` ${getExplorerUrl(hash)}`));
3695
+ console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
2649
3696
  } catch (err) {
2650
3697
  spinner.fail("Removal failed");
2651
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3698
+ console.error(chalk8.red(formatContractError(err)));
2652
3699
  process.exit(1);
2653
3700
  }
2654
3701
  });
@@ -2670,7 +3717,7 @@ syndicate.command("add").description("Register an agent on a syndicate vault (cr
2670
3717
  agentWallet
2671
3718
  );
2672
3719
  spinner.succeed(`Agent registered: ${hash}`);
2673
- console.log(chalk7.dim(` ${getExplorerUrl(hash)}`));
3720
+ console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
2674
3721
  try {
2675
3722
  const xmtp = await loadXmtp();
2676
3723
  const xmtpClient = await xmtp.getXmtpClient();
@@ -2682,26 +3729,66 @@ syndicate.command("add").description("Register an agent on a syndicate vault (cr
2682
3729
  syndicate: subdomain,
2683
3730
  timestamp: Math.floor(Date.now() / 1e3)
2684
3731
  });
2685
- console.log(chalk7.dim(` Added to chat: ${subdomain}`));
3732
+ console.log(chalk8.dim(` Added to chat: ${subdomain}`));
2686
3733
  } catch {
2687
- console.warn(chalk7.yellow(" \u26A0 Could not add agent to chat group"));
2688
- console.warn(chalk7.dim(` If no group exists, run: sherwood chat ${subdomain} init`));
3734
+ console.warn(chalk8.yellow(" \u26A0 Could not add agent to chat group"));
3735
+ console.warn(chalk8.dim(` If no group exists, run: sherwood chat ${subdomain} init`));
2689
3736
  }
2690
3737
  } catch (err) {
2691
3738
  spinner.fail("Registration failed");
2692
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3739
+ console.error(chalk8.red(formatContractError(err)));
2693
3740
  process.exit(1);
2694
3741
  }
2695
3742
  });
2696
- syndicate.command("join").description("Request to join a syndicate (creates an EAS attestation)").requiredOption("--subdomain <name>", "Syndicate subdomain to join").option("--message <text>", "Message to the creator", "Requesting to join your syndicate").action(async (opts) => {
3743
+ syndicate.command("share").description("Print the shareable dashboard URL for your syndicate").option("--subdomain <name>", "Syndicate subdomain (default: primary)").action(async (opts) => {
3744
+ try {
3745
+ const subdomain = opts.subdomain || getPrimarySyndicate(getChain().id)?.subdomain;
3746
+ if (!subdomain) {
3747
+ console.error(chalk8.red(" No syndicate configured. Pass --subdomain <name> or run 'syndicate create/join' first."));
3748
+ process.exit(1);
3749
+ }
3750
+ const syndicateInfo = await resolveSyndicate(subdomain);
3751
+ const agentId = getAgentId();
3752
+ const refParam = agentId ? `?ref=${agentId}` : "";
3753
+ const dashboardUrl = `https://sherwood.sh/syndicate/${subdomain}${refParam}`;
3754
+ console.log();
3755
+ console.log(LABEL4(" \u25C6 Share Your Syndicate"));
3756
+ SEP4();
3757
+ console.log(W4(` Dashboard: ${G4(dashboardUrl)}`));
3758
+ console.log(W4(` ENS: ${DIM4(`${subdomain}.sherwoodagent.eth`)}`));
3759
+ console.log(W4(` Vault: ${DIM4(syndicateInfo.vault)}`));
3760
+ console.log(W4(` Join CLI: ${DIM4(`sherwood syndicate join --subdomain ${subdomain}`)}`));
3761
+ SEP4();
3762
+ try {
3763
+ const { exec } = await import("child_process");
3764
+ const platform = (await import("os")).platform();
3765
+ const cmd = platform === "darwin" ? "pbcopy" : "xclip -selection clipboard";
3766
+ const proc = exec(cmd);
3767
+ proc.stdin?.write(dashboardUrl);
3768
+ proc.stdin?.end();
3769
+ console.log(G4(" \u2713 Dashboard URL copied to clipboard"));
3770
+ } catch {
3771
+ }
3772
+ console.log();
3773
+ } catch (err) {
3774
+ console.error(chalk8.red(` \u2716 ${formatContractError(err)}`));
3775
+ process.exit(1);
3776
+ }
3777
+ });
3778
+ syndicate.command("join").description("Request to join a syndicate (creates an EAS attestation)").option("--subdomain <name>", "Syndicate subdomain to join").option("--ref <agentId>", "Referrer agent ID (from invite link)").option("--message <text>", "Message to the creator", "Requesting to join your syndicate").action(async (opts) => {
2697
3779
  const spinner = ora7("Resolving syndicate...").start();
2698
3780
  try {
3781
+ const subdomain = opts.subdomain || getPrimarySyndicate(getChain().id)?.subdomain;
3782
+ if (!subdomain) {
3783
+ spinner.fail("No syndicate configured. Pass --subdomain <name> or run 'syndicate create' first.");
3784
+ process.exit(1);
3785
+ }
2699
3786
  const agentId = getAgentId();
2700
3787
  if (!agentId) {
2701
3788
  spinner.fail("No agent identity found. Run 'sherwood identity mint' first.");
2702
3789
  process.exit(1);
2703
3790
  }
2704
- const syndicate2 = await resolveSyndicate(opts.subdomain);
3791
+ const syndicate2 = await resolveSyndicate(subdomain);
2705
3792
  const callerAddress = getAccount().address;
2706
3793
  spinner.text = "Checking membership...";
2707
3794
  setVaultAddress(syndicate2.vault);
@@ -2711,17 +3798,17 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
2711
3798
  try {
2712
3799
  const xmtp = await loadXmtp();
2713
3800
  await xmtp.getXmtpClient();
2714
- console.log(chalk7.dim(" XMTP identity ready"));
3801
+ console.log(chalk8.dim(" XMTP identity ready"));
2715
3802
  } catch {
2716
- console.warn(chalk7.yellow(" \u26A0 Could not initialize XMTP identity"));
3803
+ console.warn(chalk8.yellow(" \u26A0 Could not initialize XMTP identity"));
2717
3804
  }
2718
3805
  try {
2719
3806
  const cron = await loadCron();
2720
- const cronResult = cron.registerSyndicateCrons(opts.subdomain, isTestnet(), getNotifyTo());
3807
+ const cronResult = cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
2721
3808
  if (cronResult.isOpenClaw && cronResult.registered) {
2722
- console.log(chalk7.green(" \u2713 Participation crons registered"));
3809
+ console.log(chalk8.green(" \u2713 Participation crons registered"));
2723
3810
  } else if (!cronResult.isOpenClaw) {
2724
- console.log(chalk7.dim(" Tip: Set up a scheduled process to run `sherwood session check " + opts.subdomain + "` periodically"));
3811
+ console.log(chalk8.dim(" Tip: Set up a scheduled process to run `sherwood session check " + subdomain + "` periodically"));
2725
3812
  }
2726
3813
  } catch {
2727
3814
  }
@@ -2734,24 +3821,26 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
2734
3821
  );
2735
3822
  if (existingRequest) {
2736
3823
  spinner.succeed("You already have a pending join request for this syndicate");
2737
- console.log(chalk7.dim(` Attestation: ${existingRequest.uid}`));
2738
- console.log(chalk7.dim(` Submitted: ${new Date(existingRequest.time * 1e3).toLocaleString()}`));
3824
+ console.log(chalk8.dim(` Attestation: ${existingRequest.uid}`));
3825
+ console.log(chalk8.dim(` Submitted: ${new Date(existingRequest.time * 1e3).toLocaleString()}`));
2739
3826
  try {
2740
3827
  const xmtp = await loadXmtp();
2741
3828
  await xmtp.getXmtpClient();
2742
- console.log(chalk7.dim(" XMTP identity ready"));
3829
+ console.log(chalk8.dim(" XMTP identity ready"));
2743
3830
  } catch {
2744
- console.warn(chalk7.yellow(" \u26A0 Could not initialize XMTP identity"));
3831
+ console.warn(chalk8.yellow(" \u26A0 Could not initialize XMTP identity"));
2745
3832
  }
2746
3833
  return;
2747
3834
  }
2748
3835
  spinner.text = "Creating join request attestation...";
3836
+ const referrerAgentId = opts.ref ? parseInt(opts.ref, 10) : void 0;
2749
3837
  const { uid, hash } = await createJoinRequest(
2750
3838
  syndicate2.id,
2751
3839
  BigInt(agentId),
2752
3840
  syndicate2.vault,
2753
3841
  syndicate2.creator,
2754
- opts.message
3842
+ opts.message,
3843
+ referrerAgentId
2755
3844
  );
2756
3845
  try {
2757
3846
  spinner.text = "Registering XMTP identity...";
@@ -2760,34 +3849,37 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
2760
3849
  spinner.succeed("Join request created (XMTP identity ready)");
2761
3850
  } catch {
2762
3851
  spinner.succeed("Join request created");
2763
- console.warn(chalk7.yellow(" \u26A0 Could not initialize XMTP identity \u2014 creator may not be able to auto-add you to chat"));
3852
+ console.warn(chalk8.yellow(" \u26A0 Could not initialize XMTP identity \u2014 creator may not be able to auto-add you to chat"));
2764
3853
  }
3854
+ addSyndicate(getChain().id, { subdomain, vault: syndicate2.vault, role: "agent" });
3855
+ setPrimarySyndicate(getChain().id, subdomain);
2765
3856
  try {
2766
3857
  const cron = await loadCron();
2767
- const cronResult = cron.registerSyndicateCrons(opts.subdomain, isTestnet(), getNotifyTo());
3858
+ const cronResult = cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
2768
3859
  if (cronResult.isOpenClaw && cronResult.registered) {
2769
- console.log(G3(" \u2713 Participation crons registered (will activate after approval)"));
3860
+ console.log(G4(" \u2713 Participation crons registered (will activate after approval)"));
2770
3861
  } else if (!cronResult.isOpenClaw) {
2771
- console.log(DIM3(" Tip: Set up a scheduled process to run `sherwood session check " + opts.subdomain + "` periodically"));
3862
+ console.log(DIM4(" Tip: Set up a scheduled process to run `sherwood session check " + subdomain + "` periodically"));
2772
3863
  }
2773
3864
  } catch {
2774
3865
  }
2775
3866
  console.log();
2776
- console.log(LABEL3(" \u25C6 Join Request Submitted"));
2777
- SEP3();
2778
- console.log(W3(` Syndicate: ${G3(`${opts.subdomain}.sherwoodagent.eth`)}`));
2779
- console.log(W3(` Agent ID: #${agentId}`));
2780
- console.log(W3(` Creator: ${DIM3(syndicate2.creator)}`));
2781
- console.log(W3(` Attestation: ${DIM3(uid)}`));
2782
- console.log(W3(` EAS Scan: ${DIM3(getEasScanUrl(uid))}`));
2783
- console.log(W3(` Explorer: ${DIM3(getExplorerUrl(hash))}`));
2784
- SEP3();
2785
- console.log(G3(" \u2713 The creator can review with:"));
2786
- console.log(DIM3(` sherwood syndicate requests --subdomain ${opts.subdomain}`));
3867
+ console.log(LABEL4(" \u25C6 Join Request Submitted"));
3868
+ SEP4();
3869
+ console.log(W4(` Syndicate: ${G4(`${subdomain}.sherwoodagent.eth`)}`));
3870
+ console.log(W4(` Agent ID: #${agentId}`));
3871
+ console.log(W4(` Creator: ${DIM4(syndicate2.creator)}`));
3872
+ console.log(W4(` Attestation: ${DIM4(uid)}`));
3873
+ console.log(W4(` EAS Scan: ${DIM4(getEasScanUrl(uid))}`));
3874
+ console.log(W4(` Explorer: ${DIM4(getExplorerUrl(hash))}`));
3875
+ SEP4();
3876
+ console.log(G4(" \u2713 Syndicate saved to ~/.sherwood/config.json (set as primary)"));
3877
+ console.log(G4(" \u2713 The creator can review with:"));
3878
+ console.log(DIM4(` sherwood syndicate requests --subdomain ${subdomain}`));
2787
3879
  console.log();
2788
3880
  } catch (err) {
2789
3881
  spinner.fail("Join request failed");
2790
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3882
+ console.error(chalk8.red(formatContractError(err)));
2791
3883
  process.exit(1);
2792
3884
  }
2793
3885
  });
@@ -2797,10 +3889,11 @@ syndicate.command("requests").description("View pending join requests for a synd
2797
3889
  let creatorAddress;
2798
3890
  let subdomain;
2799
3891
  let syndicateVault;
2800
- if (opts.subdomain) {
2801
- const syndicateInfo = await resolveSyndicate(opts.subdomain);
3892
+ const resolvedSubdomain = opts.subdomain || getPrimarySyndicate(getChain().id)?.subdomain;
3893
+ if (resolvedSubdomain) {
3894
+ const syndicateInfo = await resolveSyndicate(resolvedSubdomain);
2802
3895
  creatorAddress = syndicateInfo.creator;
2803
- subdomain = opts.subdomain;
3896
+ subdomain = resolvedSubdomain;
2804
3897
  syndicateVault = syndicateInfo.vault;
2805
3898
  } else {
2806
3899
  resolveVault(opts);
@@ -2827,37 +3920,43 @@ syndicate.command("requests").description("View pending join requests for a synd
2827
3920
  );
2828
3921
  spinner.stop();
2829
3922
  if (requests.length === 0) {
2830
- console.log(DIM3("\n No pending join requests.\n"));
3923
+ console.log(DIM4("\n No pending join requests.\n"));
2831
3924
  return;
2832
3925
  }
2833
3926
  console.log();
2834
- console.log(LABEL3(` \u25C6 Pending Join Requests (${requests.length})`));
2835
- SEP3();
3927
+ console.log(LABEL4(` \u25C6 Pending Join Requests (${requests.length})`));
3928
+ SEP4();
2836
3929
  for (let i = 0; i < requests.length; i++) {
2837
3930
  const req = requests[i];
2838
3931
  const date = new Date(req.time * 1e3).toLocaleString();
2839
- console.log(W3(` ${i + 1}. Agent #${req.decoded.agentId} ${DIM3(`(${req.attester})`)}`));
2840
- console.log(DIM3(` Message: "${req.decoded.message}"`));
2841
- console.log(DIM3(` Requested: ${date}`));
2842
- console.log(DIM3(` Attestation: ${req.uid}`));
3932
+ const referrer = parseReferrer(req.decoded.message);
3933
+ const cleanMessage = stripReferrerPrefix(req.decoded.message);
3934
+ console.log(W4(` ${i + 1}. Agent #${req.decoded.agentId} ${DIM4(`(${req.attester})`)}`));
3935
+ console.log(DIM4(` Message: "${cleanMessage}"`));
3936
+ if (referrer !== null) {
3937
+ console.log(G4(` Referred by: Agent #${referrer}`));
3938
+ }
3939
+ console.log(DIM4(` Requested: ${date}`));
3940
+ console.log(DIM4(` Attestation: ${req.uid}`));
2843
3941
  console.log();
2844
3942
  }
2845
- console.log(G3(" To approve:"));
2846
- console.log(DIM3(` sherwood syndicate approve --agent-id <id> --wallet <addr>`));
2847
- console.log(G3(" To reject:"));
2848
- console.log(DIM3(` sherwood syndicate reject --attestation <uid>`));
3943
+ console.log(G4(" To approve:"));
3944
+ console.log(DIM4(` sherwood syndicate approve --agent-id <id> --wallet <addr>`));
3945
+ console.log(G4(" To reject:"));
3946
+ console.log(DIM4(` sherwood syndicate reject --attestation <uid>`));
2849
3947
  console.log();
2850
3948
  } catch (err) {
2851
3949
  spinner.fail("Failed to load requests");
2852
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3950
+ console.error(chalk8.red(formatContractError(err)));
2853
3951
  process.exit(1);
2854
3952
  }
2855
3953
  });
2856
3954
  syndicate.command("approve").description("Approve an agent join request (registers agent + creates EAS approval)").option("--vault <address>", "Vault address (default: from config)").option("--subdomain <name>", "Syndicate subdomain (alternative to --vault)").requiredOption("--agent-id <id>", "Agent's ERC-8004 identity token ID").requiredOption("--wallet <address>", "Agent wallet address").action(async (opts) => {
2857
3955
  const spinner = ora7("Verifying creator...").start();
2858
3956
  try {
2859
- if (opts.subdomain && !opts.vault) {
2860
- const syndicateInfo = await resolveSyndicate(opts.subdomain);
3957
+ const approveSubdomain = opts.subdomain || getPrimarySyndicate(getChain().id)?.subdomain;
3958
+ if (approveSubdomain && !opts.vault) {
3959
+ const syndicateInfo = await resolveSyndicate(approveSubdomain);
2861
3960
  setVaultAddress(syndicateInfo.vault);
2862
3961
  } else {
2863
3962
  resolveVault(opts);
@@ -2878,11 +3977,11 @@ syndicate.command("approve").description("Approve an agent join request (registe
2878
3977
  agentWallet
2879
3978
  );
2880
3979
  agentWasRegistered = true;
2881
- console.log(DIM3(` Agent registered: ${getExplorerUrl(regHash)}`));
3980
+ console.log(DIM4(` Agent registered: ${getExplorerUrl(regHash)}`));
2882
3981
  } catch (regErr) {
2883
3982
  const msg = regErr instanceof Error ? regErr.message : String(regErr);
2884
3983
  if (msg.includes("0xe098d3ee") || msg.includes("AgentAlreadyRegistered")) {
2885
- console.log(DIM3(" Agent already registered on vault \u2014 skipping"));
3984
+ console.log(DIM4(" Agent already registered on vault \u2014 skipping"));
2886
3985
  } else {
2887
3986
  throw regErr;
2888
3987
  }
@@ -2898,7 +3997,7 @@ syndicate.command("approve").description("Approve an agent join request (registe
2898
3997
  let approvalUid;
2899
3998
  if (alreadyApproved) {
2900
3999
  approvalUid = alreadyApproved.uid;
2901
- console.log(DIM3(` Approval attestation already exists \u2014 skipping`));
4000
+ console.log(DIM4(` Approval attestation already exists \u2014 skipping`));
2902
4001
  } else {
2903
4002
  spinner.text = "Creating approval attestation...";
2904
4003
  const result = await createApproval(
@@ -2921,23 +4020,23 @@ syndicate.command("approve").description("Approve an agent join request (registe
2921
4020
  syndicate: subdomain,
2922
4021
  timestamp: Math.floor(Date.now() / 1e3)
2923
4022
  });
2924
- console.log(DIM3(` Added to chat: ${subdomain}`));
4023
+ console.log(DIM4(` Added to chat: ${subdomain}`));
2925
4024
  } catch {
2926
- console.warn(chalk7.yellow(" \u26A0 Could not add agent to chat group"));
2927
- console.warn(chalk7.dim(` If no group exists, run: sherwood chat ${subdomain} init`));
4025
+ console.warn(chalk8.yellow(" \u26A0 Could not add agent to chat group"));
4026
+ console.warn(chalk8.dim(` If no group exists, run: sherwood chat ${subdomain} init`));
2928
4027
  }
2929
4028
  spinner.succeed("Agent approved and registered");
2930
4029
  console.log();
2931
- console.log(LABEL3(" \u25C6 Agent Approved"));
2932
- SEP3();
2933
- console.log(W3(` Agent ID: #${opts.agentId}`));
2934
- console.log(W3(` Wallet: ${G3(opts.wallet)}`));
2935
- console.log(W3(` Approval: ${DIM3(approvalUid)}`));
2936
- console.log(W3(` EAS Scan: ${DIM3(getEasScanUrl(approvalUid))}`));
2937
- SEP3();
4030
+ console.log(LABEL4(" \u25C6 Agent Approved"));
4031
+ SEP4();
4032
+ console.log(W4(` Agent ID: #${opts.agentId}`));
4033
+ console.log(W4(` Wallet: ${G4(opts.wallet)}`));
4034
+ console.log(W4(` Approval: ${DIM4(approvalUid)}`));
4035
+ console.log(W4(` EAS Scan: ${DIM4(getEasScanUrl(approvalUid))}`));
4036
+ SEP4();
2938
4037
  } catch (err) {
2939
4038
  spinner.fail("Approval failed");
2940
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
4039
+ console.error(chalk8.red(formatContractError(err)));
2941
4040
  process.exit(1);
2942
4041
  }
2943
4042
  });
@@ -2949,26 +4048,134 @@ syndicate.command("reject").description("Reject a join request by revoking its a
2949
4048
  opts.attestation
2950
4049
  );
2951
4050
  spinner.succeed("Join request rejected");
2952
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4051
+ console.log(DIM4(` ${getExplorerUrl(hash)}`));
2953
4052
  } catch (err) {
2954
4053
  spinner.fail("Rejection failed");
2955
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
4054
+ console.error(chalk8.red(formatContractError(err)));
4055
+ process.exit(1);
4056
+ }
4057
+ });
4058
+ syndicate.command("leave").description("Leave a syndicate \u2014 removes participation crons and session state").option("--subdomain <name>", "Syndicate subdomain to leave").action(async (opts) => {
4059
+ const subdomain = opts.subdomain || getPrimarySyndicate(getChain().id)?.subdomain;
4060
+ if (!subdomain) {
4061
+ console.error(chalk8.red(" No syndicate configured. Pass --subdomain <name>."));
4062
+ process.exit(1);
4063
+ }
4064
+ const spinner = ora7("Cleaning up...").start();
4065
+ try {
4066
+ let cronsRemoved = false;
4067
+ try {
4068
+ const cron = await loadCron();
4069
+ const result = cron.unregisterSyndicateCrons(subdomain, isTestnet());
4070
+ cronsRemoved = result.removed;
4071
+ } catch {
4072
+ }
4073
+ const { resetSession } = await import("./session-XAW5UIXP.js");
4074
+ resetSession(subdomain);
4075
+ spinner.succeed("Left syndicate");
4076
+ if (cronsRemoved) {
4077
+ console.log(G4(" \u2713 Participation crons removed"));
4078
+ }
4079
+ console.log(G4(" \u2713 Session state cleared"));
4080
+ console.log();
4081
+ console.log(chalk8.dim(" Note: This does not remove you on-chain. To exit your position:"));
4082
+ console.log(chalk8.dim(" sherwood vault balance \u2014 check your LP share balance"));
4083
+ console.log(chalk8.dim(" Redeem shares via the vault contract or dashboard"));
4084
+ console.log();
4085
+ } catch (err) {
4086
+ spinner.fail("Leave failed");
4087
+ console.error(chalk8.red(err instanceof Error ? err.message : String(err)));
4088
+ process.exit(1);
4089
+ }
4090
+ });
4091
+ syndicate.command("set-primary").description("Set the active syndicate for CLI commands (auto-used when --subdomain is omitted)").option("--subdomain <name>", "Syndicate subdomain").option("--vault <address>", "Vault address").action(async (opts) => {
4092
+ try {
4093
+ const chainId = getChain().id;
4094
+ const memberships = getSyndicates(chainId);
4095
+ if (!opts.subdomain && !opts.vault) {
4096
+ const primary = getPrimarySyndicate(chainId);
4097
+ if (memberships.length === 0) {
4098
+ console.log(DIM4("\n No syndicates saved. Create or join one first.\n"));
4099
+ return;
4100
+ }
4101
+ console.log();
4102
+ console.log(LABEL4(" \u25C6 Your Syndicates"));
4103
+ SEP4();
4104
+ for (const m of memberships) {
4105
+ const isPrimary = primary?.subdomain === m.subdomain;
4106
+ const marker = isPrimary ? G4(" \u2605") : "";
4107
+ console.log(W4(` ${m.subdomain}.sherwoodagent.eth${marker} ${DIM4(`(${m.role})`)}`));
4108
+ console.log(DIM4(` vault: ${m.vault}`));
4109
+ }
4110
+ SEP4();
4111
+ if (memberships.length > 1) {
4112
+ console.log(DIM4(" Switch with: sherwood syndicate set-primary --subdomain <name>"));
4113
+ }
4114
+ console.log();
4115
+ return;
4116
+ }
4117
+ let target;
4118
+ if (opts.subdomain) {
4119
+ const found = memberships.find((m) => m.subdomain === opts.subdomain);
4120
+ if (!found) {
4121
+ const syndicateInfo = await resolveSyndicate(opts.subdomain);
4122
+ addSyndicate(chainId, { subdomain: opts.subdomain, vault: syndicateInfo.vault, role: "agent" });
4123
+ }
4124
+ target = opts.subdomain;
4125
+ } else if (opts.vault) {
4126
+ const found = memberships.find(
4127
+ (m) => m.vault.toLowerCase() === opts.vault.toLowerCase()
4128
+ );
4129
+ if (found) {
4130
+ target = found.subdomain;
4131
+ } else {
4132
+ const syndicateInfo = await resolveVaultSyndicate(validateAddress(opts.vault, "vault"));
4133
+ addSyndicate(chainId, { subdomain: syndicateInfo.subdomain, vault: opts.vault, role: "agent" });
4134
+ target = syndicateInfo.subdomain;
4135
+ }
4136
+ }
4137
+ if (!target) {
4138
+ console.error(chalk8.red(" Could not resolve syndicate. Pass --subdomain or --vault."));
4139
+ process.exit(1);
4140
+ }
4141
+ setPrimarySyndicate(chainId, target);
4142
+ console.log(G4(`
4143
+ \u2713 Primary syndicate set to ${target}.sherwoodagent.eth
4144
+ `));
4145
+ } catch (err) {
4146
+ console.error(chalk8.red(formatContractError(err)));
2956
4147
  process.exit(1);
2957
4148
  }
2958
4149
  });
2959
4150
  var vaultCmd = program.command("vault");
2960
- vaultCmd.command("deposit").description("Deposit into a vault").option("--vault <address>", "Vault address (default: from config)").requiredOption("--amount <amount>", "Amount to deposit (in asset units)").action(async (opts) => {
4151
+ vaultCmd.command("deposit").description("Deposit into a vault").option("--vault <address>", "Vault address (default: from config)").requiredOption("--amount <amount>", "Amount to deposit (in asset units)").option("--use-eth", "Auto-wrap ETH \u2192 WETH before depositing (for WETH vaults)").action(async (opts) => {
2961
4152
  resolveVault(opts);
2962
4153
  const decimals = await getAssetDecimals();
2963
4154
  const amount = parseUnits5(opts.amount, decimals);
4155
+ try {
4156
+ if (!opts.useEth) {
4157
+ await preflightDeposit(amount);
4158
+ }
4159
+ } catch (err) {
4160
+ console.error(chalk8.red(`
4161
+ \u2716 ${err instanceof Error ? err.message : String(err)}
4162
+ `));
4163
+ process.exit(1);
4164
+ }
2964
4165
  const spinner = ora7(`Depositing ${opts.amount}...`).start();
2965
4166
  try {
2966
- const hash = await deposit(amount);
4167
+ let hash;
4168
+ if (opts.useEth) {
4169
+ spinner.text = `Wrapping ETH + depositing ${opts.amount}...`;
4170
+ hash = await depositWithEthWrap(amount);
4171
+ } else {
4172
+ hash = await deposit(amount);
4173
+ }
2967
4174
  spinner.succeed(`Deposited: ${hash}`);
2968
- console.log(chalk7.dim(` ${getExplorerUrl(hash)}`));
4175
+ console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
2969
4176
  } catch (err) {
2970
4177
  spinner.fail("Deposit failed");
2971
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
4178
+ console.error(chalk8.red(formatContractError(err)));
2972
4179
  process.exit(1);
2973
4180
  }
2974
4181
  });
@@ -2979,8 +4186,8 @@ vaultCmd.command("info").description("Display vault state").option("--vault <add
2979
4186
  const info = await getVaultInfo();
2980
4187
  spinner.stop();
2981
4188
  console.log();
2982
- console.log(chalk7.bold("Vault Info"));
2983
- console.log(chalk7.dim("\u2500".repeat(40)));
4189
+ console.log(chalk8.bold("Vault Info"));
4190
+ console.log(chalk8.dim("\u2500".repeat(40)));
2984
4191
  console.log(` Address: ${info.address}`);
2985
4192
  console.log(` Total Assets: ${info.totalAssets}`);
2986
4193
  console.log(` Agent Count: ${info.agentCount}`);
@@ -2989,7 +4196,7 @@ vaultCmd.command("info").description("Display vault state").option("--vault <add
2989
4196
  console.log();
2990
4197
  } catch (err) {
2991
4198
  spinner.fail("Failed to load vault info");
2992
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
4199
+ console.error(chalk8.red(formatContractError(err)));
2993
4200
  process.exit(1);
2994
4201
  }
2995
4202
  });
@@ -3000,22 +4207,22 @@ vaultCmd.command("balance").description("Show LP share balance and asset value")
3000
4207
  const balance = await getBalance(opts.address);
3001
4208
  spinner.stop();
3002
4209
  console.log();
3003
- console.log(chalk7.bold("LP Position"));
3004
- console.log(chalk7.dim("\u2500".repeat(40)));
4210
+ console.log(chalk8.bold("LP Position"));
4211
+ console.log(chalk8.dim("\u2500".repeat(40)));
3005
4212
  console.log(` Shares: ${balance.shares.toString()}`);
3006
4213
  console.log(` Asset Value: ${balance.assetsValue}`);
3007
4214
  console.log(` % of Vault: ${balance.percentOfVault}`);
3008
4215
  console.log();
3009
4216
  } catch (err) {
3010
4217
  spinner.fail("Failed to load balance");
3011
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
4218
+ console.error(chalk8.red(formatContractError(err)));
3012
4219
  process.exit(1);
3013
4220
  }
3014
4221
  });
3015
4222
  var strategy = program.command("strategy").description("Strategy templates \u2014 list, clone, propose");
3016
4223
  registerStrategyTemplateCommands(strategy);
3017
4224
  program.command("providers").description("List available DeFi providers").action(async () => {
3018
- const { MessariProvider, NansenProvider } = await import("./research-DZINC4N4.js");
4225
+ const { MessariProvider, NansenProvider } = await import("./research-SJ6D3L2B.js");
3019
4226
  const providers = [new MoonwellProvider(), new UniswapProvider(), new MessariProvider(), new NansenProvider()];
3020
4227
  for (const p of providers) {
3021
4228
  const info = p.info();
@@ -3026,26 +4233,26 @@ ${info.name} (${info.type})`);
3026
4233
  }
3027
4234
  });
3028
4235
  try {
3029
- const { registerChatCommands } = await import("./chat-WKOSUZXO.js");
4236
+ const { registerChatCommands } = await import("./chat-JYAJMP4D.js");
3030
4237
  registerChatCommands(program);
3031
4238
  } catch {
3032
4239
  program.command("chat <name> [action] [actionArgs...]").description("Syndicate chat (XMTP) \u2014 requires @xmtp/cli").action(() => {
3033
- console.error(chalk7.red("XMTP CLI not available."));
3034
- console.error(chalk7.dim("Install with: npm install -g @xmtp/cli"));
3035
- console.error(chalk7.dim("Or reinstall: npm i -g @sherwoodagent/cli"));
4240
+ console.error(chalk8.red("XMTP CLI not available."));
4241
+ console.error(chalk8.dim("Install with: npm install -g @xmtp/cli"));
4242
+ console.error(chalk8.dim("Or reinstall: npm i -g @sherwoodagent/cli"));
3036
4243
  process.exit(1);
3037
4244
  });
3038
4245
  }
3039
- var { registerSessionCommands } = await import("./session-Q2WRYXYF.js");
4246
+ var { registerSessionCommands } = await import("./session-XZNKLK3I.js");
3040
4247
  registerSessionCommands(program);
3041
4248
  registerVeniceCommands(program);
3042
4249
  registerAllowanceCommands(program);
3043
4250
  registerIdentityCommands(program);
3044
4251
  registerProposalCommands(program);
3045
4252
  registerGovernorCommands(program);
3046
- var { registerResearchCommands } = await import("./research-47YMVW3N.js");
4253
+ var { registerResearchCommands } = await import("./research-ZW6XVTGZ.js");
3047
4254
  registerResearchCommands(program);
3048
- var { registerTradeCommands } = await import("./trade-D6VT5MLQ.js");
4255
+ var { registerTradeCommands } = await import("./trade-OJM5HNHL.js");
3049
4256
  registerTradeCommands(program);
3050
4257
  var configCmd = program.command("config");
3051
4258
  configCmd.command("set").description("Save settings to ~/.sherwood/config.json (persists across sessions)").option("--private-key <key>", "Wallet private key (0x-prefixed)").option("--vault <address>", "Default SyndicateVault address").option("--rpc <url>", "Custom RPC URL for the active --chain network").option("--notify-to <id>", "Destination for cron summaries (Telegram chat ID, phone, etc.)").option("--uniswap-api-key <key>", "Uniswap Trading API key (from developers.uniswap.org)").option("--venice-api-key <key>", "Venice AI inference API key").action((opts) => {
@@ -3053,42 +4260,42 @@ configCmd.command("set").description("Save settings to ~/.sherwood/config.json (
3053
4260
  if (opts.privateKey) {
3054
4261
  setPrivateKey(opts.privateKey);
3055
4262
  const account = getAccount();
3056
- console.log(chalk7.green("Private key saved to ~/.sherwood/config.json"));
3057
- console.log(chalk7.dim(` Wallet: ${account.address}`));
4263
+ console.log(chalk8.green("Private key saved to ~/.sherwood/config.json"));
4264
+ console.log(chalk8.dim(` Wallet: ${account.address}`));
3058
4265
  saved = true;
3059
4266
  }
3060
4267
  if (opts.vault) {
3061
4268
  const chainId = getChain().id;
3062
4269
  setChainContract(chainId, "vault", opts.vault);
3063
- console.log(chalk7.green(`Vault saved to ~/.sherwood/config.json (chain ${chainId})`));
3064
- console.log(chalk7.dim(` Vault: ${opts.vault}`));
4270
+ console.log(chalk8.green(`Vault saved to ~/.sherwood/config.json (chain ${chainId})`));
4271
+ console.log(chalk8.dim(` Vault: ${opts.vault}`));
3065
4272
  saved = true;
3066
4273
  }
3067
4274
  if (opts.rpc) {
3068
4275
  const network = getNetwork();
3069
4276
  setConfigRpcUrl(network, opts.rpc);
3070
- console.log(chalk7.green(`RPC URL saved for ${network}`));
3071
- console.log(chalk7.dim(` RPC: ${opts.rpc}`));
4277
+ console.log(chalk8.green(`RPC URL saved for ${network}`));
4278
+ console.log(chalk8.dim(` RPC: ${opts.rpc}`));
3072
4279
  saved = true;
3073
4280
  }
3074
4281
  if (opts.notifyTo) {
3075
4282
  setNotifyTo(opts.notifyTo);
3076
- console.log(chalk7.green("Notify destination saved to ~/.sherwood/config.json"));
3077
- console.log(chalk7.dim(` Notify to: ${opts.notifyTo}`));
4283
+ console.log(chalk8.green("Notify destination saved to ~/.sherwood/config.json"));
4284
+ console.log(chalk8.dim(` Notify to: ${opts.notifyTo}`));
3078
4285
  saved = true;
3079
4286
  }
3080
4287
  if (opts.uniswapApiKey) {
3081
4288
  setUniswapApiKey(opts.uniswapApiKey);
3082
- console.log(chalk7.green("Uniswap API key saved to ~/.sherwood/config.json"));
4289
+ console.log(chalk8.green("Uniswap API key saved to ~/.sherwood/config.json"));
3083
4290
  saved = true;
3084
4291
  }
3085
4292
  if (opts.veniceApiKey) {
3086
4293
  setVeniceApiKey(opts.veniceApiKey);
3087
- console.log(chalk7.green("Venice API key saved to ~/.sherwood/config.json"));
4294
+ console.log(chalk8.green("Venice API key saved to ~/.sherwood/config.json"));
3088
4295
  saved = true;
3089
4296
  }
3090
4297
  if (!saved) {
3091
- console.log(chalk7.red("Provide at least one of: --private-key, --vault, --rpc, --notify-to, --uniswap-api-key, --venice-api-key"));
4298
+ console.log(chalk8.red("Provide at least one of: --private-key, --vault, --rpc, --notify-to, --uniswap-api-key, --venice-api-key"));
3092
4299
  process.exit(1);
3093
4300
  }
3094
4301
  });
@@ -3099,17 +4306,17 @@ configCmd.command("show").description("Display current config for the active net
3099
4306
  const config = loadConfig();
3100
4307
  const customRpc = config.rpc?.[network];
3101
4308
  console.log();
3102
- console.log(chalk7.bold(`Sherwood Config`));
3103
- console.log(chalk7.dim("\u2500".repeat(50)));
3104
- console.log(` Network: ${chalk7.cyan(network)} (chain ${chainId})`);
3105
- console.log(` RPC: ${customRpc ? chalk7.green(customRpc) : chalk7.dim("default")}`);
3106
- console.log(` Wallet: ${config.privateKey ? chalk7.green("configured") : chalk7.dim("not set")}`);
3107
- console.log(` Agent ID: ${config.agentId ?? chalk7.dim("not set")}`);
3108
- console.log(` Vault: ${contracts.vault ?? chalk7.dim("not set")}`);
3109
- console.log(` Uniswap: ${getUniswapApiKey() ? chalk7.green("API key configured") : chalk7.dim("not set")}`);
3110
- console.log(` Venice: ${getVeniceApiKey() ? chalk7.green("API key configured") : chalk7.dim("not set")}`);
4309
+ console.log(chalk8.bold(`Sherwood Config`));
4310
+ console.log(chalk8.dim("\u2500".repeat(50)));
4311
+ console.log(` Network: ${chalk8.cyan(network)} (chain ${chainId})`);
4312
+ console.log(` RPC: ${customRpc ? chalk8.green(customRpc) : chalk8.dim("default")}`);
4313
+ console.log(` Wallet: ${config.privateKey ? chalk8.green("configured") : chalk8.dim("not set")}`);
4314
+ console.log(` Agent ID: ${config.agentId ?? chalk8.dim("not set")}`);
4315
+ console.log(` Vault: ${contracts.vault ?? chalk8.dim("not set")}`);
4316
+ console.log(` Uniswap: ${getUniswapApiKey() ? chalk8.green("API key configured") : chalk8.dim("not set")}`);
4317
+ console.log(` Venice: ${getVeniceApiKey() ? chalk8.green("API key configured") : chalk8.dim("not set")}`);
3111
4318
  console.log();
3112
- console.log(chalk7.dim(" Config file: ~/.sherwood/config.json"));
4319
+ console.log(chalk8.dim(" Config file: ~/.sherwood/config.json"));
3113
4320
  console.log();
3114
4321
  });
3115
4322
  program.parse();