@sherwoodagent/cli 0.86.1 → 0.87.1

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 (71) hide show
  1. package/dist/{abis-WVW2EBI3.js → abis-WJQ6IVYV.js} +2 -2
  2. package/dist/{agent-TBLC3WNJ.js → agent-FCGZL4GF.js} +7 -7
  3. package/dist/{chat-ETZSKQDI.js → chat-AKLYUJZV.js} +8 -8
  4. package/dist/{chunk-ZN2MHIB7.js → chunk-4ITTHKRX.js} +5 -5
  5. package/dist/{chunk-EVUKJ7CJ.js → chunk-6D3FNU75.js} +5 -5
  6. package/dist/{chunk-45FELUWY.js → chunk-6SAE2VDK.js} +2 -2
  7. package/dist/{chunk-EA65CDQH.js → chunk-BH3VTJ4F.js} +14 -23
  8. package/dist/chunk-BH3VTJ4F.js.map +1 -0
  9. package/dist/{chunk-QRMZRAMG.js → chunk-D4BSBW3Q.js} +221 -10
  10. package/dist/chunk-D4BSBW3Q.js.map +1 -0
  11. package/dist/{chunk-KVB5P2OE.js → chunk-EEI6FRTE.js} +2 -2
  12. package/dist/{chunk-IAM4VIU2.js → chunk-FWQESFFM.js} +18 -41
  13. package/dist/chunk-FWQESFFM.js.map +1 -0
  14. package/dist/{chunk-PVTZMRC6.js → chunk-G3FUEM5N.js} +2 -2
  15. package/dist/chunk-G3FUEM5N.js.map +1 -0
  16. package/dist/{chunk-YAS52NEF.js → chunk-G5MXVAFC.js} +18 -5
  17. package/dist/chunk-G5MXVAFC.js.map +1 -0
  18. package/dist/{chunk-F3MDCUGO.js → chunk-J2IZWAEW.js} +75 -227
  19. package/dist/chunk-J2IZWAEW.js.map +1 -0
  20. package/dist/{chunk-XPVXNG2K.js → chunk-N2E2Q5GL.js} +6 -6
  21. package/dist/{chunk-3IAZQMAW.js → chunk-QFGZQYZA.js} +29 -82
  22. package/dist/chunk-QFGZQYZA.js.map +1 -0
  23. package/dist/{chunk-OUYMZ5ZC.js → chunk-SI65QHTY.js} +5 -5
  24. package/dist/{chunk-6QBGTX2Q.js → chunk-VYCLG5DE.js} +264 -487
  25. package/dist/chunk-VYCLG5DE.js.map +1 -0
  26. package/dist/{client-3SE632Q2.js → client-PYFE6YQR.js} +3 -3
  27. package/dist/{eas-KMDAMLXW.js → eas-DV3Z7AT3.js} +6 -6
  28. package/dist/{governor-JNSWPJVQ.js → governor-QFBN6RYS.js} +8 -6
  29. package/dist/identity-M4TXM7EX.js +18 -0
  30. package/dist/index.js +352 -355
  31. package/dist/index.js.map +1 -1
  32. package/dist/{network-FEHRFDPJ.js → network-7445KSCF.js} +2 -2
  33. package/dist/{price-YNPYE2EO.js → price-EXZDYVDD.js} +6 -6
  34. package/dist/{providers-BMOHHVUN.js → providers-6UZKDFQD.js} +4 -4
  35. package/dist/{research-W74PNYPO.js → research-44GHYE4H.js} +7 -7
  36. package/dist/{session-HVRZ6AJM.js → session-45NQAHFI.js} +14 -12
  37. package/dist/session-45NQAHFI.js.map +1 -0
  38. package/dist/{trade-EOQY72L7.js → trade-FO52SP62.js} +15 -15
  39. package/dist/{vault-HWJQJW5U.js → vault-WWGSRNRP.js} +8 -8
  40. package/dist/{xmtp-KW73BGVO.js → xmtp-72IRK7JL.js} +6 -6
  41. package/package.json +1 -1
  42. package/dist/chunk-3IAZQMAW.js.map +0 -1
  43. package/dist/chunk-6QBGTX2Q.js.map +0 -1
  44. package/dist/chunk-EA65CDQH.js.map +0 -1
  45. package/dist/chunk-F3MDCUGO.js.map +0 -1
  46. package/dist/chunk-IAM4VIU2.js.map +0 -1
  47. package/dist/chunk-PVTZMRC6.js.map +0 -1
  48. package/dist/chunk-QRMZRAMG.js.map +0 -1
  49. package/dist/chunk-YAS52NEF.js.map +0 -1
  50. package/dist/identity-ATWV2O7Y.js +0 -15
  51. package/dist/session-HVRZ6AJM.js.map +0 -1
  52. /package/dist/{abis-WVW2EBI3.js.map → abis-WJQ6IVYV.js.map} +0 -0
  53. /package/dist/{agent-TBLC3WNJ.js.map → agent-FCGZL4GF.js.map} +0 -0
  54. /package/dist/{chat-ETZSKQDI.js.map → chat-AKLYUJZV.js.map} +0 -0
  55. /package/dist/{chunk-ZN2MHIB7.js.map → chunk-4ITTHKRX.js.map} +0 -0
  56. /package/dist/{chunk-EVUKJ7CJ.js.map → chunk-6D3FNU75.js.map} +0 -0
  57. /package/dist/{chunk-45FELUWY.js.map → chunk-6SAE2VDK.js.map} +0 -0
  58. /package/dist/{chunk-KVB5P2OE.js.map → chunk-EEI6FRTE.js.map} +0 -0
  59. /package/dist/{chunk-XPVXNG2K.js.map → chunk-N2E2Q5GL.js.map} +0 -0
  60. /package/dist/{chunk-OUYMZ5ZC.js.map → chunk-SI65QHTY.js.map} +0 -0
  61. /package/dist/{client-3SE632Q2.js.map → client-PYFE6YQR.js.map} +0 -0
  62. /package/dist/{eas-KMDAMLXW.js.map → eas-DV3Z7AT3.js.map} +0 -0
  63. /package/dist/{governor-JNSWPJVQ.js.map → governor-QFBN6RYS.js.map} +0 -0
  64. /package/dist/{identity-ATWV2O7Y.js.map → identity-M4TXM7EX.js.map} +0 -0
  65. /package/dist/{network-FEHRFDPJ.js.map → network-7445KSCF.js.map} +0 -0
  66. /package/dist/{price-YNPYE2EO.js.map → price-EXZDYVDD.js.map} +0 -0
  67. /package/dist/{providers-BMOHHVUN.js.map → providers-6UZKDFQD.js.map} +0 -0
  68. /package/dist/{research-W74PNYPO.js.map → research-44GHYE4H.js.map} +0 -0
  69. /package/dist/{trade-EOQY72L7.js.map → trade-FO52SP62.js.map} +0 -0
  70. /package/dist/{vault-HWJQJW5U.js.map → vault-WWGSRNRP.js.map} +0 -0
  71. /package/dist/{xmtp-KW73BGVO.js.map → xmtp-72IRK7JL.js.map} +0 -0
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  checkApiKeyValid,
16
16
  listModels,
17
17
  provisionApiKey
18
- } from "./chunk-KVB5P2OE.js";
18
+ } from "./chunk-EEI6FRTE.js";
19
19
  import {
20
20
  DEFAULT_FEE_TIER,
21
21
  HYPERLIQUID_PERP_STRATEGY_ABI,
@@ -31,10 +31,13 @@ import {
31
31
  parseAlignedOverrides,
32
32
  resolveToken,
33
33
  selectBestFeeTier
34
- } from "./chunk-3IAZQMAW.js";
34
+ } from "./chunk-QFGZQYZA.js";
35
35
  import {
36
36
  DEFAULT_ISSUING_CHAIN_ID,
37
+ STRATEGY_FACTORY_ABI,
37
38
  appendVidTag,
39
+ deriveStrategySalt,
40
+ effectiveStrategySalt,
38
41
  emitCalldata,
39
42
  encodeApproveAgent,
40
43
  encodeApproveDepositor,
@@ -50,7 +53,6 @@ import {
50
53
  encodeMgmtSplit,
51
54
  encodePerfSplit,
52
55
  encodePropose,
53
- encodeProposeWithSandbox,
54
56
  encodeRedeem,
55
57
  encodeRegisterAgent,
56
58
  encodeSettle,
@@ -61,11 +63,12 @@ import {
61
63
  isCalldataOnly,
62
64
  makeOwnerOfCache,
63
65
  parseVidTag,
66
+ predictStrategyCloneAddress,
64
67
  registerIdentityCommands,
65
68
  setCalldataOnly,
66
69
  stripVidTag,
67
70
  verifyVidTag
68
- } from "./chunk-6QBGTX2Q.js";
71
+ } from "./chunk-VYCLG5DE.js";
69
72
  import {
70
73
  createApproval,
71
74
  createJoinRequest,
@@ -75,20 +78,20 @@ import {
75
78
  queryJoinRequests,
76
79
  revokeJoinRequest,
77
80
  stripReferrerPrefix
78
- } from "./chunk-OUYMZ5ZC.js";
81
+ } from "./chunk-SI65QHTY.js";
79
82
  import {
80
83
  createFund,
81
84
  getActiveFunds,
82
85
  getFund,
83
86
  subdomainExists,
84
87
  updateMetadata
85
- } from "./chunk-EVUKJ7CJ.js";
88
+ } from "./chunk-6D3FNU75.js";
86
89
  import {
87
90
  resolveFund,
88
91
  resolveVaultFund,
89
92
  searchFundAcrossChains,
90
93
  setTextRecord
91
- } from "./chunk-ZN2MHIB7.js";
94
+ } from "./chunk-4ITTHKRX.js";
92
95
  import {
93
96
  approveDepositor,
94
97
  deposit,
@@ -111,8 +114,8 @@ import {
111
114
  removeDepositor,
112
115
  setAgentFeeBps,
113
116
  setVaultAddress
114
- } from "./chunk-XPVXNG2K.js";
115
- import "./chunk-EA65CDQH.js";
117
+ } from "./chunk-N2E2Q5GL.js";
118
+ import "./chunk-BH3VTJ4F.js";
116
119
  import {
117
120
  PROPOSAL_STATE,
118
121
  PROPOSAL_STATES,
@@ -137,9 +140,9 @@ import {
137
140
  getVoteWeight,
138
141
  hasVoted,
139
142
  parseDuration,
140
- parseProposalIdFromLogs,
141
143
  proposalCount,
142
144
  propose,
145
+ reclaimProposerBond,
143
146
  scanReviewCandidates,
144
147
  setCooldownPeriod,
145
148
  setExecutionWindow,
@@ -151,7 +154,7 @@ import {
151
154
  setVotingPeriod,
152
155
  settleProposal,
153
156
  vote
154
- } from "./chunk-YAS52NEF.js";
157
+ } from "./chunk-G5MXVAFC.js";
155
158
  import {
156
159
  AERODROME,
157
160
  MOONWELL,
@@ -160,7 +163,7 @@ import {
160
163
  TOKENS,
161
164
  UNISWAP,
162
165
  VENICE
163
- } from "./chunk-IAM4VIU2.js";
166
+ } from "./chunk-FWQESFFM.js";
164
167
  import {
165
168
  GAS_UNITS,
166
169
  estimateGasCushion,
@@ -171,7 +174,7 @@ import {
171
174
  sendTxWithRetry,
172
175
  waitForReceipt,
173
176
  writeContractWithRetry
174
- } from "./chunk-QRMZRAMG.js";
177
+ } from "./chunk-D4BSBW3Q.js";
175
178
  import {
176
179
  DEFAULT_NETWORK,
177
180
  VALID_NETWORKS,
@@ -181,7 +184,7 @@ import {
181
184
  isTestnet,
182
185
  setNetwork,
183
186
  withNetwork
184
- } from "./chunk-PVTZMRC6.js";
187
+ } from "./chunk-G3FUEM5N.js";
185
188
  import {
186
189
  addFund,
187
190
  cacheGroupId,
@@ -212,9 +215,10 @@ import {
212
215
  PORTFOLIO_STRATEGY_ABI,
213
216
  SWAP_ROUTER_ABI,
214
217
  SWAP_ROUTER_EXACT_INPUT_SINGLE_ABI,
218
+ SYNDICATE_GOVERNOR_ABI,
215
219
  SYNDICATE_VAULT_ABI,
216
220
  VENICE_STAKING_ABI
217
- } from "./chunk-F3MDCUGO.js";
221
+ } from "./chunk-J2IZWAEW.js";
218
222
  import {
219
223
  fetchMetadata,
220
224
  mergeFundMetadata,
@@ -225,13 +229,13 @@ import {
225
229
  import { config as loadDotenv } from "dotenv";
226
230
  import { createRequire } from "module";
227
231
  import { Command, Option } from "commander";
228
- import { parseUnits as parseUnits11, formatUnits as formatUnits9, isAddress as isAddress10 } from "viem";
232
+ import { parseUnits as parseUnits11, formatUnits as formatUnits10, isAddress as isAddress10 } from "viem";
229
233
  import chalk17 from "chalk";
230
234
  import ora9 from "ora";
231
235
  import { input, confirm, select } from "@inquirer/prompts";
232
236
 
233
237
  // src/lib/generated/contract-constants.ts
234
- var MAX_AGENT_FEE_BPS = 3e3;
238
+ var MAX_AGENT_FEE_BPS = 2500;
235
239
 
236
240
  // src/lib/format.ts
237
241
  function formatDurationShort(seconds) {
@@ -291,6 +295,126 @@ async function cloneTemplate(template) {
291
295
  return { clone: receipt.contractAddress, hash };
292
296
  }
293
297
 
298
+ // src/lib/strategy-deploy.ts
299
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
300
+ var STRATEGY_FACTORY_EXTRA_ABI = [
301
+ {
302
+ name: "registerStrategy",
303
+ type: "function",
304
+ stateMutability: "nonpayable",
305
+ inputs: [{ name: "strategy", type: "address" }],
306
+ outputs: []
307
+ },
308
+ {
309
+ name: "isRegisteredStrategy",
310
+ type: "function",
311
+ stateMutability: "view",
312
+ inputs: [{ name: "strategy", type: "address" }],
313
+ outputs: [{ name: "", type: "bool" }]
314
+ },
315
+ {
316
+ name: "approvedTemplate",
317
+ type: "function",
318
+ stateMutability: "view",
319
+ inputs: [{ name: "template", type: "address" }],
320
+ outputs: [{ name: "", type: "bool" }]
321
+ }
322
+ ];
323
+ function planStrategyCloneDeploy(args) {
324
+ const factory = args.factory ?? SHERWOOD().STRATEGY_FACTORY;
325
+ if (!factory || factory === ZERO_ADDRESS) {
326
+ return {
327
+ mode: "raw",
328
+ reason: "no STRATEGY_FACTORY in this network's address book \u2014 falling back to a raw ERC-1167 clone. A governor that gates propose on strategy registration will refuse the resulting proposal."
329
+ };
330
+ }
331
+ const salt = args.salt ?? deriveStrategySalt(args.vault, args.proposer, args.template, args.initData);
332
+ const clone = predictStrategyCloneAddress(
333
+ factory,
334
+ args.template,
335
+ effectiveStrategySalt(args.vault, salt)
336
+ );
337
+ return {
338
+ mode: "factory",
339
+ factory,
340
+ clone,
341
+ salt,
342
+ to: factory,
343
+ functionName: "cloneAndInitDeterministic",
344
+ args: [args.template, args.vault, args.proposer, args.initData, salt]
345
+ };
346
+ }
347
+ async function deployStrategyClone(args) {
348
+ const plan = planStrategyCloneDeploy(args);
349
+ const publicClient = getPublicClient();
350
+ const account = getAccount();
351
+ if (plan.mode === "factory") {
352
+ const approved = await publicClient.readContract({
353
+ address: plan.factory,
354
+ abi: STRATEGY_FACTORY_EXTRA_ABI,
355
+ functionName: "approvedTemplate",
356
+ args: [args.template]
357
+ });
358
+ if (!approved) {
359
+ throw new Error(
360
+ `Template ${args.template} is not on StrategyFactory ${plan.factory}'s allowlist \u2014 cloneAndInitDeterministic would revert TemplateNotApproved. The factory owner must call setTemplateApproval(${args.template}, true).`
361
+ );
362
+ }
363
+ const existing = await publicClient.getCode({ address: plan.clone });
364
+ if (existing && existing !== "0x") {
365
+ throw new Error(
366
+ `A strategy clone already exists at the deterministic address ${plan.clone} (same vault + template + init params as an earlier deploy). Re-run with a different --salt to deploy a fresh clone.`
367
+ );
368
+ }
369
+ const hash2 = await writeContractWithRetry({
370
+ account,
371
+ chain: getChain(),
372
+ address: plan.factory,
373
+ abi: STRATEGY_FACTORY_ABI,
374
+ functionName: plan.functionName,
375
+ args: plan.args
376
+ });
377
+ const receipt = await waitForReceipt(hash2);
378
+ if (receipt.status === "reverted") {
379
+ throw new Error(`StrategyFactory.cloneAndInitDeterministic reverted on-chain (tx: ${hash2})`);
380
+ }
381
+ const deployed = await publicClient.getCode({ address: plan.clone });
382
+ if (!deployed || deployed === "0x") {
383
+ throw new Error(
384
+ `The factory deployed a clone, but not at the predicted address ${plan.clone} (tx: ${hash2}). The CLI's CREATE2 prediction and the factory's salt fold have diverged \u2014 do not propose against this address.`
385
+ );
386
+ }
387
+ return { clone: plan.clone, hash: hash2, viaFactory: true, salt: plan.salt };
388
+ }
389
+ const { clone, hash } = await cloneTemplate(args.template);
390
+ const initHash = await writeContractWithRetry({
391
+ account,
392
+ chain: getChain(),
393
+ address: clone,
394
+ abi: BASE_STRATEGY_ABI,
395
+ functionName: "initialize",
396
+ args: [args.vault, args.proposer, args.initData]
397
+ });
398
+ const initReceipt = await waitForReceipt(initHash);
399
+ if (initReceipt.status === "reverted") {
400
+ throw new Error(`Strategy initialize reverted on-chain (tx: ${initHash})`);
401
+ }
402
+ let registerHash;
403
+ const factory = args.factory ?? SHERWOOD().STRATEGY_FACTORY;
404
+ if (factory && factory !== ZERO_ADDRESS) {
405
+ registerHash = await writeContractWithRetry({
406
+ account,
407
+ chain: getChain(),
408
+ address: factory,
409
+ abi: STRATEGY_FACTORY_EXTRA_ABI,
410
+ functionName: "registerStrategy",
411
+ args: [clone]
412
+ });
413
+ await waitForReceipt(registerHash);
414
+ }
415
+ return { clone, hash, viaFactory: false, initHash, registerHash };
416
+ }
417
+
294
418
  // src/grid/strategy-abi.ts
295
419
  var HYPERLIQUID_GRID_STRATEGY_ABI = [
296
420
  {
@@ -610,6 +734,20 @@ var TEMPLATES = [
610
734
  key: "hyperliquid-grid",
611
735
  description: "ATR-based grid trading on Hyperliquid via HyperEVM precompiles",
612
736
  addressKey: "HYPERLIQUID_GRID"
737
+ },
738
+ {
739
+ name: "Morpho Supply",
740
+ key: "morpho-supply",
741
+ description: "Supply the vault asset to a Morpho market (init encoder not wired in the CLI yet)",
742
+ addressKey: "MORPHO_SUPPLY",
743
+ encoder: false
744
+ },
745
+ {
746
+ name: "Concentrated Liquidity",
747
+ key: "concentrated-liquidity",
748
+ description: "Uniswap-v3-style concentrated LP range (init encoder not wired in the CLI yet)",
749
+ addressKey: "CONCENTRATED_LIQUIDITY",
750
+ encoder: false
613
751
  }
614
752
  ];
615
753
  function resolveTemplate(key) {
@@ -624,6 +762,19 @@ function resolveTemplate(key) {
624
762
  console.error(chalk2.red(`Template "${def.name}" not deployed on this network.`));
625
763
  process.exit(1);
626
764
  }
765
+ if (def.encoder === false) {
766
+ console.error(
767
+ chalk2.red(
768
+ `Template "${def.name}" is deployed at ${address} but the CLI cannot encode its initialize() tuple yet.`
769
+ )
770
+ );
771
+ console.error(
772
+ chalk2.yellow(
773
+ " Deploy it via StrategyFactory.cloneAndInitDeterministic with hand-encoded initData, then reference the clone from `sherwood proposal create`."
774
+ )
775
+ );
776
+ process.exit(1);
777
+ }
627
778
  return { def, address };
628
779
  }
629
780
  async function resolvePortfolioFeeTiers(opts, deps) {
@@ -892,10 +1043,10 @@ function formatBatch(calls) {
892
1043
 
893
1044
  // src/commands/strategy-propose.ts
894
1045
  function registerStrategyProposeCommands(strategy2) {
895
- 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, portfolio, hyperliquid-perp, hyperliquid-grid").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("--duration <duration>", "Strategy duration (7d, 24h, etc.)").option("--proposer <address>", "Proposer/agent address (required for --calldata-only \u2014 the external signer)").option("--metadata-uri <uri>", "Metadata URI (required for --calldata-only \u2014 IPFS pin needs a signer)").option("--salt <bytes32>", "CREATE2 salt for the deterministic clone (--calldata-only; default: derived from inputs)").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)").option("--tokens <list>", "Comma-separated token addresses or symbols (Portfolio)").option("--weights <list>", "Comma-separated weights in bps, must sum to 10000 (Portfolio)").option("--max-slippage <bps>", "Max slippage bps (Portfolio, default: 500)").option("--fee-tier <n>", "Pool fee tier (Portfolio, default: 3000)").option("--swap-adapter <address>", "Swap adapter address (Portfolio)").option("--feed-ids <list>", "Comma-separated Chainlink feed ids/addresses per token (Portfolio; auto-resolved from registry in push-feed mode)").option("--price-decimals <list>", "Comma-separated raw feed decimals per token (Portfolio; default 8 push / 18 Data Streams)").option("--swap-routes <list>", "Comma-separated per-token swap route spec (Portfolio). Per token: 'v3:<fee>' (mode 0), 'v4:<fee>:<tickSpacing>' (mode 2), or 'v4path:<hop>+<hop>...' (mode 3) where hop='<currency>@<fee>:<tickSpacing>', currency='eth'/'native'=ETH or 'self'=this token or an address. Empty entry = auto-detect / robinhood default.").option(
1046
+ 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, portfolio, hyperliquid-perp, hyperliquid-grid").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("--duration <duration>", "Strategy duration (7d, 24h, etc.)").option("--proposer <address>", "Proposer/agent address (required for --calldata-only \u2014 the external signer)").option("--metadata-uri <uri>", "Metadata URI (required for --calldata-only \u2014 IPFS pin needs a signer)").option("--salt <bytes32>", "CREATE2 salt for the deterministic clone (default: derived from vault+proposer+template+initData). Pass a fresh one to re-deploy identical params.").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)").option("--tokens <list>", "Comma-separated token addresses or symbols (Portfolio)").option("--weights <list>", "Comma-separated weights in bps, must sum to 10000 (Portfolio)").option("--max-slippage <bps>", "Max slippage bps (Portfolio, default: 500)").option("--fee-tier <n>", "Pool fee tier (Portfolio, default: 3000)").option("--swap-adapter <address>", "Swap adapter address (Portfolio)").option("--feeds <list>", "Comma-separated Chainlink AggregatorV3 push-feed addresses per token (Portfolio; auto-resolved from the registry when a slot is left empty)").option("--price-decimals <list>", "Comma-separated raw feed decimals per token (Portfolio; default 8). Must equal the feed's live decimals().").option("--swap-routes <list>", "Comma-separated per-token swap route spec (Portfolio). Per token: 'v3:<fee>' (mode 0), 'v4:<fee>:<tickSpacing>' (mode 2), or 'v4path:<hop>+<hop>...' (mode 3) where hop='<currency>@<fee>:<tickSpacing>', currency='eth'/'native'=ETH or 'self'=this token or an address. Empty entry = auto-detect / robinhood default.").option(
896
1047
  "--max-drawdown-bps <bps>",
897
1048
  "Declared loss allowance for this proposal, in bps of --amount (0-10000, default 2000 = 20%). This is the envelope voters and guardians are asked to accept, and the basis the settle-price guard sizes its tolerance from \u2014 not a formality."
898
- ).option("--max-price-ages <list>", "Comma-separated per-token max Chainlink push-feed staleness in seconds (Portfolio, push mode; robinhood-mainnet equity feeds default to 345600 = 96h, crypto/stable feeds to the contract default). Empty entry = registry default.").option("--leverage <number>", "Leverage multiplier (Hyperliquid Perp, default: 10)").option("--asset-index <number>", "Perp asset index (Hyperliquid Perp, default: 0 for BTC)").option("--max-position <amount>", "Max position size in USD (Hyperliquid Perp, default: 100000)").option("--max-trades-per-day <n>", "Max trades per day (Hyperliquid Perp, default: 50)").option("--max-order-size <amount>", "Max single-order size in asset units (Hyperliquid Grid, default: 10000)").option("--max-orders-per-tick <n>", "Max orders per tick (Hyperliquid Grid, default: 20)").option("--asset-indices <list>", "Comma-separated perp asset indices (Hyperliquid Grid, default: 0)").action(async (templateKey, opts) => {
1049
+ ).option("--leverage <number>", "Leverage multiplier (Hyperliquid Perp, default: 10)").option("--asset-index <number>", "Perp asset index (Hyperliquid Perp, default: 0 for BTC)").option("--max-position <amount>", "Max position size in USD (Hyperliquid Perp, default: 100000)").option("--max-trades-per-day <n>", "Max trades per day (Hyperliquid Perp, default: 50)").option("--max-order-size <amount>", "Max single-order size in asset units (Hyperliquid Grid, default: 10000)").option("--max-orders-per-tick <n>", "Max orders per tick (Hyperliquid Grid, default: 20)").option("--asset-indices <list>", "Comma-separated perp asset indices (Hyperliquid Grid, default: 0)").action(async (templateKey, opts) => {
899
1050
  const vault = opts.vault;
900
1051
  if (!isAddress3(vault)) {
901
1052
  console.error(chalk3.red("Invalid vault address"));
@@ -979,7 +1130,7 @@ function registerStrategyProposeCommands(strategy2) {
979
1130
  process.exit(1);
980
1131
  }
981
1132
  if (!opts.agent) opts.agent = proposer;
982
- const { parseDuration: parseDuration3, getGovernorOf: getGovernorOf2 } = await import("./governor-JNSWPJVQ.js");
1133
+ const { parseDuration: parseDuration3, getGovernorOf: getGovernorOf2 } = await import("./governor-QFBN6RYS.js");
983
1134
  const strategyDuration2 = parseDuration3(opts.duration);
984
1135
  const chainId = getChain().id;
985
1136
  try {
@@ -1102,42 +1253,40 @@ function registerStrategyProposeCommands(strategy2) {
1102
1253
  console.error(chalk3.red(formatContractError(err)));
1103
1254
  process.exit(1);
1104
1255
  }
1105
- const cloneSpinner = ora2(`Cloning ${def.name} template...`).start();
1106
1256
  let clone;
1107
- try {
1108
- const result = await cloneTemplate(templateAddr);
1109
- clone = result.clone;
1110
- cloneSpinner.succeed(`Cloned: ${chalk3.green(clone)}`);
1111
- console.log(chalk3.dim(` Tx: ${getExplorerUrl(result.hash)}`));
1112
- } catch (err) {
1113
- cloneSpinner.fail("Clone failed");
1114
- console.error(chalk3.red(formatContractError(err)));
1115
- process.exit(1);
1116
- }
1117
- const initSpinner = ora2("Initializing strategy...").start();
1118
1257
  let asset;
1119
1258
  let assetAmount;
1120
1259
  let extraApprovals;
1260
+ const deploySpinner = ora2(`Deploying ${def.name} strategy...`).start();
1121
1261
  try {
1122
1262
  const built = await resolveAndBuildInitData(templateKey, opts, vault);
1123
1263
  asset = built.asset;
1124
1264
  assetAmount = built.assetAmount;
1125
1265
  extraApprovals = built.extraApprovals;
1126
- const initHash = await writeContractWithRetry({
1127
- account,
1128
- chain: getChain(),
1129
- address: clone,
1130
- abi: BASE_STRATEGY_ABI,
1131
- functionName: "initialize",
1132
- args: [vault, account.address, built.initData]
1266
+ const deployed = await deployStrategyClone({
1267
+ template: templateAddr,
1268
+ vault,
1269
+ proposer: account.address,
1270
+ initData: built.initData,
1271
+ salt: opts.salt
1133
1272
  });
1134
- const initReceipt = await waitForReceipt(initHash);
1135
- if (initReceipt.status === "reverted") {
1136
- throw new Error("Initialize transaction reverted on-chain");
1273
+ clone = deployed.clone;
1274
+ deploySpinner.succeed(
1275
+ deployed.viaFactory ? `Deployed + registered: ${chalk3.green(clone)}` : `Cloned + initialized: ${chalk3.green(clone)}`
1276
+ );
1277
+ console.log(chalk3.dim(` Tx: ${getExplorerUrl(deployed.hash)}`));
1278
+ if (deployed.initHash) console.log(chalk3.dim(` Init tx: ${getExplorerUrl(deployed.initHash)}`));
1279
+ if (deployed.registerHash) console.log(chalk3.dim(` Register tx: ${getExplorerUrl(deployed.registerHash)}`));
1280
+ if (deployed.salt) console.log(chalk3.dim(` Salt: ${deployed.salt}`));
1281
+ if (!deployed.viaFactory) {
1282
+ console.log(
1283
+ chalk3.yellow(
1284
+ " No StrategyFactory on this network \u2014 the clone was deployed raw. A governor that gates propose on strategy registration will refuse it."
1285
+ )
1286
+ );
1137
1287
  }
1138
- initSpinner.succeed("Initialized");
1139
1288
  } catch (err) {
1140
- initSpinner.fail("Initialize failed");
1289
+ deploySpinner.fail("Strategy deploy failed");
1141
1290
  console.error(chalk3.red(formatContractError(err)));
1142
1291
  process.exit(1);
1143
1292
  }
@@ -1210,9 +1359,9 @@ function registerStrategyProposeCommands(strategy2) {
1210
1359
  console.error(chalk3.red("Missing --name or --duration. Use --write-calls to skip proposal submission."));
1211
1360
  process.exit(1);
1212
1361
  }
1213
- const { propose: propose2 } = await import("./governor-JNSWPJVQ.js");
1362
+ const { propose: propose2 } = await import("./governor-QFBN6RYS.js");
1214
1363
  const { pinJSON } = await import("./ipfs-M56BLMCW.js");
1215
- const { parseDuration: parseDuration2 } = await import("./governor-JNSWPJVQ.js");
1364
+ const { parseDuration: parseDuration2 } = await import("./governor-QFBN6RYS.js");
1216
1365
  const strategyDuration = parseDuration2(opts.duration);
1217
1366
  const metaSpinner = ora2("Pinning metadata to IPFS...").start();
1218
1367
  let metadataURI;
@@ -1244,7 +1393,7 @@ function registerStrategyProposeCommands(strategy2) {
1244
1393
  metadataURI = `data:application/json;base64,${Buffer.from(inline).toString("base64")}`;
1245
1394
  }
1246
1395
  try {
1247
- const { getAgentFeeBps: getAgentFeeBps2 } = await import("./vault-HWJQJW5U.js");
1396
+ const { getAgentFeeBps: getAgentFeeBps2 } = await import("./vault-WWGSRNRP.js");
1248
1397
  const feeBps = await getAgentFeeBps2(vault);
1249
1398
  console.log(
1250
1399
  chalk3.yellow(
@@ -1315,6 +1464,11 @@ function registerStrategyTemplateCommands(strategy2) {
1315
1464
  console.log(` ${chalk4.bold(t.name)} (${chalk4.cyan(t.key)})`);
1316
1465
  console.log(` ${t.description}`);
1317
1466
  console.log(` Template: ${chalk4.green(addr)}`);
1467
+ if (t.encoder === false) {
1468
+ console.log(
1469
+ chalk4.yellow(" No CLI init encoder yet \u2014 propose it via hand-encoded initData.")
1470
+ );
1471
+ }
1318
1472
  }
1319
1473
  }
1320
1474
  if (availableCount === 0) {
@@ -1334,49 +1488,45 @@ function registerStrategyTemplateCommands(strategy2) {
1334
1488
  console.log(chalk4.dim("Full proposal: sherwood strategy propose <template> --vault <addr> ..."));
1335
1489
  console.log();
1336
1490
  });
1337
- strategy2.command("clone").description("Clone a strategy template and initialize it").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell, mamo-yield, portfolio, hyperliquid-perp, hyperliquid-grid").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)").option("--tokens <list>", "Comma-separated token addresses or symbols (Portfolio)").option("--weights <list>", "Comma-separated weights in bps, must sum to 10000 (Portfolio)").option("--max-slippage <bps>", "Max slippage bps (Portfolio, default: 500)").option("--fee-tier <n>", "Pool fee tier (Portfolio, default: 3000)").option("--swap-adapter <address>", "Swap adapter address (Portfolio)").option("--feed-ids <list>", "Comma-separated Chainlink feed ids/addresses per token (Portfolio; auto-resolved from registry in push-feed mode)").option("--price-decimals <list>", "Comma-separated raw feed decimals per token (Portfolio; default 8 push / 18 Data Streams)").option("--swap-routes <list>", "Comma-separated per-token swap route spec (Portfolio). Per token: 'v3:<fee>' (mode 0), 'v4:<fee>:<tickSpacing>' (mode 2), or 'v4path:<hop>+<hop>...' (mode 3) where hop='<currency>@<fee>:<tickSpacing>', currency='eth'/'native'=ETH or 'self'=this token or an address. Empty entry = auto-detect / robinhood default.").option(
1491
+ strategy2.command("clone").description("Clone a strategy template and initialize it").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell, mamo-yield, portfolio, hyperliquid-perp, hyperliquid-grid").requiredOption("--vault <address>", "Vault address").option("--salt <bytes32>", "CREATE2 salt for the deterministic clone (default: derived from vault+proposer+template+initData). Pass a fresh one to re-deploy identical params.").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)").option("--tokens <list>", "Comma-separated token addresses or symbols (Portfolio)").option("--weights <list>", "Comma-separated weights in bps, must sum to 10000 (Portfolio)").option("--max-slippage <bps>", "Max slippage bps (Portfolio, default: 500)").option("--fee-tier <n>", "Pool fee tier (Portfolio, default: 3000)").option("--swap-adapter <address>", "Swap adapter address (Portfolio)").option("--feeds <list>", "Comma-separated Chainlink AggregatorV3 push-feed addresses per token (Portfolio; auto-resolved from the registry when a slot is left empty)").option("--price-decimals <list>", "Comma-separated raw feed decimals per token (Portfolio; default 8). Must equal the feed's live decimals().").option("--swap-routes <list>", "Comma-separated per-token swap route spec (Portfolio). Per token: 'v3:<fee>' (mode 0), 'v4:<fee>:<tickSpacing>' (mode 2), or 'v4path:<hop>+<hop>...' (mode 3) where hop='<currency>@<fee>:<tickSpacing>', currency='eth'/'native'=ETH or 'self'=this token or an address. Empty entry = auto-detect / robinhood default.").option(
1338
1492
  "--max-drawdown-bps <bps>",
1339
1493
  "Declared loss allowance for this proposal, in bps of --amount (0-10000, default 2000 = 20%). This is the envelope voters and guardians are asked to accept, and the basis the settle-price guard sizes its tolerance from \u2014 not a formality."
1340
- ).option("--max-price-ages <list>", "Comma-separated per-token max Chainlink push-feed staleness in seconds (Portfolio, push mode; robinhood-mainnet equity feeds default to 345600 = 96h, crypto/stable feeds to the contract default). Empty entry = registry default.").option("--leverage <number>", "Leverage multiplier (Hyperliquid Perp, default: 10)").option("--asset-index <number>", "Perp asset index (Hyperliquid Perp, default: 0 for BTC)").option("--max-position <amount>", "Max position size in USD (Hyperliquid Perp, default: 100000)").option("--max-trades-per-day <n>", "Max trades per day (Hyperliquid Perp, default: 50)").option("--max-order-size <amount>", "Max single-order size in asset units (Hyperliquid Grid, default: 10000)").option("--max-orders-per-tick <n>", "Max orders per tick (Hyperliquid Grid, default: 20)").option("--asset-indices <list>", "Comma-separated perp asset indices (Hyperliquid Grid, default: 0)").action(async (templateKey, opts) => {
1494
+ ).option("--leverage <number>", "Leverage multiplier (Hyperliquid Perp, default: 10)").option("--asset-index <number>", "Perp asset index (Hyperliquid Perp, default: 0 for BTC)").option("--max-position <amount>", "Max position size in USD (Hyperliquid Perp, default: 100000)").option("--max-trades-per-day <n>", "Max trades per day (Hyperliquid Perp, default: 50)").option("--max-order-size <amount>", "Max single-order size in asset units (Hyperliquid Grid, default: 10000)").option("--max-orders-per-tick <n>", "Max orders per tick (Hyperliquid Grid, default: 20)").option("--asset-indices <list>", "Comma-separated perp asset indices (Hyperliquid Grid, default: 0)").action(async (templateKey, opts) => {
1341
1495
  const vault = opts.vault;
1342
1496
  if (!isAddress4(vault)) {
1343
1497
  console.error(chalk4.red("Invalid vault address"));
1344
1498
  process.exit(1);
1345
1499
  }
1346
1500
  const { def, address: templateAddr } = resolveTemplate(templateKey);
1347
- const cloneSpinner = ora3(`Cloning ${def.name} template...`).start();
1348
1501
  let clone;
1349
- let cloneHash;
1350
- try {
1351
- const result = await cloneTemplate(templateAddr);
1352
- clone = result.clone;
1353
- cloneHash = result.hash;
1354
- cloneSpinner.succeed(`Cloned: ${chalk4.green(clone)}`);
1355
- console.log(chalk4.dim(` Tx: ${getExplorerUrl(cloneHash)}`));
1356
- } catch (err) {
1357
- cloneSpinner.fail("Clone failed");
1358
- console.error(chalk4.red(formatContractError(err)));
1359
- process.exit(1);
1360
- }
1361
- const initSpinner = ora3("Initializing strategy...").start();
1502
+ const deploySpinner = ora3(`Deploying ${def.name} strategy...`).start();
1362
1503
  try {
1363
1504
  const { initData } = await resolveAndBuildInitData(templateKey, opts, vault);
1364
1505
  const account = getAccount();
1365
- const initHash = await writeContractWithRetry({
1366
- account,
1367
- chain: getChain(),
1368
- address: clone,
1369
- abi: BASE_STRATEGY_ABI,
1370
- functionName: "initialize",
1371
- args: [vault, account.address, initData]
1506
+ const deployed = await deployStrategyClone({
1507
+ template: templateAddr,
1508
+ vault,
1509
+ proposer: account.address,
1510
+ initData,
1511
+ salt: opts.salt
1372
1512
  });
1373
- const receipt = await waitForReceipt(initHash);
1374
- if (receipt.status === "reverted") {
1375
- throw new Error("Initialize transaction reverted on-chain");
1513
+ clone = deployed.clone;
1514
+ deploySpinner.succeed(
1515
+ deployed.viaFactory ? `Deployed + registered: ${chalk4.green(clone)}` : `Cloned + initialized: ${chalk4.green(clone)}`
1516
+ );
1517
+ console.log(chalk4.dim(` Tx: ${getExplorerUrl(deployed.hash)}`));
1518
+ if (deployed.initHash) console.log(chalk4.dim(` Init tx: ${getExplorerUrl(deployed.initHash)}`));
1519
+ if (deployed.registerHash) console.log(chalk4.dim(` Register tx: ${getExplorerUrl(deployed.registerHash)}`));
1520
+ if (deployed.salt) console.log(chalk4.dim(` Salt: ${deployed.salt}`));
1521
+ if (!deployed.viaFactory) {
1522
+ console.log(
1523
+ chalk4.yellow(
1524
+ " No StrategyFactory on this network \u2014 the clone was deployed raw. A governor that gates propose on strategy registration will refuse it."
1525
+ )
1526
+ );
1376
1527
  }
1377
- initSpinner.succeed("Initialized");
1378
1528
  } catch (err) {
1379
- initSpinner.fail("Initialize failed");
1529
+ deploySpinner.fail("Strategy deploy failed");
1380
1530
  console.error(chalk4.red(formatContractError(err)));
1381
1531
  process.exit(1);
1382
1532
  }
@@ -1405,10 +1555,10 @@ function registerStrategyTemplateCommands(strategy2) {
1405
1555
  console.log(chalk4.dim("Use this address in your proposal batch calls."));
1406
1556
  console.log();
1407
1557
  });
1408
- 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, portfolio, hyperliquid-perp, hyperliquid-grid").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)").option("--tokens <list>", "Comma-separated token addresses or symbols (Portfolio)").option("--weights <list>", "Comma-separated weights in bps, must sum to 10000 (Portfolio)").option("--max-slippage <bps>", "Max slippage bps (Portfolio, default: 500)").option("--fee-tier <n>", "Pool fee tier (Portfolio, default: 3000)").option("--swap-adapter <address>", "Swap adapter address (Portfolio)").option("--feed-ids <list>", "Comma-separated Chainlink feed ids/addresses per token (Portfolio; auto-resolved from registry in push-feed mode)").option("--price-decimals <list>", "Comma-separated raw feed decimals per token (Portfolio; default 8 push / 18 Data Streams)").option("--swap-routes <list>", "Comma-separated per-token swap route spec (Portfolio). Per token: 'v3:<fee>' (mode 0), 'v4:<fee>:<tickSpacing>' (mode 2), or 'v4path:<hop>+<hop>...' (mode 3) where hop='<currency>@<fee>:<tickSpacing>', currency='eth'/'native'=ETH or 'self'=this token or an address. Empty entry = auto-detect / robinhood default.").option(
1558
+ 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, portfolio, hyperliquid-perp, hyperliquid-grid").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)").option("--tokens <list>", "Comma-separated token addresses or symbols (Portfolio)").option("--weights <list>", "Comma-separated weights in bps, must sum to 10000 (Portfolio)").option("--max-slippage <bps>", "Max slippage bps (Portfolio, default: 500)").option("--fee-tier <n>", "Pool fee tier (Portfolio, default: 3000)").option("--swap-adapter <address>", "Swap adapter address (Portfolio)").option("--feeds <list>", "Comma-separated Chainlink AggregatorV3 push-feed addresses per token (Portfolio; auto-resolved from the registry when a slot is left empty)").option("--price-decimals <list>", "Comma-separated raw feed decimals per token (Portfolio; default 8). Must equal the feed's live decimals().").option("--swap-routes <list>", "Comma-separated per-token swap route spec (Portfolio). Per token: 'v3:<fee>' (mode 0), 'v4:<fee>:<tickSpacing>' (mode 2), or 'v4path:<hop>+<hop>...' (mode 3) where hop='<currency>@<fee>:<tickSpacing>', currency='eth'/'native'=ETH or 'self'=this token or an address. Empty entry = auto-detect / robinhood default.").option(
1409
1559
  "--max-drawdown-bps <bps>",
1410
1560
  "Declared loss allowance for this proposal, in bps of --amount (0-10000, default 2000 = 20%). This is the envelope voters and guardians are asked to accept, and the basis the settle-price guard sizes its tolerance from \u2014 not a formality."
1411
- ).option("--max-price-ages <list>", "Comma-separated per-token max Chainlink push-feed staleness in seconds (Portfolio, push mode; robinhood-mainnet equity feeds default to 345600 = 96h, crypto/stable feeds to the contract default). Empty entry = registry default.").option("--leverage <number>", "Leverage multiplier (Hyperliquid Perp, default: 10)").option("--asset-index <number>", "Perp asset index (Hyperliquid Perp, default: 0 for BTC)").option("--max-position <amount>", "Max position size in USD (Hyperliquid Perp, default: 100000)").option("--max-trades-per-day <n>", "Max trades per day (Hyperliquid Perp, default: 50)").option("--max-order-size <amount>", "Max single-order size in asset units (Hyperliquid Grid, default: 10000)").option("--max-orders-per-tick <n>", "Max orders per tick (Hyperliquid Grid, default: 20)").option("--asset-indices <list>", "Comma-separated perp asset indices (Hyperliquid Grid, default: 0)").action(async (templateKey, opts) => {
1561
+ ).option("--leverage <number>", "Leverage multiplier (Hyperliquid Perp, default: 10)").option("--asset-index <number>", "Perp asset index (Hyperliquid Perp, default: 0 for BTC)").option("--max-position <amount>", "Max position size in USD (Hyperliquid Perp, default: 100000)").option("--max-trades-per-day <n>", "Max trades per day (Hyperliquid Perp, default: 50)").option("--max-order-size <amount>", "Max single-order size in asset units (Hyperliquid Grid, default: 10000)").option("--max-orders-per-tick <n>", "Max orders per tick (Hyperliquid Grid, default: 20)").option("--asset-indices <list>", "Comma-separated perp asset indices (Hyperliquid Grid, default: 0)").action(async (templateKey, opts) => {
1412
1562
  const clone = opts.clone;
1413
1563
  const vault = opts.vault;
1414
1564
  if (!isAddress4(clone)) {
@@ -1515,7 +1665,7 @@ function registerStrategyTemplateCommands(strategy2) {
1515
1665
  let prices = [];
1516
1666
  let priceSource = null;
1517
1667
  try {
1518
- const { getTokenPricesInAsset } = await import("./price-YNPYE2EO.js");
1668
+ const { getTokenPricesInAsset } = await import("./price-EXZDYVDD.js");
1519
1669
  prices = await getTokenPricesInAsset({
1520
1670
  tokens: allocations.map((a, i) => ({
1521
1671
  token: a.token,
@@ -1666,7 +1816,7 @@ function registerStrategyTemplateCommands(strategy2) {
1666
1816
  if (stateStr === "Executed") {
1667
1817
  console.log();
1668
1818
  console.log(chalk4.dim("Rebalance: sherwood strategy rebalance " + clone));
1669
- console.log(chalk4.dim("With new weights: sherwood strategy rebalance " + clone + " --new-weights 2500,2500,2000,1500,1500"));
1819
+ console.log(chalk4.dim("Tighten slippage first: sherwood strategy rebalance " + clone + " --max-slippage 200"));
1670
1820
  }
1671
1821
  console.log();
1672
1822
  } catch (err) {
@@ -1675,7 +1825,7 @@ function registerStrategyTemplateCommands(strategy2) {
1675
1825
  process.exit(1);
1676
1826
  }
1677
1827
  });
1678
- strategy2.command("rebalance").description("Rebalance a portfolio strategy \u2014 sell all positions, re-buy at target weights (Portfolio only)").argument("<clone>", "Strategy clone address").option("--new-weights <list>", "Comma-separated new weights in bps (must sum to 10000). Updates weights before rebalancing.").option("--max-slippage <bps>", "New max slippage bps (used with --new-weights)").option("--dry-run", "Show what would happen without executing").action(async (cloneArg, opts) => {
1828
+ strategy2.command("rebalance").description("Rebalance a portfolio strategy \u2014 trade each allocation back to its target weight (Portfolio only)").argument("<clone>", "Strategy clone address").option("--max-slippage <bps>", "Tighten max slippage bps before rebalancing (weights and routes are frozen at init \u2014 only a LOWER tolerance is accepted)").option("--dry-run", "Show what would happen without executing").action(async (cloneArg, opts) => {
1679
1829
  if (!isAddress4(cloneArg)) {
1680
1830
  console.error(chalk4.red("Invalid clone address"));
1681
1831
  process.exit(1);
@@ -1723,50 +1873,29 @@ function registerStrategyTemplateCommands(strategy2) {
1723
1873
  ` ${chalk4.bold(symbol.padEnd(8))} ${weightPct.padEnd(8)} bal: ${formatUnits5(balance, decimals)}`
1724
1874
  );
1725
1875
  }
1726
- let newWeightsBps;
1727
- if (opts.newWeights) {
1728
- newWeightsBps = opts.newWeights.split(",").map((w) => Number(w.trim()));
1729
- if (newWeightsBps.length !== allocations.length) {
1730
- console.error(chalk4.red(`--new-weights must have ${allocations.length} values (one per token)`));
1731
- process.exit(1);
1732
- }
1733
- const weightSum = newWeightsBps.reduce((a, b) => a + b, 0);
1734
- if (weightSum !== 1e4) {
1735
- console.error(chalk4.red(`Weights must sum to 10000, got ${weightSum}`));
1736
- process.exit(1);
1737
- }
1738
- console.log();
1739
- console.log(chalk4.bold("New Target Weights"));
1740
- console.log(chalk4.dim("\u2500".repeat(50)));
1741
- for (let i = 0; i < allocations.length; i++) {
1742
- const [, symbol] = tokenData[i];
1743
- const oldPct = (Number(allocations[i].targetWeightBps) / 100).toFixed(1);
1744
- const newPct = (newWeightsBps[i] / 100).toFixed(1);
1745
- const arrow = oldPct !== newPct ? chalk4.yellow("\u2192") : chalk4.dim("\u2192");
1746
- console.log(` ${chalk4.bold(symbol.padEnd(8))} ${oldPct}% ${arrow} ${newPct}%`);
1747
- }
1876
+ const newMaxSlippageBps = opts.maxSlippage ? Number(opts.maxSlippage) : void 0;
1877
+ if (newMaxSlippageBps !== void 0 && (!Number.isInteger(newMaxSlippageBps) || newMaxSlippageBps <= 0)) {
1878
+ console.error(chalk4.red(`--max-slippage must be a positive integer bps, got '${opts.maxSlippage}'`));
1879
+ process.exit(1);
1748
1880
  }
1749
1881
  if (opts.dryRun) {
1750
1882
  console.log();
1751
1883
  console.log(chalk4.yellow("Dry run \u2014 no transactions sent."));
1752
1884
  console.log(chalk4.dim("The rebalance would:"));
1753
- console.log(chalk4.dim(" 1. Sell all current token positions back to " + assetSymbol));
1754
- console.log(chalk4.dim(" 2. Re-buy at " + (newWeightsBps ? "new" : "current") + " target weights"));
1885
+ if (newMaxSlippageBps !== void 0) {
1886
+ console.log(chalk4.dim(" 0. Tighten max slippage to " + newMaxSlippageBps + " bps"));
1887
+ }
1888
+ console.log(chalk4.dim(" 1. Price every allocation off its bound push feed"));
1889
+ console.log(chalk4.dim(` 2. Trade each slot's drift back to its target weight, via ${assetSymbol}`));
1755
1890
  console.log();
1756
1891
  return;
1757
1892
  }
1758
- if (newWeightsBps) {
1759
- const maxSlip = Number(opts.maxSlippage || "0");
1760
- const swapData = await publicClient.readContract({
1761
- address: clone,
1762
- abi: PORTFOLIO_STRATEGY_ABI,
1763
- functionName: "getSwapExtraData"
1764
- });
1765
- const updateSpinner = ora3("Updating target weights...").start();
1893
+ if (newMaxSlippageBps !== void 0) {
1894
+ const updateSpinner = ora3("Tightening max slippage...").start();
1766
1895
  try {
1767
1896
  const innerData = encodeAbiParameters3(
1768
1897
  [{ type: "uint256[]" }, { type: "uint256" }, { type: "bytes[]" }],
1769
- [newWeightsBps.map((w) => BigInt(w)), BigInt(maxSlip), swapData]
1898
+ [[], BigInt(newMaxSlippageBps), []]
1770
1899
  );
1771
1900
  const updateHash = await writeContractWithRetry({
1772
1901
  account,
@@ -1778,25 +1907,25 @@ function registerStrategyTemplateCommands(strategy2) {
1778
1907
  });
1779
1908
  const receipt = await waitForReceipt(updateHash);
1780
1909
  if (receipt.status === "reverted") throw new Error("updateParams reverted");
1781
- updateSpinner.succeed("Target weights updated");
1910
+ updateSpinner.succeed(`Max slippage tightened to ${newMaxSlippageBps} bps`);
1782
1911
  console.log(chalk4.dim(` Tx: ${getExplorerUrl(updateHash)}`));
1783
1912
  } catch (err) {
1784
- updateSpinner.fail("Failed to update weights");
1913
+ updateSpinner.fail("Failed to tighten slippage");
1785
1914
  console.error(chalk4.red(formatContractError(err)));
1786
1915
  process.exit(1);
1787
1916
  }
1788
1917
  }
1789
- const rebalanceSpinner = ora3("Rebalancing portfolio (sell all \u2192 re-buy at targets)...").start();
1918
+ const rebalanceSpinner = ora3("Rebalancing portfolio (trading each slot's drift)...").start();
1790
1919
  try {
1791
1920
  const rebalanceHash = await writeContractWithRetry({
1792
1921
  account,
1793
1922
  chain,
1794
1923
  address: clone,
1795
1924
  abi: PORTFOLIO_STRATEGY_ABI,
1796
- functionName: "rebalance"
1925
+ functionName: "rebalanceDelta"
1797
1926
  });
1798
1927
  const receipt = await waitForReceipt(rebalanceHash);
1799
- if (receipt.status === "reverted") throw new Error("rebalance() reverted on-chain");
1928
+ if (receipt.status === "reverted") throw new Error("rebalanceDelta() reverted on-chain");
1800
1929
  rebalanceSpinner.succeed("Portfolio rebalanced");
1801
1930
  console.log(chalk4.dim(` Tx: ${getExplorerUrl(rebalanceHash)}`));
1802
1931
  } catch (err) {
@@ -1945,7 +2074,7 @@ function registerVeniceCommands(program2) {
1945
2074
  console.log(chalk5.dim(` Key: ${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`));
1946
2075
  console.log(chalk5.dim(" Saved to ~/.sherwood/config.json"));
1947
2076
  try {
1948
- const { createVeniceProvisionAttestation, getEasScanUrl } = await import("./eas-KMDAMLXW.js");
2077
+ const { createVeniceProvisionAttestation, getEasScanUrl } = await import("./eas-DV3Z7AT3.js");
1949
2078
  const attestation = await createVeniceProvisionAttestation(account.address);
1950
2079
  if (attestation) {
1951
2080
  console.log(chalk5.dim(` Attested: ${getEasScanUrl(attestation.uid)}`));
@@ -2301,10 +2430,10 @@ ${opts.prompt}`;
2301
2430
  console.log(chalk5.dim(`Model: ${result.model} | Tokens: ${result.usage.promptTokens} in, ${result.usage.completionTokens} out, ${result.usage.totalTokens} total`));
2302
2431
  }
2303
2432
  try {
2304
- const { createVeniceInferenceAttestation, getEasScanUrl } = await import("./eas-KMDAMLXW.js");
2433
+ const { createVeniceInferenceAttestation, getEasScanUrl } = await import("./eas-DV3Z7AT3.js");
2305
2434
  const { keccak256: keccak2562, toHex, isAddress: isAddr } = await import("viem");
2306
2435
  const { getChainContracts: getChainContracts2 } = await import("./config-ZGFS4I7K.js");
2307
- const { getChain: getActiveChain } = await import("./network-FEHRFDPJ.js");
2436
+ const { getChain: getActiveChain } = await import("./network-7445KSCF.js");
2308
2437
  const vaultRecipient = opts.vault && isAddr(opts.vault) ? opts.vault : getChainContracts2(getActiveChain().id).vault;
2309
2438
  const promptHash = keccak2562(toHex(userContent)).slice(0, 18);
2310
2439
  const attestation = await createVeniceInferenceAttestation(
@@ -2602,7 +2731,7 @@ function registerAllowanceCommands(program2) {
2602
2731
  }
2603
2732
 
2604
2733
  // src/commands/proposal.ts
2605
- import { isAddress as isAddress7, encodeFunctionData as encodeFunctionData3, parseUnits as parseUnits8 } from "viem";
2734
+ import { isAddress as isAddress7, encodeFunctionData as encodeFunctionData3, formatUnits as formatUnits8, parseUnits as parseUnits8 } from "viem";
2606
2735
  import chalk8 from "chalk";
2607
2736
  import ora6 from "ora";
2608
2737
  import { readFileSync as readFileSync2 } from "fs";
@@ -3300,8 +3429,8 @@ function printSimulationResult(result, callType) {
3300
3429
  }
3301
3430
  async function sendSimulationAlert(subdomain, proposalId, vault, execResult, settleResult) {
3302
3431
  try {
3303
- const xmtp = await import("./xmtp-KW73BGVO.js");
3304
- const { getAccount: getAccount2 } = await import("./client-3SE632Q2.js");
3432
+ const xmtp = await import("./xmtp-72IRK7JL.js");
3433
+ const { getAccount: getAccount2 } = await import("./client-PYFE6YQR.js");
3305
3434
  const group = await xmtp.getGroup("", subdomain);
3306
3435
  const allRisks = [...execResult.risks, ...settleResult?.risks ?? []];
3307
3436
  const criticals = allRisks.filter((r) => r.level === "critical");
@@ -3410,47 +3539,12 @@ function parseCallsFile(path) {
3410
3539
  value: BigInt(c.value || "0")
3411
3540
  }));
3412
3541
  }
3413
- function parseSandboxCallsFile(path) {
3414
- const raw = readFileSync2(path, "utf-8");
3415
- const parsed = JSON.parse(raw);
3416
- if (!Array.isArray(parsed)) {
3417
- console.error(chalk8.red(" sandbox calls file must be a JSON array of {target, data} objects"));
3418
- process.exit(1);
3542
+ function parseWoodAmount(raw, flag) {
3543
+ const s = String(raw ?? "").trim();
3544
+ if (!/^\d+(\.\d+)?$/.test(s)) {
3545
+ throw new Error(`${flag} must be a non-negative decimal WOOD amount, got "${s}".`);
3419
3546
  }
3420
- return parsed.map((c, i) => {
3421
- if (!c || typeof c !== "object") {
3422
- console.error(chalk8.red(` sandbox.calls[${i}] must be an object`));
3423
- process.exit(1);
3424
- }
3425
- const rec = c;
3426
- if (rec.value !== void 0 && rec.value !== null) {
3427
- console.error(
3428
- chalk8.red(
3429
- ` sandbox.calls[${i}] must be (address,bytes) only \u2014 ICallSandbox.Call has no value field.`
3430
- )
3431
- );
3432
- process.exit(1);
3433
- }
3434
- if (typeof rec.target !== "string" || !isAddress7(rec.target)) {
3435
- console.error(chalk8.red(` sandbox.calls[${i}].target must be a 0x-prefixed address`));
3436
- process.exit(1);
3437
- }
3438
- if (typeof rec.data !== "string" || !/^0x[0-9a-fA-F]*$/.test(rec.data)) {
3439
- console.error(chalk8.red(` sandbox.calls[${i}].data must be a 0x-prefixed hex string`));
3440
- process.exit(1);
3441
- }
3442
- return { target: rec.target, data: rec.data };
3443
- });
3444
- }
3445
- function parseSandboxTokens(list) {
3446
- if (list === void 0 || list.trim().length === 0) return [];
3447
- return list.split(",").map((x) => x.trim()).filter((x) => x.length > 0).map((a, i) => {
3448
- if (!isAddress7(a)) {
3449
- console.error(chalk8.red(` --sandbox-tokens[${i}] is not a 0x-prefixed address: ${a}`));
3450
- process.exit(1);
3451
- }
3452
- return a;
3453
- });
3547
+ return parseUnits8(s, 18);
3454
3548
  }
3455
3549
  var GUARDIAN_REGISTRY_ABI = [
3456
3550
  {
@@ -3488,7 +3582,10 @@ var GUARDIAN_REGISTRY_ABI = [
3488
3582
  inputs: [
3489
3583
  { name: "governor", type: "address" },
3490
3584
  { name: "proposalId", type: "uint256" },
3491
- { name: "support", type: "uint8" }
3585
+ { name: "support", type: "uint8" },
3586
+ // The approver's declaration: WOOD it puts behind this proposal,
3587
+ // clamped to its free budget by ExposureLedger. Ignored on a Block.
3588
+ { name: "lockWood", type: "uint256" }
3492
3589
  ],
3493
3590
  outputs: [],
3494
3591
  stateMutability: "nonpayable"
@@ -3746,171 +3843,6 @@ function registerProposalCommands(program2) {
3746
3843
  process.exit(1);
3747
3844
  }
3748
3845
  });
3749
- proposal.command("create-sandbox").description("Submit a strategy proposal with a CallSandbox payload (ICallSandbox.Call is (address,bytes) only \u2014 no value)").requiredOption("--vault <address>", "Vault address the proposal targets").requiredOption("--name <name>", "Strategy name").requiredOption("--description <text>", "Strategy rationale and risk summary").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").requiredOption(
3750
- "--sandbox-calls <path>",
3751
- "Path to JSON file of ICallSandbox.Call[] \u2014 [{target, data}, ...]. A value field is refused."
3752
- ).requiredOption(
3753
- "--sandbox-funding <amount>",
3754
- "Vault-asset amount the sandbox is funded with (decimals resolved from the asset). Must be > 0 and <= --max-capital."
3755
- ).option("--sandbox-tokens <list>", "Comma-separated declared non-asset token addresses the payload may hold").option("--strategy <address>", "Live-NAV strategy clone address (default: none \u2014 queue-only)").option(
3756
- "--max-capital <amount>",
3757
- "REQUIRED. Ceiling on what the execute batch may deploy, in vault-asset units."
3758
- ).option(
3759
- "--max-drawdown-bps <bps>",
3760
- "Declared loss allowance in bps of --max-capital (0-10000, default 2000 = 20%)."
3761
- ).option("--execute-caps <list>", "Comma-separated per-execute-call outflow caps in asset units").option("--settle-caps <list>", "Comma-separated per-settlement-call outflow caps in asset units").option("--metadata-uri <uri>", "Override \u2014 skip IPFS upload and use this URI directly").action(async (opts) => {
3762
- try {
3763
- const vault = opts.vault;
3764
- if (!isAddress7(vault)) {
3765
- console.error(chalk8.red(`Invalid vault address: ${opts.vault}`));
3766
- process.exit(1);
3767
- }
3768
- const strategyDuration = parseDuration(opts.duration);
3769
- const executeCalls = parseCallsFile(opts.executeCalls);
3770
- const settleCalls = parseCallsFile(opts.settleCalls);
3771
- const sandboxCalls = parseSandboxCallsFile(opts.sandboxCalls);
3772
- const declaredTokens = parseSandboxTokens(opts.sandboxTokens);
3773
- const ZERO_STRATEGY = "0x0000000000000000000000000000000000000000";
3774
- let strategy2 = ZERO_STRATEGY;
3775
- if (opts.strategy) {
3776
- if (!isAddress7(opts.strategy)) {
3777
- console.error(chalk8.red(`Invalid strategy address: ${opts.strategy}`));
3778
- process.exit(1);
3779
- }
3780
- strategy2 = opts.strategy;
3781
- }
3782
- const envelope = await resolveRiskEnvelope(opts, vault);
3783
- const asset = await getAssetAddress();
3784
- const decimals = await getPublicClient().readContract({
3785
- address: asset,
3786
- abi: ERC20_ABI,
3787
- functionName: "decimals"
3788
- });
3789
- const funding = parseUnits8(String(opts.sandboxFunding), decimals);
3790
- const executeCallCaps = resolveCaps(
3791
- opts.executeCaps,
3792
- executeCalls.length,
3793
- envelope.maxCapital,
3794
- "--execute-caps"
3795
- );
3796
- const settleCallCaps = resolveCaps(
3797
- opts.settleCaps,
3798
- settleCalls.length,
3799
- 0n,
3800
- "--settle-caps"
3801
- );
3802
- const buildAction = async (metadataURI2) => encodeProposeWithSandbox(
3803
- {
3804
- vault,
3805
- governor: await getGovernorOf(vault),
3806
- strategy: strategy2,
3807
- metadataURI: metadataURI2,
3808
- strategyDuration,
3809
- envelope,
3810
- executeCalls,
3811
- executeCallCaps,
3812
- settlementCalls: settleCalls,
3813
- settlementCallCaps: settleCallCaps,
3814
- sandbox: { funding, calls: sandboxCalls, declaredTokens }
3815
- },
3816
- getChain().id
3817
- );
3818
- if (isCalldataOnly()) {
3819
- if (!opts.metadataUri) {
3820
- console.error(
3821
- chalk8.red(
3822
- "--calldata-only requires --metadata-uri (IPFS metadata upload needs a signing wallet)"
3823
- )
3824
- );
3825
- process.exit(1);
3826
- }
3827
- try {
3828
- emitCalldata(await buildAction(opts.metadataUri));
3829
- } catch (err) {
3830
- console.error(chalk8.red(err instanceof Error ? err.message : String(err)));
3831
- process.exit(1);
3832
- }
3833
- return;
3834
- }
3835
- let metadataURI = opts.metadataUri || "";
3836
- if (!metadataURI) {
3837
- const spinner2 = ora6({ text: W3("Uploading metadata to IPFS..."), color: "green" }).start();
3838
- try {
3839
- const account = getAccount();
3840
- const metadata = {
3841
- schema: "sherwood/proposal/v1",
3842
- name: opts.name,
3843
- description: opts.description,
3844
- chain: getNetwork(),
3845
- strategies: [],
3846
- terms: {},
3847
- links: {}
3848
- };
3849
- const proposalMeta = {
3850
- ...metadata,
3851
- proposer: account.address,
3852
- vault,
3853
- strategyDuration: Number(strategyDuration),
3854
- sandbox: true,
3855
- createdAt: Math.floor(Date.now() / 1e3)
3856
- };
3857
- metadataURI = await uploadMetadata(proposalMeta);
3858
- spinner2.succeed(G3(`Metadata pinned: ${DIM3(metadataURI)}`));
3859
- } catch (err) {
3860
- spinner2.warn(chalk8.yellow("IPFS upload failed \u2014 using inline metadata"));
3861
- const json = JSON.stringify({ name: opts.name, description: opts.description, vault, sandbox: true });
3862
- metadataURI = `data:application/json;base64,${Buffer.from(json).toString("base64")}`;
3863
- }
3864
- }
3865
- console.log();
3866
- console.log(LABEL3(" \u25C6 Sandbox Proposal Summary"));
3867
- SEP3();
3868
- console.log(W3(` Name: ${BOLD3(opts.name)}`));
3869
- console.log(W3(` Vault: ${G3(vault)}`));
3870
- console.log(W3(` Duration: ${formatDurationShort(strategyDuration)}`));
3871
- console.log(W3(` Calls: ${executeCalls.length} execute + ${settleCalls.length} settle + ${sandboxCalls.length} sandbox`));
3872
- console.log(W3(` Sandbox funding: ${opts.sandboxFunding}`));
3873
- console.log(W3(` Metadata: ${DIM3(metadataURI.length > 50 ? metadataURI.slice(0, 50) + "..." : metadataURI)}`));
3874
- SEP3();
3875
- const spinner = ora6({ text: W3("Submitting sandbox proposal..."), color: "green" }).start();
3876
- let action;
3877
- try {
3878
- action = await buildAction(metadataURI);
3879
- } catch (err) {
3880
- spinner.fail(err instanceof Error ? err.message : String(err));
3881
- process.exit(1);
3882
- }
3883
- const tx = action.txs[0];
3884
- const hash = await sendTxWithRetry({
3885
- account: getAccount(),
3886
- chain: getChain(),
3887
- to: tx.to,
3888
- data: tx.data,
3889
- value: 0n
3890
- });
3891
- const receipt = await waitForReceipt(hash);
3892
- let proposalId = parseProposalIdFromLogs(receipt.logs);
3893
- if (proposalId === void 0) {
3894
- try {
3895
- proposalId = await proposalCount(vault);
3896
- } catch {
3897
- proposalId = 0n;
3898
- }
3899
- }
3900
- spinner.succeed(G3("Sandbox proposal submitted"));
3901
- console.log();
3902
- console.log(LABEL3(" \u25C6 Sandbox Proposal Created"));
3903
- SEP3();
3904
- console.log(W3(` Proposal ID: ${G3(`#${proposalId}`)}`));
3905
- console.log(W3(` Tx: ${DIM3(getExplorerUrl(receipt.transactionHash))}`));
3906
- SEP3();
3907
- console.log();
3908
- } catch (err) {
3909
- console.error(chalk8.red(`
3910
- \u2716 ${formatContractError(err)}`));
3911
- process.exit(1);
3912
- }
3913
- });
3914
3846
  proposal.command("list").description("List a vault's proposals (per-vault governor \u2014 IDs are scoped to the vault)").requiredOption("--vault <address>", "Vault whose proposals to list").option("--state <filter>", "Filter by state: draft, pending, approved, executed, settled, all", "all").action(async (opts) => {
3915
3847
  const spinner = ora6("Loading proposals...").start();
3916
3848
  try {
@@ -3943,7 +3875,7 @@ function registerProposalCommands(program2) {
3943
3875
  console.log(BOLD3(`Proposals (${proposals.length})`));
3944
3876
  console.log(DIM3("\u2500".repeat(90)));
3945
3877
  console.log(
3946
- DIM3(" ID ") + DIM3("Agent".padEnd(14)) + DIM3("State".padEnd(12)) + DIM3("Fee".padEnd(8)) + DIM3("Votes (For/Against)".padEnd(22)) + DIM3("Duration".padEnd(10)) + DIM3("Created")
3878
+ DIM3(" ID ") + DIM3("Agent".padEnd(14)) + DIM3("State".padEnd(12)) + DIM3("Fee".padEnd(8)) + DIM3("Against/Votable".padEnd(22)) + DIM3("Duration".padEnd(10)) + DIM3("Created")
3947
3879
  );
3948
3880
  console.log(DIM3("\u2500".repeat(90)));
3949
3881
  for (const p of proposals) {
@@ -3951,7 +3883,7 @@ function registerProposalCommands(program2) {
3951
3883
  const created = p.snapshotTimestamp > 0n ? new Date(Number(p.snapshotTimestamp) * 1e3).toLocaleDateString() : "\u2014";
3952
3884
  const agent = `${p.proposer.slice(0, 6)}...${p.proposer.slice(-4)}`;
3953
3885
  const dur = formatDurationShort(p.strategyDuration);
3954
- const votes = `${formatShares(p.votesFor)}/${formatShares(p.votesAgainst)}`;
3886
+ const votes = `${formatShares(p.votesAgainst)}/${formatShares(p.votableSupply)}`;
3955
3887
  const fee = `${Number(p.performanceFeeBps) / 100}%`;
3956
3888
  console.log(
3957
3889
  ` ${String(p.id).padEnd(4)}${agent.padEnd(14)}${state.padEnd(12)}${fee.padEnd(8)}${votes.padEnd(22)}${dur.padEnd(10)}${created}`
@@ -3977,11 +3909,9 @@ function registerProposalCommands(program2) {
3977
3909
  }
3978
3910
  const p = await getProposal(vault, id);
3979
3911
  const state = await getProposalState(vault, id);
3980
- const params = await getGovernorParams(vault);
3981
3912
  spinner.stop();
3982
3913
  const stateLabel = PROPOSAL_STATES[state] || "Unknown";
3983
- const totalVotes = p.votesFor + p.votesAgainst;
3984
- const quorumNeeded = totalVotes > 0n ? `${Number(params.vetoThresholdBps) / 100}%` : "\u2014";
3914
+ const vetoBar = p.votableSupply > 0n ? p.votableSupply * p.vetoThresholdBps / 10000n || 1n : null;
3985
3915
  console.log();
3986
3916
  console.log(LABEL3(` \u25C6 Proposal #${p.id}`));
3987
3917
  SEP3();
@@ -4009,10 +3939,11 @@ function registerProposalCommands(program2) {
4009
3939
  console.log(W3(` Executed At: ${formatTimestamp(p.executedAt)}`));
4010
3940
  console.log();
4011
3941
  console.log(LABEL3(" Votes"));
4012
- console.log(W3(` For: ${formatShares(p.votesFor)}`));
4013
3942
  console.log(W3(` Against: ${formatShares(p.votesAgainst)}`));
4014
- console.log(W3(` Abstain: ${formatShares(p.votesAbstain)}`));
4015
- console.log(W3(` Veto Threshold: ${quorumNeeded}`));
3943
+ console.log(W3(` Votable Supply: ${formatShares(p.votableSupply)}`));
3944
+ console.log(W3(
3945
+ ` Veto Threshold: ${vetoBar === null ? DIM3("\u2014 (no votable supply \u2014 veto check skipped)") : `${Number(p.vetoThresholdBps) / 100}% (${formatShares(vetoBar)})`}`
3946
+ ));
4016
3947
  if (state === PROPOSAL_STATE.Executed || state === PROPOSAL_STATE.Settled) {
4017
3948
  try {
4018
3949
  const cap = await getCapitalSnapshot(vault, id);
@@ -4099,15 +4030,19 @@ function registerProposalCommands(program2) {
4099
4030
  process.exit(1);
4100
4031
  }
4101
4032
  });
4102
- proposal.command("review-vote").description("Cast a guardian review vote (Approve or Block) on a live review").requiredOption("--id <proposalId>", "Proposal ID").requiredOption("--vault <address>", "Vault the proposal belongs to (per-vault governor)").requiredOption("--support <approve|block>", "Guardian review vote: approve or block").action(async (opts) => {
4033
+ proposal.command("review-vote").description("Cast a guardian review vote (Approve or Block) on a live review").requiredOption("--id <proposalId>", "Proposal ID").requiredOption("--vault <address>", "Vault the proposal belongs to (per-vault governor)").requiredOption("--support <approve|block>", "Guardian review vote: approve or block").requiredOption(
4034
+ "--lock-wood <wood>",
4035
+ "WOOD you declare behind an Approve (decimal WOOD). Clamped to your free budget by ExposureLedger; ignored on a Block."
4036
+ ).action(async (opts) => {
4103
4037
  try {
4104
4038
  const proposalId = parseBigIntArg(opts.id, "proposal ID");
4105
4039
  const vault = parseVaultOption(opts.vault);
4106
4040
  const vote2 = parseGuardianReviewVote(String(opts.support));
4041
+ const lockWood = parseWoodAmount(opts.lockWood, "--lock-wood");
4107
4042
  const chainId = getChain().id;
4108
4043
  const governor = await getGovernorOf(vault);
4109
4044
  if (isCalldataOnly()) {
4110
- emitCalldata(encodeVoteOnProposal({ governor, proposalId, vote: vote2 }, chainId));
4045
+ emitCalldata(encodeVoteOnProposal({ governor, proposalId, vote: vote2, lockWood }, chainId));
4111
4046
  return;
4112
4047
  }
4113
4048
  const account = getAccount();
@@ -4168,6 +4103,9 @@ function registerProposalCommands(program2) {
4168
4103
  SEP3();
4169
4104
  if (vote2 === "Approve") {
4170
4105
  console.log(chalk8.yellow.bold(" \u26A0 UNDERWRITING"));
4106
+ console.log(chalk8.yellow(
4107
+ ` Declared lock: ${formatUnits8(lockWood, 18)} WOOD`
4108
+ ));
4171
4109
  console.log(chalk8.yellow(
4172
4110
  " Approve books this proposal's coverage against YOUR own slashable"
4173
4111
  ));
@@ -4185,7 +4123,7 @@ function registerProposalCommands(program2) {
4185
4123
  ));
4186
4124
  SEP3();
4187
4125
  }
4188
- const action = encodeVoteOnProposal({ governor, proposalId, vote: vote2 }, chainId);
4126
+ const action = encodeVoteOnProposal({ governor, proposalId, vote: vote2, lockWood }, chainId);
4189
4127
  const tx = action.txs[0];
4190
4128
  const voteSpinner = ora6({ text: W3("Submitting review vote..."), color: "green" }).start();
4191
4129
  const hash = await sendTxWithRetry({
@@ -4776,6 +4714,65 @@ function registerProposalCommands(program2) {
4776
4714
  process.exit(1);
4777
4715
  }
4778
4716
  });
4717
+ proposal.command("reclaim-bond").description("Reclaim the proposer bond of a terminal proposal (cancelled / rejected / expired / settled)").requiredOption("--id <proposalId>", "Proposal ID").requiredOption("--vault <address>", "Vault the proposal belongs to (per-vault governor)").action(async (opts) => {
4718
+ try {
4719
+ const proposalId = parseBigIntArg(opts.id, "proposal ID");
4720
+ const vault = parseVaultOption(opts.vault);
4721
+ if (isCalldataOnly()) {
4722
+ const governor = await getGovernorOf(vault);
4723
+ emitCalldata({
4724
+ txs: [
4725
+ {
4726
+ to: governor,
4727
+ data: encodeFunctionData3({
4728
+ abi: SYNDICATE_GOVERNOR_ABI,
4729
+ functionName: "reclaimProposerBond",
4730
+ args: [proposalId]
4731
+ }),
4732
+ value: "0x0",
4733
+ chainId: getChain().id
4734
+ }
4735
+ ],
4736
+ preconditions: [],
4737
+ description: `Release proposal #${proposalId}'s proposer bond from escrow back to the proposer.`,
4738
+ note: "Permissionless \u2014 any wallet may broadcast it; the WOOD goes to whoever posted the bond. The proposal must be Rejected / Expired / Cancelled / Settled, and for an EXECUTED proposal the challenge window (executedAt + strategyDuration + challengeWindow) must have closed."
4739
+ });
4740
+ return;
4741
+ }
4742
+ const spinner = ora6("Loading proposal...").start();
4743
+ const [p, state] = await Promise.all([
4744
+ getProposal(vault, proposalId),
4745
+ getProposalState(vault, proposalId)
4746
+ ]);
4747
+ const TERMINAL = /* @__PURE__ */ new Set([
4748
+ PROPOSAL_STATE.Rejected,
4749
+ PROPOSAL_STATE.Expired,
4750
+ PROPOSAL_STATE.Cancelled,
4751
+ PROPOSAL_STATE.Settled
4752
+ ]);
4753
+ if (!TERMINAL.has(state)) {
4754
+ spinner.fail(
4755
+ `Proposal is ${PROPOSAL_STATES[state] || "Unknown"} \u2014 a bond is only reclaimable from Rejected / Expired / Cancelled / Settled.`
4756
+ );
4757
+ process.exit(1);
4758
+ }
4759
+ if (p.proposerBondWood === 0n) {
4760
+ spinner.fail(
4761
+ "This proposal has no proposer bond left to reclaim (never bonded, already reclaimed, or forfeited)."
4762
+ );
4763
+ process.exit(1);
4764
+ }
4765
+ spinner.text = W3(`Reclaiming ${formatUnits8(p.proposerBondWood, 18)} WOOD...`);
4766
+ const hash = await reclaimProposerBond(vault, proposalId);
4767
+ spinner.succeed(G3(`Bond released: ${formatUnits8(p.proposerBondWood, 18)} WOOD \u2192 ${p.proposer}`));
4768
+ console.log(DIM3(` ${getExplorerUrl(hash)}`));
4769
+ console.log();
4770
+ } catch (err) {
4771
+ console.error(chalk8.red(`
4772
+ \u2716 ${formatContractError(err)}`));
4773
+ process.exit(1);
4774
+ }
4775
+ });
4779
4776
  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 \u2014 proposal IDs are per-vault)").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 vault XMTP chat").action(async (opts) => {
4780
4777
  try {
4781
4778
  let vault;
@@ -4851,7 +4848,7 @@ function registerProposalCommands(program2) {
4851
4848
  }
4852
4849
 
4853
4850
  // src/commands/queue.ts
4854
- import { parseUnits as parseUnits9, formatUnits as formatUnits8, isAddress as isAddress8, parseEventLogs } from "viem";
4851
+ import { parseUnits as parseUnits9, formatUnits as formatUnits9, isAddress as isAddress8, parseEventLogs } from "viem";
4855
4852
  import chalk9 from "chalk";
4856
4853
  import ora7 from "ora";
4857
4854
  var WETH_ABI = [
@@ -5040,7 +5037,7 @@ function registerQueueCommands(program2) {
5040
5037
  );
5041
5038
  if (ethBalance < shortfall + minGas) {
5042
5039
  throw new Error(
5043
- `Insufficient ETH on ${getChain().name}. Have ${formatUnits8(ethBalance, 18)} ETH, need ${formatUnits8(shortfall, 18)} to wrap + ~${formatUnits8(minGas, 18)} for gas.`
5040
+ `Insufficient ETH on ${getChain().name}. Have ${formatUnits9(ethBalance, 18)} ETH, need ${formatUnits9(shortfall, 18)} to wrap + ~${formatUnits9(minGas, 18)} for gas.`
5044
5041
  );
5045
5042
  }
5046
5043
  spinner.text = "Wrapping ETH \u2192 WETH...";
@@ -8051,9 +8048,9 @@ try {
8051
8048
  } catch {
8052
8049
  }
8053
8050
  var require2 = createRequire(import.meta.url);
8054
- var CLI_VERSION = "0.86.1";
8051
+ var CLI_VERSION = "0.87.1";
8055
8052
  async function loadXmtp() {
8056
- return import("./xmtp-KW73BGVO.js");
8053
+ return import("./xmtp-72IRK7JL.js");
8057
8054
  }
8058
8055
  async function loadCron() {
8059
8056
  return import("./cron-E3UAZF5B.js");
@@ -8099,7 +8096,7 @@ program.name("sherwood").description("CLI for agent-managed onchain vaults").ver
8099
8096
  }).addOption(
8100
8097
  new Option(
8101
8098
  "--chain <network>",
8102
- "Target network (default: robinhood-fork, chain 9994663 \u2014 where the product lives; robinhood-testnet is chain 46630)"
8099
+ "Target network (default: robinhood-fork, chain 9994663 \u2014 where the product lives; robinhood-testnet is chain 46630 and still runs a PRE-V1 stack this CLI cannot talk to)"
8103
8100
  ).choices(VALID_NETWORKS).default(DEFAULT_NETWORK)
8104
8101
  ).option(
8105
8102
  "--calldata-only",
@@ -8724,7 +8721,7 @@ vaultCmd.command("add").description("Register an agent on a vault (creator only)
8724
8721
  agentId = BigInt(opts.agentId);
8725
8722
  } else {
8726
8723
  spinner.text = "Looking up ERC-8004 identity from wallet...";
8727
- const { lookupAgentIdByWallet } = await import("./identity-ATWV2O7Y.js");
8724
+ const { lookupAgentIdByWallet } = await import("./identity-M4TXM7EX.js");
8728
8725
  const resolved = await lookupAgentIdByWallet(agentWallet);
8729
8726
  if (resolved === null) {
8730
8727
  spinner.fail("Agent wallet does not own an ERC-8004 identity NFT");
@@ -9408,7 +9405,7 @@ vaultCmd.command("redeem").description("Redeem vault shares for the underlying a
9408
9405
  `));
9409
9406
  process.exit(1);
9410
9407
  }
9411
- const sharesDisplay = formatUnits9(shares, shareDecimals);
9408
+ const sharesDisplay = formatUnits10(shares, shareDecimals);
9412
9409
  const spinner = ora9(`Redeeming ${sharesDisplay} shares...`).start();
9413
9410
  try {
9414
9411
  const { hash, assetsOut } = await redeem(
@@ -9417,7 +9414,7 @@ vaultCmd.command("redeem").description("Redeem vault shares for the underlying a
9417
9414
  );
9418
9415
  spinner.succeed(`Redeemed: ${hash}`);
9419
9416
  console.log(chalk17.dim(` ${getExplorerUrl(hash)}`));
9420
- console.log(` Assets received: ${formatUnits9(assetsOut, assetDecimals)}`);
9417
+ console.log(` Assets received: ${formatUnits10(assetsOut, assetDecimals)}`);
9421
9418
  } catch (err) {
9422
9419
  spinner.fail("Redeem failed");
9423
9420
  console.error(chalk17.red(formatContractError(err)));
@@ -9427,7 +9424,7 @@ vaultCmd.command("redeem").description("Redeem vault shares for the underlying a
9427
9424
  var strategy = program.command("strategy").description("Strategy templates \u2014 list, clone, propose");
9428
9425
  registerStrategyTemplateCommands(strategy);
9429
9426
  program.command("providers").description("List available providers (research + swap routing)").action(async () => {
9430
- const { listProviders } = await import("./providers-BMOHHVUN.js");
9427
+ const { listProviders } = await import("./providers-6UZKDFQD.js");
9431
9428
  for (const info of listProviders()) {
9432
9429
  console.log(`
9433
9430
  ${info.name} (${info.type})`);
@@ -9437,7 +9434,7 @@ ${info.name} (${info.type})`);
9437
9434
  }
9438
9435
  });
9439
9436
  try {
9440
- const { registerChatCommands } = await import("./chat-ETZSKQDI.js");
9437
+ const { registerChatCommands } = await import("./chat-AKLYUJZV.js");
9441
9438
  registerChatCommands(program);
9442
9439
  } catch {
9443
9440
  program.command("chat <name> [action] [actionArgs...]").description("Vault chat (XMTP) \u2014 requires @xmtp/cli").action(() => {
@@ -9447,7 +9444,7 @@ try {
9447
9444
  process.exit(1);
9448
9445
  });
9449
9446
  }
9450
- var { registerSessionCommands } = await import("./session-HVRZ6AJM.js");
9447
+ var { registerSessionCommands } = await import("./session-45NQAHFI.js");
9451
9448
  registerSessionCommands(program);
9452
9449
  registerVeniceCommands(program);
9453
9450
  registerAllowanceCommands(program);
@@ -9456,11 +9453,11 @@ registerProposalCommands(program);
9456
9453
  registerQueueCommands(program);
9457
9454
  registerGovernorCommands(program);
9458
9455
  registerGuardianCommands(program);
9459
- var { registerResearchCommands } = await import("./research-W74PNYPO.js");
9456
+ var { registerResearchCommands } = await import("./research-44GHYE4H.js");
9460
9457
  registerResearchCommands(program);
9461
- var { registerAgentCommands } = await import("./agent-TBLC3WNJ.js");
9458
+ var { registerAgentCommands } = await import("./agent-FCGZL4GF.js");
9462
9459
  registerAgentCommands(program);
9463
- var { registerTradeCommands } = await import("./trade-EOQY72L7.js");
9460
+ var { registerTradeCommands } = await import("./trade-FO52SP62.js");
9464
9461
  registerTradeCommands(program);
9465
9462
  registerGridCommand(program);
9466
9463
  var configCmd = program.command("config");