@sherwoodagent/cli 0.19.2 → 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-WWH4GD3N.js → chunk-4Y6Y4SVH.js} +37 -27
  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-MPDYDPJK.js → eas-X5UIEM6D.js} +14 -8
  26. package/dist/{governor-J3W67NXA.js → governor-ZKRSAHG4.js} +6 -6
  27. package/dist/index.js +1594 -391
  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-LOSOHKOJ.js → research-ZW6XVTGZ.js} +17 -16
  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-JLQPIA5W.js → trade-OJM5HNHL.js} +133 -70
  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-I7CKBBOG.js +0 -129
  44. package/dist/chunk-I7CKBBOG.js.map +0 -1
  45. package/dist/chunk-LHDWONBI.js.map +0 -1
  46. package/dist/chunk-P4J6FKAP.js.map +0 -1
  47. package/dist/chunk-QT7BUKWR.js.map +0 -1
  48. package/dist/chunk-SZ5QQJGA.js.map +0 -1
  49. package/dist/chunk-WWH4GD3N.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-DZINC4N4.js +0 -14
  53. package/dist/research-LOSOHKOJ.js.map +0 -1
  54. package/dist/session-Q2WRYXYF.js.map +0 -1
  55. package/dist/trade-JLQPIA5W.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-MPDYDPJK.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-WWH4GD3N.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-MPDYDPJK.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,7 +1391,7 @@ 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
  });
@@ -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,10 +1437,10 @@ ${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-MPDYDPJK.js");
1440
+ const { createVeniceInferenceAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-X5UIEM6D.js");
1304
1441
  const { keccak256, toHex, isAddress: isAddr } = await import("viem");
1305
- const { getChainContracts: getChainContracts2 } = await import("./config-IDAHD7S3.js");
1306
- const { getChain: getActiveChain } = await import("./network-DBUZ7GRF.js");
1442
+ const { getChainContracts: getChainContracts2 } = await import("./config-BGS5MX4D.js");
1443
+ const { getChain: getActiveChain } = await import("./network-UTSOP7EF.js");
1307
1444
  const vaultRecipient = opts.vault && isAddr(opts.vault) ? opts.vault : getChainContracts2(getActiveChain().id).vault;
1308
1445
  const promptHash = keccak256(toHex(userContent)).slice(0, 18);
1309
1446
  const { uid } = await createVeniceInferenceAttestation(
@@ -1320,7 +1457,7 @@ ${opts.prompt}`;
1320
1457
  }
1321
1458
  } catch (err) {
1322
1459
  spinner.fail("Inference failed");
1323
- console.error(chalk2.red(err instanceof Error ? err.message : String(err)));
1460
+ console.error(chalk2.red(formatContractError(err)));
1324
1461
  process.exit(1);
1325
1462
  }
1326
1463
  });
@@ -1332,7 +1469,7 @@ import chalk3 from "chalk";
1332
1469
  import ora3 from "ora";
1333
1470
 
1334
1471
  // src/strategies/allowance-disburse.ts
1335
- import { encodeFunctionData as encodeFunctionData5, parseUnits as parseUnits3 } from "viem";
1472
+ import { encodeFunctionData as encodeFunctionData6, parseUnits as parseUnits3 } from "viem";
1336
1473
  function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDecimals, minUsdc, swapPath) {
1337
1474
  const assetAmount = parseUnits3(config.amount, assetDecimals);
1338
1475
  const isUsdc = assetAddress.toLowerCase() === TOKENS().USDC.toLowerCase();
@@ -1341,7 +1478,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
1341
1478
  if (!isUsdc) {
1342
1479
  calls.push({
1343
1480
  target: assetAddress,
1344
- data: encodeFunctionData5({
1481
+ data: encodeFunctionData6({
1345
1482
  abi: ERC20_ABI,
1346
1483
  functionName: "approve",
1347
1484
  args: [UNISWAP().SWAP_ROUTER, assetAmount]
@@ -1351,7 +1488,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
1351
1488
  if (isWeth) {
1352
1489
  calls.push({
1353
1490
  target: UNISWAP().SWAP_ROUTER,
1354
- data: encodeFunctionData5({
1491
+ data: encodeFunctionData6({
1355
1492
  abi: SWAP_ROUTER_EXACT_INPUT_SINGLE_ABI,
1356
1493
  functionName: "exactInputSingle",
1357
1494
  args: [
@@ -1371,7 +1508,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
1371
1508
  } else {
1372
1509
  calls.push({
1373
1510
  target: UNISWAP().SWAP_ROUTER,
1374
- data: encodeFunctionData5({
1511
+ data: encodeFunctionData6({
1375
1512
  abi: SWAP_ROUTER_ABI,
1376
1513
  functionName: "exactInput",
1377
1514
  args: [
@@ -1391,7 +1528,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
1391
1528
  for (const agent of agents) {
1392
1529
  calls.push({
1393
1530
  target: TOKENS().USDC,
1394
- data: encodeFunctionData5({
1531
+ data: encodeFunctionData6({
1395
1532
  abi: ERC20_ABI,
1396
1533
  functionName: "transfer",
1397
1534
  args: [agent, perAgent]
@@ -1440,7 +1577,7 @@ function registerAllowanceCommands(program2) {
1440
1577
  spinner.stop();
1441
1578
  } catch (err) {
1442
1579
  spinner.fail("Failed to read vault state");
1443
- console.error(chalk3.red(err instanceof Error ? err.message : String(err)));
1580
+ console.error(chalk3.red(formatContractError(err)));
1444
1581
  process.exit(1);
1445
1582
  }
1446
1583
  if (agents.length === 0) {
@@ -1505,7 +1642,7 @@ function registerAllowanceCommands(program2) {
1505
1642
  );
1506
1643
  } catch (err) {
1507
1644
  quoteSpinner.fail("Failed to fetch quote");
1508
- console.error(chalk3.red(err instanceof Error ? err.message : String(err)));
1645
+ console.error(chalk3.red(formatContractError(err)));
1509
1646
  process.exit(1);
1510
1647
  }
1511
1648
  }
@@ -1536,7 +1673,7 @@ function registerAllowanceCommands(program2) {
1536
1673
  console.log(chalk3.dim(` ${getExplorerUrl(txHash)}`));
1537
1674
  } catch (err) {
1538
1675
  execSpinner.fail("Execution failed");
1539
- console.error(chalk3.red(err instanceof Error ? err.message : String(err)));
1676
+ console.error(chalk3.red(formatContractError(err)));
1540
1677
  process.exit(1);
1541
1678
  }
1542
1679
  });
@@ -1590,7 +1727,7 @@ function registerAllowanceCommands(program2) {
1590
1727
  console.log();
1591
1728
  } catch (err) {
1592
1729
  spinner.fail("Failed to load status");
1593
- console.error(chalk3.red(err instanceof Error ? err.message : String(err)));
1730
+ console.error(chalk3.red(formatContractError(err)));
1594
1731
  process.exit(1);
1595
1732
  }
1596
1733
  });
@@ -1667,7 +1804,7 @@ function registerIdentityCommands(program2) {
1667
1804
  console.log(chalk4.dim(` sherwood syndicate create --agent-id ${tokenId} --subdomain <name> --name <name>`));
1668
1805
  } catch (err) {
1669
1806
  spinner.fail("Failed to register identity");
1670
- console.error(chalk4.red(err instanceof Error ? err.message : String(err)));
1807
+ console.error(chalk4.red(formatContractError(err)));
1671
1808
  process.exit(1);
1672
1809
  }
1673
1810
  });
@@ -1694,7 +1831,7 @@ function registerIdentityCommands(program2) {
1694
1831
  console.log(chalk4.dim(` Saved to ~/.sherwood/config.json`));
1695
1832
  } catch (err) {
1696
1833
  spinner.fail("Failed to load identity");
1697
- console.error(chalk4.red(err instanceof Error ? err.message : String(err)));
1834
+ console.error(chalk4.red(formatContractError(err)));
1698
1835
  process.exit(1);
1699
1836
  }
1700
1837
  });
@@ -1747,7 +1884,7 @@ function registerIdentityCommands(program2) {
1747
1884
  console.log();
1748
1885
  } catch (err) {
1749
1886
  spinner.fail("Failed to check identity");
1750
- console.error(chalk4.red(err instanceof Error ? err.message : String(err)));
1887
+ console.error(chalk4.red(formatContractError(err)));
1751
1888
  process.exit(1);
1752
1889
  }
1753
1890
  });
@@ -1755,7 +1892,7 @@ function registerIdentityCommands(program2) {
1755
1892
 
1756
1893
  // src/commands/proposal.ts
1757
1894
  import { isAddress as isAddress4 } from "viem";
1758
- import chalk5 from "chalk";
1895
+ import chalk6 from "chalk";
1759
1896
  import ora5 from "ora";
1760
1897
  import { readFileSync as readFileSync2 } from "fs";
1761
1898
 
@@ -1789,13 +1926,737 @@ function parseBigIntArg(value, name) {
1789
1926
  return BigInt(value);
1790
1927
  }
1791
1928
 
1792
- // src/commands/proposal.ts
1929
+ // src/lib/simulate.ts
1930
+ import { decodeFunctionData, encodeFunctionData as encodeFunctionData7 } from "viem";
1931
+ import chalk5 from "chalk";
1793
1932
  var G = chalk5.green;
1794
1933
  var W = chalk5.white;
1795
1934
  var DIM = chalk5.gray;
1796
1935
  var BOLD = chalk5.white.bold;
1797
1936
  var LABEL = chalk5.green.bold;
1798
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)));
1799
2660
  function formatTimestamp(ts) {
1800
2661
  if (ts === 0n) return "\u2014";
1801
2662
  return new Date(Number(ts) * 1e3).toLocaleString();
@@ -1811,11 +2672,11 @@ function parseCallsFile(path) {
1811
2672
  }
1812
2673
  function registerProposalCommands(program2) {
1813
2674
  const proposal = program2.command("proposal").description("Governance proposals \u2014 create, vote, execute, settle");
1814
- 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) => {
1815
2676
  try {
1816
2677
  const vault = opts.vault;
1817
2678
  if (!isAddress4(vault)) {
1818
- console.error(chalk5.red(`Invalid vault address: ${opts.vault}`));
2679
+ console.error(chalk6.red(`Invalid vault address: ${opts.vault}`));
1819
2680
  process.exit(1);
1820
2681
  }
1821
2682
  const performanceFeeBps = parseBigIntArg(opts.performanceFee, "performance-fee");
@@ -1824,7 +2685,7 @@ function registerProposalCommands(program2) {
1824
2685
  const settleCalls = parseCallsFile(opts.settleCalls);
1825
2686
  let metadataURI = opts.metadataUri || "";
1826
2687
  if (!metadataURI) {
1827
- 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();
1828
2689
  try {
1829
2690
  const account = getAccount();
1830
2691
  const metadata = {
@@ -1845,36 +2706,67 @@ function registerProposalCommands(program2) {
1845
2706
  createdAt: Math.floor(Date.now() / 1e3)
1846
2707
  };
1847
2708
  metadataURI = await uploadMetadata(proposalMeta);
1848
- spinner2.succeed(G(`Metadata pinned: ${DIM(metadataURI)}`));
2709
+ spinner2.succeed(G2(`Metadata pinned: ${DIM2(metadataURI)}`));
1849
2710
  } catch (err) {
1850
- spinner2.warn(chalk5.yellow("IPFS upload failed \u2014 using inline metadata"));
2711
+ spinner2.warn(chalk6.yellow("IPFS upload failed \u2014 using inline metadata"));
1851
2712
  const json = JSON.stringify({ name: opts.name, description: opts.description, vault });
1852
2713
  metadataURI = `data:application/json;base64,${Buffer.from(json).toString("base64")}`;
1853
2714
  }
1854
2715
  }
1855
2716
  console.log();
1856
- console.log(LABEL(" \u25C6 Proposal Summary"));
1857
- SEP();
1858
- console.log(W(` Name: ${BOLD(opts.name)}`));
1859
- console.log(W(` Vault: ${G(vault)}`));
1860
- console.log(W(` Performance Fee: ${Number(performanceFeeBps) / 100}%`));
1861
- console.log(W(` Duration: ${formatDurationShort(strategyDuration)}`));
1862
- console.log(W(` Calls: ${executeCalls.length} execute + ${settleCalls.length} settle`));
1863
- console.log(W(` Metadata: ${DIM(metadataURI.length > 50 ? metadataURI.slice(0, 50) + "..." : metadataURI)}`));
1864
- SEP();
1865
- 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();
1866
2758
  const result = await propose(vault, metadataURI, performanceFeeBps, strategyDuration, executeCalls, settleCalls);
1867
- spinner.succeed(G("Proposal submitted"));
2759
+ spinner.succeed(G2("Proposal submitted"));
1868
2760
  console.log();
1869
- console.log(LABEL(" \u25C6 Proposal Created"));
1870
- SEP();
1871
- console.log(W(` Proposal ID: ${G(`#${result.proposalId}`)}`));
1872
- console.log(W(` Tx: ${DIM(getExplorerUrl(result.hash))}`));
1873
- 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();
1874
2766
  console.log();
1875
2767
  } catch (err) {
1876
- console.error(chalk5.red(`
1877
- \u2716 ${err instanceof Error ? err.message : String(err)}`));
2768
+ console.error(chalk6.red(`
2769
+ \u2716 ${formatContractError(err)}`));
1878
2770
  process.exit(1);
1879
2771
  }
1880
2772
  });
@@ -1884,7 +2776,7 @@ function registerProposalCommands(program2) {
1884
2776
  const count = await proposalCount();
1885
2777
  if (count === 0n) {
1886
2778
  spinner.stop();
1887
- console.log(DIM("\n No proposals found.\n"));
2779
+ console.log(DIM2("\n No proposals found.\n"));
1888
2780
  return;
1889
2781
  }
1890
2782
  const vaultFilter = opts.vault ? opts.vault.toLowerCase() : null;
@@ -1904,16 +2796,16 @@ function registerProposalCommands(program2) {
1904
2796
  });
1905
2797
  spinner.stop();
1906
2798
  if (proposals.length === 0) {
1907
- console.log(DIM("\n No matching proposals.\n"));
2799
+ console.log(DIM2("\n No matching proposals.\n"));
1908
2800
  return;
1909
2801
  }
1910
2802
  console.log();
1911
- console.log(BOLD(`Proposals (${proposals.length})`));
1912
- console.log(DIM("\u2500".repeat(90)));
2803
+ console.log(BOLD2(`Proposals (${proposals.length})`));
2804
+ console.log(DIM2("\u2500".repeat(90)));
1913
2805
  console.log(
1914
- 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")
1915
2807
  );
1916
- console.log(DIM("\u2500".repeat(90)));
2808
+ console.log(DIM2("\u2500".repeat(90)));
1917
2809
  for (const p of proposals) {
1918
2810
  const state = PROPOSAL_STATES[p.computedState] || "Unknown";
1919
2811
  const created = p.snapshotTimestamp > 0n ? new Date(Number(p.snapshotTimestamp) * 1e3).toLocaleDateString() : "\u2014";
@@ -1928,7 +2820,7 @@ function registerProposalCommands(program2) {
1928
2820
  console.log();
1929
2821
  } catch (err) {
1930
2822
  spinner.fail("Failed to load proposals");
1931
- console.error(chalk5.red(err instanceof Error ? err.message : String(err)));
2823
+ console.error(chalk6.red(formatContractError(err)));
1932
2824
  process.exit(1);
1933
2825
  }
1934
2826
  });
@@ -1944,63 +2836,63 @@ function registerProposalCommands(program2) {
1944
2836
  const totalVotes = p.votesFor + p.votesAgainst;
1945
2837
  const quorumNeeded = totalVotes > 0n ? `${Number(params.vetoThresholdBps) / 100}%` : "\u2014";
1946
2838
  console.log();
1947
- console.log(LABEL(` \u25C6 Proposal #${p.id}`));
1948
- SEP();
2839
+ console.log(LABEL2(` \u25C6 Proposal #${p.id}`));
2840
+ SEP2();
1949
2841
  if (p.metadataURI && p.metadataURI.startsWith("ipfs://")) {
1950
2842
  try {
1951
2843
  const meta = await fetchMetadata(p.metadataURI);
1952
- console.log(W(` Name: ${BOLD(meta.name)}`));
1953
- console.log(W(` Description: ${DIM(meta.description)}`));
2844
+ console.log(W2(` Name: ${BOLD2(meta.name)}`));
2845
+ console.log(W2(` Description: ${DIM2(meta.description)}`));
1954
2846
  } catch {
1955
- console.log(W(` Metadata: ${DIM(p.metadataURI)}`));
2847
+ console.log(W2(` Metadata: ${DIM2(p.metadataURI)}`));
1956
2848
  }
1957
2849
  } else if (p.metadataURI) {
1958
- console.log(W(` Metadata: ${DIM(p.metadataURI)}`));
2850
+ console.log(W2(` Metadata: ${DIM2(p.metadataURI)}`));
1959
2851
  }
1960
- console.log(W(` State: ${BOLD(stateLabel)}`));
1961
- console.log(W(` Proposer: ${G(p.proposer)}`));
1962
- console.log(W(` Vault: ${G(p.vault)}`));
1963
- console.log(W(` Performance Fee: ${Number(p.performanceFeeBps) / 100}%`));
1964
- 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)}`));
1965
2857
  console.log();
1966
- console.log(LABEL(" Timestamps"));
1967
- console.log(W(` Snapshot: ${formatTimestamp(p.snapshotTimestamp)}`));
1968
- console.log(W(` Vote End: ${formatTimestamp(p.voteEnd)}`));
1969
- console.log(W(` Execute By: ${formatTimestamp(p.executeBy)}`));
1970
- 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)}`));
1971
2863
  console.log();
1972
- console.log(LABEL(" Votes"));
1973
- console.log(W(` For: ${formatShares(p.votesFor)}`));
1974
- console.log(W(` Against: ${formatShares(p.votesAgainst)}`));
1975
- console.log(W(` Abstain: ${formatShares(p.votesAbstain)}`));
1976
- 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}`));
1977
2869
  if (state === PROPOSAL_STATE.Executed || state === PROPOSAL_STATE.Settled) {
1978
2870
  try {
1979
2871
  const cap = await getCapitalSnapshot(id);
1980
2872
  console.log();
1981
- console.log(LABEL(" Capital"));
1982
- console.log(W(` Snapshot: ${formatUSDC(cap)}`));
2873
+ console.log(LABEL2(" Capital"));
2874
+ console.log(W2(` Snapshot: ${formatUSDC(cap)}`));
1983
2875
  } catch {
1984
2876
  }
1985
2877
  }
1986
2878
  const execCalls = await getExecuteCalls(id);
1987
2879
  const settlCalls = await getSettlementCalls(id);
1988
2880
  console.log();
1989
- console.log(LABEL(` Execute Calls (${execCalls.length})`));
2881
+ console.log(LABEL2(` Execute Calls (${execCalls.length})`));
1990
2882
  for (let i = 0; i < execCalls.length; i++) {
1991
- console.log(DIM(` [${i}] target=${execCalls[i].target}`));
1992
- 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}`));
1993
2885
  }
1994
- console.log(LABEL(` Settlement Calls (${settlCalls.length})`));
2886
+ console.log(LABEL2(` Settlement Calls (${settlCalls.length})`));
1995
2887
  for (let i = 0; i < settlCalls.length; i++) {
1996
- console.log(DIM(` [${i}] target=${settlCalls[i].target}`));
1997
- 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}`));
1998
2890
  }
1999
- SEP();
2891
+ SEP2();
2000
2892
  console.log();
2001
2893
  } catch (err) {
2002
2894
  spinner.fail("Failed to load proposal");
2003
- console.error(chalk5.red(err instanceof Error ? err.message : String(err)));
2895
+ console.error(chalk6.red(formatContractError(err)));
2004
2896
  process.exit(1);
2005
2897
  }
2006
2898
  });
@@ -2010,7 +2902,7 @@ function registerProposalCommands(program2) {
2010
2902
  const supportRaw = String(opts.support).toLowerCase();
2011
2903
  const support = supportRaw === "yes" || supportRaw === "for" ? VOTE_TYPE.For : supportRaw === "no" || supportRaw === "against" ? VOTE_TYPE.Against : supportRaw === "abstain" ? VOTE_TYPE.Abstain : null;
2012
2904
  if (support === null) {
2013
- 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.`));
2014
2906
  process.exit(1);
2015
2907
  }
2016
2908
  const account = getAccount();
@@ -2029,27 +2921,27 @@ function registerProposalCommands(program2) {
2029
2921
  const weight = await getVoteWeight(proposalId, account.address);
2030
2922
  spinner.stop();
2031
2923
  console.log();
2032
- console.log(LABEL(" \u25C6 Cast Vote"));
2033
- SEP();
2034
- console.log(W(` Proposal: #${proposalId}`));
2035
- console.log(W(` Vault: ${G(p.vault)}`));
2036
- console.log(W(
2037
- ` 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")}`
2038
2930
  ));
2039
- console.log(W(` Weight: ${formatShares(weight)} shares`));
2040
- SEP();
2041
- 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();
2042
2934
  const hash = await vote(proposalId, support);
2043
- voteSpinner.succeed(G("Vote cast"));
2044
- console.log(DIM(` ${getExplorerUrl(hash)}`));
2935
+ voteSpinner.succeed(G2("Vote cast"));
2936
+ console.log(DIM2(` ${getExplorerUrl(hash)}`));
2045
2937
  console.log();
2046
2938
  } catch (err) {
2047
- console.error(chalk5.red(`
2048
- \u2716 ${err instanceof Error ? err.message : String(err)}`));
2939
+ console.error(chalk6.red(`
2940
+ \u2716 ${formatContractError(err)}`));
2049
2941
  process.exit(1);
2050
2942
  }
2051
2943
  });
2052
- 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) => {
2053
2945
  try {
2054
2946
  const proposalId = parseBigIntArg(opts.id, "proposal ID");
2055
2947
  const spinner = ora5("Loading proposal...").start();
@@ -2058,19 +2950,34 @@ function registerProposalCommands(program2) {
2058
2950
  spinner.fail(`Proposal is ${PROPOSAL_STATES[state] || "Unknown"}, not Approved`);
2059
2951
  process.exit(1);
2060
2952
  }
2061
- 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...");
2062
2969
  const hash = await executeProposal(proposalId);
2063
- spinner.succeed(G("Proposal executed"));
2064
- console.log(DIM(` ${getExplorerUrl(hash)}`));
2970
+ spinner.succeed(G2("Proposal executed"));
2971
+ console.log(DIM2(` ${getExplorerUrl(hash)}`));
2065
2972
  try {
2066
2973
  const cap = await getCapitalSnapshot(proposalId);
2067
- console.log(DIM(` Capital snapshot: ${formatUSDC(cap)}`));
2974
+ console.log(DIM2(` Capital snapshot: ${formatUSDC(cap)}`));
2068
2975
  } catch {
2069
2976
  }
2070
2977
  console.log();
2071
2978
  } catch (err) {
2072
- console.error(chalk5.red(`
2073
- \u2716 ${err instanceof Error ? err.message : String(err)}`));
2979
+ console.error(chalk6.red(`
2980
+ \u2716 ${formatContractError(err)}`));
2074
2981
  process.exit(1);
2075
2982
  }
2076
2983
  });
@@ -2090,27 +2997,27 @@ function registerProposalCommands(program2) {
2090
2997
  const durationElapsed = p.executedAt > 0n && now >= p.executedAt + p.strategyDuration;
2091
2998
  let hash;
2092
2999
  if (isProposer && !durationElapsed) {
2093
- spinner.text = W("Settling (proposer)...");
3000
+ spinner.text = W2("Settling (proposer)...");
2094
3001
  hash = await settleProposal(proposalId);
2095
- spinner.succeed(G("Settled by proposer"));
3002
+ spinner.succeed(G2("Settled by proposer"));
2096
3003
  } else if (durationElapsed && !opts.calls) {
2097
- spinner.text = W("Settling (permissionless)...");
3004
+ spinner.text = W2("Settling (permissionless)...");
2098
3005
  hash = await settleProposal(proposalId);
2099
- spinner.succeed(G("Settled (permissionless)"));
3006
+ spinner.succeed(G2("Settled (permissionless)"));
2100
3007
  } else if (durationElapsed && opts.calls) {
2101
- spinner.text = W("Emergency settling...");
3008
+ spinner.text = W2("Emergency settling...");
2102
3009
  const calls = parseCallsFile(opts.calls);
2103
3010
  hash = await emergencySettle(proposalId, calls);
2104
- spinner.succeed(G("Emergency settled"));
3011
+ spinner.succeed(G2("Emergency settled"));
2105
3012
  } else {
2106
3013
  spinner.fail("Cannot settle: duration not elapsed and you are not the proposer.");
2107
3014
  process.exit(1);
2108
3015
  }
2109
- console.log(DIM(` ${getExplorerUrl(hash)}`));
3016
+ console.log(DIM2(` ${getExplorerUrl(hash)}`));
2110
3017
  console.log();
2111
3018
  } catch (err) {
2112
- console.error(chalk5.red(`
2113
- \u2716 ${err instanceof Error ? err.message : String(err)}`));
3019
+ console.error(chalk6.red(`
3020
+ \u2716 ${formatContractError(err)}`));
2114
3021
  process.exit(1);
2115
3022
  }
2116
3023
  });
@@ -2125,22 +3032,90 @@ function registerProposalCommands(program2) {
2125
3032
  }
2126
3033
  let hash;
2127
3034
  if (opts.emergency) {
2128
- spinner.text = W("Emergency cancelling...");
3035
+ spinner.text = W2("Emergency cancelling...");
2129
3036
  hash = await emergencyCancel(proposalId);
2130
- spinner.succeed(G("Emergency cancelled"));
3037
+ spinner.succeed(G2("Emergency cancelled"));
2131
3038
  } else {
2132
3039
  if (state !== PROPOSAL_STATE.Draft && state !== PROPOSAL_STATE.Pending) {
2133
3040
  spinner.fail(`Proposal is ${PROPOSAL_STATES[state] || "Unknown"} \u2014 use --emergency for non-pending/approved`);
2134
3041
  process.exit(1);
2135
3042
  }
2136
- spinner.text = W("Cancelling proposal...");
3043
+ spinner.text = W2("Cancelling proposal...");
2137
3044
  hash = await cancelProposal(proposalId);
2138
- spinner.succeed(G("Proposal cancelled"));
3045
+ spinner.succeed(G2("Proposal cancelled"));
2139
3046
  }
2140
- console.log(DIM(` ${getExplorerUrl(hash)}`));
3047
+ console.log(DIM2(` ${getExplorerUrl(hash)}`));
2141
3048
  console.log();
2142
3049
  } catch (err) {
2143
- 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(`
2144
3119
  \u2716 ${err instanceof Error ? err.message : String(err)}`));
2145
3120
  process.exit(1);
2146
3121
  }
@@ -2148,14 +3123,14 @@ function registerProposalCommands(program2) {
2148
3123
  }
2149
3124
 
2150
3125
  // src/commands/governor.ts
2151
- import chalk6 from "chalk";
3126
+ import chalk7 from "chalk";
2152
3127
  import ora6 from "ora";
2153
- var G2 = chalk6.green;
2154
- var W2 = chalk6.white;
2155
- var DIM2 = chalk6.gray;
2156
- var BOLD2 = chalk6.white.bold;
2157
- var LABEL2 = chalk6.green.bold;
2158
- 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)));
2159
3134
  function registerGovernorCommands(program2) {
2160
3135
  const governor = program2.command("governor").description("Governor parameters and vault management");
2161
3136
  governor.command("info").description("Display current governor parameters and registered vaults").action(async () => {
@@ -2167,29 +3142,29 @@ function registerGovernorCommands(program2) {
2167
3142
  ]);
2168
3143
  spinner.stop();
2169
3144
  console.log();
2170
- console.log(LABEL2(" \u25C6 Governor Parameters"));
2171
- SEP2();
2172
- console.log(W2(` Address: ${G2(getGovernorAddress())}`));
2173
- console.log(W2(` Voting Period: ${BOLD2(formatDurationLong(params.votingPeriod))}`));
2174
- console.log(W2(` Execution Window: ${BOLD2(formatDurationLong(params.executionWindow))}`));
2175
- console.log(W2(` Veto Threshold: ${BOLD2(`${Number(params.vetoThresholdBps) / 100}%`)}`));
2176
- console.log(W2(` Max Performance Fee: ${BOLD2(`${Number(params.maxPerformanceFeeBps) / 100}%`)}`));
2177
- console.log(W2(` Max Strategy Duration:${BOLD2(` ${formatDurationLong(params.maxStrategyDuration)}`)}`));
2178
- 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))}`));
2179
3154
  console.log();
2180
- console.log(LABEL2(` Registered Vaults (${vaults.length})`));
3155
+ console.log(LABEL3(` Registered Vaults (${vaults.length})`));
2181
3156
  if (vaults.length === 0) {
2182
- console.log(DIM2(" (none)"));
3157
+ console.log(DIM3(" (none)"));
2183
3158
  } else {
2184
3159
  for (const v of vaults) {
2185
- console.log(W2(` ${G2(v)}`));
3160
+ console.log(W3(` ${G3(v)}`));
2186
3161
  }
2187
3162
  }
2188
- SEP2();
3163
+ SEP3();
2189
3164
  console.log();
2190
3165
  } catch (err) {
2191
3166
  spinner.fail("Failed to load governor info");
2192
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3167
+ console.error(chalk7.red(formatContractError(err)));
2193
3168
  process.exit(1);
2194
3169
  }
2195
3170
  });
@@ -2197,11 +3172,11 @@ function registerGovernorCommands(program2) {
2197
3172
  const spinner = ora6("Setting voting period...").start();
2198
3173
  try {
2199
3174
  const hash = await setVotingPeriod(parseBigIntArg(opts.seconds, "seconds"));
2200
- spinner.succeed(G2(`Voting period change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
2201
- 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)}`));
2202
3177
  } catch (err) {
2203
3178
  spinner.fail("Failed to set voting period");
2204
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3179
+ console.error(chalk7.red(formatContractError(err)));
2205
3180
  process.exit(1);
2206
3181
  }
2207
3182
  });
@@ -2209,11 +3184,11 @@ function registerGovernorCommands(program2) {
2209
3184
  const spinner = ora6("Setting execution window...").start();
2210
3185
  try {
2211
3186
  const hash = await setExecutionWindow(parseBigIntArg(opts.seconds, "seconds"));
2212
- spinner.succeed(G2(`Execution window change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
2213
- 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)}`));
2214
3189
  } catch (err) {
2215
3190
  spinner.fail("Failed to set execution window");
2216
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3191
+ console.error(chalk7.red(formatContractError(err)));
2217
3192
  process.exit(1);
2218
3193
  }
2219
3194
  });
@@ -2221,11 +3196,11 @@ function registerGovernorCommands(program2) {
2221
3196
  const spinner = ora6("Setting veto threshold...").start();
2222
3197
  try {
2223
3198
  const hash = await setVetoThresholdBps(parseBigIntArg(opts.bps, "bps"));
2224
- spinner.succeed(G2(`Veto threshold change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
2225
- 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)}`));
2226
3201
  } catch (err) {
2227
3202
  spinner.fail("Failed to set veto threshold");
2228
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3203
+ console.error(chalk7.red(formatContractError(err)));
2229
3204
  process.exit(1);
2230
3205
  }
2231
3206
  });
@@ -2233,11 +3208,11 @@ function registerGovernorCommands(program2) {
2233
3208
  const spinner = ora6("Setting max fee...").start();
2234
3209
  try {
2235
3210
  const hash = await setMaxPerformanceFeeBps(parseBigIntArg(opts.bps, "bps"));
2236
- spinner.succeed(G2(`Max performance fee change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
2237
- 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)}`));
2238
3213
  } catch (err) {
2239
3214
  spinner.fail("Failed to set max fee");
2240
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3215
+ console.error(chalk7.red(formatContractError(err)));
2241
3216
  process.exit(1);
2242
3217
  }
2243
3218
  });
@@ -2245,11 +3220,11 @@ function registerGovernorCommands(program2) {
2245
3220
  const spinner = ora6("Setting max duration...").start();
2246
3221
  try {
2247
3222
  const hash = await setMaxStrategyDuration(parseBigIntArg(opts.seconds, "seconds"));
2248
- spinner.succeed(G2(`Max strategy duration change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
2249
- 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)}`));
2250
3225
  } catch (err) {
2251
3226
  spinner.fail("Failed to set max duration");
2252
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3227
+ console.error(chalk7.red(formatContractError(err)));
2253
3228
  process.exit(1);
2254
3229
  }
2255
3230
  });
@@ -2257,11 +3232,11 @@ function registerGovernorCommands(program2) {
2257
3232
  const spinner = ora6("Setting cooldown...").start();
2258
3233
  try {
2259
3234
  const hash = await setCooldownPeriod(parseBigIntArg(opts.seconds, "seconds"));
2260
- spinner.succeed(G2(`Cooldown period change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
2261
- 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)}`));
2262
3237
  } catch (err) {
2263
3238
  spinner.fail("Failed to set cooldown");
2264
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3239
+ console.error(chalk7.red(formatContractError(err)));
2265
3240
  process.exit(1);
2266
3241
  }
2267
3242
  });
@@ -2269,11 +3244,11 @@ function registerGovernorCommands(program2) {
2269
3244
  const spinner = ora6("Setting protocol fee...").start();
2270
3245
  try {
2271
3246
  const hash = await setProtocolFeeBps(parseBigIntArg(opts.bps, "bps"));
2272
- spinner.succeed(G2(`Protocol fee change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
2273
- 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)}`));
2274
3249
  } catch (err) {
2275
3250
  spinner.fail("Failed to set protocol fee");
2276
- console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
3251
+ console.error(chalk7.red(formatContractError(err)));
2277
3252
  process.exit(1);
2278
3253
  }
2279
3254
  });
@@ -2287,20 +3262,20 @@ try {
2287
3262
  var require2 = createRequire(import.meta.url);
2288
3263
  var { version: CLI_VERSION } = require2("../package.json");
2289
3264
  async function loadXmtp() {
2290
- return import("./xmtp-7DF7UG66.js");
3265
+ return import("./xmtp-XKHXATPQ.js");
2291
3266
  }
2292
3267
  async function loadCron() {
2293
- return import("./cron-SKYKVZ6K.js");
3268
+ return import("./cron-EOULYO3R.js");
2294
3269
  }
2295
- var G3 = chalk7.green;
2296
- var W3 = chalk7.white;
2297
- var DIM3 = chalk7.gray;
2298
- var BOLD3 = chalk7.white.bold;
2299
- var LABEL3 = chalk7.green.bold;
2300
- 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)));
2301
3276
  function validateAddress(value, name) {
2302
3277
  if (!isAddress5(value)) {
2303
- console.error(chalk7.red(`Invalid ${name} address: ${value}`));
3278
+ console.error(chalk8.red(`Invalid ${name} address: ${value}`));
2304
3279
  process.exit(1);
2305
3280
  }
2306
3281
  return value;
@@ -2320,7 +3295,7 @@ program.name("sherwood").description("CLI for agent-managed investment syndicate
2320
3295
  process.env.ENABLE_TESTNET = "true";
2321
3296
  if (network !== "base") {
2322
3297
  console.warn(
2323
- chalk7.yellow("[warn] --testnet ignored, --chain takes precedence")
3298
+ chalk8.yellow("[warn] --testnet ignored, --chain takes precedence")
2324
3299
  );
2325
3300
  } else {
2326
3301
  network = "base-sepolia";
@@ -2328,39 +3303,39 @@ program.name("sherwood").description("CLI for agent-managed investment syndicate
2328
3303
  }
2329
3304
  setNetwork(network);
2330
3305
  if (getNetwork() !== "base") {
2331
- console.log(chalk7.yellow(`[${getNetwork()}]`));
3306
+ console.log(chalk8.yellow(`[${getNetwork()}]`));
2332
3307
  }
2333
3308
  });
2334
3309
  var syndicate = program.command("syndicate");
2335
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) => {
2336
3311
  try {
2337
3312
  console.log();
2338
- console.log(LABEL3(" \u25C6 Create Syndicate"));
2339
- SEP3();
3313
+ console.log(LABEL4(" \u25C6 Create Syndicate"));
3314
+ SEP4();
2340
3315
  const wallet = getAccount();
2341
- console.log(DIM3(` Wallet: ${wallet.address}`));
2342
- console.log(DIM3(` Network: ${getChain().name}`));
2343
- SEP3();
3316
+ console.log(DIM4(` Wallet: ${wallet.address}`));
3317
+ console.log(DIM4(` Network: ${getChain().name}`));
3318
+ SEP4();
2344
3319
  const savedAgentId = getAgentId();
2345
3320
  const nonInteractive = opts.yes;
2346
3321
  const name = opts.name || (nonInteractive ? (() => {
2347
3322
  throw new Error("--name is required in non-interactive mode (-y)");
2348
3323
  })() : await input({
2349
- message: G3("Syndicate name"),
3324
+ message: G4("Syndicate name"),
2350
3325
  validate: (v) => v.length > 0 || "Name is required"
2351
3326
  }));
2352
3327
  const subdomain = opts.subdomain || (nonInteractive ? name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "") : await input({
2353
- message: G3("ENS subdomain"),
3328
+ message: G4("ENS subdomain"),
2354
3329
  default: name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, ""),
2355
3330
  validate: (v) => v.length >= 3 || "Must be at least 3 characters"
2356
3331
  }));
2357
3332
  const description = opts.description || (nonInteractive ? `${name} \u2014 a Sherwood syndicate` : await input({
2358
- message: G3("Description"),
3333
+ message: G4("Description"),
2359
3334
  default: `${name} \u2014 a Sherwood syndicate`
2360
3335
  }));
2361
- 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" }));
2362
3337
  const openDeposits = opts.openDeposits !== void 0 ? opts.openDeposits : nonInteractive ? true : await confirm({
2363
- message: G3("Open deposits? (anyone can deposit)"),
3338
+ message: G4("Open deposits? (anyone can deposit)"),
2364
3339
  default: true
2365
3340
  });
2366
3341
  const ASSET_SYMBOLS = {
@@ -2376,14 +3351,14 @@ syndicate.command("create").description("Create a new syndicate via the factory
2376
3351
  asset = opts.asset;
2377
3352
  } else {
2378
3353
  const supported = Object.keys(ASSET_SYMBOLS).join(", ");
2379
- 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.`));
2380
3355
  process.exit(1);
2381
3356
  }
2382
3357
  } else if (nonInteractive) {
2383
3358
  asset = TOKENS().USDC !== "0x0000000000000000000000000000000000000000" ? ASSET_SYMBOLS.USDC : ASSET_SYMBOLS.WETH;
2384
3359
  } else {
2385
3360
  const assetChoice = await select({
2386
- message: G3("Vault asset (what token do depositors provide?)"),
3361
+ message: G4("Vault asset (what token do depositors provide?)"),
2387
3362
  choices: [
2388
3363
  { name: "USDC", value: "USDC", description: "USD Coin (6 decimals)" },
2389
3364
  { name: "WETH", value: "WETH", description: "Wrapped Ether (18 decimals)" }
@@ -2398,26 +3373,26 @@ syndicate.command("create").description("Create a new syndicate via the factory
2398
3373
  ]);
2399
3374
  const symbol = `sw${assetSymbol}`;
2400
3375
  console.log();
2401
- console.log(LABEL3(" \u25C6 Review"));
2402
- SEP3();
2403
- console.log(W3(` Name: ${BOLD3(name)}`));
2404
- console.log(W3(` ENS: ${G3(`${subdomain}.sherwoodagent.eth`)}`));
2405
- console.log(W3(` Description: ${DIM3(description)}`));
2406
- console.log(W3(` Agent ID: #${agentIdStr}`));
2407
- console.log(W3(` Asset: ${assetSymbol} (${asset.slice(0, 10)}...)`));
2408
- console.log(W3(` Share token: ${symbol}`));
2409
- console.log(W3(` Open deposits: ${openDeposits ? G3("yes") : chalk7.red("no (whitelist)")}`));
2410
- 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();
2411
3386
  if (!nonInteractive) {
2412
- const go = await confirm({ message: G3("Deploy syndicate?"), default: true });
3387
+ const go = await confirm({ message: G4("Deploy syndicate?"), default: true });
2413
3388
  if (!go) {
2414
- console.log(DIM3(" Cancelled."));
3389
+ console.log(DIM4(" Cancelled."));
2415
3390
  return;
2416
3391
  }
2417
3392
  }
2418
3393
  let metadataURI = opts.metadataUri || "";
2419
3394
  if (!metadataURI) {
2420
- 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();
2421
3396
  try {
2422
3397
  const metadata = {
2423
3398
  schema: "sherwood/syndicate/v1",
@@ -2431,14 +3406,77 @@ syndicate.command("create").description("Create a new syndicate via the factory
2431
3406
  links: {}
2432
3407
  };
2433
3408
  metadataURI = await uploadMetadata(metadata);
2434
- spinner2.succeed(G3(`Metadata pinned: ${DIM3(metadataURI)}`));
3409
+ spinner2.succeed(G4(`Metadata pinned: ${DIM4(metadataURI)}`));
2435
3410
  } catch (err) {
2436
- spinner2.warn(chalk7.yellow(`IPFS upload failed \u2014 using inline metadata`));
3411
+ spinner2.warn(chalk8.yellow(`IPFS upload failed \u2014 using inline metadata`));
2437
3412
  const json = JSON.stringify({ name, description, subdomain, asset: assetSymbol, openDeposits, createdBy: "@sherwoodagent/cli" });
2438
3413
  metadataURI = `data:application/json;base64,${Buffer.from(json).toString("base64")}`;
2439
3414
  }
2440
3415
  }
2441
- 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();
2442
3480
  const result = await createSyndicate({
2443
3481
  creatorAgentId: BigInt(agentIdStr),
2444
3482
  metadataURI,
@@ -2448,9 +3486,12 @@ syndicate.command("create").description("Create a new syndicate via the factory
2448
3486
  openDeposits,
2449
3487
  subdomain
2450
3488
  });
2451
- 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);
2452
3493
  await new Promise((r) => setTimeout(r, 2e3));
2453
- spinner.text = W3("Registering creator as agent...");
3494
+ spinner.text = W4("Registering creator as agent...");
2454
3495
  try {
2455
3496
  setVaultAddress(result.vault);
2456
3497
  const creatorAddress = getAccount().address;
@@ -2460,9 +3501,9 @@ syndicate.command("create").description("Create a new syndicate via the factory
2460
3501
  // agentAddress = creator EOA (direct execution)
2461
3502
  );
2462
3503
  } catch (regErr) {
2463
- 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`"));
2464
3505
  }
2465
- spinner.text = W3("Setting up chat...");
3506
+ spinner.text = W4("Setting up chat...");
2466
3507
  try {
2467
3508
  const xmtp = await loadXmtp();
2468
3509
  const xmtpClient = await xmtp.getXmtpClient();
@@ -2470,35 +3511,37 @@ syndicate.command("create").description("Create a new syndicate via the factory
2470
3511
  await setTextRecord(subdomain, "xmtpGroupId", groupId, result.vault);
2471
3512
  cacheGroupId(subdomain, groupId);
2472
3513
  } catch {
2473
- console.warn(chalk7.yellow("\n \u26A0 Could not create XMTP chat group"));
2474
- 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`));
2475
3516
  }
2476
3517
  try {
2477
3518
  const cron = await loadCron();
2478
3519
  const cronResult = cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
2479
3520
  if (cronResult.isOpenClaw && cronResult.registered) {
2480
- console.log(G3(" \u2713 Participation crons registered (15m check + hourly summary)"));
3521
+ console.log(G4(" \u2713 Participation crons registered (15m check + hourly summary)"));
2481
3522
  } else if (!cronResult.isOpenClaw) {
2482
- 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"));
2483
3524
  }
2484
3525
  } catch {
2485
3526
  }
2486
3527
  spinner.stop();
2487
3528
  console.log();
2488
- console.log(LABEL3(" \u25C6 Syndicate Created"));
2489
- SEP3();
2490
- console.log(W3(` ID: ${G3(`#${result.syndicateId}`)}`));
2491
- console.log(W3(` Vault: ${G3(result.vault)}`));
2492
- console.log(W3(` ENS: ${G3(`${subdomain}.sherwoodagent.eth`)}`));
2493
- console.log(W3(` Metadata: ${DIM3(metadataURI.length > 50 ? metadataURI.slice(0, 50) + "..." : metadataURI)}`));
2494
- console.log(W3(` Explorer: ${DIM3(getExplorerUrl(result.hash))}`));
2495
- console.log(W3(` Chat: ${DIM3(`sherwood chat ${subdomain}`)}`));
2496
- SEP3();
2497
- 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)"));
2498
3541
  console.log();
2499
3542
  } catch (err) {
2500
- console.error(chalk7.red(`
2501
- \u2716 ${err instanceof Error ? err.message : String(err)}`));
3543
+ console.error(chalk8.red(`
3544
+ \u2716 ${formatContractError(err)}`));
2502
3545
  process.exit(1);
2503
3546
  }
2504
3547
  });
@@ -2522,34 +3565,34 @@ syndicate.command("list").description("List active syndicates (queries subgraph,
2522
3565
  }
2523
3566
  spinner.stop();
2524
3567
  if (syndicates.length === 0) {
2525
- console.log(chalk7.dim("No active syndicates found."));
3568
+ console.log(chalk8.dim("No active syndicates found."));
2526
3569
  return;
2527
3570
  }
2528
3571
  console.log();
2529
- console.log(chalk7.bold(`Active Syndicates (${syndicates.length})`));
3572
+ console.log(chalk8.bold(`Active Syndicates (${syndicates.length})`));
2530
3573
  if (!process.env.SUBGRAPH_URL) {
2531
- console.log(chalk7.dim(" (Set SUBGRAPH_URL for faster indexed queries)"));
3574
+ console.log(chalk8.dim(" (Set SUBGRAPH_URL for faster indexed queries)"));
2532
3575
  }
2533
- console.log(chalk7.dim("\u2500".repeat(70)));
3576
+ console.log(chalk8.dim("\u2500".repeat(70)));
2534
3577
  for (const s of syndicates) {
2535
3578
  const ts = typeof s.createdAt === "string" ? Number(s.createdAt) : Number(s.createdAt);
2536
3579
  const date = new Date(ts * 1e3).toLocaleDateString();
2537
3580
  const ensName = s.subdomain ? `${s.subdomain}.sherwoodagent.eth` : "";
2538
- console.log(` #${s.id} ${chalk7.bold(ensName || String(s.vault))}`);
2539
- 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))}`);
2540
3583
  console.log(` Creator: ${s.creator}`);
2541
3584
  console.log(` Created: ${date}`);
2542
3585
  if (s.totalDeposits) {
2543
3586
  console.log(` Deposits: ${s.totalDeposits} USDC`);
2544
3587
  }
2545
3588
  if (s.metadataURI) {
2546
- console.log(` Metadata: ${chalk7.dim(s.metadataURI)}`);
3589
+ console.log(` Metadata: ${chalk8.dim(s.metadataURI)}`);
2547
3590
  }
2548
3591
  console.log();
2549
3592
  }
2550
3593
  } catch (err) {
2551
3594
  spinner.fail("Failed to load syndicates");
2552
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3595
+ console.error(chalk8.red(formatContractError(err)));
2553
3596
  process.exit(1);
2554
3597
  }
2555
3598
  });
@@ -2560,27 +3603,27 @@ syndicate.command("info").description("Display syndicate details by ID").argumen
2560
3603
  const info = await getSyndicate(id);
2561
3604
  spinner.stop();
2562
3605
  if (!info.vault || info.vault === "0x0000000000000000000000000000000000000000") {
2563
- console.log(chalk7.red(`Syndicate #${id} not found.`));
3606
+ console.log(chalk8.red(`Syndicate #${id} not found.`));
2564
3607
  process.exit(1);
2565
3608
  }
2566
3609
  const date = new Date(Number(info.createdAt) * 1e3).toLocaleDateString();
2567
3610
  console.log();
2568
- console.log(chalk7.bold(`Syndicate #${info.id}`));
2569
- console.log(chalk7.dim("\u2500".repeat(40)));
3611
+ console.log(chalk8.bold(`Syndicate #${info.id}`));
3612
+ console.log(chalk8.dim("\u2500".repeat(40)));
2570
3613
  if (info.subdomain) {
2571
- console.log(` ENS: ${chalk7.bold(`${info.subdomain}.sherwoodagent.eth`)}`);
3614
+ console.log(` ENS: ${chalk8.bold(`${info.subdomain}.sherwoodagent.eth`)}`);
2572
3615
  }
2573
- console.log(` Vault: ${chalk7.cyan(info.vault)}`);
3616
+ console.log(` Vault: ${chalk8.cyan(info.vault)}`);
2574
3617
  console.log(` Creator: ${info.creator}`);
2575
3618
  console.log(` Created: ${date}`);
2576
- console.log(` Active: ${info.active ? chalk7.green("yes") : chalk7.red("no")}`);
3619
+ console.log(` Active: ${info.active ? chalk8.green("yes") : chalk8.red("no")}`);
2577
3620
  if (info.metadataURI) {
2578
- console.log(` Metadata: ${chalk7.dim(info.metadataURI)}`);
3621
+ console.log(` Metadata: ${chalk8.dim(info.metadataURI)}`);
2579
3622
  }
2580
3623
  setVaultAddress(info.vault);
2581
3624
  const vaultInfo = await getVaultInfo();
2582
3625
  console.log();
2583
- console.log(chalk7.bold(" Vault Stats"));
3626
+ console.log(chalk8.bold(" Vault Stats"));
2584
3627
  console.log(` Total Assets: ${vaultInfo.totalAssets}`);
2585
3628
  console.log(` Agent Count: ${vaultInfo.agentCount}`);
2586
3629
  console.log(` Redemptions Locked: ${vaultInfo.redemptionsLocked}`);
@@ -2588,12 +3631,12 @@ syndicate.command("info").description("Display syndicate details by ID").argumen
2588
3631
  console.log();
2589
3632
  } catch (err) {
2590
3633
  spinner.fail("Failed to load syndicate info");
2591
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3634
+ console.error(chalk8.red(formatContractError(err)));
2592
3635
  process.exit(1);
2593
3636
  }
2594
3637
  });
2595
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) => {
2596
- const spinner = ora7({ text: W3("Loading syndicate..."), color: "green" }).start();
3639
+ const spinner = ora7({ text: W4("Loading syndicate..."), color: "green" }).start();
2597
3640
  try {
2598
3641
  const syndicateId = BigInt(opts.id);
2599
3642
  let metadataURI = opts.uri;
@@ -2605,7 +3648,7 @@ syndicate.command("update-metadata").description("Update syndicate metadata (cre
2605
3648
  }
2606
3649
  const name = opts.name || info.subdomain;
2607
3650
  const description = opts.description || `${name} \u2014 a Sherwood syndicate on ${info.subdomain}.sherwoodagent.eth`;
2608
- spinner.text = W3("Uploading metadata to IPFS...");
3651
+ spinner.text = W4("Uploading metadata to IPFS...");
2609
3652
  const metadata = {
2610
3653
  schema: "sherwood/syndicate/v1",
2611
3654
  name,
@@ -2616,15 +3659,15 @@ syndicate.command("update-metadata").description("Update syndicate metadata (cre
2616
3659
  links: {}
2617
3660
  };
2618
3661
  metadataURI = await uploadMetadata(metadata);
2619
- spinner.text = W3("Updating on-chain metadata...");
3662
+ spinner.text = W4("Updating on-chain metadata...");
2620
3663
  }
2621
3664
  const hash = await updateMetadata(syndicateId, metadataURI);
2622
- spinner.succeed(G3(`Metadata updated`));
2623
- console.log(DIM3(` IPFS: ${metadataURI}`));
2624
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
3665
+ spinner.succeed(G4(`Metadata updated`));
3666
+ console.log(DIM4(` IPFS: ${metadataURI}`));
3667
+ console.log(DIM4(` ${getExplorerUrl(hash)}`));
2625
3668
  } catch (err) {
2626
3669
  spinner.fail("Metadata update failed");
2627
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3670
+ console.error(chalk8.red(formatContractError(err)));
2628
3671
  process.exit(1);
2629
3672
  }
2630
3673
  });
@@ -2635,10 +3678,10 @@ syndicate.command("approve-depositor").description("Approve an address to deposi
2635
3678
  const depositor = validateAddress(opts.depositor, "depositor");
2636
3679
  const hash = await approveDepositor(depositor);
2637
3680
  spinner.succeed(`Depositor approved: ${hash}`);
2638
- console.log(chalk7.dim(` ${getExplorerUrl(hash)}`));
3681
+ console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
2639
3682
  } catch (err) {
2640
3683
  spinner.fail("Approval failed");
2641
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3684
+ console.error(chalk8.red(formatContractError(err)));
2642
3685
  process.exit(1);
2643
3686
  }
2644
3687
  });
@@ -2649,10 +3692,10 @@ syndicate.command("remove-depositor").description("Remove an address from the de
2649
3692
  const depositor = validateAddress(opts.depositor, "depositor");
2650
3693
  const hash = await removeDepositor(depositor);
2651
3694
  spinner.succeed(`Depositor removed: ${hash}`);
2652
- console.log(chalk7.dim(` ${getExplorerUrl(hash)}`));
3695
+ console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
2653
3696
  } catch (err) {
2654
3697
  spinner.fail("Removal failed");
2655
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3698
+ console.error(chalk8.red(formatContractError(err)));
2656
3699
  process.exit(1);
2657
3700
  }
2658
3701
  });
@@ -2674,7 +3717,7 @@ syndicate.command("add").description("Register an agent on a syndicate vault (cr
2674
3717
  agentWallet
2675
3718
  );
2676
3719
  spinner.succeed(`Agent registered: ${hash}`);
2677
- console.log(chalk7.dim(` ${getExplorerUrl(hash)}`));
3720
+ console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
2678
3721
  try {
2679
3722
  const xmtp = await loadXmtp();
2680
3723
  const xmtpClient = await xmtp.getXmtpClient();
@@ -2686,26 +3729,66 @@ syndicate.command("add").description("Register an agent on a syndicate vault (cr
2686
3729
  syndicate: subdomain,
2687
3730
  timestamp: Math.floor(Date.now() / 1e3)
2688
3731
  });
2689
- console.log(chalk7.dim(` Added to chat: ${subdomain}`));
3732
+ console.log(chalk8.dim(` Added to chat: ${subdomain}`));
2690
3733
  } catch {
2691
- console.warn(chalk7.yellow(" \u26A0 Could not add agent to chat group"));
2692
- 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`));
2693
3736
  }
2694
3737
  } catch (err) {
2695
3738
  spinner.fail("Registration failed");
2696
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3739
+ console.error(chalk8.red(formatContractError(err)));
2697
3740
  process.exit(1);
2698
3741
  }
2699
3742
  });
2700
- 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) => {
2701
3779
  const spinner = ora7("Resolving syndicate...").start();
2702
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
+ }
2703
3786
  const agentId = getAgentId();
2704
3787
  if (!agentId) {
2705
3788
  spinner.fail("No agent identity found. Run 'sherwood identity mint' first.");
2706
3789
  process.exit(1);
2707
3790
  }
2708
- const syndicate2 = await resolveSyndicate(opts.subdomain);
3791
+ const syndicate2 = await resolveSyndicate(subdomain);
2709
3792
  const callerAddress = getAccount().address;
2710
3793
  spinner.text = "Checking membership...";
2711
3794
  setVaultAddress(syndicate2.vault);
@@ -2715,17 +3798,17 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
2715
3798
  try {
2716
3799
  const xmtp = await loadXmtp();
2717
3800
  await xmtp.getXmtpClient();
2718
- console.log(chalk7.dim(" XMTP identity ready"));
3801
+ console.log(chalk8.dim(" XMTP identity ready"));
2719
3802
  } catch {
2720
- console.warn(chalk7.yellow(" \u26A0 Could not initialize XMTP identity"));
3803
+ console.warn(chalk8.yellow(" \u26A0 Could not initialize XMTP identity"));
2721
3804
  }
2722
3805
  try {
2723
3806
  const cron = await loadCron();
2724
- const cronResult = cron.registerSyndicateCrons(opts.subdomain, isTestnet(), getNotifyTo());
3807
+ const cronResult = cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
2725
3808
  if (cronResult.isOpenClaw && cronResult.registered) {
2726
- console.log(chalk7.green(" \u2713 Participation crons registered"));
3809
+ console.log(chalk8.green(" \u2713 Participation crons registered"));
2727
3810
  } else if (!cronResult.isOpenClaw) {
2728
- 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"));
2729
3812
  }
2730
3813
  } catch {
2731
3814
  }
@@ -2738,24 +3821,26 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
2738
3821
  );
2739
3822
  if (existingRequest) {
2740
3823
  spinner.succeed("You already have a pending join request for this syndicate");
2741
- console.log(chalk7.dim(` Attestation: ${existingRequest.uid}`));
2742
- 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()}`));
2743
3826
  try {
2744
3827
  const xmtp = await loadXmtp();
2745
3828
  await xmtp.getXmtpClient();
2746
- console.log(chalk7.dim(" XMTP identity ready"));
3829
+ console.log(chalk8.dim(" XMTP identity ready"));
2747
3830
  } catch {
2748
- console.warn(chalk7.yellow(" \u26A0 Could not initialize XMTP identity"));
3831
+ console.warn(chalk8.yellow(" \u26A0 Could not initialize XMTP identity"));
2749
3832
  }
2750
3833
  return;
2751
3834
  }
2752
3835
  spinner.text = "Creating join request attestation...";
3836
+ const referrerAgentId = opts.ref ? parseInt(opts.ref, 10) : void 0;
2753
3837
  const { uid, hash } = await createJoinRequest(
2754
3838
  syndicate2.id,
2755
3839
  BigInt(agentId),
2756
3840
  syndicate2.vault,
2757
3841
  syndicate2.creator,
2758
- opts.message
3842
+ opts.message,
3843
+ referrerAgentId
2759
3844
  );
2760
3845
  try {
2761
3846
  spinner.text = "Registering XMTP identity...";
@@ -2764,34 +3849,37 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
2764
3849
  spinner.succeed("Join request created (XMTP identity ready)");
2765
3850
  } catch {
2766
3851
  spinner.succeed("Join request created");
2767
- 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"));
2768
3853
  }
3854
+ addSyndicate(getChain().id, { subdomain, vault: syndicate2.vault, role: "agent" });
3855
+ setPrimarySyndicate(getChain().id, subdomain);
2769
3856
  try {
2770
3857
  const cron = await loadCron();
2771
- const cronResult = cron.registerSyndicateCrons(opts.subdomain, isTestnet(), getNotifyTo());
3858
+ const cronResult = cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
2772
3859
  if (cronResult.isOpenClaw && cronResult.registered) {
2773
- console.log(G3(" \u2713 Participation crons registered (will activate after approval)"));
3860
+ console.log(G4(" \u2713 Participation crons registered (will activate after approval)"));
2774
3861
  } else if (!cronResult.isOpenClaw) {
2775
- 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"));
2776
3863
  }
2777
3864
  } catch {
2778
3865
  }
2779
3866
  console.log();
2780
- console.log(LABEL3(" \u25C6 Join Request Submitted"));
2781
- SEP3();
2782
- console.log(W3(` Syndicate: ${G3(`${opts.subdomain}.sherwoodagent.eth`)}`));
2783
- console.log(W3(` Agent ID: #${agentId}`));
2784
- console.log(W3(` Creator: ${DIM3(syndicate2.creator)}`));
2785
- console.log(W3(` Attestation: ${DIM3(uid)}`));
2786
- console.log(W3(` EAS Scan: ${DIM3(getEasScanUrl(uid))}`));
2787
- console.log(W3(` Explorer: ${DIM3(getExplorerUrl(hash))}`));
2788
- SEP3();
2789
- console.log(G3(" \u2713 The creator can review with:"));
2790
- 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}`));
2791
3879
  console.log();
2792
3880
  } catch (err) {
2793
3881
  spinner.fail("Join request failed");
2794
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3882
+ console.error(chalk8.red(formatContractError(err)));
2795
3883
  process.exit(1);
2796
3884
  }
2797
3885
  });
@@ -2801,10 +3889,11 @@ syndicate.command("requests").description("View pending join requests for a synd
2801
3889
  let creatorAddress;
2802
3890
  let subdomain;
2803
3891
  let syndicateVault;
2804
- if (opts.subdomain) {
2805
- const syndicateInfo = await resolveSyndicate(opts.subdomain);
3892
+ const resolvedSubdomain = opts.subdomain || getPrimarySyndicate(getChain().id)?.subdomain;
3893
+ if (resolvedSubdomain) {
3894
+ const syndicateInfo = await resolveSyndicate(resolvedSubdomain);
2806
3895
  creatorAddress = syndicateInfo.creator;
2807
- subdomain = opts.subdomain;
3896
+ subdomain = resolvedSubdomain;
2808
3897
  syndicateVault = syndicateInfo.vault;
2809
3898
  } else {
2810
3899
  resolveVault(opts);
@@ -2831,37 +3920,43 @@ syndicate.command("requests").description("View pending join requests for a synd
2831
3920
  );
2832
3921
  spinner.stop();
2833
3922
  if (requests.length === 0) {
2834
- console.log(DIM3("\n No pending join requests.\n"));
3923
+ console.log(DIM4("\n No pending join requests.\n"));
2835
3924
  return;
2836
3925
  }
2837
3926
  console.log();
2838
- console.log(LABEL3(` \u25C6 Pending Join Requests (${requests.length})`));
2839
- SEP3();
3927
+ console.log(LABEL4(` \u25C6 Pending Join Requests (${requests.length})`));
3928
+ SEP4();
2840
3929
  for (let i = 0; i < requests.length; i++) {
2841
3930
  const req = requests[i];
2842
3931
  const date = new Date(req.time * 1e3).toLocaleString();
2843
- console.log(W3(` ${i + 1}. Agent #${req.decoded.agentId} ${DIM3(`(${req.attester})`)}`));
2844
- console.log(DIM3(` Message: "${req.decoded.message}"`));
2845
- console.log(DIM3(` Requested: ${date}`));
2846
- 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}`));
2847
3941
  console.log();
2848
3942
  }
2849
- console.log(G3(" To approve:"));
2850
- console.log(DIM3(` sherwood syndicate approve --agent-id <id> --wallet <addr>`));
2851
- console.log(G3(" To reject:"));
2852
- 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>`));
2853
3947
  console.log();
2854
3948
  } catch (err) {
2855
3949
  spinner.fail("Failed to load requests");
2856
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
3950
+ console.error(chalk8.red(formatContractError(err)));
2857
3951
  process.exit(1);
2858
3952
  }
2859
3953
  });
2860
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) => {
2861
3955
  const spinner = ora7("Verifying creator...").start();
2862
3956
  try {
2863
- if (opts.subdomain && !opts.vault) {
2864
- 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);
2865
3960
  setVaultAddress(syndicateInfo.vault);
2866
3961
  } else {
2867
3962
  resolveVault(opts);
@@ -2882,11 +3977,11 @@ syndicate.command("approve").description("Approve an agent join request (registe
2882
3977
  agentWallet
2883
3978
  );
2884
3979
  agentWasRegistered = true;
2885
- console.log(DIM3(` Agent registered: ${getExplorerUrl(regHash)}`));
3980
+ console.log(DIM4(` Agent registered: ${getExplorerUrl(regHash)}`));
2886
3981
  } catch (regErr) {
2887
3982
  const msg = regErr instanceof Error ? regErr.message : String(regErr);
2888
3983
  if (msg.includes("0xe098d3ee") || msg.includes("AgentAlreadyRegistered")) {
2889
- console.log(DIM3(" Agent already registered on vault \u2014 skipping"));
3984
+ console.log(DIM4(" Agent already registered on vault \u2014 skipping"));
2890
3985
  } else {
2891
3986
  throw regErr;
2892
3987
  }
@@ -2902,7 +3997,7 @@ syndicate.command("approve").description("Approve an agent join request (registe
2902
3997
  let approvalUid;
2903
3998
  if (alreadyApproved) {
2904
3999
  approvalUid = alreadyApproved.uid;
2905
- console.log(DIM3(` Approval attestation already exists \u2014 skipping`));
4000
+ console.log(DIM4(` Approval attestation already exists \u2014 skipping`));
2906
4001
  } else {
2907
4002
  spinner.text = "Creating approval attestation...";
2908
4003
  const result = await createApproval(
@@ -2925,23 +4020,23 @@ syndicate.command("approve").description("Approve an agent join request (registe
2925
4020
  syndicate: subdomain,
2926
4021
  timestamp: Math.floor(Date.now() / 1e3)
2927
4022
  });
2928
- console.log(DIM3(` Added to chat: ${subdomain}`));
4023
+ console.log(DIM4(` Added to chat: ${subdomain}`));
2929
4024
  } catch {
2930
- console.warn(chalk7.yellow(" \u26A0 Could not add agent to chat group"));
2931
- 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`));
2932
4027
  }
2933
4028
  spinner.succeed("Agent approved and registered");
2934
4029
  console.log();
2935
- console.log(LABEL3(" \u25C6 Agent Approved"));
2936
- SEP3();
2937
- console.log(W3(` Agent ID: #${opts.agentId}`));
2938
- console.log(W3(` Wallet: ${G3(opts.wallet)}`));
2939
- console.log(W3(` Approval: ${DIM3(approvalUid)}`));
2940
- console.log(W3(` EAS Scan: ${DIM3(getEasScanUrl(approvalUid))}`));
2941
- 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();
2942
4037
  } catch (err) {
2943
4038
  spinner.fail("Approval failed");
2944
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
4039
+ console.error(chalk8.red(formatContractError(err)));
2945
4040
  process.exit(1);
2946
4041
  }
2947
4042
  });
@@ -2953,26 +4048,134 @@ syndicate.command("reject").description("Reject a join request by revoking its a
2953
4048
  opts.attestation
2954
4049
  );
2955
4050
  spinner.succeed("Join request rejected");
2956
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4051
+ console.log(DIM4(` ${getExplorerUrl(hash)}`));
2957
4052
  } catch (err) {
2958
4053
  spinner.fail("Rejection failed");
2959
- 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)));
2960
4147
  process.exit(1);
2961
4148
  }
2962
4149
  });
2963
4150
  var vaultCmd = program.command("vault");
2964
- 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) => {
2965
4152
  resolveVault(opts);
2966
4153
  const decimals = await getAssetDecimals();
2967
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
+ }
2968
4165
  const spinner = ora7(`Depositing ${opts.amount}...`).start();
2969
4166
  try {
2970
- 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
+ }
2971
4174
  spinner.succeed(`Deposited: ${hash}`);
2972
- console.log(chalk7.dim(` ${getExplorerUrl(hash)}`));
4175
+ console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
2973
4176
  } catch (err) {
2974
4177
  spinner.fail("Deposit failed");
2975
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
4178
+ console.error(chalk8.red(formatContractError(err)));
2976
4179
  process.exit(1);
2977
4180
  }
2978
4181
  });
@@ -2983,8 +4186,8 @@ vaultCmd.command("info").description("Display vault state").option("--vault <add
2983
4186
  const info = await getVaultInfo();
2984
4187
  spinner.stop();
2985
4188
  console.log();
2986
- console.log(chalk7.bold("Vault Info"));
2987
- console.log(chalk7.dim("\u2500".repeat(40)));
4189
+ console.log(chalk8.bold("Vault Info"));
4190
+ console.log(chalk8.dim("\u2500".repeat(40)));
2988
4191
  console.log(` Address: ${info.address}`);
2989
4192
  console.log(` Total Assets: ${info.totalAssets}`);
2990
4193
  console.log(` Agent Count: ${info.agentCount}`);
@@ -2993,7 +4196,7 @@ vaultCmd.command("info").description("Display vault state").option("--vault <add
2993
4196
  console.log();
2994
4197
  } catch (err) {
2995
4198
  spinner.fail("Failed to load vault info");
2996
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
4199
+ console.error(chalk8.red(formatContractError(err)));
2997
4200
  process.exit(1);
2998
4201
  }
2999
4202
  });
@@ -3004,22 +4207,22 @@ vaultCmd.command("balance").description("Show LP share balance and asset value")
3004
4207
  const balance = await getBalance(opts.address);
3005
4208
  spinner.stop();
3006
4209
  console.log();
3007
- console.log(chalk7.bold("LP Position"));
3008
- console.log(chalk7.dim("\u2500".repeat(40)));
4210
+ console.log(chalk8.bold("LP Position"));
4211
+ console.log(chalk8.dim("\u2500".repeat(40)));
3009
4212
  console.log(` Shares: ${balance.shares.toString()}`);
3010
4213
  console.log(` Asset Value: ${balance.assetsValue}`);
3011
4214
  console.log(` % of Vault: ${balance.percentOfVault}`);
3012
4215
  console.log();
3013
4216
  } catch (err) {
3014
4217
  spinner.fail("Failed to load balance");
3015
- console.error(chalk7.red(err instanceof Error ? err.message : String(err)));
4218
+ console.error(chalk8.red(formatContractError(err)));
3016
4219
  process.exit(1);
3017
4220
  }
3018
4221
  });
3019
4222
  var strategy = program.command("strategy").description("Strategy templates \u2014 list, clone, propose");
3020
4223
  registerStrategyTemplateCommands(strategy);
3021
4224
  program.command("providers").description("List available DeFi providers").action(async () => {
3022
- const { MessariProvider, NansenProvider } = await import("./research-DZINC4N4.js");
4225
+ const { MessariProvider, NansenProvider } = await import("./research-SJ6D3L2B.js");
3023
4226
  const providers = [new MoonwellProvider(), new UniswapProvider(), new MessariProvider(), new NansenProvider()];
3024
4227
  for (const p of providers) {
3025
4228
  const info = p.info();
@@ -3030,26 +4233,26 @@ ${info.name} (${info.type})`);
3030
4233
  }
3031
4234
  });
3032
4235
  try {
3033
- const { registerChatCommands } = await import("./chat-WKOSUZXO.js");
4236
+ const { registerChatCommands } = await import("./chat-JYAJMP4D.js");
3034
4237
  registerChatCommands(program);
3035
4238
  } catch {
3036
4239
  program.command("chat <name> [action] [actionArgs...]").description("Syndicate chat (XMTP) \u2014 requires @xmtp/cli").action(() => {
3037
- console.error(chalk7.red("XMTP CLI not available."));
3038
- console.error(chalk7.dim("Install with: npm install -g @xmtp/cli"));
3039
- 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"));
3040
4243
  process.exit(1);
3041
4244
  });
3042
4245
  }
3043
- var { registerSessionCommands } = await import("./session-Q2WRYXYF.js");
4246
+ var { registerSessionCommands } = await import("./session-XZNKLK3I.js");
3044
4247
  registerSessionCommands(program);
3045
4248
  registerVeniceCommands(program);
3046
4249
  registerAllowanceCommands(program);
3047
4250
  registerIdentityCommands(program);
3048
4251
  registerProposalCommands(program);
3049
4252
  registerGovernorCommands(program);
3050
- var { registerResearchCommands } = await import("./research-LOSOHKOJ.js");
4253
+ var { registerResearchCommands } = await import("./research-ZW6XVTGZ.js");
3051
4254
  registerResearchCommands(program);
3052
- var { registerTradeCommands } = await import("./trade-JLQPIA5W.js");
4255
+ var { registerTradeCommands } = await import("./trade-OJM5HNHL.js");
3053
4256
  registerTradeCommands(program);
3054
4257
  var configCmd = program.command("config");
3055
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) => {
@@ -3057,42 +4260,42 @@ configCmd.command("set").description("Save settings to ~/.sherwood/config.json (
3057
4260
  if (opts.privateKey) {
3058
4261
  setPrivateKey(opts.privateKey);
3059
4262
  const account = getAccount();
3060
- console.log(chalk7.green("Private key saved to ~/.sherwood/config.json"));
3061
- 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}`));
3062
4265
  saved = true;
3063
4266
  }
3064
4267
  if (opts.vault) {
3065
4268
  const chainId = getChain().id;
3066
4269
  setChainContract(chainId, "vault", opts.vault);
3067
- console.log(chalk7.green(`Vault saved to ~/.sherwood/config.json (chain ${chainId})`));
3068
- 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}`));
3069
4272
  saved = true;
3070
4273
  }
3071
4274
  if (opts.rpc) {
3072
4275
  const network = getNetwork();
3073
4276
  setConfigRpcUrl(network, opts.rpc);
3074
- console.log(chalk7.green(`RPC URL saved for ${network}`));
3075
- 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}`));
3076
4279
  saved = true;
3077
4280
  }
3078
4281
  if (opts.notifyTo) {
3079
4282
  setNotifyTo(opts.notifyTo);
3080
- console.log(chalk7.green("Notify destination saved to ~/.sherwood/config.json"));
3081
- 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}`));
3082
4285
  saved = true;
3083
4286
  }
3084
4287
  if (opts.uniswapApiKey) {
3085
4288
  setUniswapApiKey(opts.uniswapApiKey);
3086
- 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"));
3087
4290
  saved = true;
3088
4291
  }
3089
4292
  if (opts.veniceApiKey) {
3090
4293
  setVeniceApiKey(opts.veniceApiKey);
3091
- 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"));
3092
4295
  saved = true;
3093
4296
  }
3094
4297
  if (!saved) {
3095
- 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"));
3096
4299
  process.exit(1);
3097
4300
  }
3098
4301
  });
@@ -3103,17 +4306,17 @@ configCmd.command("show").description("Display current config for the active net
3103
4306
  const config = loadConfig();
3104
4307
  const customRpc = config.rpc?.[network];
3105
4308
  console.log();
3106
- console.log(chalk7.bold(`Sherwood Config`));
3107
- console.log(chalk7.dim("\u2500".repeat(50)));
3108
- console.log(` Network: ${chalk7.cyan(network)} (chain ${chainId})`);
3109
- console.log(` RPC: ${customRpc ? chalk7.green(customRpc) : chalk7.dim("default")}`);
3110
- console.log(` Wallet: ${config.privateKey ? chalk7.green("configured") : chalk7.dim("not set")}`);
3111
- console.log(` Agent ID: ${config.agentId ?? chalk7.dim("not set")}`);
3112
- console.log(` Vault: ${contracts.vault ?? chalk7.dim("not set")}`);
3113
- console.log(` Uniswap: ${getUniswapApiKey() ? chalk7.green("API key configured") : chalk7.dim("not set")}`);
3114
- 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")}`);
3115
4318
  console.log();
3116
- console.log(chalk7.dim(" Config file: ~/.sherwood/config.json"));
4319
+ console.log(chalk8.dim(" Config file: ~/.sherwood/config.json"));
3117
4320
  console.log();
3118
4321
  });
3119
4322
  program.parse();