@sherwoodagent/cli 0.83.1 → 0.84.0

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 (69) hide show
  1. package/dist/{abis-ZQ6I3S6L.js → abis-WVW2EBI3.js} +2 -2
  2. package/dist/{addresses-MS6BB7AB.js → addresses-LFYVOPOH.js} +3 -3
  3. package/dist/{agent-C5XZSK7Z.js → agent-TBLC3WNJ.js} +7 -7
  4. package/dist/{chat-FGTYCJ4B.js → chat-UZ3BBEXV.js} +8 -8
  5. package/dist/{chunk-G34TNE53.js → chunk-45FELUWY.js} +2 -2
  6. package/dist/{chunk-4WAEM2TC.js → chunk-56YYNNNQ.js} +5 -5
  7. package/dist/{chunk-AJ46XIGY.js → chunk-6DRNSZ2Z.js} +41 -5
  8. package/dist/chunk-6DRNSZ2Z.js.map +1 -0
  9. package/dist/{chunk-4GO7I33U.js → chunk-BOC7VB5B.js} +36 -5
  10. package/dist/chunk-BOC7VB5B.js.map +1 -0
  11. package/dist/{chunk-I7TMY7AS.js → chunk-DNRIKCJ5.js} +85 -31
  12. package/dist/chunk-DNRIKCJ5.js.map +1 -0
  13. package/dist/{chunk-XSP64L7L.js → chunk-F3MDCUGO.js} +4 -368
  14. package/dist/chunk-F3MDCUGO.js.map +1 -0
  15. package/dist/{chunk-NKRMMFQG.js → chunk-JUVXLTL2.js} +56 -5
  16. package/dist/chunk-JUVXLTL2.js.map +1 -0
  17. package/dist/{chunk-CCNOXDZ7.js → chunk-KVB5P2OE.js} +2 -2
  18. package/dist/{chunk-MBWFTXF5.js → chunk-MQ55QOGD.js} +5 -5
  19. package/dist/{chunk-M34JVPJO.js → chunk-NCFN66ZH.js} +39 -6
  20. package/dist/chunk-NCFN66ZH.js.map +1 -0
  21. package/dist/{chunk-ZW2VKCFZ.js → chunk-PVTZMRC6.js} +34 -2
  22. package/dist/chunk-PVTZMRC6.js.map +1 -0
  23. package/dist/{chunk-AQPTBVEA.js → chunk-QRMZRAMG.js} +2 -2
  24. package/dist/{chunk-AOL2DJTK.js → chunk-SNR5R5TO.js} +6 -6
  25. package/dist/{client-OPZ2ORWW.js → client-3SE632Q2.js} +3 -3
  26. package/dist/{eas-32PEJETG.js → eas-QPKX6A5A.js} +12 -6
  27. package/dist/{governor-GSCXEZWI.js → governor-7QKGNHMR.js} +12 -6
  28. package/dist/index.js +1053 -269
  29. package/dist/index.js.map +1 -1
  30. package/dist/{network-HCFCNZUI.js → network-FEHRFDPJ.js} +6 -2
  31. package/dist/{price-OUL7WIXQ.js → price-SVF25X4O.js} +6 -6
  32. package/dist/{providers-4HQHB7V5.js → providers-BMOHHVUN.js} +4 -4
  33. package/dist/{research-2RWAMWWH.js → research-YSNJ5HTM.js} +15 -12
  34. package/dist/research-YSNJ5HTM.js.map +1 -0
  35. package/dist/{session-SIOJZO6P.js → session-RMAIFRS6.js} +44 -13
  36. package/dist/session-RMAIFRS6.js.map +1 -0
  37. package/dist/{trade-ZEIY3MCL.js → trade-YPLTIS56.js} +21 -21
  38. package/dist/trade-YPLTIS56.js.map +1 -0
  39. package/dist/{vault-VDADZFSV.js → vault-UCFFOOTS.js} +8 -8
  40. package/dist/{xmtp-2U7CSBXM.js → xmtp-S7IGTIMZ.js} +6 -6
  41. package/package.json +2 -2
  42. package/dist/chunk-4GO7I33U.js.map +0 -1
  43. package/dist/chunk-AJ46XIGY.js.map +0 -1
  44. package/dist/chunk-I7TMY7AS.js.map +0 -1
  45. package/dist/chunk-M34JVPJO.js.map +0 -1
  46. package/dist/chunk-NKRMMFQG.js.map +0 -1
  47. package/dist/chunk-XSP64L7L.js.map +0 -1
  48. package/dist/chunk-ZW2VKCFZ.js.map +0 -1
  49. package/dist/research-2RWAMWWH.js.map +0 -1
  50. package/dist/session-SIOJZO6P.js.map +0 -1
  51. package/dist/trade-ZEIY3MCL.js.map +0 -1
  52. /package/dist/{abis-ZQ6I3S6L.js.map → abis-WVW2EBI3.js.map} +0 -0
  53. /package/dist/{addresses-MS6BB7AB.js.map → addresses-LFYVOPOH.js.map} +0 -0
  54. /package/dist/{agent-C5XZSK7Z.js.map → agent-TBLC3WNJ.js.map} +0 -0
  55. /package/dist/{chat-FGTYCJ4B.js.map → chat-UZ3BBEXV.js.map} +0 -0
  56. /package/dist/{chunk-G34TNE53.js.map → chunk-45FELUWY.js.map} +0 -0
  57. /package/dist/{chunk-4WAEM2TC.js.map → chunk-56YYNNNQ.js.map} +0 -0
  58. /package/dist/{chunk-CCNOXDZ7.js.map → chunk-KVB5P2OE.js.map} +0 -0
  59. /package/dist/{chunk-MBWFTXF5.js.map → chunk-MQ55QOGD.js.map} +0 -0
  60. /package/dist/{chunk-AQPTBVEA.js.map → chunk-QRMZRAMG.js.map} +0 -0
  61. /package/dist/{chunk-AOL2DJTK.js.map → chunk-SNR5R5TO.js.map} +0 -0
  62. /package/dist/{client-OPZ2ORWW.js.map → client-3SE632Q2.js.map} +0 -0
  63. /package/dist/{eas-32PEJETG.js.map → eas-QPKX6A5A.js.map} +0 -0
  64. /package/dist/{governor-GSCXEZWI.js.map → governor-7QKGNHMR.js.map} +0 -0
  65. /package/dist/{network-HCFCNZUI.js.map → network-FEHRFDPJ.js.map} +0 -0
  66. /package/dist/{price-OUL7WIXQ.js.map → price-SVF25X4O.js.map} +0 -0
  67. /package/dist/{providers-4HQHB7V5.js.map → providers-BMOHHVUN.js.map} +0 -0
  68. /package/dist/{vault-VDADZFSV.js.map → vault-UCFFOOTS.js.map} +0 -0
  69. /package/dist/{xmtp-2U7CSBXM.js.map → xmtp-S7IGTIMZ.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-CCNOXDZ7.js";
18
+ } from "./chunk-KVB5P2OE.js";
19
19
  import {
20
20
  approveDepositor,
21
21
  deposit,
@@ -38,50 +38,55 @@ import {
38
38
  removeDepositor,
39
39
  setAgentFeeBps,
40
40
  setVaultAddress
41
- } from "./chunk-AOL2DJTK.js";
42
- import "./chunk-NKRMMFQG.js";
41
+ } from "./chunk-SNR5R5TO.js";
42
+ import "./chunk-JUVXLTL2.js";
43
43
  import {
44
+ DEFAULT_FEE_TIER,
44
45
  HYPERLIQUID_PERP_STRATEGY_ABI,
45
46
  applySlippage,
46
47
  assetDecimalsForSymbol,
47
48
  buildCallsForTemplate,
48
49
  buildInitDataForTemplate,
49
50
  computeProposalAssetChecks,
51
+ defaultPortfolioAsset,
50
52
  encodeSwapPath,
51
53
  getMultiHopQuote,
52
54
  getQuote,
53
- resolveToken
54
- } from "./chunk-AJ46XIGY.js";
55
+ parseAlignedOverrides,
56
+ resolveToken,
57
+ selectBestFeeTier
58
+ } from "./chunk-6DRNSZ2Z.js";
55
59
  import {
56
60
  createApproval,
57
61
  createJoinRequest,
58
62
  getEasExplorerUrl,
59
- getEasScanUrl,
60
63
  parseReferrer,
61
64
  queryApprovals,
62
65
  queryJoinRequests,
63
66
  revokeJoinRequest,
64
67
  stripReferrerPrefix
65
- } from "./chunk-M34JVPJO.js";
68
+ } from "./chunk-NCFN66ZH.js";
66
69
  import {
67
70
  createFund,
68
71
  getActiveFunds,
69
72
  getFund,
70
73
  subdomainExists,
71
74
  updateMetadata
72
- } from "./chunk-4WAEM2TC.js";
75
+ } from "./chunk-56YYNNNQ.js";
73
76
  import {
74
77
  resolveFund,
75
78
  resolveVaultFund,
76
79
  searchFundAcrossChains,
77
80
  setTextRecord
78
- } from "./chunk-MBWFTXF5.js";
81
+ } from "./chunk-MQ55QOGD.js";
79
82
  import {
80
83
  PROPOSAL_STATE,
81
84
  PROPOSAL_STATES,
82
85
  VOTE_TYPE,
86
+ assertReceiptSuccess,
83
87
  cancelProposal,
84
88
  classifyReview,
89
+ classifyReviewOpen,
85
90
  emergencyCancel,
86
91
  emergencySettle,
87
92
  evaluateExecuteGate,
@@ -98,8 +103,10 @@ import {
98
103
  getVoteWeight,
99
104
  hasVoted,
100
105
  parseDuration,
106
+ parseProposalIdFromLogs,
101
107
  proposalCount,
102
108
  propose,
109
+ scanReviewCandidates,
103
110
  setCooldownPeriod,
104
111
  setExecutionWindow,
105
112
  setMaxPerformanceFeeBps,
@@ -109,7 +116,7 @@ import {
109
116
  setVotingPeriod,
110
117
  settleProposal,
111
118
  vote
112
- } from "./chunk-4GO7I33U.js";
119
+ } from "./chunk-BOC7VB5B.js";
113
120
  import {
114
121
  AERODROME,
115
122
  AGENT_REGISTRY,
@@ -119,7 +126,7 @@ import {
119
126
  TOKENS,
120
127
  UNISWAP,
121
128
  VENICE
122
- } from "./chunk-I7TMY7AS.js";
129
+ } from "./chunk-DNRIKCJ5.js";
123
130
  import {
124
131
  GAS_UNITS,
125
132
  estimateGasCushion,
@@ -130,7 +137,7 @@ import {
130
137
  sendTxWithRetry,
131
138
  waitForReceipt,
132
139
  writeContractWithRetry
133
- } from "./chunk-AQPTBVEA.js";
140
+ } from "./chunk-QRMZRAMG.js";
134
141
  import {
135
142
  DEFAULT_NETWORK,
136
143
  VALID_NETWORKS,
@@ -142,7 +149,7 @@ import {
142
149
  isTestnet,
143
150
  setNetwork,
144
151
  withNetwork
145
- } from "./chunk-ZW2VKCFZ.js";
152
+ } from "./chunk-PVTZMRC6.js";
146
153
  import {
147
154
  addFund,
148
155
  cacheGroupId,
@@ -178,7 +185,7 @@ import {
178
185
  SWAP_ROUTER_EXACT_INPUT_SINGLE_ABI,
179
186
  SYNDICATE_VAULT_ABI,
180
187
  VENICE_STAKING_ABI
181
- } from "./chunk-XSP64L7L.js";
188
+ } from "./chunk-F3MDCUGO.js";
182
189
  import {
183
190
  fetchMetadata,
184
191
  mergeFundMetadata,
@@ -289,7 +296,7 @@ var HYPERLIQUID_GRID_STRATEGY_ABI = [
289
296
  ];
290
297
 
291
298
  // src/commands/strategy-template-lib.ts
292
- import { parseUnits as parseUnits2, formatUnits as formatUnits4, erc20Abi } from "viem";
299
+ import { parseUnits as parseUnits2, formatUnits as formatUnits4, isAddress as isAddress3, erc20Abi } from "viem";
293
300
  import chalk2 from "chalk";
294
301
 
295
302
  // src/lib/proposer-bond-error.ts
@@ -324,6 +331,13 @@ function emitCalldata(action) {
324
331
  process.stdout.write(JSON.stringify(action, null, 2) + "\n");
325
332
  }
326
333
 
334
+ // ../sdk/dist/generated/state-machines.js
335
+ var GUARDIAN_VOTE_TYPES = [
336
+ "None",
337
+ "Approve",
338
+ "Block"
339
+ ];
340
+
327
341
  // ../sdk/dist/types.js
328
342
  var CHAIN_IDS = {
329
343
  BASE: 8453,
@@ -519,6 +533,27 @@ var SYNDICATE_VAULT_ABI2 = [
519
533
  { name: "agentId", type: "uint256" }
520
534
  ],
521
535
  outputs: []
536
+ },
537
+ // Governor-only: mint + fund + run a single-use CallSandbox.
538
+ // ICallSandbox.Call is TWO fields (address,bytes). There is NO `value`.
539
+ {
540
+ name: "runSandbox",
541
+ type: "function",
542
+ stateMutability: "nonpayable",
543
+ inputs: [
544
+ { name: "pid", type: "uint256" },
545
+ {
546
+ name: "calls",
547
+ type: "tuple[]",
548
+ components: [
549
+ { name: "target", type: "address" },
550
+ { name: "data", type: "bytes" }
551
+ ]
552
+ },
553
+ { name: "declaredTokens", type: "address[]" },
554
+ { name: "funding", type: "uint256" }
555
+ ],
556
+ outputs: [{ name: "sandbox", type: "address" }]
522
557
  }
523
558
  ];
524
559
  var SYNDICATE_GOVERNOR_ABI = [
@@ -580,6 +615,74 @@ var SYNDICATE_GOVERNOR_ABI = [
580
615
  ],
581
616
  outputs: [{ name: "proposalId", type: "uint256" }]
582
617
  },
618
+ // proposeWithSandbox — same 10 args as propose, prefixed with SandboxPayload.
619
+ // ICallSandbox.Call is TWO fields (address,bytes). There is NO `value`:
620
+ // native value would open a transfer channel with no metering story
621
+ // (ICallSandbox.sol:44-51). Aliasing BatchExecutorLib.Call would reintroduce it.
622
+ {
623
+ name: "proposeWithSandbox",
624
+ type: "function",
625
+ stateMutability: "nonpayable",
626
+ inputs: [
627
+ {
628
+ name: "sandbox",
629
+ type: "tuple",
630
+ components: [
631
+ { name: "funding", type: "uint256" },
632
+ {
633
+ name: "calls",
634
+ type: "tuple[]",
635
+ components: [
636
+ { name: "target", type: "address" },
637
+ { name: "data", type: "bytes" }
638
+ ]
639
+ },
640
+ { name: "declaredTokens", type: "address[]" }
641
+ ]
642
+ },
643
+ { name: "vault", type: "address" },
644
+ { name: "strategy", type: "address" },
645
+ { name: "metadataURI", type: "string" },
646
+ { name: "strategyDuration", type: "uint256" },
647
+ {
648
+ name: "envelope",
649
+ type: "tuple",
650
+ components: [
651
+ { name: "maxCapital", type: "uint256" },
652
+ { name: "maxDrawdownBps", type: "uint16" }
653
+ ]
654
+ },
655
+ {
656
+ name: "executeCalls",
657
+ type: "tuple[]",
658
+ components: [
659
+ { name: "target", type: "address" },
660
+ { name: "data", type: "bytes" },
661
+ { name: "value", type: "uint256" }
662
+ ]
663
+ },
664
+ { name: "executeCallCaps", type: "uint256[]" },
665
+ {
666
+ name: "settlementCalls",
667
+ type: "tuple[]",
668
+ components: [
669
+ { name: "target", type: "address" },
670
+ { name: "data", type: "bytes" },
671
+ { name: "value", type: "uint256" }
672
+ ]
673
+ },
674
+ { name: "settlementCallCaps", type: "uint256[]" },
675
+ {
676
+ name: "coProposers",
677
+ type: "tuple[]",
678
+ components: [
679
+ { name: "agent", type: "address" },
680
+ { name: "splitBps", type: "uint256" }
681
+ ]
682
+ }
683
+ ],
684
+ outputs: [{ name: "proposalId", type: "uint256" }]
685
+ },
583
686
  // Lifecycle writes
584
687
  {
585
688
  name: "vote",
@@ -720,6 +823,35 @@ var SYNDICATE_GOVERNOR_ABI = [
720
823
  }
721
824
  ]
722
825
  },
826
+ // sandboxPayload — ISyndicateGovernor.sol ~730 / SyndicateGovernor.sol:506.
827
+ // Returns SandboxPayload {funding, calls, declaredTokens}. Calls are
828
+ // ICallSandbox.Call = (address target, bytes data) ONLY — no `value`.
829
+ // Non-sandbox (and unknown) pids return the mapping default: zero funding,
830
+ // empty calls, empty declaredTokens. The contract does not revert.
831
+ {
832
+ name: "sandboxPayload",
833
+ type: "function",
834
+ stateMutability: "view",
835
+ inputs: [{ name: "proposalId", type: "uint256" }],
836
+ outputs: [
837
+ {
838
+ name: "payload",
839
+ type: "tuple",
840
+ components: [
841
+ { name: "funding", type: "uint256" },
842
+ {
843
+ name: "calls",
844
+ type: "tuple[]",
845
+ components: [
846
+ { name: "target", type: "address" },
847
+ { name: "data", type: "bytes" }
848
+ ]
849
+ },
850
+ { name: "declaredTokens", type: "address[]" }
851
+ ]
852
+ }
853
+ ]
854
+ },
723
855
  {
724
856
  name: "getProposalState",
725
857
  type: "function",
@@ -942,60 +1074,64 @@ var GUARDIAN_REGISTRY_ABI = [
942
1074
  inputs: [],
943
1075
  outputs: []
944
1076
  },
945
- // Delegation
1077
+ // Delegation and commission fragments removed (SHE-86): delegateStake,
1078
+ // requestUnstakeDelegation, cancelUnstakeDelegation, claimUnstakeDelegation
1079
+ // and setCommission do not exist on the post-audit contracts.
1080
+ // Reads
946
1081
  {
947
- name: "delegateStake",
1082
+ name: "guardianStake",
948
1083
  type: "function",
949
- stateMutability: "nonpayable",
950
- inputs: [
951
- { name: "delegate", type: "address" },
952
- { name: "amount", type: "uint256" }
953
- ],
954
- outputs: []
1084
+ stateMutability: "view",
1085
+ inputs: [{ name: "guardian", type: "address" }],
1086
+ outputs: [{ name: "", type: "uint256" }]
955
1087
  },
956
1088
  {
957
- name: "requestUnstakeDelegation",
1089
+ name: "isActiveGuardian",
958
1090
  type: "function",
959
- stateMutability: "nonpayable",
960
- inputs: [{ name: "delegate", type: "address" }],
961
- outputs: []
1091
+ stateMutability: "view",
1092
+ inputs: [{ name: "guardian", type: "address" }],
1093
+ outputs: [{ name: "", type: "bool" }]
962
1094
  },
1095
+ // Guardian review vote — 3 args, no slashBps. Selector 0x3ad95b6d.
1096
+ // GuardianVoteType: None=0, Approve=1, Block=2 (uint8).
963
1097
  {
964
- name: "cancelUnstakeDelegation",
1098
+ name: "voteOnProposal",
965
1099
  type: "function",
966
1100
  stateMutability: "nonpayable",
967
- inputs: [{ name: "delegate", type: "address" }],
1101
+ inputs: [
1102
+ { name: "governor", type: "address" },
1103
+ { name: "proposalId", type: "uint256" },
1104
+ { name: "support", type: "uint8" }
1105
+ ],
968
1106
  outputs: []
969
1107
  },
1108
+ // Cached review flags. Returns THREE bools (opened, resolved, blocked).
1109
+ // A fourth output is the SHE-88 arity break: viem refuses the 96-byte returndata.
970
1110
  {
971
- name: "claimUnstakeDelegation",
1111
+ name: "getReviewState",
972
1112
  type: "function",
973
- stateMutability: "nonpayable",
974
- inputs: [{ name: "delegate", type: "address" }],
975
- outputs: []
976
- },
977
- // Commission
1113
+ stateMutability: "view",
1114
+ inputs: [
1115
+ { name: "governor", type: "address" },
1116
+ { name: "proposalId", type: "uint256" }
1117
+ ],
1118
+ outputs: [
1119
+ { name: "opened", type: "bool" },
1120
+ { name: "resolved", type: "bool" },
1121
+ { name: "blocked", type: "bool" }
1122
+ ]
1123
+ }
1124
+ ];
1125
+ var STAKED_WOOD_ABI = [
978
1126
  {
979
- name: "setCommission",
1127
+ name: "stakeAsGuardian",
980
1128
  type: "function",
981
1129
  stateMutability: "nonpayable",
982
- inputs: [{ name: "bps", type: "uint16" }],
1130
+ inputs: [
1131
+ { name: "amount", type: "uint256" },
1132
+ { name: "agentId", type: "uint256" }
1133
+ ],
983
1134
  outputs: []
984
- },
985
- // Reads
986
- {
987
- name: "guardianStake",
988
- type: "function",
989
- stateMutability: "view",
990
- inputs: [{ name: "guardian", type: "address" }],
991
- outputs: [{ name: "", type: "uint256" }]
992
- },
993
- {
994
- name: "isActiveGuardian",
995
- type: "function",
996
- stateMutability: "view",
997
- inputs: [{ name: "guardian", type: "address" }],
998
- outputs: [{ name: "", type: "bool" }]
999
1135
  }
1000
1136
  ];
1001
1137
  var ERC20_ABI2 = [
@@ -1127,6 +1263,17 @@ var STRATEGY_FACTORY_ABI = [
1127
1263
  // ../sdk/dist/addresses.js
1128
1264
  var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
1129
1265
  var ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
1266
+ var NO_GUARDIAN_ERA = {
1267
+ governorBeacon: ZERO_ADDRESS,
1268
+ protocolConfig: ZERO_ADDRESS,
1269
+ priceRouter: ZERO_ADDRESS,
1270
+ tierRegistry: ZERO_ADDRESS,
1271
+ callSandboxImpl: ZERO_ADDRESS,
1272
+ exposureLedger: ZERO_ADDRESS,
1273
+ proposerBondEscrow: ZERO_ADDRESS,
1274
+ challengeGame: ZERO_ADDRESS,
1275
+ tokenCourt: ZERO_ADDRESS
1276
+ };
1130
1277
  var BASE_EAS = {
1131
1278
  chainId: CHAIN_IDS.BASE,
1132
1279
  address: "0x4200000000000000000000000000000000000021",
@@ -1147,11 +1294,15 @@ var BASE = {
1147
1294
  sherwood: {
1148
1295
  factory: "0xAC74EC56858d7F1f7618c8e77F65Fc26aDf33c82",
1149
1296
  guardianRegistry: "0x49E4163b5e4b23F8f3d469Cf6fa197FB6b06A26E",
1297
+ stakedWood: ZERO_ADDRESS,
1298
+ // not in chains/8453.json
1150
1299
  vaultImpl: "0xfce4bcE08E9C047E4736f75C2B8557e2754Ce36A",
1151
1300
  batchExecutorLib: "0xbC79FbD5036C1Cc4A9d10BDf8628BF09a558496E",
1152
1301
  // Not yet deployed on Base — fill in `STRATEGY_FACTORY` from
1153
1302
  // contracts/chains/8453.json when it lands (issue #387).
1154
- strategyFactory: ZERO_ADDRESS
1303
+ strategyFactory: ZERO_ADDRESS,
1304
+ // None of the guardian-era contracts are in chains/8453.json yet.
1305
+ ...NO_GUARDIAN_ERA
1155
1306
  },
1156
1307
  identityRegistry: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
1157
1308
  eas: BASE_EAS,
@@ -1177,9 +1328,13 @@ var HYPEREVM = {
1177
1328
  sherwood: {
1178
1329
  factory: "0xd05Ae0E8bcf13075C29817c805d6Cc14F214393a",
1179
1330
  guardianRegistry: "0x8b5710EB4e2fA639F364Dcc3F3B30c8f12F460b9",
1331
+ stakedWood: ZERO_ADDRESS,
1332
+ // no known sWOOD deployment
1180
1333
  vaultImpl: "0x2cbBe36Cf907A2BB410bacB0e4Fd632C7b012846",
1181
1334
  batchExecutorLib: "0x2c454bEF1b09c8a306a7058b8B510bF0DfF7179D",
1182
- strategyFactory: ZERO_ADDRESS
1335
+ strategyFactory: ZERO_ADDRESS,
1336
+ // No canonical chain book for HyperEVM — guardian-era contracts unknown.
1337
+ ...NO_GUARDIAN_ERA
1183
1338
  },
1184
1339
  // ERC-8004 identity is minted on Base; HyperEVM funds reference the
1185
1340
  // Base-minted token id. Coordination attestations are pinned to Base EAS.
@@ -1202,9 +1357,13 @@ var ROBINHOOD = {
1202
1357
  sherwood: {
1203
1358
  factory: ZERO_ADDRESS,
1204
1359
  guardianRegistry: ZERO_ADDRESS,
1360
+ stakedWood: ZERO_ADDRESS,
1361
+ // 4663.json has WOOD, no STAKED_WOOD
1205
1362
  vaultImpl: ZERO_ADDRESS,
1206
1363
  batchExecutorLib: ZERO_ADDRESS,
1207
- strategyFactory: ZERO_ADDRESS
1364
+ strategyFactory: ZERO_ADDRESS,
1365
+ // Sherwood not deployed on Robinhood mainnet — no guardian-era contracts.
1366
+ ...NO_GUARDIAN_ERA
1208
1367
  },
1209
1368
  // No ERC-8004 registry and no EAS path on Robinhood Chain.
1210
1369
  identityRegistry: ZERO_ADDRESS,
@@ -1233,14 +1392,32 @@ var ROBINHOOD_L2 = {
1233
1392
  sherwood: {
1234
1393
  factory: "0xa91AA45AFF32f52b6357044B02a16EBA775feC0b",
1235
1394
  guardianRegistry: "0xA400eFcfFc820C6f812203C58ee00423AeCC0903",
1395
+ stakedWood: "0x21A69A6c9814c0d339C57fDdafed3B283702a739",
1236
1396
  vaultImpl: "0xC57e12d6e2d8Ed49316F0b69c51893CcA44151F7",
1237
1397
  batchExecutorLib: "0xF3b8db5aa41c7Ce92478A0Fa9C55a6460533eb86",
1238
- strategyFactory: "0xb683Bb8EEcBc2419BC3801df6FeA88f96657e670"
1398
+ strategyFactory: "0xb683Bb8EEcBc2419BC3801df6FeA88f96657e670",
1399
+ // Guardian-era contracts from contracts/chains/46630.json (SHE-93).
1400
+ governorBeacon: "0x3D46Ec018cd5893b685b4dfdc3921A4Eb64E11d1",
1401
+ protocolConfig: "0xC104Eb6a522d6718cA28F344B2373B29d57FF2E0",
1402
+ priceRouter: "0xDd302ffcfA08071780eC1A2f12BccFB9ba6b6731",
1403
+ tierRegistry: "0x99b8068Dc0F6093466964D581f72d947e3e380DB",
1404
+ callSandboxImpl: "0xf09f6AF7DeBB964eD731376C9Af389F2Ce3d872A",
1405
+ // Not in chains/46630.json (econ-security stack not deployed there yet).
1406
+ exposureLedger: ZERO_ADDRESS,
1407
+ proposerBondEscrow: ZERO_ADDRESS,
1408
+ challengeGame: ZERO_ADDRESS,
1409
+ tokenCourt: ZERO_ADDRESS
1239
1410
  },
1240
1411
  // No ERC-8004 registry and no EAS path on Robinhood L2.
1241
1412
  identityRegistry: ZERO_ADDRESS,
1242
1413
  eas: NO_EAS,
1243
- tokens: {},
1414
+ tokens: {
1415
+ wood: {
1416
+ address: "0xCCb4fB59cf40de1E23083037ee81Da1DD747D8d7",
1417
+ symbol: "WOOD",
1418
+ decimals: 18
1419
+ }
1420
+ },
1244
1421
  explorer: "https://explorer.testnet.chain.robinhood.com",
1245
1422
  rpcs: ["https://rpc.testnet.chain.robinhood.com"]
1246
1423
  };
@@ -1251,9 +1428,21 @@ var ROBINHOOD_FORK = {
1251
1428
  sherwood: {
1252
1429
  factory: "0x02bc9B9C1A4322D5A5df86DadA0a2a02e45108B9",
1253
1430
  guardianRegistry: "0xdfEe38C4D7595c9c04AdBcaFE01E4Fd6FD6117f4",
1431
+ stakedWood: "0xD3037D28693cc7BB3DbF1E17B6e01C4ce628f6DF",
1254
1432
  vaultImpl: "0x9BF51da59B13746e6cCeb1b35c398EDA9776a4Dc",
1255
1433
  batchExecutorLib: "0xdBdBce2af4222Abe0cCDAc05f84364a50577B5a2",
1256
- strategyFactory: "0xb3C009aECAeDd5ccC62Ec12eDAAA55F19C4A1eFb"
1434
+ strategyFactory: "0xb3C009aECAeDd5ccC62Ec12eDAAA55F19C4A1eFb",
1435
+ // Guardian-era contracts from sherwood-protocol/chains/9994663.json (SHE-93).
1436
+ governorBeacon: "0x2e8aB422Db9127F1a70d8C38A15903f22B51DA97",
1437
+ protocolConfig: "0x36C45dC52FbC267d8536E8456ADe5255eC83A5ab",
1438
+ // Not in chains/9994663.json (no PriceRouter / CallSandbox on the fork).
1439
+ priceRouter: ZERO_ADDRESS,
1440
+ tierRegistry: "0x42B089B816F994d792481fAa308199E9CF63be9f",
1441
+ callSandboxImpl: ZERO_ADDRESS,
1442
+ exposureLedger: "0x223e2Fd41aD6487333146fc3998d000E74CB1549",
1443
+ proposerBondEscrow: "0xef2D6994faCad11e6CfAF3DaBf7b0B2091009B05",
1444
+ challengeGame: "0x202c1F133006D6706e872433CCAfD729c7A8AE56",
1445
+ tokenCourt: "0x5DC603881f5d77f41bbd4202F1d518263Dfd497f"
1257
1446
  },
1258
1447
  // No ERC-8004 registry and no EAS path on the Robinhood fork.
1259
1448
  identityRegistry: ZERO_ADDRESS,
@@ -1263,6 +1452,11 @@ var ROBINHOOD_FORK = {
1263
1452
  address: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73",
1264
1453
  symbol: "WETH",
1265
1454
  decimals: 18
1455
+ },
1456
+ wood: {
1457
+ address: "0xF8BC08092C06dB6148114DCf82AF881F1085f92b",
1458
+ symbol: "WOOD",
1459
+ decimals: 18
1266
1460
  }
1267
1461
  },
1268
1462
  // A Tenderly vnet scopes its blocks under /explorer/vnet/<id> — the bare
@@ -1689,6 +1883,155 @@ function encodePropose(args, chainId) {
1689
1883
  note: "Reverts if the vault has an active proposal (cooldown not elapsed). The strategy address is immutable post-propose; pass address(0) to opt out of live NAV. `coProposers[].splitBps` total must satisfy contract-side constraints."
1690
1884
  };
1691
1885
  }
1886
+ var MAX_SANDBOX_CALLS = 32;
1887
+ var MAX_SANDBOX_TOKENS = 16;
1888
+ function validateSandboxCall(call, idx) {
1889
+ if (!call || typeof call !== "object") {
1890
+ throw usage(`\`sandbox.calls[${idx}]\` must be an object of {target, data}`);
1891
+ }
1892
+ const c = call;
1893
+ if ("value" in c && c.value !== void 0 && c.value !== null) {
1894
+ throw usage(`\`sandbox.calls[${idx}]\` must be (address,bytes) only \u2014 ICallSandbox.Call has no \`value\` field (native value would open an unmetered transfer channel). Do not alias BatchExecutorLib.Call.`);
1895
+ }
1896
+ if (typeof c.target !== "string" || !isAddress(c.target)) {
1897
+ throw usage(`\`sandbox.calls[${idx}].target\` must be a 0x-prefixed address`);
1898
+ }
1899
+ if (getAddress2(c.target) === ZERO_ADDRESS2) {
1900
+ throw usage(`\`sandbox.calls[${idx}].target\` must not be the zero address (the governor reverts ZeroSandboxTarget)`);
1901
+ }
1902
+ if (typeof c.data !== "string" || !/^0x[0-9a-fA-F]*$/.test(c.data)) {
1903
+ throw usage(`\`sandbox.calls[${idx}].data\` must be a 0x-prefixed hex string`);
1904
+ }
1905
+ return { target: getAddress2(c.target), data: c.data };
1906
+ }
1907
+ function validateSandboxPayload(sandbox, maxCapital) {
1908
+ if (!sandbox || typeof sandbox !== "object") {
1909
+ throw usage("`sandbox` must be an object of {funding, calls, declaredTokens}");
1910
+ }
1911
+ const s = sandbox;
1912
+ const funding = toBigInt(s.funding, "sandbox.funding");
1913
+ if (funding === 0n) {
1914
+ throw usage("`sandbox.funding` must be > 0 (the governor rejects zero as ZeroSandboxFunding)");
1915
+ }
1916
+ if (funding > maxCapital) {
1917
+ throw usage(`\`sandbox.funding\` (${funding}) exceeds \`envelope.maxCapital\` (${maxCapital}) \u2014 the sandbox is funded out of the declared envelope, not beside it`);
1918
+ }
1919
+ if (!Array.isArray(s.calls)) {
1920
+ throw usage("`sandbox.calls` must be an array of {target, data} objects (no value)");
1921
+ }
1922
+ if (s.calls.length === 0) {
1923
+ throw usage("`sandbox.calls` must be non-empty (the governor rejects empty as EmptySandboxCalls)");
1924
+ }
1925
+ if (s.calls.length > MAX_SANDBOX_CALLS) {
1926
+ throw usage(`\`sandbox.calls\` has ${s.calls.length} entries; max is ${MAX_SANDBOX_CALLS}`);
1927
+ }
1928
+ const calls = s.calls.map((c, i) => validateSandboxCall(c, i));
1929
+ const rawTokens = s.declaredTokens ?? [];
1930
+ if (!Array.isArray(rawTokens)) {
1931
+ throw usage("`sandbox.declaredTokens` must be an array of addresses");
1932
+ }
1933
+ if (rawTokens.length > MAX_SANDBOX_TOKENS) {
1934
+ throw usage(`\`sandbox.declaredTokens\` has ${rawTokens.length} entries; max is ${MAX_SANDBOX_TOKENS}`);
1935
+ }
1936
+ const declaredTokens = [];
1937
+ const seen = /* @__PURE__ */ new Set();
1938
+ for (let i = 0; i < rawTokens.length; i++) {
1939
+ const t = rawTokens[i];
1940
+ if (typeof t !== "string" || !isAddress(t)) {
1941
+ throw usage(`\`sandbox.declaredTokens[${i}]\` must be a 0x-prefixed address`);
1942
+ }
1943
+ const addr = getAddress2(t);
1944
+ if (seen.has(addr.toLowerCase())) {
1945
+ throw usage(`\`sandbox.declaredTokens[${i}]\` duplicates ${addr} (the governor reverts DuplicateSandboxToken)`);
1946
+ }
1947
+ seen.add(addr.toLowerCase());
1948
+ declaredTokens.push(addr);
1949
+ }
1950
+ return { funding, calls, declaredTokens };
1951
+ }
1952
+ function encodeProposeWithSandbox(args, chainId) {
1953
+ const vault = getAddress2(args.vault);
1954
+ const governor = parseGovernor(args.governor);
1955
+ const strategy2 = args.strategy === ZERO_ADDRESS2 ? ZERO_ADDRESS2 : getAddress2(args.strategy);
1956
+ if (typeof args.metadataURI !== "string" || args.metadataURI.length === 0) {
1957
+ throw usage("`metadataURI` must be a non-empty string (e.g. ipfs://...)");
1958
+ }
1959
+ const strategyDuration = typeof args.strategyDuration === "bigint" ? args.strategyDuration : BigInt(args.strategyDuration);
1960
+ if (strategyDuration <= 0n) {
1961
+ throw usage("`strategyDuration` must be > 0 (seconds)");
1962
+ }
1963
+ if (!Array.isArray(args.executeCalls)) {
1964
+ throw usage("`executeCalls` must be an array of {target, data, value} objects");
1965
+ }
1966
+ if (!Array.isArray(args.settlementCalls)) {
1967
+ throw usage("`settlementCalls` must be an array of {target, data, value} objects");
1968
+ }
1969
+ const executeCalls = args.executeCalls.map((c, i) => validateBatchCall(c, "executeCalls", i));
1970
+ const settlementCalls = args.settlementCalls.map((c, i) => validateBatchCall(c, "settlementCalls", i));
1971
+ const coProposers = (args.coProposers ?? []).map((cp, i) => {
1972
+ if (!cp || typeof cp !== "object") {
1973
+ throw usage(`\`coProposers[${i}]\` must be an object`);
1974
+ }
1975
+ const agent = cp.agent;
1976
+ const splitBps = cp.splitBps;
1977
+ if (typeof agent !== "string" || !isAddress(agent)) {
1978
+ throw usage(`\`coProposers[${i}].agent\` must be a 0x-prefixed address`);
1979
+ }
1980
+ let bps;
1981
+ if (typeof splitBps === "string" && /^\d+$/.test(splitBps)) {
1982
+ bps = BigInt(splitBps);
1983
+ } else if (typeof splitBps === "bigint") {
1984
+ bps = splitBps;
1985
+ } else if (typeof splitBps === "number" && Number.isInteger(splitBps) && splitBps >= 0) {
1986
+ bps = BigInt(splitBps);
1987
+ } else {
1988
+ throw usage(`\`coProposers[${i}].splitBps\` must be a non-negative integer (string, number, or bigint)`);
1989
+ }
1990
+ if (bps < 0n || bps > 10000n) {
1991
+ throw usage(`\`coProposers[${i}].splitBps\` must be in [0, 10000]`);
1992
+ }
1993
+ return { agent: getAddress2(agent), splitBps: bps };
1994
+ });
1995
+ if (!args.envelope || typeof args.envelope !== "object") {
1996
+ throw usage("`envelope` must be an object of {maxCapital, maxDrawdownBps}");
1997
+ }
1998
+ const maxCapital = toBigInt(args.envelope.maxCapital, "envelope.maxCapital");
1999
+ if (maxCapital === 0n) {
2000
+ throw usage("`envelope.maxCapital` must be > 0 (the governor rejects zero as ZeroMaxCapital)");
2001
+ }
2002
+ const maxDrawdownBps = args.envelope.maxDrawdownBps;
2003
+ if (!Number.isInteger(maxDrawdownBps) || maxDrawdownBps < 0 || maxDrawdownBps > 1e4) {
2004
+ throw usage("`envelope.maxDrawdownBps` must be an integer in [0, 10000]");
2005
+ }
2006
+ const executeCallCaps = validateCaps(args.executeCallCaps, executeCalls, maxCapital, "executeCallCaps");
2007
+ const settlementCallCaps = validateCaps(args.settlementCallCaps, settlementCalls, maxCapital, "settlementCallCaps");
2008
+ const sandbox = validateSandboxPayload(args.sandbox, maxCapital);
2009
+ const data = encodeFunctionData2({
2010
+ abi: SYNDICATE_GOVERNOR_ABI,
2011
+ functionName: "proposeWithSandbox",
2012
+ args: [
2013
+ sandbox,
2014
+ vault,
2015
+ strategy2,
2016
+ args.metadataURI,
2017
+ strategyDuration,
2018
+ { maxCapital, maxDrawdownBps },
2019
+ executeCalls,
2020
+ executeCallCaps,
2021
+ settlementCalls,
2022
+ settlementCallCaps,
2023
+ coProposers
2024
+ ]
2025
+ });
2026
+ return {
2027
+ txs: [governorTx(governor, chainId, data)],
2028
+ preconditions: [
2029
+ { type: "vault-not-locked", vault }
2030
+ ],
2031
+ description: `Propose a sandbox strategy on ${vault} (funding ${sandbox.funding.toString()}, ${sandbox.calls.length} sandbox call(s), duration ${strategyDuration.toString()}s).`,
2032
+ note: "Reverts if the vault has an active proposal, has no CallSandbox implementation bound, or the payload fails propose-time sandbox gates (empty calls, zero funding, funding > maxCapital, zero target, duplicate declared tokens). ICallSandbox.Call is (address,bytes) only \u2014 no native value."
2033
+ };
2034
+ }
1692
2035
  function encodeUnstick(args, chainId) {
1693
2036
  const governor = parseGovernor(args.governor);
1694
2037
  const id = parseProposalId(args.proposalId);
@@ -2057,6 +2400,13 @@ function encodeApproveAgent(args, chainId) {
2057
2400
  // ../sdk/dist/encoders/guardian.js
2058
2401
  import { encodeFunctionData as encodeFunctionData7, getAddress as getAddress7 } from "viem";
2059
2402
  var ZERO_VALUE5 = "0x0";
2403
+ function swoodTx(chainId, data) {
2404
+ const swood2 = getDeployment(chainId).sherwood.stakedWood;
2405
+ if (swood2 === "0x0000000000000000000000000000000000000000") {
2406
+ throw unsupported(`sWOOD (StakedWood) is not deployed on chain ${chainId} yet`);
2407
+ }
2408
+ return { to: swood2, data, value: ZERO_VALUE5, chainId };
2409
+ }
2060
2410
  function registryTx(chainId, data) {
2061
2411
  const registry = getDeployment(chainId).sherwood.guardianRegistry;
2062
2412
  if (registry === "0x0000000000000000000000000000000000000000") {
@@ -2086,26 +2436,31 @@ function encodeGuardianStake(args, chainId) {
2086
2436
  throw usage("`amount` must be > 0");
2087
2437
  const agentId = parseUint(args.agentId, "agentId");
2088
2438
  const data = encodeFunctionData7({
2089
- abi: GUARDIAN_REGISTRY_ABI,
2439
+ abi: STAKED_WOOD_ABI,
2090
2440
  functionName: "stakeAsGuardian",
2091
2441
  args: [args.amount, agentId]
2092
2442
  });
2093
- const registry = getDeployment(chainId).sherwood.guardianRegistry;
2443
+ const deployment = getDeployment(chainId);
2444
+ const wood2 = deployment.tokens.wood?.address;
2445
+ const swood2 = deployment.sherwood.stakedWood;
2446
+ if (!wood2 || wood2 === "0x0000000000000000000000000000000000000000" || swood2 === "0x0000000000000000000000000000000000000000") {
2447
+ throw unsupported(`WOOD / sWOOD is not deployed on chain ${chainId} yet`);
2448
+ }
2094
2449
  return {
2095
- txs: [registryTx(chainId, data)],
2450
+ txs: [swoodTx(chainId, data)],
2096
2451
  preconditions: [
2097
- // Caller must have approved WOOD for the registry; agent should
2452
+ // Caller must have approved WOOD for sWOOD (StakedWood); agent should
2098
2453
  // check this client-side and prepend an ERC20.approve if needed.
2099
2454
  {
2100
2455
  type: "allowance",
2101
- token: registry,
2102
- spender: registry,
2456
+ token: wood2,
2457
+ spender: swood2,
2103
2458
  min: args.amount.toString(),
2104
2459
  minDecimal: args.amount.toString()
2105
2460
  }
2106
2461
  ],
2107
2462
  description: `Stake ${args.amount.toString()} WOOD as guardian (agent ${agentId.toString()}).`,
2108
- note: "Caller must have approved WOOD for the registry first. Stake locks the WOOD; use /prepare/guardian-unstake later to start the 7d unstake cooldown."
2463
+ note: "Caller must have approved WOOD for sWOOD (StakedWood) first. Stake locks the WOOD; use /prepare/guardian-unstake later to start the 7d unstake cooldown."
2109
2464
  };
2110
2465
  }
2111
2466
  var GUARDIAN_UNSTAKE_FN = {
@@ -2130,59 +2485,29 @@ function encodeGuardianUnstake(args, chainId) {
2130
2485
  note: args.action === "request" ? "Starts a 7-day cooldown. After it elapses, call again with action=claim to withdraw." : args.action === "claim" ? "Withdraws the unstake amount once the 7-day cooldown has elapsed." : "Cancels a pending unstake request and re-activates the guardian."
2131
2486
  };
2132
2487
  }
2133
- function encodeGuardianDelegate(args, chainId) {
2134
- if (args.amount <= 0n)
2135
- throw usage("`amount` must be > 0");
2136
- const delegate = getAddress7(args.delegate);
2137
- const data = encodeFunctionData7({
2138
- abi: GUARDIAN_REGISTRY_ABI,
2139
- functionName: "delegateStake",
2140
- args: [delegate, args.amount]
2141
- });
2142
- return {
2143
- txs: [registryTx(chainId, data)],
2144
- preconditions: [],
2145
- description: `Delegate ${args.amount.toString()} WOOD to ${delegate}.`,
2146
- note: "Caller must have approved WOOD for the registry first. Delegation does NOT activate the delegate as a guardian \u2014 they must self-stake separately."
2147
- };
2148
- }
2149
- var DELEGATION_UNSTAKE_FN = {
2150
- request: "requestUnstakeDelegation",
2151
- cancel: "cancelUnstakeDelegation",
2152
- claim: "claimUnstakeDelegation"
2153
- };
2154
- function encodeDelegationUnstake(args, chainId) {
2155
- const fn = DELEGATION_UNSTAKE_FN[args.action];
2156
- if (!fn) {
2157
- throw usage(`\`action\` must be one of: ${Object.keys(DELEGATION_UNSTAKE_FN).join(", ")}`);
2488
+ function encodeVoteOnProposal(args, chainId) {
2489
+ let governor;
2490
+ try {
2491
+ governor = getAddress7(args.governor);
2492
+ } catch {
2493
+ throw usage("`governor` must be a 0x-prefixed 40-char hex address");
2158
2494
  }
2159
- const delegate = getAddress7(args.delegate);
2160
- const data = encodeFunctionData7({
2161
- abi: GUARDIAN_REGISTRY_ABI,
2162
- functionName: fn,
2163
- args: [delegate]
2164
- });
2165
- return {
2166
- txs: [registryTx(chainId, data)],
2167
- preconditions: [],
2168
- description: `Delegation unstake ${args.action} for delegate ${delegate}.`,
2169
- note: args.action === "request" ? "Starts a 7-day cooldown on the delegated amount." : args.action === "claim" ? "Withdraws the delegated amount once the 7-day cooldown has elapsed." : "Cancels a pending unstake of the delegation."
2170
- };
2171
- }
2172
- function encodeSetCommission(args, chainId) {
2173
- if (!Number.isInteger(args.bps) || args.bps < 0 || args.bps > 5e3) {
2174
- throw usage("`bps` must be an integer in [0, 5000] (0\u201350%)");
2495
+ const support = GUARDIAN_VOTE_TYPES.indexOf(args.vote);
2496
+ if (support <= 0) {
2497
+ throw usage("`vote` must be one of: Approve, Block");
2175
2498
  }
2499
+ const proposalId = parseUint(args.proposalId, "proposalId");
2176
2500
  const data = encodeFunctionData7({
2177
2501
  abi: GUARDIAN_REGISTRY_ABI,
2178
- functionName: "setCommission",
2179
- args: [args.bps]
2502
+ functionName: "voteOnProposal",
2503
+ args: [governor, proposalId, support]
2180
2504
  });
2505
+ const underwriting = args.vote === "Approve" ? " APPROVE books coverage against the CALLER'S OWN slashable guardian bond (ExposureLedger.recordApproval). If this proposal is later challenged or slashed, your WOOD stake is at risk \u2014 not another guardian's. An over-exposed guardian books nothing rather than reverting; the vote still lands." : " Block votes do not book coverage. Slash severity is computed at resolveReview from block-side decisiveness.";
2181
2506
  return {
2182
2507
  txs: [registryTx(chainId, data)],
2183
2508
  preconditions: [],
2184
- description: `Set DPoS commission to ${args.bps} bps.`,
2185
- note: "Raise-rate is capped at 500 bps above the epoch-start rate; first set on a delegate is exempt. Decreases are unbounded."
2509
+ description: `Guardian review vote ${args.vote} on proposal ${proposalId.toString()} (governor ${governor}).`,
2510
+ note: `voteOnProposal(governor, proposalId, vote) \u2014 3 args, selector 0x3ad95b6d, no slashBps.${underwriting}`
2186
2511
  };
2187
2512
  }
2188
2513
 
@@ -2196,7 +2521,7 @@ var DIM = chalk.gray;
2196
2521
  var BOLD = chalk.white.bold;
2197
2522
  var LABEL = chalk.green.bold;
2198
2523
  var SEP = () => console.log(DIM("\u2500".repeat(60)));
2199
- var STAKED_WOOD_ABI = [
2524
+ var STAKED_WOOD_ABI2 = [
2200
2525
  // Guardian stake
2201
2526
  { type: "function", name: "stakeAsGuardian", inputs: [{ type: "uint256" }, { type: "uint256" }], outputs: [], stateMutability: "nonpayable" },
2202
2527
  // Owner bond (required before SyndicateFactory.createSyndicate)
@@ -2204,19 +2529,9 @@ var STAKED_WOOD_ABI = [
2204
2529
  { type: "function", name: "requestUnstakeGuardian", inputs: [], outputs: [], stateMutability: "nonpayable" },
2205
2530
  { type: "function", name: "cancelUnstakeGuardian", inputs: [], outputs: [], stateMutability: "nonpayable" },
2206
2531
  { type: "function", name: "claimUnstakeGuardian", inputs: [], outputs: [], stateMutability: "nonpayable" },
2207
- // Delegation
2208
- { type: "function", name: "delegateStake", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [], stateMutability: "nonpayable" },
2209
- { type: "function", name: "requestUnstakeDelegation", inputs: [{ type: "address" }], outputs: [], stateMutability: "nonpayable" },
2210
- { type: "function", name: "cancelUnstakeDelegation", inputs: [{ type: "address" }], outputs: [], stateMutability: "nonpayable" },
2211
- { type: "function", name: "claimUnstakeDelegation", inputs: [{ type: "address" }], outputs: [], stateMutability: "nonpayable" },
2212
- // Commission
2213
- { type: "function", name: "setCommission", inputs: [{ type: "uint256" }], outputs: [], stateMutability: "nonpayable" },
2214
2532
  // Views
2215
2533
  { type: "function", name: "guardianStake", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }], stateMutability: "view" },
2216
- { type: "function", name: "isActiveGuardian", inputs: [{ type: "address" }], outputs: [{ type: "bool" }], stateMutability: "view" },
2217
- { type: "function", name: "commissionOf", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }], stateMutability: "view" },
2218
- { type: "function", name: "delegationOf", inputs: [{ type: "address" }, { type: "address" }], outputs: [{ type: "uint256" }], stateMutability: "view" },
2219
- { type: "function", name: "delegatedInbound", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }], stateMutability: "view" }
2534
+ { type: "function", name: "isActiveGuardian", inputs: [{ type: "address" }], outputs: [{ type: "bool" }], stateMutability: "view" }
2220
2535
  ];
2221
2536
  var ERC20_ABI3 = [
2222
2537
  { type: "function", name: "approve", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }], stateMutability: "nonpayable" },
@@ -2267,8 +2582,8 @@ async function ensureWoodAllowance(amount, spender, label = "WOOD") {
2267
2582
  }
2268
2583
  }
2269
2584
  function registerGuardianCommands(program2) {
2270
- const guardian = program2.command("guardian").description("Guardian staking, delegation, and rewards (V1.5)");
2271
- guardian.command("status [address]").description("Show guardian stake + commission + active status for an address (default: caller)").action(async (addressArg) => {
2585
+ const guardian = program2.command("guardian").description("Guardian staking and rewards");
2586
+ guardian.command("status [address]").description("Show guardian stake + active status for an address (default: caller)").action(async (addressArg) => {
2272
2587
  const pc = getPublicClient();
2273
2588
  let target;
2274
2589
  if (addressArg) {
@@ -2283,17 +2598,10 @@ function registerGuardianCommands(program2) {
2283
2598
  }
2284
2599
  const spinner = ora("Loading...").start();
2285
2600
  try {
2286
- const [stake, active, commission] = await Promise.all([
2287
- pc.readContract({ address: swood(), abi: STAKED_WOOD_ABI, functionName: "guardianStake", args: [target] }),
2288
- pc.readContract({ address: swood(), abi: STAKED_WOOD_ABI, functionName: "isActiveGuardian", args: [target] }),
2289
- pc.readContract({ address: swood(), abi: STAKED_WOOD_ABI, functionName: "commissionOf", args: [target] })
2601
+ const [stake, active] = await Promise.all([
2602
+ pc.readContract({ address: swood(), abi: STAKED_WOOD_ABI2, functionName: "guardianStake", args: [target] }),
2603
+ pc.readContract({ address: swood(), abi: STAKED_WOOD_ABI2, functionName: "isActiveGuardian", args: [target] })
2290
2604
  ]);
2291
- const delegatedIn = await pc.readContract({
2292
- address: swood(),
2293
- abi: STAKED_WOOD_ABI,
2294
- functionName: "delegatedInbound",
2295
- args: [target]
2296
- });
2297
2605
  spinner.stop();
2298
2606
  console.log();
2299
2607
  console.log(LABEL(" \u25C6 Guardian Status"));
@@ -2301,8 +2609,6 @@ function registerGuardianCommands(program2) {
2301
2609
  console.log(W(` Address: ${G(target)}`));
2302
2610
  console.log(W(` Active guardian: ${active ? G("yes") : DIM("no")}`));
2303
2611
  console.log(W(` Own stake: ${BOLD(formatUnits3(stake, 18))} WOOD`));
2304
- console.log(W(` Delegated inbound: ${BOLD(formatUnits3(delegatedIn, 18))} WOOD`));
2305
- console.log(W(` Commission: ${BOLD(`${Number(commission) / 100}%`)}`));
2306
2612
  SEP();
2307
2613
  console.log();
2308
2614
  } catch (err) {
@@ -2339,7 +2645,7 @@ function registerGuardianCommands(program2) {
2339
2645
  try {
2340
2646
  const hash = await wallet.writeContract({
2341
2647
  address: swood(),
2342
- abi: STAKED_WOOD_ABI,
2648
+ abi: STAKED_WOOD_ABI2,
2343
2649
  functionName: "stakeAsGuardian",
2344
2650
  args: [amount, agentId],
2345
2651
  chain: null,
@@ -2364,7 +2670,7 @@ function registerGuardianCommands(program2) {
2364
2670
  try {
2365
2671
  const hash = await wallet.writeContract({
2366
2672
  address: swood(),
2367
- abi: STAKED_WOOD_ABI,
2673
+ abi: STAKED_WOOD_ABI2,
2368
2674
  functionName: "prepareOwnerStake",
2369
2675
  args: [amount],
2370
2676
  chain: null,
@@ -2394,7 +2700,7 @@ function registerGuardianCommands(program2) {
2394
2700
  const pc = getPublicClient();
2395
2701
  const spinner = ora(`${action}...`).start();
2396
2702
  try {
2397
- const hash = await wallet.writeContract({ address: swood(), abi: STAKED_WOOD_ABI, functionName: fn, args: [], chain: null, account: wallet.account });
2703
+ const hash = await wallet.writeContract({ address: swood(), abi: STAKED_WOOD_ABI2, functionName: fn, args: [], chain: null, account: wallet.account });
2398
2704
  await pc.waitForTransactionReceipt({ hash });
2399
2705
  spinner.succeed(`${action} confirmed. Tx: ${hash}`);
2400
2706
  } catch (err) {
@@ -2403,80 +2709,6 @@ function registerGuardianCommands(program2) {
2403
2709
  process.exit(1);
2404
2710
  }
2405
2711
  });
2406
- guardian.command("delegate <delegate> <amount>").description("Delegate WOOD stake to a guardian. Amount in WOOD.").action(async (delegate, amountStr) => {
2407
- if (!isAddress2(delegate)) {
2408
- console.error(chalk.red("Invalid delegate address"));
2409
- process.exit(1);
2410
- }
2411
- const amount = parseUnits(amountStr, 18);
2412
- if (isCalldataOnly()) {
2413
- emitCalldata(encodeGuardianDelegate({ delegate, amount }, getChain().id));
2414
- return;
2415
- }
2416
- await ensureWoodAllowance(amount);
2417
- const wallet = getWalletClient();
2418
- const pc = getPublicClient();
2419
- const spinner = ora(`Delegating ${amountStr} WOOD to ${delegate}...`).start();
2420
- try {
2421
- const hash = await wallet.writeContract({ address: swood(), abi: STAKED_WOOD_ABI, functionName: "delegateStake", args: [delegate, amount], chain: null, account: wallet.account });
2422
- await pc.waitForTransactionReceipt({ hash });
2423
- spinner.succeed(`Delegated. Tx: ${hash}`);
2424
- } catch (err) {
2425
- spinner.fail("Delegation failed");
2426
- console.error(chalk.red(formatContractError(err)));
2427
- process.exit(1);
2428
- }
2429
- });
2430
- guardian.command("undelegate <delegate> <action>").description("Unstake-delegation flow: 'request' | 'cancel' | 'claim'").action(async (delegate, action) => {
2431
- if (!isAddress2(delegate)) {
2432
- console.error(chalk.red("Invalid delegate address"));
2433
- process.exit(1);
2434
- }
2435
- const fn = { request: "requestUnstakeDelegation", cancel: "cancelUnstakeDelegation", claim: "claimUnstakeDelegation" }[action];
2436
- if (!fn) {
2437
- console.error(chalk.red(`Unknown action '${action}'. Use request | cancel | claim.`));
2438
- process.exit(1);
2439
- }
2440
- if (isCalldataOnly()) {
2441
- emitCalldata(encodeDelegationUnstake({ action, delegate }, getChain().id));
2442
- return;
2443
- }
2444
- const wallet = getWalletClient();
2445
- const pc = getPublicClient();
2446
- const spinner = ora(`${action}...`).start();
2447
- try {
2448
- const hash = await wallet.writeContract({ address: swood(), abi: STAKED_WOOD_ABI, functionName: fn, args: [delegate], chain: null, account: wallet.account });
2449
- await pc.waitForTransactionReceipt({ hash });
2450
- spinner.succeed(`${action} confirmed. Tx: ${hash}`);
2451
- } catch (err) {
2452
- spinner.fail(`${action} failed`);
2453
- console.error(chalk.red(formatContractError(err)));
2454
- process.exit(1);
2455
- }
2456
- });
2457
- guardian.command("set-commission <bps>").description("Set your commission rate in bps (0-5000 = 0-50%). Raises capped cumulatively at 500 bps/epoch.").action(async (bpsStr) => {
2458
- const bps = BigInt(bpsStr);
2459
- if (bps > 5000n) {
2460
- console.error(chalk.red("Commission cannot exceed 5000 bps (50%)"));
2461
- process.exit(1);
2462
- }
2463
- if (isCalldataOnly()) {
2464
- emitCalldata(encodeSetCommission({ bps: Number(bps) }, getChain().id));
2465
- return;
2466
- }
2467
- const wallet = getWalletClient();
2468
- const pc = getPublicClient();
2469
- const spinner = ora(`Setting commission to ${Number(bps) / 100}%...`).start();
2470
- try {
2471
- const hash = await wallet.writeContract({ address: swood(), abi: STAKED_WOOD_ABI, functionName: "setCommission", args: [bps], chain: null, account: wallet.account });
2472
- await pc.waitForTransactionReceipt({ hash });
2473
- spinner.succeed(`Commission set. Tx: ${hash}`);
2474
- } catch (err) {
2475
- spinner.fail("setCommission failed");
2476
- console.error(chalk.red(formatContractError(err)));
2477
- process.exit(1);
2478
- }
2479
- });
2480
2712
  guardian.command("claim-wood").description("Open merkl.xyz to claim guardian WOOD rewards (block-rewards + guardian fees, off-chain distribution).").action(async () => {
2481
2713
  const wallet = getWalletClient();
2482
2714
  const addr = wallet.account.address;
@@ -2562,7 +2794,85 @@ function resolveTemplate(key) {
2562
2794
  }
2563
2795
  return { def, address };
2564
2796
  }
2565
- async function resolveChainInputsForTemplate(templateKey, opts) {
2797
+ async function resolvePortfolioFeeTiers(opts, deps) {
2798
+ if (!opts.tokens || !opts.weights || !opts.amount || opts.feeTier) return {};
2799
+ if (UNISWAP().QUOTER_V2 === ZERO) return {};
2800
+ const tokens = TOKENS();
2801
+ const tokenSpecs = opts.tokens.split(",").map((t) => t.trim());
2802
+ const tokenAddrs = tokenSpecs.map((spec) => {
2803
+ if (isAddress3(spec)) return spec;
2804
+ const resolved = tokens[spec.toUpperCase()];
2805
+ return resolved && resolved !== ZERO ? resolved : null;
2806
+ });
2807
+ const weightsBps = opts.weights.split(",").map((w) => Number(w.trim()));
2808
+ if (tokenAddrs.length !== weightsBps.length) return {};
2809
+ if (weightsBps.some((w) => !Number.isFinite(w) || w < 0)) return {};
2810
+ const assetSymbol = opts.asset || defaultPortfolioAsset();
2811
+ let asset;
2812
+ let totalAmount;
2813
+ try {
2814
+ asset = resolveToken(assetSymbol);
2815
+ totalAmount = parseUnits2(opts.amount, assetDecimalsForSymbol(assetSymbol));
2816
+ } catch {
2817
+ return {};
2818
+ }
2819
+ const swapRouteOverrides = parseAlignedOverrides(
2820
+ opts.swapRoutes,
2821
+ tokenAddrs.length
2822
+ ).values;
2823
+ console.log(chalk2.dim(" Quoting v3 fee tiers for best execution..."));
2824
+ const portfolioFeeTiers = [];
2825
+ for (let i = 0; i < tokenAddrs.length; i++) {
2826
+ const token = tokenAddrs[i];
2827
+ if (!token || swapRouteOverrides[i] || token.toLowerCase() === asset.toLowerCase()) {
2828
+ portfolioFeeTiers.push(null);
2829
+ continue;
2830
+ }
2831
+ const legAmount = totalAmount * BigInt(Math.round(weightsBps[i])) / 10000n;
2832
+ const best = await selectBestFeeTier(
2833
+ {
2834
+ tokenIn: asset,
2835
+ tokenOut: token,
2836
+ amountIn: legAmount > 0n ? legAmount : 1n,
2837
+ preferredFeeTier: DEFAULT_FEE_TIER
2838
+ },
2839
+ deps
2840
+ );
2841
+ if (!best) {
2842
+ console.warn(
2843
+ chalk2.yellow(
2844
+ ` ${tokenSpecs[i]}: no fee tier quoted \u2014 falling back to fee ${DEFAULT_FEE_TIER}`
2845
+ )
2846
+ );
2847
+ portfolioFeeTiers.push(null);
2848
+ continue;
2849
+ }
2850
+ const others = best.quoted.filter((q) => q.feeTier !== best.feeTier).map((q) => `${q.feeTier}: ${q.amountOut}`).join(", ");
2851
+ console.log(
2852
+ chalk2.dim(
2853
+ ` ${tokenSpecs[i]}: fee ${best.feeTier} (out ${best.amountOut}` + (others ? `; beat ${others}` : "; only tier that quoted") + `)`
2854
+ )
2855
+ );
2856
+ portfolioFeeTiers.push({
2857
+ feeTier: best.feeTier,
2858
+ routeDesc: `direct (fee ${best.feeTier}, best of ${best.quoted.length} quoted)`
2859
+ });
2860
+ }
2861
+ return { portfolioFeeTiers };
2862
+ }
2863
+ async function resolveChainInputsForTemplate(templateKey, opts, deps = {}) {
2864
+ if (templateKey === "portfolio") {
2865
+ try {
2866
+ return await resolvePortfolioFeeTiers(opts, deps);
2867
+ } catch (err) {
2868
+ console.warn(
2869
+ chalk2.yellow(
2870
+ ` Fee-tier quoting unavailable (${err instanceof Error ? err.message : String(err)}) \u2014 using default tier ${DEFAULT_FEE_TIER}`
2871
+ )
2872
+ );
2873
+ return {};
2874
+ }
2875
+ }
2566
2876
  if (templateKey === "venice-inference") {
2567
2877
  if (!opts.amount) return {};
2568
2878
  const assetSymbol = opts.asset || "USDC";
@@ -2837,7 +3147,7 @@ function registerStrategyProposeCommands(strategy2) {
2837
3147
  process.exit(1);
2838
3148
  }
2839
3149
  if (!opts.agent) opts.agent = proposer;
2840
- const { parseDuration: parseDuration3, getGovernorOf: getGovernorOf2 } = await import("./governor-GSCXEZWI.js");
3150
+ const { parseDuration: parseDuration3, getGovernorOf: getGovernorOf2 } = await import("./governor-7QKGNHMR.js");
2841
3151
  const strategyDuration2 = parseDuration3(opts.duration);
2842
3152
  const chainId = getChain().id;
2843
3153
  try {
@@ -3068,9 +3378,9 @@ function registerStrategyProposeCommands(strategy2) {
3068
3378
  console.error(chalk3.red("Missing --name or --duration. Use --write-calls to skip proposal submission."));
3069
3379
  process.exit(1);
3070
3380
  }
3071
- const { propose: propose2 } = await import("./governor-GSCXEZWI.js");
3381
+ const { propose: propose2 } = await import("./governor-7QKGNHMR.js");
3072
3382
  const { pinJSON } = await import("./ipfs-EJ7IHXCU.js");
3073
- const { parseDuration: parseDuration2 } = await import("./governor-GSCXEZWI.js");
3383
+ const { parseDuration: parseDuration2 } = await import("./governor-7QKGNHMR.js");
3074
3384
  const strategyDuration = parseDuration2(opts.duration);
3075
3385
  const metaSpinner = ora2("Pinning metadata to IPFS...").start();
3076
3386
  let metadataURI;
@@ -3102,7 +3412,7 @@ function registerStrategyProposeCommands(strategy2) {
3102
3412
  metadataURI = `data:application/json;base64,${Buffer.from(inline).toString("base64")}`;
3103
3413
  }
3104
3414
  try {
3105
- const { getAgentFeeBps: getAgentFeeBps2 } = await import("./vault-VDADZFSV.js");
3415
+ const { getAgentFeeBps: getAgentFeeBps2 } = await import("./vault-UCFFOOTS.js");
3106
3416
  const feeBps = await getAgentFeeBps2(vault);
3107
3417
  console.log(
3108
3418
  chalk3.yellow(
@@ -3373,7 +3683,7 @@ function registerStrategyTemplateCommands(strategy2) {
3373
3683
  let prices = [];
3374
3684
  let priceSource = null;
3375
3685
  try {
3376
- const { getTokenPricesInAsset } = await import("./price-OUL7WIXQ.js");
3686
+ const { getTokenPricesInAsset } = await import("./price-SVF25X4O.js");
3377
3687
  prices = await getTokenPricesInAsset({
3378
3688
  tokens: allocations.map((a, i) => ({
3379
3689
  token: a.token,
@@ -3803,10 +4113,10 @@ function registerVeniceCommands(program2) {
3803
4113
  console.log(chalk5.dim(` Key: ${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`));
3804
4114
  console.log(chalk5.dim(" Saved to ~/.sherwood/config.json"));
3805
4115
  try {
3806
- const { createVeniceProvisionAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-32PEJETG.js");
3807
- const { uid } = await createVeniceProvisionAttestation(account.address);
3808
- if (uid !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
3809
- console.log(chalk5.dim(` Attested: ${getEasScanUrl2(uid)}`));
4116
+ const { createVeniceProvisionAttestation, getEasScanUrl } = await import("./eas-QPKX6A5A.js");
4117
+ const attestation = await createVeniceProvisionAttestation(account.address);
4118
+ if (attestation) {
4119
+ console.log(chalk5.dim(` Attested: ${getEasScanUrl(attestation.uid)}`));
3810
4120
  }
3811
4121
  } catch {
3812
4122
  }
@@ -4159,21 +4469,21 @@ ${opts.prompt}`;
4159
4469
  console.log(chalk5.dim(`Model: ${result.model} | Tokens: ${result.usage.promptTokens} in, ${result.usage.completionTokens} out, ${result.usage.totalTokens} total`));
4160
4470
  }
4161
4471
  try {
4162
- const { createVeniceInferenceAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-32PEJETG.js");
4472
+ const { createVeniceInferenceAttestation, getEasScanUrl } = await import("./eas-QPKX6A5A.js");
4163
4473
  const { keccak256: keccak2563, toHex, isAddress: isAddr } = await import("viem");
4164
4474
  const { getChainContracts: getChainContracts2 } = await import("./config-ZGFS4I7K.js");
4165
- const { getChain: getActiveChain } = await import("./network-HCFCNZUI.js");
4475
+ const { getChain: getActiveChain } = await import("./network-FEHRFDPJ.js");
4166
4476
  const vaultRecipient = opts.vault && isAddr(opts.vault) ? opts.vault : getChainContracts2(getActiveChain().id).vault;
4167
4477
  const promptHash = keccak2563(toHex(userContent)).slice(0, 18);
4168
- const { uid } = await createVeniceInferenceAttestation(
4478
+ const attestation = await createVeniceInferenceAttestation(
4169
4479
  result.model,
4170
4480
  result.usage.promptTokens,
4171
4481
  result.usage.completionTokens,
4172
4482
  promptHash,
4173
4483
  vaultRecipient
4174
4484
  );
4175
- if (uid !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
4176
- console.log(chalk5.dim(`Attested: ${getEasScanUrl2(uid)}`));
4485
+ if (attestation) {
4486
+ console.log(chalk5.dim(`Attested: ${getEasScanUrl(attestation.uid)}`));
4177
4487
  }
4178
4488
  } catch {
4179
4489
  }
@@ -5806,8 +6116,8 @@ function printSimulationResult(result, callType) {
5806
6116
  }
5807
6117
  async function sendSimulationAlert(subdomain, proposalId, vault, execResult, settleResult) {
5808
6118
  try {
5809
- const xmtp = await import("./xmtp-2U7CSBXM.js");
5810
- const { getAccount: getAccount2 } = await import("./client-OPZ2ORWW.js");
6119
+ const xmtp = await import("./xmtp-S7IGTIMZ.js");
6120
+ const { getAccount: getAccount2 } = await import("./client-3SE632Q2.js");
5811
6121
  const group = await xmtp.getGroup("", subdomain);
5812
6122
  const allRisks = [...execResult.risks, ...settleResult?.risks ?? []];
5813
6123
  const criticals = allRisks.filter((r) => r.level === "critical");
@@ -5916,6 +6226,48 @@ function parseCallsFile(path) {
5916
6226
  value: BigInt(c.value || "0")
5917
6227
  }));
5918
6228
  }
6229
+ function parseSandboxCallsFile(path) {
6230
+ const raw = readFileSync2(path, "utf-8");
6231
+ const parsed = JSON.parse(raw);
6232
+ if (!Array.isArray(parsed)) {
6233
+ console.error(chalk9.red(" sandbox calls file must be a JSON array of {target, data} objects"));
6234
+ process.exit(1);
6235
+ }
6236
+ return parsed.map((c, i) => {
6237
+ if (!c || typeof c !== "object") {
6238
+ console.error(chalk9.red(` sandbox.calls[${i}] must be an object`));
6239
+ process.exit(1);
6240
+ }
6241
+ const rec = c;
6242
+ if (rec.value !== void 0 && rec.value !== null) {
6243
+ console.error(
6244
+ chalk9.red(
6245
+ ` sandbox.calls[${i}] must be (address,bytes) only \u2014 ICallSandbox.Call has no value field.`
6246
+ )
6247
+ );
6248
+ process.exit(1);
6249
+ }
6250
+ if (typeof rec.target !== "string" || !isAddress9(rec.target)) {
6251
+ console.error(chalk9.red(` sandbox.calls[${i}].target must be a 0x-prefixed address`));
6252
+ process.exit(1);
6253
+ }
6254
+ if (typeof rec.data !== "string" || !/^0x[0-9a-fA-F]*$/.test(rec.data)) {
6255
+ console.error(chalk9.red(` sandbox.calls[${i}].data must be a 0x-prefixed hex string`));
6256
+ process.exit(1);
6257
+ }
6258
+ return { target: rec.target, data: rec.data };
6259
+ });
6260
+ }
6261
+ function parseSandboxTokens(list) {
6262
+ if (list === void 0 || list.trim().length === 0) return [];
6263
+ return list.split(",").map((x) => x.trim()).filter((x) => x.length > 0).map((a, i) => {
6264
+ if (!isAddress9(a)) {
6265
+ console.error(chalk9.red(` --sandbox-tokens[${i}] is not a 0x-prefixed address: ${a}`));
6266
+ process.exit(1);
6267
+ }
6268
+ return a;
6269
+ });
6270
+ }
5919
6271
  var GUARDIAN_REGISTRY_ABI2 = [
5920
6272
  {
5921
6273
  type: "function",
@@ -5924,6 +6276,17 @@ var GUARDIAN_REGISTRY_ABI2 = [
5924
6276
  outputs: [{ type: "bool" }],
5925
6277
  stateMutability: "nonpayable"
5926
6278
  },
6279
+ {
6280
+ // Permissionless keeper entrypoint (SHE-162): opens the guardian review
6281
+ // once `block.timestamp >= voteEnd`. Idempotent on-chain
6282
+ // (`if (r.opened || r.resolved) return;`), so keeper races are harmless.
6283
+ // No outputs — matches GuardianRegistry.openReview(address,uint256).
6284
+ type: "function",
6285
+ name: "openReview",
6286
+ inputs: [{ name: "governor", type: "address" }, { name: "proposalId", type: "uint256" }],
6287
+ outputs: [],
6288
+ stateMutability: "nonpayable"
6289
+ },
5927
6290
  {
5928
6291
  type: "function",
5929
6292
  name: "getReviewState",
@@ -5934,8 +6297,30 @@ var GUARDIAN_REGISTRY_ABI2 = [
5934
6297
  { name: "blocked", type: "bool" }
5935
6298
  ],
5936
6299
  stateMutability: "view"
6300
+ },
6301
+ {
6302
+ type: "function",
6303
+ name: "voteOnProposal",
6304
+ inputs: [
6305
+ { name: "governor", type: "address" },
6306
+ { name: "proposalId", type: "uint256" },
6307
+ { name: "support", type: "uint8" }
6308
+ ],
6309
+ outputs: [],
6310
+ stateMutability: "nonpayable"
5937
6311
  }
5938
6312
  ];
6313
+ function parseGuardianReviewVote(raw) {
6314
+ const v = String(raw).trim().toLowerCase();
6315
+ if (v === "approve" || v === "yes") return "Approve";
6316
+ if (v === "block" || v === "no") return "Block";
6317
+ throw new Error(`Invalid support "${raw}". Use approve|block.`);
6318
+ }
6319
+ function formatUsd18(raw) {
6320
+ const n = Number(raw) / 1e18;
6321
+ if (!Number.isFinite(n)) return `${raw.toString()} (USD-18)`;
6322
+ return `$${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
6323
+ }
5939
6324
  function getRegistryAddress() {
5940
6325
  const addr = SHERWOOD().GUARDIAN_REGISTRY;
5941
6326
  if (addr === "0x0000000000000000000000000000000000000000") {
@@ -6177,6 +6562,171 @@ function registerProposalCommands(program2) {
6177
6562
  process.exit(1);
6178
6563
  }
6179
6564
  });
6565
+ 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(
6566
+ "--sandbox-calls <path>",
6567
+ "Path to JSON file of ICallSandbox.Call[] \u2014 [{target, data}, ...]. A value field is refused."
6568
+ ).requiredOption(
6569
+ "--sandbox-funding <amount>",
6570
+ "Vault-asset amount the sandbox is funded with (decimals resolved from the asset). Must be > 0 and <= --max-capital."
6571
+ ).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(
6572
+ "--max-capital <amount>",
6573
+ "REQUIRED. Ceiling on what the execute batch may deploy, in vault-asset units."
6574
+ ).option(
6575
+ "--max-drawdown-bps <bps>",
6576
+ "Declared loss allowance in bps of --max-capital (0-10000, default 2000 = 20%)."
6577
+ ).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) => {
6578
+ try {
6579
+ const vault = opts.vault;
6580
+ if (!isAddress9(vault)) {
6581
+ console.error(chalk9.red(`Invalid vault address: ${opts.vault}`));
6582
+ process.exit(1);
6583
+ }
6584
+ const strategyDuration = parseDuration(opts.duration);
6585
+ const executeCalls = parseCallsFile(opts.executeCalls);
6586
+ const settleCalls = parseCallsFile(opts.settleCalls);
6587
+ const sandboxCalls = parseSandboxCallsFile(opts.sandboxCalls);
6588
+ const declaredTokens = parseSandboxTokens(opts.sandboxTokens);
6589
+ const ZERO_STRATEGY = "0x0000000000000000000000000000000000000000";
6590
+ let strategy2 = ZERO_STRATEGY;
6591
+ if (opts.strategy) {
6592
+ if (!isAddress9(opts.strategy)) {
6593
+ console.error(chalk9.red(`Invalid strategy address: ${opts.strategy}`));
6594
+ process.exit(1);
6595
+ }
6596
+ strategy2 = opts.strategy;
6597
+ }
6598
+ const envelope = await resolveRiskEnvelope(opts, vault);
6599
+ const asset = await getAssetAddress();
6600
+ const decimals = await getPublicClient().readContract({
6601
+ address: asset,
6602
+ abi: ERC20_ABI,
6603
+ functionName: "decimals"
6604
+ });
6605
+ const funding = parseUnits8(String(opts.sandboxFunding), decimals);
6606
+ const executeCallCaps = resolveCaps(
6607
+ opts.executeCaps,
6608
+ executeCalls.length,
6609
+ envelope.maxCapital,
6610
+ "--execute-caps"
6611
+ );
6612
+ const settleCallCaps = resolveCaps(
6613
+ opts.settleCaps,
6614
+ settleCalls.length,
6615
+ 0n,
6616
+ "--settle-caps"
6617
+ );
6618
+ const buildAction = async (metadataURI2) => encodeProposeWithSandbox(
6619
+ {
6620
+ vault,
6621
+ governor: await getGovernorOf(vault),
6622
+ strategy: strategy2,
6623
+ metadataURI: metadataURI2,
6624
+ strategyDuration,
6625
+ envelope,
6626
+ executeCalls,
6627
+ executeCallCaps,
6628
+ settlementCalls: settleCalls,
6629
+ settlementCallCaps: settleCallCaps,
6630
+ sandbox: { funding, calls: sandboxCalls, declaredTokens }
6631
+ },
6632
+ getChain().id
6633
+ );
6634
+ if (isCalldataOnly()) {
6635
+ if (!opts.metadataUri) {
6636
+ console.error(
6637
+ chalk9.red(
6638
+ "--calldata-only requires --metadata-uri (IPFS metadata upload needs a signing wallet)"
6639
+ )
6640
+ );
6641
+ process.exit(1);
6642
+ }
6643
+ try {
6644
+ emitCalldata(await buildAction(opts.metadataUri));
6645
+ } catch (err) {
6646
+ console.error(chalk9.red(err instanceof Error ? err.message : String(err)));
6647
+ process.exit(1);
6648
+ }
6649
+ return;
6650
+ }
6651
+ let metadataURI = opts.metadataUri || "";
6652
+ if (!metadataURI) {
6653
+ const spinner2 = ora7({ text: W3("Uploading metadata to IPFS..."), color: "green" }).start();
6654
+ try {
6655
+ const account = getAccount();
6656
+ const metadata = {
6657
+ schema: "sherwood/proposal/v1",
6658
+ name: opts.name,
6659
+ description: opts.description,
6660
+ chain: getNetwork(),
6661
+ strategies: [],
6662
+ terms: {},
6663
+ links: {}
6664
+ };
6665
+ const proposalMeta = {
6666
+ ...metadata,
6667
+ proposer: account.address,
6668
+ vault,
6669
+ strategyDuration: Number(strategyDuration),
6670
+ sandbox: true,
6671
+ createdAt: Math.floor(Date.now() / 1e3)
6672
+ };
6673
+ metadataURI = await uploadMetadata(proposalMeta);
6674
+ spinner2.succeed(G3(`Metadata pinned: ${DIM3(metadataURI)}`));
6675
+ } catch (err) {
6676
+ spinner2.warn(chalk9.yellow("IPFS upload failed \u2014 using inline metadata"));
6677
+ const json = JSON.stringify({ name: opts.name, description: opts.description, vault, sandbox: true });
6678
+ metadataURI = `data:application/json;base64,${Buffer.from(json).toString("base64")}`;
6679
+ }
6680
+ }
6681
+ console.log();
6682
+ console.log(LABEL3(" \u25C6 Sandbox Proposal Summary"));
6683
+ SEP3();
6684
+ console.log(W3(` Name: ${BOLD3(opts.name)}`));
6685
+ console.log(W3(` Vault: ${G3(vault)}`));
6686
+ console.log(W3(` Duration: ${formatDurationShort(strategyDuration)}`));
6687
+ console.log(W3(` Calls: ${executeCalls.length} execute + ${settleCalls.length} settle + ${sandboxCalls.length} sandbox`));
6688
+ console.log(W3(` Sandbox funding: ${opts.sandboxFunding}`));
6689
+ console.log(W3(` Metadata: ${DIM3(metadataURI.length > 50 ? metadataURI.slice(0, 50) + "..." : metadataURI)}`));
6690
+ SEP3();
6691
+ const spinner = ora7({ text: W3("Submitting sandbox proposal..."), color: "green" }).start();
6692
+ let action;
6693
+ try {
6694
+ action = await buildAction(metadataURI);
6695
+ } catch (err) {
6696
+ spinner.fail(err instanceof Error ? err.message : String(err));
6697
+ process.exit(1);
6698
+ }
6699
+ const tx = action.txs[0];
6700
+ const hash = await sendTxWithRetry({
6701
+ account: getAccount(),
6702
+ chain: getChain(),
6703
+ to: tx.to,
6704
+ data: tx.data,
6705
+ value: 0n
6706
+ });
6707
+ const receipt = await waitForReceipt(hash);
6708
+ let proposalId = parseProposalIdFromLogs(receipt.logs);
6709
+ if (proposalId === void 0) {
6710
+ try {
6711
+ proposalId = await proposalCount(vault);
6712
+ } catch {
6713
+ proposalId = 0n;
6714
+ }
6715
+ }
6716
+ spinner.succeed(G3("Sandbox proposal submitted"));
6717
+ console.log();
6718
+ console.log(LABEL3(" \u25C6 Sandbox Proposal Created"));
6719
+ SEP3();
6720
+ console.log(W3(` Proposal ID: ${G3(`#${proposalId}`)}`));
6721
+ console.log(W3(` Tx: ${DIM3(getExplorerUrl(receipt.transactionHash))}`));
6722
+ SEP3();
6723
+ console.log();
6724
+ } catch (err) {
6725
+ console.error(chalk9.red(`
6726
+ \u2716 ${formatContractError(err)}`));
6727
+ process.exit(1);
6728
+ }
6729
+ });
6180
6730
  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) => {
6181
6731
  const spinner = ora7("Loading proposals...").start();
6182
6732
  try {
@@ -6365,6 +6915,112 @@ function registerProposalCommands(program2) {
6365
6915
  process.exit(1);
6366
6916
  }
6367
6917
  });
6918
+ 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) => {
6919
+ try {
6920
+ const proposalId = parseBigIntArg(opts.id, "proposal ID");
6921
+ const vault = parseVaultOption(opts.vault);
6922
+ const vote2 = parseGuardianReviewVote(String(opts.support));
6923
+ const chainId = getChain().id;
6924
+ const governor = await getGovernorOf(vault);
6925
+ if (isCalldataOnly()) {
6926
+ emitCalldata(encodeVoteOnProposal({ governor, proposalId, vote: vote2 }, chainId));
6927
+ return;
6928
+ }
6929
+ const account = getAccount();
6930
+ const spinner = ora7("Loading review...").start();
6931
+ const registryAddr = getRegistryAddress();
6932
+ const pc = getPublicClient();
6933
+ const [p, state, reviewState] = await Promise.all([
6934
+ getProposal(vault, proposalId),
6935
+ getProposalState(vault, proposalId),
6936
+ pc.readContract({
6937
+ address: registryAddr,
6938
+ abi: GUARDIAN_REGISTRY_ABI2,
6939
+ functionName: "getReviewState",
6940
+ args: [governor, proposalId]
6941
+ })
6942
+ ]);
6943
+ const [opened, resolved] = reviewState;
6944
+ const now = (await pc.getBlock()).timestamp;
6945
+ spinner.stop();
6946
+ if (!opened) {
6947
+ console.error(chalk9.red(
6948
+ `No live guardian review is open for proposal #${proposalId}. openReview must run (and the shareholder vote must have ended) before guardians can vote.`
6949
+ ));
6950
+ process.exit(1);
6951
+ }
6952
+ if (resolved) {
6953
+ console.error(chalk9.red(`Guardian review for proposal #${proposalId} is already resolved.`));
6954
+ process.exit(1);
6955
+ }
6956
+ if (now < p.voteEnd) {
6957
+ console.error(chalk9.red(
6958
+ `Shareholder vote is still open until ${formatTimestamp(p.voteEnd)}. Guardian review voting starts after voteEnd.`
6959
+ ));
6960
+ process.exit(1);
6961
+ }
6962
+ if (now >= p.reviewEnd) {
6963
+ console.error(chalk9.red(
6964
+ `Review window elapsed at ${formatTimestamp(p.reviewEnd)}. Use \`sherwood proposal resolve-reviews\`.`
6965
+ ));
6966
+ process.exit(1);
6967
+ }
6968
+ console.log();
6969
+ console.log(LABEL3(" \u25C6 Guardian Review Vote"));
6970
+ SEP3();
6971
+ console.log(W3(` Proposal: #${proposalId}`));
6972
+ console.log(W3(` Vault: ${G3(p.vault)}`));
6973
+ console.log(W3(` Governor: ${governor}`));
6974
+ console.log(W3(` Strategy: ${p.strategy}`));
6975
+ console.log(W3(` State: ${PROPOSAL_STATES[state] || "Unknown"}`));
6976
+ console.log(W3(` Review ends: ${formatTimestamp(p.reviewEnd)}`));
6977
+ console.log(W3(
6978
+ ` Support: ${vote2 === "Approve" ? G3("APPROVE") : chalk9.red("BLOCK")}`
6979
+ ));
6980
+ console.log(W3(` Max capital: ${p.maxCapital.toString()} (asset base units)`));
6981
+ console.log(W3(` Required coverage: ${formatUsd18(p.requiredCoverage)} (USD)`));
6982
+ console.log(W3(` Envelope tier: ${p.envelopeTier}`));
6983
+ console.log(W3(` Caller: ${account.address}`));
6984
+ SEP3();
6985
+ if (vote2 === "Approve") {
6986
+ console.log(chalk9.yellow.bold(" \u26A0 UNDERWRITING"));
6987
+ console.log(chalk9.yellow(
6988
+ " Approve books this proposal's coverage against YOUR own slashable"
6989
+ ));
6990
+ console.log(chalk9.yellow(
6991
+ " guardian bond (ExposureLedger.recordApproval). If the proposal is"
6992
+ ));
6993
+ console.log(chalk9.yellow(
6994
+ " later challenged or slashed, your WOOD stake is at risk \u2014 not"
6995
+ ));
6996
+ console.log(chalk9.yellow(
6997
+ " another guardian's. An over-exposed bond books nothing rather than"
6998
+ ));
6999
+ console.log(chalk9.yellow(
7000
+ " reverting; the vote still lands and the shortfall shows at execute."
7001
+ ));
7002
+ SEP3();
7003
+ }
7004
+ const action = encodeVoteOnProposal({ governor, proposalId, vote: vote2 }, chainId);
7005
+ const tx = action.txs[0];
7006
+ const voteSpinner = ora7({ text: W3("Submitting review vote..."), color: "green" }).start();
7007
+ const hash = await sendTxWithRetry({
7008
+ account,
7009
+ chain: getChain(),
7010
+ to: tx.to,
7011
+ data: tx.data,
7012
+ value: BigInt(tx.value)
7013
+ });
7014
+ await waitForReceipt(hash);
7015
+ voteSpinner.succeed(G3(`Review vote cast (${vote2})`));
7016
+ console.log(DIM3(` ${getExplorerUrl(hash)}`));
7017
+ console.log();
7018
+ } catch (err) {
7019
+ console.error(chalk9.red(`
7020
+ \u2716 ${formatContractError(err)}`));
7021
+ process.exit(1);
7022
+ }
7023
+ });
6368
7024
  proposal.command("execute").description("Execute an approved proposal").requiredOption("--id <proposalId>", "Proposal ID").requiredOption("--vault <address>", "Vault the proposal belongs to (per-vault governor)").option("--dry-run", "Simulate execution without sending a transaction").action(async (opts) => {
6369
7025
  try {
6370
7026
  const proposalId = parseBigIntArg(opts.id, "proposal ID");
@@ -6635,6 +7291,113 @@ function registerProposalCommands(program2) {
6635
7291
  process.exit(1);
6636
7292
  }
6637
7293
  });
7294
+ proposal.command("open-reviews").description("Keeper: open guardian reviews whose vote has ended (window [voteEnd, reviewEnd))").requiredOption("--vault <address>", "Vault whose governor's reviews to scan (per-vault governor)").option("--dry-run", "List openable reviews without sending transactions").action(async (opts) => {
7295
+ const spinner = ora7("Scanning proposals for openable reviews...").start();
7296
+ try {
7297
+ const vault = parseVaultOption(opts.vault, spinner);
7298
+ const registryAddr = getRegistryAddress();
7299
+ const pc = getPublicClient();
7300
+ const governorAddr = await getGovernorOf(vault);
7301
+ const count = await proposalCount(vault);
7302
+ if (count === 0n) {
7303
+ spinner.stop();
7304
+ console.log(DIM3("\n No proposals \u2014 nothing to open.\n"));
7305
+ return;
7306
+ }
7307
+ const now = (await pc.getBlock()).timestamp;
7308
+ const ids = Array.from({ length: Number(count) }, (_, i) => BigInt(i + 1));
7309
+ const { candidates, failures: scanFailures } = await scanReviewCandidates(
7310
+ ids,
7311
+ async (id) => {
7312
+ const [p, reviewState] = await Promise.all([
7313
+ getProposal(vault, id),
7314
+ pc.readContract({
7315
+ address: registryAddr,
7316
+ abi: GUARDIAN_REGISTRY_ABI2,
7317
+ functionName: "getReviewState",
7318
+ args: [governorAddr, id]
7319
+ })
7320
+ ]);
7321
+ const [opened, resolved] = reviewState;
7322
+ const cls = classifyReviewOpen({
7323
+ opened,
7324
+ resolved,
7325
+ voteEnd: p.voteEnd,
7326
+ reviewEnd: p.reviewEnd,
7327
+ now
7328
+ });
7329
+ return { id, cls, reviewEnd: p.reviewEnd, vault: p.vault };
7330
+ }
7331
+ );
7332
+ const openable = candidates.filter((c) => c.cls === "openable");
7333
+ spinner.stop();
7334
+ const scanIncomplete = scanFailures.length > 0;
7335
+ if (scanIncomplete) {
7336
+ console.log();
7337
+ for (const f of scanFailures) {
7338
+ console.log(
7339
+ chalk9.yellow(` \u26A0 Proposal #${f.id} failed to load: ${formatContractError(f.error)}`)
7340
+ );
7341
+ }
7342
+ console.log(
7343
+ chalk9.yellow(
7344
+ ` \u26A0 ${scanFailures.length} of ${ids.length} proposals failed to load \u2014 treat this run as incomplete.`
7345
+ )
7346
+ );
7347
+ }
7348
+ if (openable.length === 0) {
7349
+ console.log(DIM3("\n No reviews ready to open.\n"));
7350
+ if (scanIncomplete) process.exit(1);
7351
+ return;
7352
+ }
7353
+ console.log();
7354
+ console.log(LABEL3(` \u25C6 Openable Guardian Reviews (${openable.length})`));
7355
+ SEP3();
7356
+ for (const c of openable) {
7357
+ console.log(
7358
+ W3(` Proposal #${c.id} vault=${c.vault} reviewEnd=${formatTimestamp(c.reviewEnd)}`)
7359
+ );
7360
+ }
7361
+ SEP3();
7362
+ if (opts.dryRun) {
7363
+ console.log(G3("\n (dry-run) Not broadcasting. Re-run without --dry-run to open.\n"));
7364
+ if (scanIncomplete) process.exit(1);
7365
+ return;
7366
+ }
7367
+ const openedOk = [];
7368
+ for (const c of openable) {
7369
+ const txSpinner = ora7(`Opening review for proposal #${c.id}...`).start();
7370
+ try {
7371
+ const hash = await writeContractWithRetry({
7372
+ account: getAccount(),
7373
+ chain: getChain(),
7374
+ address: registryAddr,
7375
+ abi: GUARDIAN_REGISTRY_ABI2,
7376
+ functionName: "openReview",
7377
+ args: [governorAddr, c.id]
7378
+ });
7379
+ assertReceiptSuccess(await waitForReceipt(hash));
7380
+ openedOk.push({ id: c.id, hash });
7381
+ txSpinner.succeed(G3(`Proposal #${c.id} review opened`));
7382
+ } catch (err) {
7383
+ txSpinner.fail(`Proposal #${c.id} open failed: ${formatContractError(err)}`);
7384
+ }
7385
+ }
7386
+ console.log();
7387
+ console.log(LABEL3(` \u25C6 Opened ${openedOk.length}/${openable.length}`));
7388
+ SEP3();
7389
+ for (const r of openedOk) {
7390
+ console.log(W3(` Proposal #${r.id} `) + DIM3(getExplorerUrl(r.hash)));
7391
+ }
7392
+ SEP3();
7393
+ console.log();
7394
+ if (openedOk.length < openable.length || scanIncomplete) process.exit(1);
7395
+ } catch (err) {
7396
+ spinner.fail("Failed to scan reviews");
7397
+ console.error(chalk9.red(formatContractError(err)));
7398
+ process.exit(1);
7399
+ }
7400
+ });
6638
7401
  proposal.command("resolve-reviews").description("Keeper: resolve guardian reviews whose review window has elapsed").requiredOption("--vault <address>", "Vault whose governor's reviews to scan (per-vault governor)").option("--dry-run", "List resolvable reviews without sending transactions").action(async (opts) => {
6639
7402
  const spinner = ora7("Scanning proposals for resolvable reviews...").start();
6640
7403
  try {
@@ -6650,29 +7413,40 @@ function registerProposalCommands(program2) {
6650
7413
  }
6651
7414
  const now = (await pc.getBlock()).timestamp;
6652
7415
  const ids = Array.from({ length: Number(count) }, (_, i) => BigInt(i + 1));
6653
- const candidates = await Promise.all(
6654
- ids.map(async (id) => {
6655
- try {
6656
- const [p, reviewState] = await Promise.all([
6657
- getProposal(vault, id),
6658
- pc.readContract({
6659
- address: registryAddr,
6660
- abi: GUARDIAN_REGISTRY_ABI2,
6661
- functionName: "getReviewState",
6662
- args: [governorAddr, id]
6663
- })
6664
- ]);
6665
- const [opened, resolved2] = reviewState;
6666
- const cls = classifyReview({ opened, resolved: resolved2, reviewEnd: p.reviewEnd, now });
6667
- return { id, cls, reviewEnd: p.reviewEnd, vault: p.vault };
6668
- } catch {
6669
- return { id, cls: "none", reviewEnd: 0n, vault: "0x" };
6670
- }
6671
- })
7416
+ const { candidates, failures: scanFailures } = await scanReviewCandidates(
7417
+ ids,
7418
+ async (id) => {
7419
+ const [p, reviewState] = await Promise.all([
7420
+ getProposal(vault, id),
7421
+ pc.readContract({
7422
+ address: registryAddr,
7423
+ abi: GUARDIAN_REGISTRY_ABI2,
7424
+ functionName: "getReviewState",
7425
+ args: [governorAddr, id]
7426
+ })
7427
+ ]);
7428
+ const [opened, resolved2] = reviewState;
7429
+ const cls = classifyReview({ opened, resolved: resolved2, reviewEnd: p.reviewEnd, now });
7430
+ return { id, cls, reviewEnd: p.reviewEnd, vault: p.vault };
7431
+ }
6672
7432
  );
6673
7433
  const resolvable = candidates.filter((c) => c.cls === "resolvable");
6674
7434
  const coldStart = candidates.filter((c) => c.cls === "cold-start");
6675
7435
  spinner.stop();
7436
+ const scanIncomplete = scanFailures.length > 0;
7437
+ if (scanIncomplete) {
7438
+ console.log();
7439
+ for (const f of scanFailures) {
7440
+ console.log(
7441
+ chalk9.yellow(` \u26A0 Proposal #${f.id} failed to load: ${formatContractError(f.error)}`)
7442
+ );
7443
+ }
7444
+ console.log(
7445
+ chalk9.yellow(
7446
+ ` \u26A0 ${scanFailures.length} of ${ids.length} proposals failed to load \u2014 treat this run as incomplete.`
7447
+ )
7448
+ );
7449
+ }
6676
7450
  if (coldStart.length > 0) {
6677
7451
  console.log();
6678
7452
  console.log(LABEL3(` \u25C6 Unopened Cold-Start Reviews (${coldStart.length})`));
@@ -6697,6 +7471,7 @@ function registerProposalCommands(program2) {
6697
7471
  } else {
6698
7472
  console.log(DIM3("\n No opened reviews need an on-chain resolve.\n"));
6699
7473
  }
7474
+ if (scanIncomplete) process.exit(1);
6700
7475
  return;
6701
7476
  }
6702
7477
  console.log();
@@ -6710,6 +7485,7 @@ function registerProposalCommands(program2) {
6710
7485
  SEP3();
6711
7486
  if (opts.dryRun) {
6712
7487
  console.log(G3("\n (dry-run) Not broadcasting. Re-run without --dry-run to resolve.\n"));
7488
+ if (scanIncomplete) process.exit(1);
6713
7489
  return;
6714
7490
  }
6715
7491
  const resolved = [];
@@ -6731,7 +7507,7 @@ function registerProposalCommands(program2) {
6731
7507
  functionName: "resolveReview",
6732
7508
  args: [governorAddr, c.id]
6733
7509
  });
6734
- await waitForReceipt(hash);
7510
+ assertReceiptSuccess(await waitForReceipt(hash));
6735
7511
  resolved.push({ id: c.id, blocked, hash });
6736
7512
  txSpinner.succeed(
6737
7513
  G3(`Proposal #${c.id} resolved \u2014 blocked=${blocked ? chalk9.red("true") : "false"}`)
@@ -6750,7 +7526,7 @@ function registerProposalCommands(program2) {
6750
7526
  }
6751
7527
  SEP3();
6752
7528
  console.log();
6753
- if (resolved.length < resolvable.length) process.exit(1);
7529
+ if (resolved.length < resolvable.length || scanIncomplete) process.exit(1);
6754
7530
  } catch (err) {
6755
7531
  spinner.fail("Failed to scan reviews");
6756
7532
  console.error(chalk9.red(formatContractError(err)));
@@ -10027,9 +10803,9 @@ try {
10027
10803
  } catch {
10028
10804
  }
10029
10805
  var require2 = createRequire(import.meta.url);
10030
- var CLI_VERSION = "0.83.1";
10806
+ var CLI_VERSION = "0.84.0";
10031
10807
  async function loadXmtp() {
10032
- return import("./xmtp-2U7CSBXM.js");
10808
+ return import("./xmtp-S7IGTIMZ.js");
10033
10809
  }
10034
10810
  async function loadCron() {
10035
10811
  return import("./cron-E3UAZF5B.js");
@@ -10652,7 +11428,7 @@ fund.command("add").description("Register an agent on a fund vault (creator only
10652
11428
  if (opts.agentId) {
10653
11429
  agentId = BigInt(opts.agentId);
10654
11430
  } else {
10655
- const { AGENT_REGISTRY: AGENT_REGISTRY2 } = await import("./addresses-MS6BB7AB.js");
11431
+ const { AGENT_REGISTRY: AGENT_REGISTRY2 } = await import("./addresses-LFYVOPOH.js");
10656
11432
  const registry = AGENT_REGISTRY2().IDENTITY_REGISTRY;
10657
11433
  if (registry === "0x0000000000000000000000000000000000000000") {
10658
11434
  agentId = 0n;
@@ -10858,7 +11634,7 @@ fund.command("join").description("Request to join a fund (creates an EAS attesta
10858
11634
  }
10859
11635
  spinner.text = "Creating join request attestation...";
10860
11636
  const referrerAgentId = opts.ref ? parseInt(opts.ref, 10) : void 0;
10861
- const { uid, hash } = await createJoinRequest(
11637
+ const joinAttestation = await createJoinRequest(
10862
11638
  fund2.id,
10863
11639
  BigInt(agentId),
10864
11640
  fund2.vault,
@@ -10893,9 +11669,12 @@ fund.command("join").description("Request to join a fund (creates an EAS attesta
10893
11669
  console.log(W6(` Fund: ${G6(`${subdomain}.sherwoodagent.eth`)}`));
10894
11670
  console.log(W6(` Agent ID: #${agentId}`));
10895
11671
  console.log(W6(` Creator: ${DIM6(fund2.creator)}`));
10896
- console.log(W6(` Attestation: ${DIM6(uid)}`));
10897
- console.log(W6(` EAS Scan: ${DIM6(getEasScanUrl(uid))}`));
10898
- console.log(W6(` Explorer: ${DIM6(getEasExplorerUrl(hash))}`));
11672
+ if (joinAttestation) {
11673
+ console.log(W6(` Attestation: ${DIM6(joinAttestation.uid)}`));
11674
+ console.log(W6(` Explorer: ${DIM6(getEasExplorerUrl(joinAttestation.hash))}`));
11675
+ } else {
11676
+ console.log(W6(` Attestation: ${DIM6("skipped on this chain")}`));
11677
+ }
10899
11678
  SEP6();
10900
11679
  console.log(G6(" \u2713 Fund saved to ~/.sherwood/config.json (set as primary)"));
10901
11680
  console.log(G6(" \u2713 The creator can review with:"));
@@ -11056,7 +11835,7 @@ fund.command("approve").description("Approve an agent join request (registers ag
11056
11835
  vaultAddress,
11057
11836
  agentWallet
11058
11837
  );
11059
- approvalUid = result.uid;
11838
+ approvalUid = result?.uid ?? null;
11060
11839
  }
11061
11840
  try {
11062
11841
  spinner.text = "Adding to chat...";
@@ -11081,8 +11860,9 @@ fund.command("approve").description("Approve an agent join request (registers ag
11081
11860
  SEP6();
11082
11861
  console.log(W6(` Agent ID: #${opts.agentId}`));
11083
11862
  console.log(W6(` Wallet: ${G6(opts.wallet)}`));
11084
- console.log(W6(` Approval: ${DIM6(approvalUid)}`));
11085
- console.log(W6(` EAS Scan: ${DIM6(getEasScanUrl(approvalUid))}`));
11863
+ console.log(
11864
+ W6(` Approval: ${DIM6(approvalUid ?? "skipped on this chain")}`)
11865
+ );
11086
11866
  SEP6();
11087
11867
  } catch (err) {
11088
11868
  spinner.fail("Approval failed");
@@ -11094,8 +11874,12 @@ fund.command("reject").description("Reject a join request by revoking its attest
11094
11874
  const spinner = ora10("Revoking attestation...").start();
11095
11875
  try {
11096
11876
  const hash = await revokeJoinRequest(opts.attestation);
11097
- spinner.succeed("Join request rejected");
11098
- console.log(DIM6(` ${getEasExplorerUrl(hash)}`));
11877
+ if (!hash) {
11878
+ spinner.info("Revocation skipped: attestation writes are disabled on this chain");
11879
+ } else {
11880
+ spinner.succeed("Join request rejected");
11881
+ console.log(DIM6(` ${getEasExplorerUrl(hash)}`));
11882
+ }
11099
11883
  } catch (err) {
11100
11884
  spinner.fail("Rejection failed");
11101
11885
  console.error(chalk18.red(formatContractError(err)));
@@ -11399,7 +12183,7 @@ vaultCmd.command("redeem").description("Redeem vault shares for the underlying a
11399
12183
  var strategy = program.command("strategy").description("Strategy templates \u2014 list, clone, propose");
11400
12184
  registerStrategyTemplateCommands(strategy);
11401
12185
  program.command("providers").description("List available providers (research + swap routing)").action(async () => {
11402
- const { listProviders } = await import("./providers-4HQHB7V5.js");
12186
+ const { listProviders } = await import("./providers-BMOHHVUN.js");
11403
12187
  for (const info of listProviders()) {
11404
12188
  console.log(`
11405
12189
  ${info.name} (${info.type})`);
@@ -11409,7 +12193,7 @@ ${info.name} (${info.type})`);
11409
12193
  }
11410
12194
  });
11411
12195
  try {
11412
- const { registerChatCommands } = await import("./chat-FGTYCJ4B.js");
12196
+ const { registerChatCommands } = await import("./chat-UZ3BBEXV.js");
11413
12197
  registerChatCommands(program);
11414
12198
  } catch {
11415
12199
  program.command("chat <name> [action] [actionArgs...]").description("Fund chat (XMTP) \u2014 requires @xmtp/cli").action(() => {
@@ -11419,7 +12203,7 @@ try {
11419
12203
  process.exit(1);
11420
12204
  });
11421
12205
  }
11422
- var { registerSessionCommands } = await import("./session-SIOJZO6P.js");
12206
+ var { registerSessionCommands } = await import("./session-RMAIFRS6.js");
11423
12207
  registerSessionCommands(program);
11424
12208
  registerVeniceCommands(program);
11425
12209
  registerAllowanceCommands(program);
@@ -11428,11 +12212,11 @@ registerProposalCommands(program);
11428
12212
  registerQueueCommands(program);
11429
12213
  registerGovernorCommands(program);
11430
12214
  registerGuardianCommands(program);
11431
- var { registerResearchCommands } = await import("./research-2RWAMWWH.js");
12215
+ var { registerResearchCommands } = await import("./research-YSNJ5HTM.js");
11432
12216
  registerResearchCommands(program);
11433
- var { registerAgentCommands } = await import("./agent-C5XZSK7Z.js");
12217
+ var { registerAgentCommands } = await import("./agent-TBLC3WNJ.js");
11434
12218
  registerAgentCommands(program);
11435
- var { registerTradeCommands } = await import("./trade-ZEIY3MCL.js");
12219
+ var { registerTradeCommands } = await import("./trade-YPLTIS56.js");
11436
12220
  registerTradeCommands(program);
11437
12221
  registerGridCommand(program);
11438
12222
  var configCmd = program.command("config");