@sherwoodagent/cli 0.59.18 → 0.64.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.
package/dist/index.js CHANGED
@@ -171,7 +171,7 @@ import {
171
171
  import { config as loadDotenv } from "dotenv";
172
172
  import { createRequire } from "module";
173
173
  import { Command, Option } from "commander";
174
- import { parseUnits as parseUnits7, formatUnits as formatUnits5, isAddress as isAddress6 } from "viem";
174
+ import { parseUnits as parseUnits7, formatUnits as formatUnits6, isAddress as isAddress7 } from "viem";
175
175
  import chalk15 from "chalk";
176
176
  import ora8 from "ora";
177
177
  import { input, confirm, select } from "@inquirer/prompts";
@@ -281,6 +281,18 @@ function formatBatch(calls) {
281
281
  }).join("\n");
282
282
  }
283
283
 
284
+ // src/lib/calldata.ts
285
+ var _calldataOnly = false;
286
+ function setCalldataOnly(value) {
287
+ _calldataOnly = value;
288
+ }
289
+ function isCalldataOnly() {
290
+ return _calldataOnly;
291
+ }
292
+ function emitCalldata(action) {
293
+ process.stdout.write(JSON.stringify(action, null, 2) + "\n");
294
+ }
295
+
284
296
  // src/strategies/moonwell-supply-template.ts
285
297
  import { encodeAbiParameters, encodeFunctionData } from "viem";
286
298
  function buildInitData(underlying, mToken, supplyAmount, minRedeemAmount) {
@@ -1383,11 +1395,11 @@ function registerStrategyTemplateCommands(strategy2) {
1383
1395
  console.log();
1384
1396
  console.log(chalk.yellow(" No strategy templates deployed on this network."));
1385
1397
  }
1386
- const unavailable = TEMPLATES.filter((t) => templates[t.addressKey] === ZERO);
1387
- if (unavailable.length > 0) {
1398
+ const unavailable2 = TEMPLATES.filter((t) => templates[t.addressKey] === ZERO);
1399
+ if (unavailable2.length > 0) {
1388
1400
  console.log();
1389
1401
  console.log(chalk.dim(` Not available on ${network}:`));
1390
- for (const t of unavailable) {
1402
+ for (const t of unavailable2) {
1391
1403
  console.log(chalk.dim(` ${t.name} (${t.key})`));
1392
1404
  }
1393
1405
  }
@@ -1527,6 +1539,29 @@ function registerStrategyTemplateCommands(strategy2) {
1527
1539
  process.exit(1);
1528
1540
  }
1529
1541
  const { def, address: templateAddr } = resolveTemplate(templateKey);
1542
+ if (isCalldataOnly()) {
1543
+ console.error(
1544
+ chalk.red(
1545
+ "strategy propose does not support --calldata-only (it clones + initializes on-chain first; the clone address isn't known until those txs are mined)."
1546
+ )
1547
+ );
1548
+ console.error(
1549
+ chalk.yellow(
1550
+ `
1551
+ Keyless flow:
1552
+ 1. Build the calls (clone + init still need a signer):
1553
+ sherwood strategy propose ${templateKey} --vault <vault> --write-calls ./calls [template flags]
1554
+ This prints the strategy clone address + writes execute.json / settle.json.
1555
+ 2. Emit the propose calldata for your external signer:
1556
+ sherwood proposal create --calldata-only --vault <vault> \\
1557
+ --strategy <clone> --name '...' --description '...' \\
1558
+ --performance-fee <bps> --duration <d> \\
1559
+ --execute-calls ./calls/execute.json --settle-calls ./calls/settle.json \\
1560
+ --metadata-uri ipfs://...`
1561
+ )
1562
+ );
1563
+ process.exit(1);
1564
+ }
1530
1565
  const account = getAccount();
1531
1566
  const preflightSpinner = ora("Preflight checks...").start();
1532
1567
  try {
@@ -2898,121 +2933,1708 @@ function registerAllowanceCommands(program2) {
2898
2933
  import chalk4 from "chalk";
2899
2934
  import ora4 from "ora";
2900
2935
  import { SDK } from "agent0-sdk";
2901
- var IDENTITY_REGISTRY_ABI = [
2936
+
2937
+ // ../sdk/dist/types.js
2938
+ var CHAIN_IDS = {
2939
+ BASE: 8453,
2940
+ HYPEREVM: 999,
2941
+ ROBINHOOD_L2: 46630
2942
+ };
2943
+ var SUPPORTED_CHAIN_IDS = [
2944
+ CHAIN_IDS.BASE,
2945
+ CHAIN_IDS.HYPEREVM,
2946
+ CHAIN_IDS.ROBINHOOD_L2
2947
+ ];
2948
+
2949
+ // ../sdk/dist/errors.js
2950
+ var SdkError = class extends Error {
2951
+ code;
2952
+ cause;
2953
+ constructor(code, message, cause) {
2954
+ super(message);
2955
+ this.code = code;
2956
+ this.cause = cause;
2957
+ this.name = "SdkError";
2958
+ }
2959
+ };
2960
+ var usage = (message) => new SdkError("USAGE", message);
2961
+ var unsupported = (message) => new SdkError("UNSUPPORTED", message);
2962
+
2963
+ // ../sdk/dist/abis.js
2964
+ var SYNDICATE_VAULT_ABI2 = [
2965
+ // ERC-4626
2966
+ {
2967
+ name: "deposit",
2968
+ type: "function",
2969
+ stateMutability: "nonpayable",
2970
+ inputs: [
2971
+ { name: "assets", type: "uint256" },
2972
+ { name: "receiver", type: "address" }
2973
+ ],
2974
+ outputs: [{ name: "shares", type: "uint256" }]
2975
+ },
2976
+ {
2977
+ name: "redeem",
2978
+ type: "function",
2979
+ stateMutability: "nonpayable",
2980
+ inputs: [
2981
+ { name: "shares", type: "uint256" },
2982
+ { name: "receiver", type: "address" },
2983
+ { name: "owner", type: "address" }
2984
+ ],
2985
+ outputs: [{ name: "", type: "uint256" }]
2986
+ },
2987
+ {
2988
+ name: "requestRedeem",
2989
+ type: "function",
2990
+ stateMutability: "nonpayable",
2991
+ inputs: [
2992
+ { name: "shares", type: "uint256" },
2993
+ { name: "owner", type: "address" }
2994
+ ],
2995
+ outputs: [{ name: "requestId", type: "uint256" }]
2996
+ },
2997
+ // Reads
2998
+ {
2999
+ name: "asset",
3000
+ type: "function",
3001
+ stateMutability: "view",
3002
+ inputs: [],
3003
+ outputs: [{ name: "", type: "address" }]
3004
+ },
3005
+ {
3006
+ name: "totalAssets",
3007
+ type: "function",
3008
+ stateMutability: "view",
3009
+ inputs: [],
3010
+ outputs: [{ name: "", type: "uint256" }]
3011
+ },
3012
+ {
3013
+ name: "totalSupply",
3014
+ type: "function",
3015
+ stateMutability: "view",
3016
+ inputs: [],
3017
+ outputs: [{ name: "", type: "uint256" }]
3018
+ },
3019
+ {
3020
+ name: "decimals",
3021
+ type: "function",
3022
+ stateMutability: "view",
3023
+ inputs: [],
3024
+ outputs: [{ name: "", type: "uint8" }]
3025
+ },
2902
3026
  {
2903
3027
  name: "balanceOf",
2904
3028
  type: "function",
2905
3029
  stateMutability: "view",
2906
- inputs: [{ name: "owner", type: "address" }],
3030
+ inputs: [{ name: "account", type: "address" }],
2907
3031
  outputs: [{ name: "", type: "uint256" }]
2908
3032
  },
2909
3033
  {
2910
- name: "ownerOf",
3034
+ name: "owner",
2911
3035
  type: "function",
2912
3036
  stateMutability: "view",
2913
- inputs: [{ name: "tokenId", type: "uint256" }],
3037
+ inputs: [],
3038
+ outputs: [{ name: "", type: "address" }]
3039
+ },
3040
+ {
3041
+ name: "governor",
3042
+ type: "function",
3043
+ stateMutability: "view",
3044
+ inputs: [],
2914
3045
  outputs: [{ name: "", type: "address" }]
3046
+ },
3047
+ {
3048
+ name: "redemptionsLocked",
3049
+ type: "function",
3050
+ stateMutability: "view",
3051
+ inputs: [],
3052
+ outputs: [{ name: "", type: "bool" }]
3053
+ },
3054
+ {
3055
+ name: "paused",
3056
+ type: "function",
3057
+ stateMutability: "view",
3058
+ inputs: [],
3059
+ outputs: [{ name: "", type: "bool" }]
3060
+ },
3061
+ {
3062
+ name: "openDeposits",
3063
+ type: "function",
3064
+ stateMutability: "view",
3065
+ inputs: [],
3066
+ outputs: [{ name: "", type: "bool" }]
3067
+ },
3068
+ {
3069
+ name: "getAgentCount",
3070
+ type: "function",
3071
+ stateMutability: "view",
3072
+ inputs: [],
3073
+ outputs: [{ name: "", type: "uint256" }]
3074
+ },
3075
+ {
3076
+ name: "isApprovedDepositor",
3077
+ type: "function",
3078
+ stateMutability: "view",
3079
+ inputs: [{ name: "depositor", type: "address" }],
3080
+ outputs: [{ name: "", type: "bool" }]
3081
+ },
3082
+ // Owner-side write surface
3083
+ {
3084
+ name: "approveDepositor",
3085
+ type: "function",
3086
+ stateMutability: "nonpayable",
3087
+ inputs: [{ name: "depositor", type: "address" }],
3088
+ outputs: []
3089
+ },
3090
+ {
3091
+ name: "removeDepositor",
3092
+ type: "function",
3093
+ stateMutability: "nonpayable",
3094
+ inputs: [{ name: "depositor", type: "address" }],
3095
+ outputs: []
3096
+ },
3097
+ {
3098
+ name: "registerAgent",
3099
+ type: "function",
3100
+ stateMutability: "nonpayable",
3101
+ inputs: [
3102
+ { name: "agentAddress", type: "address" },
3103
+ { name: "agentId", type: "uint256" }
3104
+ ],
3105
+ outputs: []
2915
3106
  }
2916
3107
  ];
2917
- function getAgent0SDK() {
2918
- const config = loadConfig();
2919
- const key = config.privateKey || process.env.PRIVATE_KEY;
2920
- if (!key) {
2921
- throw new Error(
2922
- "Private key not found. Run 'sherwood config set --private-key <key>' or set PRIVATE_KEY env var."
2923
- );
2924
- }
2925
- return new SDK({
2926
- chainId: getChain().id,
2927
- rpcUrl: getRpcUrl(),
2928
- privateKey: key.startsWith("0x") ? key : `0x${key}`
2929
- });
2930
- }
2931
- function registerIdentityCommands(program2) {
2932
- const identity = program2.command("identity").description("Manage ERC-8004 agent identity (via Agent0 SDK)");
2933
- identity.command("mint").description("Register a new ERC-8004 agent identity (required before creating/joining syndicates)").requiredOption("--name <name>", "Agent name (e.g. 'Alpha Seeker Agent')").option("--description <desc>", "Agent description", "Sherwood syndicate agent").option("--image <uri>", "Agent image URI (IPFS recommended)").action(async (opts) => {
2934
- const account = getAccount();
2935
- const existingId = getAgentId();
2936
- if (existingId) {
2937
- console.log(chalk4.yellow(`You already have an agent identity saved: #${existingId}`));
2938
- console.log(chalk4.dim(" Minting a new one anyway. The old ID is not affected."));
2939
- console.log();
2940
- }
2941
- const spinner = ora4("Initializing Agent0 SDK...").start();
2942
- try {
2943
- const sdk = getAgent0SDK();
2944
- spinner.text = "Creating agent profile...";
2945
- const agent = sdk.createAgent(opts.name, opts.description, opts.image);
2946
- spinner.text = "Registering on-chain (minting ERC-8004 identity)...";
2947
- const txHandle = await agent.registerOnChain();
2948
- spinner.text = "Waiting for confirmation...";
2949
- await txHandle.waitMined();
2950
- const agentId = agent.agentId;
2951
- if (!agentId) {
2952
- spinner.warn("Identity registered but could not read agentId");
2953
- console.log(chalk4.dim(" Check the transaction on the explorer."));
2954
- return;
3108
+ var SYNDICATE_GOVERNOR_ABI = [
3109
+ // Propose (multi-arg). Order MUST match contract: vault, strategy,
3110
+ // metadataURI, performanceFeeBps, strategyDuration, executeCalls,
3111
+ // settlementCalls, coProposers.
3112
+ {
3113
+ name: "propose",
3114
+ type: "function",
3115
+ stateMutability: "nonpayable",
3116
+ inputs: [
3117
+ { name: "vault", type: "address" },
3118
+ { name: "strategy", type: "address" },
3119
+ { name: "metadataURI", type: "string" },
3120
+ { name: "performanceFeeBps", type: "uint256" },
3121
+ { name: "strategyDuration", type: "uint256" },
3122
+ {
3123
+ name: "executeCalls",
3124
+ type: "tuple[]",
3125
+ components: [
3126
+ { name: "target", type: "address" },
3127
+ { name: "data", type: "bytes" },
3128
+ { name: "value", type: "uint256" }
3129
+ ]
3130
+ },
3131
+ {
3132
+ name: "settlementCalls",
3133
+ type: "tuple[]",
3134
+ components: [
3135
+ { name: "target", type: "address" },
3136
+ { name: "data", type: "bytes" },
3137
+ { name: "value", type: "uint256" }
3138
+ ]
3139
+ },
3140
+ {
3141
+ name: "coProposers",
3142
+ type: "tuple[]",
3143
+ components: [
3144
+ { name: "agent", type: "address" },
3145
+ { name: "splitBps", type: "uint256" }
3146
+ ]
2955
3147
  }
2956
- const tokenId = Number(agentId.includes(":") ? agentId.split(":")[1] : agentId);
2957
- setAgentId(tokenId);
2958
- spinner.succeed(`Agent identity registered: #${tokenId}`);
2959
- console.log(chalk4.dim(` Agent0 ID: ${agentId}`));
2960
- console.log(chalk4.dim(` Name: ${opts.name}`));
2961
- console.log(chalk4.dim(` Owner: ${account.address}`));
2962
- console.log(chalk4.dim(` Saved to ~/.sherwood/config.json`));
2963
- console.log();
2964
- console.log(chalk4.green("You can now create syndicates:"));
2965
- console.log(chalk4.dim(` sherwood syndicate create --agent-id ${tokenId} --subdomain <name> --name <name>`));
2966
- } catch (err) {
2967
- spinner.fail("Failed to register identity");
2968
- console.error(chalk4.red(formatContractError(err)));
2969
- process.exit(1);
2970
- }
2971
- });
2972
- identity.command("load").description("Load an existing ERC-8004 agent identity into your config").requiredOption("--id <tokenId>", "Agent token ID to load").action(async (opts) => {
2973
- const account = getAccount();
2974
- const client = getPublicClient();
2975
- const registry2 = AGENT_REGISTRY().IDENTITY_REGISTRY;
2976
- const tokenId = Number(opts.id);
2977
- const spinner = ora4(`Verifying ownership of agent #${tokenId}...`).start();
2978
- try {
2979
- const owner = await client.readContract({
2980
- address: registry2,
2981
- abi: IDENTITY_REGISTRY_ABI,
2982
- functionName: "ownerOf",
2983
- args: [BigInt(tokenId)]
2984
- });
2985
- if (owner.toLowerCase() !== account.address.toLowerCase()) {
2986
- spinner.fail(`Agent #${tokenId} is owned by ${owner}, not your wallet`);
2987
- process.exit(1);
3148
+ ],
3149
+ outputs: [{ name: "proposalId", type: "uint256" }]
3150
+ },
3151
+ // Lifecycle writes
3152
+ {
3153
+ name: "vote",
3154
+ type: "function",
3155
+ stateMutability: "nonpayable",
3156
+ inputs: [
3157
+ { name: "proposalId", type: "uint256" },
3158
+ { name: "support", type: "uint8" }
3159
+ ],
3160
+ outputs: []
3161
+ },
3162
+ {
3163
+ name: "executeProposal",
3164
+ type: "function",
3165
+ stateMutability: "nonpayable",
3166
+ inputs: [{ name: "proposalId", type: "uint256" }],
3167
+ outputs: []
3168
+ },
3169
+ {
3170
+ name: "settleProposal",
3171
+ type: "function",
3172
+ stateMutability: "nonpayable",
3173
+ inputs: [{ name: "proposalId", type: "uint256" }],
3174
+ outputs: []
3175
+ },
3176
+ {
3177
+ name: "cancelProposal",
3178
+ type: "function",
3179
+ stateMutability: "nonpayable",
3180
+ inputs: [{ name: "proposalId", type: "uint256" }],
3181
+ outputs: []
3182
+ },
3183
+ {
3184
+ name: "vetoProposal",
3185
+ type: "function",
3186
+ stateMutability: "nonpayable",
3187
+ inputs: [{ name: "proposalId", type: "uint256" }],
3188
+ outputs: []
3189
+ },
3190
+ // Emergency settlement (GovernorEmergency, V1.5). `unstick` is the
3191
+ // owner-instant pre-committed-calls path; `emergencySettleWithCalls` commits
3192
+ // a new calldata hash and opens a guardian-reviewed window. The removed
3193
+ // `emergencySettle` is intentionally absent — it is not on the deployed impl.
3194
+ {
3195
+ name: "unstick",
3196
+ type: "function",
3197
+ stateMutability: "nonpayable",
3198
+ inputs: [{ name: "proposalId", type: "uint256" }],
3199
+ outputs: []
3200
+ },
3201
+ {
3202
+ name: "emergencySettleWithCalls",
3203
+ type: "function",
3204
+ stateMutability: "nonpayable",
3205
+ inputs: [
3206
+ { name: "proposalId", type: "uint256" },
3207
+ {
3208
+ name: "calls",
3209
+ type: "tuple[]",
3210
+ components: [
3211
+ { name: "target", type: "address" },
3212
+ { name: "data", type: "bytes" },
3213
+ { name: "value", type: "uint256" }
3214
+ ]
2988
3215
  }
2989
- setAgentId(tokenId);
2990
- spinner.succeed(`Agent #${tokenId} loaded and saved to config`);
2991
- console.log(chalk4.dim(` Owner: ${account.address}`));
2992
- console.log(chalk4.dim(` Saved to ~/.sherwood/config.json`));
2993
- } catch (err) {
2994
- spinner.fail("Failed to load identity");
2995
- console.error(chalk4.red(formatContractError(err)));
2996
- process.exit(1);
2997
- }
2998
- });
2999
- identity.command("find").description("Search ERC-8004 agents by wallet address or name (recover a lost agent ID)").option("--wallet <address>", "Filter by owner / wallet address").option("--name <text>", "Filter by agent name (exact or substring match, depending on registry)").option("--all-chains", "Search across all chains, not just the active one").action(async (opts) => {
3000
- if (!opts.wallet && !opts.name) {
3001
- console.error(chalk4.red("Pass --wallet <address> or --name <text> (or both)."));
3002
- process.exit(1);
3003
- }
3004
- const spinner = ora4("Searching ERC-8004 registry...").start();
3005
- try {
3006
- const sdk = getAgent0SDK();
3007
- const filters = {};
3008
- if (opts.wallet) filters.walletAddress = opts.wallet;
3009
- if (opts.name) filters.name = opts.name;
3010
- filters.chains = opts.allChains ? "all" : [getChain().id];
3011
- const results = await sdk.searchAgents(filters);
3012
- spinner.stop();
3013
- if (results.length === 0) {
3014
- console.log(chalk4.dim("No agents found matching those filters."));
3015
- console.log(chalk4.dim("If you minted on a different chain, retry with --all-chains."));
3216
+ ],
3217
+ outputs: []
3218
+ },
3219
+ // Parameter setters (GovernorParameters, V1.5 owner-instant, no timelock).
3220
+ { name: "setVotingPeriod", type: "function", stateMutability: "nonpayable", inputs: [{ name: "newValue", type: "uint256" }], outputs: [] },
3221
+ { name: "setExecutionWindow", type: "function", stateMutability: "nonpayable", inputs: [{ name: "newValue", type: "uint256" }], outputs: [] },
3222
+ { name: "setVetoThresholdBps", type: "function", stateMutability: "nonpayable", inputs: [{ name: "newValue", type: "uint256" }], outputs: [] },
3223
+ { name: "setMaxPerformanceFeeBps", type: "function", stateMutability: "nonpayable", inputs: [{ name: "newValue", type: "uint256" }], outputs: [] },
3224
+ { name: "setMaxStrategyDuration", type: "function", stateMutability: "nonpayable", inputs: [{ name: "newValue", type: "uint256" }], outputs: [] },
3225
+ { name: "setCooldownPeriod", type: "function", stateMutability: "nonpayable", inputs: [{ name: "newValue", type: "uint256" }], outputs: [] },
3226
+ { name: "setProtocolFeeBps", type: "function", stateMutability: "nonpayable", inputs: [{ name: "newValue", type: "uint256" }], outputs: [] },
3227
+ // Reads
3228
+ {
3229
+ name: "getProposal",
3230
+ type: "function",
3231
+ stateMutability: "view",
3232
+ inputs: [{ name: "proposalId", type: "uint256" }],
3233
+ outputs: [
3234
+ {
3235
+ name: "",
3236
+ type: "tuple",
3237
+ components: [
3238
+ { name: "id", type: "uint256" },
3239
+ { name: "proposer", type: "address" },
3240
+ { name: "vault", type: "address" },
3241
+ { name: "strategy", type: "address" },
3242
+ { name: "metadataURI", type: "string" },
3243
+ { name: "performanceFeeBps", type: "uint256" },
3244
+ { name: "strategyDuration", type: "uint256" },
3245
+ { name: "votesFor", type: "uint256" },
3246
+ { name: "votesAgainst", type: "uint256" },
3247
+ { name: "votesAbstain", type: "uint256" },
3248
+ { name: "snapshotTimestamp", type: "uint256" },
3249
+ { name: "voteEnd", type: "uint256" },
3250
+ { name: "reviewEnd", type: "uint256" },
3251
+ { name: "executeBy", type: "uint256" },
3252
+ { name: "executedAt", type: "uint256" },
3253
+ { name: "state", type: "uint8" },
3254
+ { name: "vetoThresholdBps", type: "uint256" }
3255
+ ]
3256
+ }
3257
+ ]
3258
+ },
3259
+ {
3260
+ name: "getProposalState",
3261
+ type: "function",
3262
+ stateMutability: "view",
3263
+ inputs: [{ name: "proposalId", type: "uint256" }],
3264
+ outputs: [{ name: "", type: "uint8" }]
3265
+ },
3266
+ {
3267
+ name: "getActiveProposal",
3268
+ type: "function",
3269
+ stateMutability: "view",
3270
+ inputs: [{ name: "vault", type: "address" }],
3271
+ outputs: [{ name: "", type: "uint256" }]
3272
+ },
3273
+ {
3274
+ name: "proposalCount",
3275
+ type: "function",
3276
+ stateMutability: "view",
3277
+ inputs: [],
3278
+ outputs: [{ name: "", type: "uint256" }]
3279
+ },
3280
+ {
3281
+ name: "getGovernorParams",
3282
+ type: "function",
3283
+ stateMutability: "view",
3284
+ inputs: [],
3285
+ outputs: [
3286
+ {
3287
+ name: "",
3288
+ type: "tuple",
3289
+ components: [
3290
+ { name: "votingPeriod", type: "uint256" },
3291
+ { name: "executionWindow", type: "uint256" },
3292
+ { name: "vetoThresholdBps", type: "uint256" },
3293
+ { name: "maxPerformanceFeeBps", type: "uint256" },
3294
+ { name: "cooldownPeriod", type: "uint256" },
3295
+ { name: "collaborationWindow", type: "uint256" },
3296
+ { name: "maxCoProposers", type: "uint256" },
3297
+ { name: "minStrategyDuration", type: "uint256" },
3298
+ { name: "maxStrategyDuration", type: "uint256" }
3299
+ ]
3300
+ }
3301
+ ]
3302
+ },
3303
+ {
3304
+ name: "protocolFeeBps",
3305
+ type: "function",
3306
+ stateMutability: "view",
3307
+ inputs: [],
3308
+ outputs: [{ name: "", type: "uint256" }]
3309
+ },
3310
+ {
3311
+ name: "guardianFeeBps",
3312
+ type: "function",
3313
+ stateMutability: "view",
3314
+ inputs: [],
3315
+ outputs: [{ name: "", type: "uint256" }]
3316
+ },
3317
+ {
3318
+ name: "protocolFeeRecipient",
3319
+ type: "function",
3320
+ stateMutability: "view",
3321
+ inputs: [],
3322
+ outputs: [{ name: "", type: "address" }]
3323
+ }
3324
+ ];
3325
+ var SYNDICATE_FACTORY_ABI = [
3326
+ {
3327
+ name: "createSyndicate",
3328
+ type: "function",
3329
+ stateMutability: "nonpayable",
3330
+ inputs: [
3331
+ { name: "creatorAgentId", type: "uint256" },
3332
+ {
3333
+ name: "config",
3334
+ type: "tuple",
3335
+ components: [
3336
+ { name: "metadataURI", type: "string" },
3337
+ { name: "asset", type: "address" },
3338
+ { name: "name", type: "string" },
3339
+ { name: "symbol", type: "string" },
3340
+ { name: "openDeposits", type: "bool" },
3341
+ { name: "subdomain", type: "string" }
3342
+ ]
3343
+ }
3344
+ ],
3345
+ outputs: [
3346
+ { name: "syndicateId", type: "uint256" },
3347
+ { name: "vault", type: "address" }
3348
+ ]
3349
+ },
3350
+ {
3351
+ name: "syndicateCount",
3352
+ type: "function",
3353
+ stateMutability: "view",
3354
+ inputs: [],
3355
+ outputs: [{ name: "", type: "uint256" }]
3356
+ },
3357
+ {
3358
+ name: "subdomainToSyndicate",
3359
+ type: "function",
3360
+ stateMutability: "view",
3361
+ inputs: [{ name: "subdomain", type: "string" }],
3362
+ outputs: [{ name: "", type: "uint256" }]
3363
+ },
3364
+ {
3365
+ name: "vaultToSyndicate",
3366
+ type: "function",
3367
+ stateMutability: "view",
3368
+ inputs: [{ name: "vault", type: "address" }],
3369
+ outputs: [{ name: "", type: "uint256" }]
3370
+ },
3371
+ {
3372
+ name: "syndicates",
3373
+ type: "function",
3374
+ stateMutability: "view",
3375
+ inputs: [{ name: "id", type: "uint256" }],
3376
+ outputs: [
3377
+ { name: "id", type: "uint256" },
3378
+ { name: "vault", type: "address" },
3379
+ { name: "creator", type: "address" },
3380
+ { name: "metadataURI", type: "string" },
3381
+ { name: "createdAt", type: "uint256" },
3382
+ { name: "active", type: "bool" },
3383
+ { name: "subdomain", type: "string" }
3384
+ ]
3385
+ },
3386
+ {
3387
+ name: "getAllActiveSyndicates",
3388
+ type: "function",
3389
+ stateMutability: "view",
3390
+ inputs: [],
3391
+ outputs: [
3392
+ {
3393
+ name: "",
3394
+ type: "tuple[]",
3395
+ components: [
3396
+ { name: "id", type: "uint256" },
3397
+ { name: "vault", type: "address" },
3398
+ { name: "creator", type: "address" },
3399
+ { name: "metadataURI", type: "string" },
3400
+ { name: "createdAt", type: "uint256" },
3401
+ { name: "active", type: "bool" },
3402
+ { name: "subdomain", type: "string" }
3403
+ ]
3404
+ }
3405
+ ]
3406
+ }
3407
+ ];
3408
+ var GUARDIAN_REGISTRY_ABI = [
3409
+ // Guardian stake
3410
+ {
3411
+ name: "stakeAsGuardian",
3412
+ type: "function",
3413
+ stateMutability: "nonpayable",
3414
+ inputs: [
3415
+ { name: "amount", type: "uint256" },
3416
+ { name: "agentId", type: "uint256" }
3417
+ ],
3418
+ outputs: []
3419
+ },
3420
+ {
3421
+ name: "requestUnstakeGuardian",
3422
+ type: "function",
3423
+ stateMutability: "nonpayable",
3424
+ inputs: [],
3425
+ outputs: []
3426
+ },
3427
+ {
3428
+ name: "cancelUnstakeGuardian",
3429
+ type: "function",
3430
+ stateMutability: "nonpayable",
3431
+ inputs: [],
3432
+ outputs: []
3433
+ },
3434
+ {
3435
+ name: "claimUnstakeGuardian",
3436
+ type: "function",
3437
+ stateMutability: "nonpayable",
3438
+ inputs: [],
3439
+ outputs: []
3440
+ },
3441
+ // Delegation
3442
+ {
3443
+ name: "delegateStake",
3444
+ type: "function",
3445
+ stateMutability: "nonpayable",
3446
+ inputs: [
3447
+ { name: "delegate", type: "address" },
3448
+ { name: "amount", type: "uint256" }
3449
+ ],
3450
+ outputs: []
3451
+ },
3452
+ {
3453
+ name: "requestUnstakeDelegation",
3454
+ type: "function",
3455
+ stateMutability: "nonpayable",
3456
+ inputs: [{ name: "delegate", type: "address" }],
3457
+ outputs: []
3458
+ },
3459
+ {
3460
+ name: "cancelUnstakeDelegation",
3461
+ type: "function",
3462
+ stateMutability: "nonpayable",
3463
+ inputs: [{ name: "delegate", type: "address" }],
3464
+ outputs: []
3465
+ },
3466
+ {
3467
+ name: "claimUnstakeDelegation",
3468
+ type: "function",
3469
+ stateMutability: "nonpayable",
3470
+ inputs: [{ name: "delegate", type: "address" }],
3471
+ outputs: []
3472
+ },
3473
+ // Commission
3474
+ {
3475
+ name: "setCommission",
3476
+ type: "function",
3477
+ stateMutability: "nonpayable",
3478
+ inputs: [{ name: "bps", type: "uint16" }],
3479
+ outputs: []
3480
+ },
3481
+ // Claims
3482
+ {
3483
+ name: "claimProposalReward",
3484
+ type: "function",
3485
+ stateMutability: "nonpayable",
3486
+ inputs: [{ name: "proposalId", type: "uint256" }],
3487
+ outputs: []
3488
+ },
3489
+ {
3490
+ name: "claimDelegatorProposalReward",
3491
+ type: "function",
3492
+ stateMutability: "nonpayable",
3493
+ inputs: [
3494
+ { name: "delegate", type: "address" },
3495
+ { name: "proposalId", type: "uint256" }
3496
+ ],
3497
+ outputs: []
3498
+ },
3499
+ // Reads
3500
+ {
3501
+ name: "guardianStake",
3502
+ type: "function",
3503
+ stateMutability: "view",
3504
+ inputs: [{ name: "guardian", type: "address" }],
3505
+ outputs: [{ name: "", type: "uint256" }]
3506
+ },
3507
+ {
3508
+ name: "isActiveGuardian",
3509
+ type: "function",
3510
+ stateMutability: "view",
3511
+ inputs: [{ name: "guardian", type: "address" }],
3512
+ outputs: [{ name: "", type: "bool" }]
3513
+ }
3514
+ ];
3515
+ var ERC20_ABI2 = [
3516
+ {
3517
+ name: "approve",
3518
+ type: "function",
3519
+ stateMutability: "nonpayable",
3520
+ inputs: [
3521
+ { name: "spender", type: "address" },
3522
+ { name: "amount", type: "uint256" }
3523
+ ],
3524
+ outputs: [{ name: "", type: "bool" }]
3525
+ },
3526
+ {
3527
+ name: "allowance",
3528
+ type: "function",
3529
+ stateMutability: "view",
3530
+ inputs: [
3531
+ { name: "owner", type: "address" },
3532
+ { name: "spender", type: "address" }
3533
+ ],
3534
+ outputs: [{ name: "", type: "uint256" }]
3535
+ },
3536
+ {
3537
+ name: "balanceOf",
3538
+ type: "function",
3539
+ stateMutability: "view",
3540
+ inputs: [{ name: "account", type: "address" }],
3541
+ outputs: [{ name: "", type: "uint256" }]
3542
+ },
3543
+ {
3544
+ name: "decimals",
3545
+ type: "function",
3546
+ stateMutability: "view",
3547
+ inputs: [],
3548
+ outputs: [{ name: "", type: "uint8" }]
3549
+ },
3550
+ {
3551
+ name: "symbol",
3552
+ type: "function",
3553
+ stateMutability: "view",
3554
+ inputs: [],
3555
+ outputs: [{ name: "", type: "string" }]
3556
+ }
3557
+ ];
3558
+ var IDENTITY_REGISTRY_ABI = [
3559
+ {
3560
+ name: "register",
3561
+ type: "function",
3562
+ stateMutability: "nonpayable",
3563
+ inputs: [
3564
+ { name: "agentURI", type: "string" },
3565
+ {
3566
+ name: "metadata",
3567
+ type: "tuple[]",
3568
+ components: [
3569
+ { name: "metadataKey", type: "string" },
3570
+ { name: "metadataValue", type: "bytes" }
3571
+ ]
3572
+ }
3573
+ ],
3574
+ outputs: [{ name: "agentId", type: "uint256" }]
3575
+ }
3576
+ ];
3577
+ var EAS_ABI = [
3578
+ {
3579
+ name: "attest",
3580
+ type: "function",
3581
+ stateMutability: "payable",
3582
+ inputs: [
3583
+ {
3584
+ name: "request",
3585
+ type: "tuple",
3586
+ components: [
3587
+ { name: "schema", type: "bytes32" },
3588
+ {
3589
+ name: "data",
3590
+ type: "tuple",
3591
+ components: [
3592
+ { name: "recipient", type: "address" },
3593
+ { name: "expirationTime", type: "uint64" },
3594
+ { name: "revocable", type: "bool" },
3595
+ { name: "refUID", type: "bytes32" },
3596
+ { name: "data", type: "bytes" },
3597
+ { name: "value", type: "uint256" }
3598
+ ]
3599
+ }
3600
+ ]
3601
+ }
3602
+ ],
3603
+ outputs: [{ name: "", type: "bytes32" }]
3604
+ }
3605
+ ];
3606
+
3607
+ // ../sdk/dist/addresses.js
3608
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
3609
+ var ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
3610
+ var BASE_EAS = {
3611
+ chainId: CHAIN_IDS.BASE,
3612
+ address: "0x4200000000000000000000000000000000000021",
3613
+ schemas: {
3614
+ joinRequest: "0x1e7ce17b16233977ba913b156033e98f52029f4bee273a4abefe6c15ce11d5ef",
3615
+ agentApproved: "0x1013f7b38f433b2a93fc5ac162482813081c64edd67cea9b5a90698531ddb607"
3616
+ }
3617
+ };
3618
+ var NO_EAS = {
3619
+ chainId: CHAIN_IDS.BASE,
3620
+ address: ZERO_ADDRESS,
3621
+ schemas: { joinRequest: ZERO_BYTES32, agentApproved: ZERO_BYTES32 }
3622
+ };
3623
+ var BASE = {
3624
+ chainId: CHAIN_IDS.BASE,
3625
+ name: "Base",
3626
+ slug: "base",
3627
+ sherwood: {
3628
+ factory: "0xAC74EC56858d7F1f7618c8e77F65Fc26aDf33c82",
3629
+ governor: "0x9Fd3c87B34F254e3c5652A0394B9780c2F05d367",
3630
+ guardianRegistry: "0x49E4163b5e4b23F8f3d469Cf6fa197FB6b06A26E",
3631
+ vaultImpl: "0xfce4bcE08E9C047E4736f75C2B8557e2754Ce36A",
3632
+ batchExecutorLib: "0xbC79FbD5036C1Cc4A9d10BDf8628BF09a558496E"
3633
+ },
3634
+ identityRegistry: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
3635
+ eas: BASE_EAS,
3636
+ tokens: {
3637
+ usdc: {
3638
+ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
3639
+ symbol: "USDC",
3640
+ decimals: 6
3641
+ },
3642
+ weth: {
3643
+ address: "0x4200000000000000000000000000000000000006",
3644
+ symbol: "WETH",
3645
+ decimals: 18
3646
+ }
3647
+ },
3648
+ explorer: "https://basescan.org",
3649
+ rpcs: ["https://mainnet.base.org"]
3650
+ };
3651
+ var HYPEREVM = {
3652
+ chainId: CHAIN_IDS.HYPEREVM,
3653
+ name: "HyperEVM",
3654
+ slug: "hyperevm",
3655
+ sherwood: {
3656
+ factory: "0xd05Ae0E8bcf13075C29817c805d6Cc14F214393a",
3657
+ governor: "0x67AD3D5F3d127Ef923Fd6f67b178633c408D3fd3",
3658
+ guardianRegistry: "0x8b5710EB4e2fA639F364Dcc3F3B30c8f12F460b9",
3659
+ vaultImpl: "0x2cbBe36Cf907A2BB410bacB0e4Fd632C7b012846",
3660
+ batchExecutorLib: "0x2c454bEF1b09c8a306a7058b8B510bF0DfF7179D"
3661
+ },
3662
+ // ERC-8004 identity is minted on Base; HyperEVM syndicates reference the
3663
+ // Base-minted token id. Coordination attestations are pinned to Base EAS.
3664
+ identityRegistry: ZERO_ADDRESS,
3665
+ eas: BASE_EAS,
3666
+ tokens: {
3667
+ usdc: {
3668
+ address: "0xb88339CB7199b77E23DB6E890353E22632Ba630f",
3669
+ symbol: "USDC",
3670
+ decimals: 6
3671
+ }
3672
+ },
3673
+ explorer: "https://hyperliquid.cloud.blockscout.com",
3674
+ rpcs: ["https://rpc.hyperliquid.xyz/evm"]
3675
+ };
3676
+ var ROBINHOOD_L2 = {
3677
+ chainId: CHAIN_IDS.ROBINHOOD_L2,
3678
+ name: "Robinhood L2 Testnet",
3679
+ slug: "robinhood-l2",
3680
+ sherwood: {
3681
+ factory: "0x6d026e2f5Ff0C34A01690EC46Cb601B8fF391985",
3682
+ governor: "0xd882056ba6b0aEd8908c541884B327121E2f2C9C",
3683
+ // Robinhood L2 doesn't have a guardian registry yet; use zero so callers
3684
+ // get a clear UNSUPPORTED rather than a confusing revert downstream.
3685
+ guardianRegistry: "0x0000000000000000000000000000000000000000",
3686
+ vaultImpl: "0xF4720523325f9A4546F43391484DCd1D28dFc266",
3687
+ batchExecutorLib: "0x1493f5a7E5d82e1e56c34e2Ba300f56F97186017"
3688
+ },
3689
+ // No ERC-8004 registry and no EAS path on Robinhood L2.
3690
+ identityRegistry: ZERO_ADDRESS,
3691
+ eas: NO_EAS,
3692
+ tokens: {},
3693
+ explorer: "https://explorer-robinhood-l2.t.conduit.xyz",
3694
+ rpcs: ["https://rpc-robinhood-l2.t.conduit.xyz"]
3695
+ };
3696
+ var REGISTRY = {
3697
+ [CHAIN_IDS.BASE]: BASE,
3698
+ [CHAIN_IDS.HYPEREVM]: HYPEREVM,
3699
+ [CHAIN_IDS.ROBINHOOD_L2]: ROBINHOOD_L2
3700
+ };
3701
+ function getDeployment(chainId) {
3702
+ const entry = REGISTRY[chainId];
3703
+ if (!entry)
3704
+ throw unsupported(`No Sherwood deployment for chain ${chainId}`);
3705
+ return entry;
3706
+ }
3707
+
3708
+ // ../sdk/dist/encoders/vault.js
3709
+ import { encodeFunctionData as encodeFunctionData10, getAddress, maxUint256 as maxUint2564 } from "viem";
3710
+ var WETH_ABI = [
3711
+ {
3712
+ name: "deposit",
3713
+ type: "function",
3714
+ stateMutability: "payable",
3715
+ inputs: [],
3716
+ outputs: []
3717
+ }
3718
+ ];
3719
+ var ZERO_VALUE = "0x0";
3720
+ function toHexValue(value) {
3721
+ return `0x${value.toString(16)}`;
3722
+ }
3723
+ function encodeApproveTx(token, spender, amount, chainId) {
3724
+ return {
3725
+ to: token,
3726
+ data: encodeFunctionData10({
3727
+ abi: ERC20_ABI2,
3728
+ functionName: "approve",
3729
+ args: [spender, amount]
3730
+ }),
3731
+ value: ZERO_VALUE,
3732
+ chainId
3733
+ };
3734
+ }
3735
+ function encodeDepositTx(vault, assets, receiver, chainId) {
3736
+ return {
3737
+ to: vault,
3738
+ data: encodeFunctionData10({
3739
+ abi: SYNDICATE_VAULT_ABI2,
3740
+ functionName: "deposit",
3741
+ args: [assets, receiver]
3742
+ }),
3743
+ value: ZERO_VALUE,
3744
+ chainId
3745
+ };
3746
+ }
3747
+ function encodeDeposit(args, chainId) {
3748
+ if (args.assets <= 0n) {
3749
+ throw usage("`assets` must be > 0");
3750
+ }
3751
+ const vault = getAddress(args.vault);
3752
+ const receiver = getAddress(args.receiver);
3753
+ const asset = getAddress(args.asset);
3754
+ const txs = [];
3755
+ let wrapped = false;
3756
+ if (args.wrapEth) {
3757
+ const weth = getDeployment(chainId).tokens.weth;
3758
+ if (!weth || getAddress(weth.address) !== asset) {
3759
+ throw usage("`wrapEth` is only valid for WETH vaults \u2014 the vault asset must be the chain's WETH.");
3760
+ }
3761
+ const held = args.currentWethBalance ?? 0n;
3762
+ const shortfall = held >= args.assets ? 0n : args.assets - held;
3763
+ if (shortfall > 0n) {
3764
+ txs.push({
3765
+ to: asset,
3766
+ data: encodeFunctionData10({ abi: WETH_ABI, functionName: "deposit", args: [] }),
3767
+ value: toHexValue(shortfall),
3768
+ chainId
3769
+ });
3770
+ wrapped = true;
3771
+ }
3772
+ }
3773
+ const needsApprove = args.currentAllowance === void 0 || args.currentAllowance < args.assets;
3774
+ if (needsApprove) {
3775
+ txs.push(encodeApproveTx(asset, vault, maxUint2564, chainId));
3776
+ }
3777
+ txs.push(encodeDepositTx(vault, args.assets, receiver, chainId));
3778
+ const minDecimal = formatUnits4(args.assets, args.assetDecimals);
3779
+ let description;
3780
+ if (wrapped) {
3781
+ description = needsApprove ? `Wrap ETH\u2192WETH, approve ${args.assetSymbol} for ${vault}, then deposit ${minDecimal} ${args.assetSymbol}.` : `Wrap ETH\u2192WETH, then deposit ${minDecimal} ${args.assetSymbol}.`;
3782
+ } else {
3783
+ description = needsApprove ? `Approve ${args.assetSymbol} for ${vault}, then deposit ${minDecimal} ${args.assetSymbol}.` : `Deposit ${minDecimal} ${args.assetSymbol} into ${vault}.`;
3784
+ }
3785
+ return {
3786
+ txs,
3787
+ preconditions: [
3788
+ {
3789
+ type: "balance",
3790
+ asset,
3791
+ assetSymbol: args.assetSymbol,
3792
+ min: args.assets.toString(),
3793
+ minDecimal
3794
+ },
3795
+ {
3796
+ type: "allowance",
3797
+ token: asset,
3798
+ spender: vault,
3799
+ min: args.assets.toString(),
3800
+ minDecimal
3801
+ },
3802
+ { type: "vault-not-locked", vault },
3803
+ { type: "depositor-approved", vault, depositor: receiver }
3804
+ ],
3805
+ description,
3806
+ note: "Both txs are gated by vault state. If `redemptionsLocked()` is true the deposit reverts unless live NAV is available; check the vault read endpoint before broadcasting."
3807
+ };
3808
+ }
3809
+ function formatUnits4(value, decimals) {
3810
+ if (decimals === 0)
3811
+ return value.toString();
3812
+ const negative = value < 0n;
3813
+ const abs = negative ? -value : value;
3814
+ const str = abs.toString().padStart(decimals + 1, "0");
3815
+ const head = str.slice(0, str.length - decimals);
3816
+ const tail = str.slice(str.length - decimals).replace(/0+$/, "");
3817
+ const out = tail.length > 0 ? `${head}.${tail}` : head;
3818
+ return negative ? `-${out}` : out;
3819
+ }
3820
+ function encodeRedeem(args, chainId) {
3821
+ if (args.shares <= 0n)
3822
+ throw usage("`shares` must be > 0");
3823
+ const vault = getAddress(args.vault);
3824
+ const receiver = getAddress(args.receiver);
3825
+ const owner = getAddress(args.owner);
3826
+ return {
3827
+ txs: [
3828
+ {
3829
+ to: vault,
3830
+ data: encodeFunctionData10({
3831
+ abi: SYNDICATE_VAULT_ABI2,
3832
+ functionName: "redeem",
3833
+ args: [args.shares, receiver, owner]
3834
+ }),
3835
+ value: ZERO_VALUE,
3836
+ chainId
3837
+ }
3838
+ ],
3839
+ preconditions: [
3840
+ { type: "vault-not-locked", vault }
3841
+ ],
3842
+ description: `Redeem ${args.shares.toString()} shares of ${vault} \u2192 ${receiver}.`,
3843
+ note: "Synchronous path. Reverts if the vault has an active proposal AND live NAV is unavailable; in that case use /prepare/request-redeem (queue path) instead."
3844
+ };
3845
+ }
3846
+ function encodeApproveDepositor(args, chainId) {
3847
+ const vault = getAddress(args.vault);
3848
+ const depositor = getAddress(args.depositor);
3849
+ return {
3850
+ txs: [
3851
+ {
3852
+ to: vault,
3853
+ data: encodeFunctionData10({
3854
+ abi: SYNDICATE_VAULT_ABI2,
3855
+ functionName: "approveDepositor",
3856
+ args: [depositor]
3857
+ }),
3858
+ value: ZERO_VALUE,
3859
+ chainId
3860
+ }
3861
+ ],
3862
+ preconditions: [],
3863
+ description: `Approve depositor ${depositor} on vault ${vault}.`,
3864
+ note: "Vault owner only."
3865
+ };
3866
+ }
3867
+ function encodeRegisterAgent(args, chainId) {
3868
+ const vault = getAddress(args.vault);
3869
+ const agentAddress = getAddress(args.agentAddress);
3870
+ const id = typeof args.agentId === "bigint" ? args.agentId : BigInt(args.agentId);
3871
+ if (id < 0n)
3872
+ throw usage("`agentId` must be a non-negative integer");
3873
+ return {
3874
+ txs: [
3875
+ {
3876
+ to: vault,
3877
+ data: encodeFunctionData10({
3878
+ abi: SYNDICATE_VAULT_ABI2,
3879
+ functionName: "registerAgent",
3880
+ args: [agentAddress, id]
3881
+ }),
3882
+ value: ZERO_VALUE,
3883
+ chainId
3884
+ }
3885
+ ],
3886
+ preconditions: [],
3887
+ description: `Register agent ${agentAddress} (id ${id.toString()}) on vault ${vault}.`,
3888
+ note: "Vault owner only. The ERC-8004 NFT must be owned by `agentAddress` or by the vault owner at registration time."
3889
+ };
3890
+ }
3891
+
3892
+ // ../sdk/dist/encoders/governor.js
3893
+ import { encodeFunctionData as encodeFunctionData11, getAddress as getAddress2, isAddress as isAddress4 } from "viem";
3894
+ var ZERO_VALUE2 = "0x0";
3895
+ var VOTE_TYPES = {
3896
+ For: 0,
3897
+ Against: 1,
3898
+ Abstain: 2
3899
+ };
3900
+ function governorTx(chainId, data) {
3901
+ return {
3902
+ to: getDeployment(chainId).sherwood.governor,
3903
+ data,
3904
+ value: ZERO_VALUE2,
3905
+ chainId
3906
+ };
3907
+ }
3908
+ function parseProposalId(value) {
3909
+ if (typeof value === "bigint") {
3910
+ if (value < 0n)
3911
+ throw usage("`proposalId` must be a non-negative integer");
3912
+ return value;
3913
+ }
3914
+ if (typeof value === "number") {
3915
+ if (!Number.isInteger(value) || value < 0) {
3916
+ throw usage("`proposalId` must be a non-negative integer");
3917
+ }
3918
+ return BigInt(value);
3919
+ }
3920
+ if (!/^\d+$/.test(value)) {
3921
+ throw usage(`\`proposalId\` must be a non-negative integer string, got "${value}"`);
3922
+ }
3923
+ return BigInt(value);
3924
+ }
3925
+ function encodeVote(args, chainId) {
3926
+ const support = VOTE_TYPES[args.vote];
3927
+ if (support === void 0) {
3928
+ throw usage(`Unsupported vote "${args.vote}" \u2014 must be one of: ${Object.keys(VOTE_TYPES).join(", ")}`);
3929
+ }
3930
+ const proposalId = parseProposalId(args.proposalId);
3931
+ const data = encodeFunctionData11({
3932
+ abi: SYNDICATE_GOVERNOR_ABI,
3933
+ functionName: "vote",
3934
+ args: [proposalId, support]
3935
+ });
3936
+ return {
3937
+ txs: [governorTx(chainId, data)],
3938
+ preconditions: [],
3939
+ description: `Vote ${args.vote} on proposal ${proposalId.toString()}.`,
3940
+ note: "Voter must hold ERC20Votes shares of the vault at the proposal's snapshot timestamp; double-voting reverts. Use the proposal read endpoint to check `voteEnd` first."
3941
+ };
3942
+ }
3943
+ function lifecycleAction(fn, proposalId, chainId, description, note) {
3944
+ const data = encodeFunctionData11({
3945
+ abi: SYNDICATE_GOVERNOR_ABI,
3946
+ functionName: fn,
3947
+ args: [proposalId]
3948
+ });
3949
+ return {
3950
+ txs: [governorTx(chainId, data)],
3951
+ preconditions: [],
3952
+ description,
3953
+ note
3954
+ };
3955
+ }
3956
+ function encodeExecute(args, chainId) {
3957
+ const id = parseProposalId(args.proposalId);
3958
+ return lifecycleAction("executeProposal", id, chainId, `Execute proposal ${id.toString()} (open the strategy).`, "Reverts if the proposal is not in the `Approved` state. Check `state == Approved` via the proposal read endpoint first.");
3959
+ }
3960
+ function encodeSettle(args, chainId) {
3961
+ const id = parseProposalId(args.proposalId);
3962
+ return lifecycleAction("settleProposal", id, chainId, `Settle proposal ${id.toString()} (close the strategy and distribute fees).`, "Proposer can settle anytime; anyone can settle after the strategy duration elapses.");
3963
+ }
3964
+ function encodeCancel(args, chainId) {
3965
+ const id = parseProposalId(args.proposalId);
3966
+ return lifecycleAction("cancelProposal", id, chainId, `Cancel proposal ${id.toString()} (proposer-only).`, "Only the proposer can cancel, and only while the proposal is in `Pending`, `GuardianReview`, or `Approved`.");
3967
+ }
3968
+ var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
3969
+ function validateBatchCall(call, scope, idx) {
3970
+ if (!call || typeof call !== "object") {
3971
+ throw usage(`\`${scope}[${idx}]\` must be an object`);
3972
+ }
3973
+ const c = call;
3974
+ if (typeof c.target !== "string" || !isAddress4(c.target)) {
3975
+ throw usage(`\`${scope}[${idx}].target\` must be a 0x-prefixed address`);
3976
+ }
3977
+ if (typeof c.data !== "string" || !/^0x[0-9a-fA-F]*$/.test(c.data)) {
3978
+ throw usage(`\`${scope}[${idx}].data\` must be a 0x-prefixed hex string`);
3979
+ }
3980
+ let value;
3981
+ if (typeof c.value === "string") {
3982
+ if (!/^\d+$/.test(c.value)) {
3983
+ throw usage(`\`${scope}[${idx}].value\` must be a non-negative integer string`);
3984
+ }
3985
+ value = BigInt(c.value);
3986
+ } else if (typeof c.value === "bigint") {
3987
+ if (c.value < 0n) {
3988
+ throw usage(`\`${scope}[${idx}].value\` must be non-negative`);
3989
+ }
3990
+ value = c.value;
3991
+ } else if (c.value === void 0 || c.value === null) {
3992
+ value = 0n;
3993
+ } else {
3994
+ throw usage(`\`${scope}[${idx}].value\` must be a non-negative integer string (or omitted for 0)`);
3995
+ }
3996
+ return { target: getAddress2(c.target), data: c.data, value };
3997
+ }
3998
+ function encodePropose(args, chainId) {
3999
+ const vault = getAddress2(args.vault);
4000
+ const strategy2 = args.strategy === ZERO_ADDRESS2 ? ZERO_ADDRESS2 : getAddress2(args.strategy);
4001
+ if (typeof args.metadataURI !== "string" || args.metadataURI.length === 0) {
4002
+ throw usage("`metadataURI` must be a non-empty string (e.g. ipfs://...)");
4003
+ }
4004
+ if (!Number.isInteger(args.performanceFeeBps) || args.performanceFeeBps < 0 || args.performanceFeeBps > 1e4) {
4005
+ throw usage("`performanceFeeBps` must be an integer in [0, 10000]");
4006
+ }
4007
+ const strategyDuration = typeof args.strategyDuration === "bigint" ? args.strategyDuration : BigInt(args.strategyDuration);
4008
+ if (strategyDuration <= 0n) {
4009
+ throw usage("`strategyDuration` must be > 0 (seconds)");
4010
+ }
4011
+ if (!Array.isArray(args.executeCalls)) {
4012
+ throw usage("`executeCalls` must be an array of {target, data, value} objects");
4013
+ }
4014
+ if (!Array.isArray(args.settlementCalls)) {
4015
+ throw usage("`settlementCalls` must be an array of {target, data, value} objects");
4016
+ }
4017
+ const executeCalls = args.executeCalls.map((c, i) => validateBatchCall(c, "executeCalls", i));
4018
+ const settlementCalls = args.settlementCalls.map((c, i) => validateBatchCall(c, "settlementCalls", i));
4019
+ const coProposers = (args.coProposers ?? []).map((cp, i) => {
4020
+ if (!cp || typeof cp !== "object") {
4021
+ throw usage(`\`coProposers[${i}]\` must be an object`);
4022
+ }
4023
+ const agent = cp.agent;
4024
+ const splitBps = cp.splitBps;
4025
+ if (typeof agent !== "string" || !isAddress4(agent)) {
4026
+ throw usage(`\`coProposers[${i}].agent\` must be a 0x-prefixed address`);
4027
+ }
4028
+ let bps;
4029
+ if (typeof splitBps === "string" && /^\d+$/.test(splitBps)) {
4030
+ bps = BigInt(splitBps);
4031
+ } else if (typeof splitBps === "bigint") {
4032
+ bps = splitBps;
4033
+ } else if (typeof splitBps === "number" && Number.isInteger(splitBps) && splitBps >= 0) {
4034
+ bps = BigInt(splitBps);
4035
+ } else {
4036
+ throw usage(`\`coProposers[${i}].splitBps\` must be a non-negative integer (string, number, or bigint)`);
4037
+ }
4038
+ if (bps < 0n || bps > 10000n) {
4039
+ throw usage(`\`coProposers[${i}].splitBps\` must be in [0, 10000]`);
4040
+ }
4041
+ return { agent: getAddress2(agent), splitBps: bps };
4042
+ });
4043
+ const data = encodeFunctionData11({
4044
+ abi: SYNDICATE_GOVERNOR_ABI,
4045
+ functionName: "propose",
4046
+ args: [
4047
+ vault,
4048
+ strategy2,
4049
+ args.metadataURI,
4050
+ BigInt(args.performanceFeeBps),
4051
+ strategyDuration,
4052
+ executeCalls,
4053
+ settlementCalls,
4054
+ coProposers
4055
+ ]
4056
+ });
4057
+ return {
4058
+ txs: [governorTx(chainId, data)],
4059
+ preconditions: [
4060
+ { type: "vault-not-locked", vault }
4061
+ ],
4062
+ description: `Propose a strategy on ${vault} (duration ${strategyDuration.toString()}s, fee ${args.performanceFeeBps} bps).`,
4063
+ 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."
4064
+ };
4065
+ }
4066
+ function encodeUnstick(args, chainId) {
4067
+ const id = parseProposalId(args.proposalId);
4068
+ const data = encodeFunctionData11({
4069
+ abi: SYNDICATE_GOVERNOR_ABI,
4070
+ functionName: "unstick",
4071
+ args: [id]
4072
+ });
4073
+ return {
4074
+ txs: [governorTx(chainId, data)],
4075
+ preconditions: [],
4076
+ description: `Unstick proposal ${id.toString()} \u2014 run its governance-approved settlement calls (owner-instant, no funds beyond the approved calls).`,
4077
+ note: "Vault-owner only. Valid only when the proposal is Executed AND its strategy duration has elapsed. Runs the proposal's pre-committed settlement calls and finalizes settlement; no owner stake required. Reverts if those calls revert \u2014 if they can't succeed, open an emergency-settle with custom unwind calls instead."
4078
+ };
4079
+ }
4080
+ function encodeEmergencySettle(args, chainId) {
4081
+ const id = parseProposalId(args.proposalId);
4082
+ if (!Array.isArray(args.calls)) {
4083
+ throw usage("`calls` must be an array of {target, data, value} objects");
4084
+ }
4085
+ const calls = args.calls.map((c, i) => validateBatchCall(c, "calls", i));
4086
+ const data = encodeFunctionData11({
4087
+ abi: SYNDICATE_GOVERNOR_ABI,
4088
+ functionName: "emergencySettleWithCalls",
4089
+ args: [id, calls]
4090
+ });
4091
+ return {
4092
+ txs: [governorTx(chainId, data)],
4093
+ preconditions: [],
4094
+ description: `Open an emergency-settle review on proposal ${id.toString()} with ${calls.length} unwind call(s).`,
4095
+ note: "Vault-owner only, requires bonded owner stake. Does NOT execute immediately \u2014 it commits the calls' hash and opens a guardian-reviewed window (default ~24h). After the window, finalize with finalizeEmergencySettle; guardians can block within it (owner stake burned on block-quorum)."
4096
+ };
4097
+ }
4098
+ function parseUintArg(value, label) {
4099
+ if (typeof value === "bigint") {
4100
+ if (value < 0n)
4101
+ throw usage(`\`${label}\` must be a non-negative integer`);
4102
+ return value;
4103
+ }
4104
+ if (typeof value === "number") {
4105
+ if (!Number.isInteger(value) || value < 0) {
4106
+ throw usage(`\`${label}\` must be a non-negative integer`);
4107
+ }
4108
+ return BigInt(value);
4109
+ }
4110
+ if (!/^\d+$/.test(value)) {
4111
+ throw usage(`\`${label}\` must be a non-negative integer string, got "${value}"`);
4112
+ }
4113
+ return BigInt(value);
4114
+ }
4115
+ var GOVERNOR_PARAM_SETTERS = {
4116
+ votingPeriod: "setVotingPeriod",
4117
+ executionWindow: "setExecutionWindow",
4118
+ vetoThresholdBps: "setVetoThresholdBps",
4119
+ maxPerformanceFeeBps: "setMaxPerformanceFeeBps",
4120
+ maxStrategyDuration: "setMaxStrategyDuration",
4121
+ cooldownPeriod: "setCooldownPeriod",
4122
+ protocolFeeBps: "setProtocolFeeBps"
4123
+ };
4124
+ function encodeGovernorParam(args, chainId) {
4125
+ const fn = GOVERNOR_PARAM_SETTERS[args.param];
4126
+ if (!fn) {
4127
+ throw usage(`Unknown governor param "${args.param}" \u2014 must be one of: ${Object.keys(GOVERNOR_PARAM_SETTERS).join(", ")}`);
4128
+ }
4129
+ const value = parseUintArg(args.value, args.param);
4130
+ const data = encodeFunctionData11({
4131
+ abi: SYNDICATE_GOVERNOR_ABI,
4132
+ functionName: fn,
4133
+ args: [value]
4134
+ });
4135
+ return {
4136
+ txs: [governorTx(chainId, data)],
4137
+ preconditions: [],
4138
+ description: `Set governor parameter ${args.param} = ${value.toString()}.`,
4139
+ note: "Owner-only (the owner multisig). V1.5 has no on-chain parameter timelock \u2014 the change applies immediately on this call. Bounds are validated on-chain; out-of-range values revert. Setting a non-zero protocolFeeBps reverts unless protocolFeeRecipient is already set."
4140
+ };
4141
+ }
4142
+
4143
+ // ../sdk/dist/encoders/factory.js
4144
+ import { encodeFunctionData as encodeFunctionData12, getAddress as getAddress3 } from "viem";
4145
+ var ZERO_VALUE3 = "0x0";
4146
+ var SUBDOMAIN_RE = /^[a-z0-9-]{1,63}$/;
4147
+ function encodeCreateSyndicate(args, chainId) {
4148
+ if (typeof args.metadataURI !== "string" || args.metadataURI.length === 0) {
4149
+ throw usage("`metadataURI` must be a non-empty string (e.g. ipfs://...)");
4150
+ }
4151
+ if (typeof args.name !== "string" || args.name.length === 0) {
4152
+ throw usage("`name` must be a non-empty string");
4153
+ }
4154
+ if (typeof args.symbol !== "string" || args.symbol.length === 0) {
4155
+ throw usage("`symbol` must be a non-empty string");
4156
+ }
4157
+ if (typeof args.openDeposits !== "boolean") {
4158
+ throw usage("`openDeposits` must be a boolean");
4159
+ }
4160
+ if (!SUBDOMAIN_RE.test(args.subdomain)) {
4161
+ throw usage("`subdomain` must match /^[a-z0-9-]{1,63}$/ (lowercase letters, digits, hyphens)");
4162
+ }
4163
+ const asset = getAddress3(args.asset);
4164
+ const creatorAgentId = typeof args.creatorAgentId === "bigint" ? args.creatorAgentId : BigInt(args.creatorAgentId);
4165
+ if (creatorAgentId < 0n)
4166
+ throw usage("`creatorAgentId` must be non-negative");
4167
+ const factory = getDeployment(chainId).sherwood.factory;
4168
+ const data = encodeFunctionData12({
4169
+ abi: SYNDICATE_FACTORY_ABI,
4170
+ functionName: "createSyndicate",
4171
+ args: [
4172
+ creatorAgentId,
4173
+ {
4174
+ metadataURI: args.metadataURI,
4175
+ asset,
4176
+ name: args.name,
4177
+ symbol: args.symbol,
4178
+ openDeposits: args.openDeposits,
4179
+ subdomain: args.subdomain
4180
+ }
4181
+ ]
4182
+ });
4183
+ return {
4184
+ txs: [{ to: factory, data, value: ZERO_VALUE3, chainId }],
4185
+ preconditions: [
4186
+ // Creator must own the ERC-8004 NFT for `creatorAgentId` and have
4187
+ // pre-staked the owner bond via guardianRegistry.prepareOwnerStake.
4188
+ // No on-chain check from the SDK side — agent verifies upstream.
4189
+ ],
4190
+ description: `Create syndicate "${args.subdomain}" with ${args.openDeposits ? "open" : "whitelisted"} deposits.`,
4191
+ note: "Caller must own the ERC-8004 NFT for `creatorAgentId` and must have called `guardianRegistry.prepareOwnerStake` first to bond the owner stake. The factory's `createSyndicate` deploys a vault proxy + binds the owner stake atomically."
4192
+ };
4193
+ }
4194
+
4195
+ // ../sdk/dist/encoders/identity.js
4196
+ import { encodeFunctionData as encodeFunctionData13, getAddress as getAddress4 } from "viem";
4197
+ var ZERO_ADDRESS3 = "0x0000000000000000000000000000000000000000";
4198
+ var ERC8004_TYPE = "https://eips.ethereum.org/EIPS/eip-8004#registration-v1";
4199
+ var B64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4200
+ function toBase64Utf8(str) {
4201
+ const bin = encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, h) => String.fromCharCode(parseInt(h, 16)));
4202
+ let out = "";
4203
+ for (let i = 0; i < bin.length; i += 3) {
4204
+ const a = bin.charCodeAt(i);
4205
+ const hasB = i + 1 < bin.length;
4206
+ const hasC = i + 2 < bin.length;
4207
+ const b = hasB ? bin.charCodeAt(i + 1) : 0;
4208
+ const c = hasC ? bin.charCodeAt(i + 2) : 0;
4209
+ out += B64_ALPHABET[a >> 2];
4210
+ out += B64_ALPHABET[(a & 3) << 4 | b >> 4];
4211
+ out += hasB ? B64_ALPHABET[(b & 15) << 2 | c >> 6] : "=";
4212
+ out += hasC ? B64_ALPHABET[c & 63] : "=";
4213
+ }
4214
+ return out;
4215
+ }
4216
+ function encodeIdentityMint(args, chainId) {
4217
+ const registry2 = getDeployment(chainId).identityRegistry;
4218
+ if (registry2 === ZERO_ADDRESS3) {
4219
+ throw unsupported(`ERC-8004 identity registry is not deployed on chain ${chainId} \u2014 mint on Base (8453).`);
4220
+ }
4221
+ if (!args.name)
4222
+ throw usage("`name` is required");
4223
+ if (!args.description)
4224
+ throw usage("`description` is required");
4225
+ const reg = {
4226
+ type: ERC8004_TYPE,
4227
+ name: args.name,
4228
+ description: args.description
4229
+ };
4230
+ if (args.image)
4231
+ reg.image = args.image;
4232
+ reg.services = [];
4233
+ reg.active = args.active ?? true;
4234
+ reg.x402Support = args.x402Support ?? false;
4235
+ const json = JSON.stringify(reg);
4236
+ const dataUri = `data:application/json;base64,${toBase64Utf8(json)}`;
4237
+ return {
4238
+ txs: [
4239
+ {
4240
+ to: getAddress4(registry2),
4241
+ data: encodeFunctionData13({
4242
+ abi: IDENTITY_REGISTRY_ABI,
4243
+ functionName: "register",
4244
+ args: [dataUri, []]
4245
+ }),
4246
+ value: "0x0",
4247
+ chainId
4248
+ }
4249
+ ],
4250
+ preconditions: [],
4251
+ description: `Mint an ERC-8004 agent identity "${args.name}" on chain ${chainId}.`,
4252
+ note: "ERC-8004 identity is minted on Base. Read the resulting token id from the mint receipt (chainId:tokenId); reference it when creating or joining syndicates."
4253
+ };
4254
+ }
4255
+
4256
+ // ../sdk/dist/encoders/eas.js
4257
+ import { encodeAbiParameters as encodeAbiParameters10, encodeFunctionData as encodeFunctionData14, getAddress as getAddress5, parseAbiParameters } from "viem";
4258
+ var ZERO_ADDRESS4 = "0x0000000000000000000000000000000000000000";
4259
+ var ZERO_BYTES322 = "0x0000000000000000000000000000000000000000000000000000000000000000";
4260
+ var JOIN_REQUEST_PARAMS = parseAbiParameters("uint256, uint256, address, string");
4261
+ var AGENT_APPROVED_PARAMS = parseAbiParameters("uint256, uint256, address");
4262
+ var toBig = (x) => typeof x === "bigint" ? x : BigInt(x);
4263
+ function formatMessageWithRef(message, referrerAgentId) {
4264
+ if (referrerAgentId == null)
4265
+ return message;
4266
+ return `[ref:${referrerAgentId}] ${message}`;
4267
+ }
4268
+ function formatMessageWithChain(message, sourceSlug, easSlug) {
4269
+ if (sourceSlug === easSlug)
4270
+ return message;
4271
+ return `[chain:${sourceSlug}] ${message}`;
4272
+ }
4273
+ function buildAttestTx(easAddress, schema, recipient, data, chainId) {
4274
+ return {
4275
+ to: getAddress5(easAddress),
4276
+ data: encodeFunctionData14({
4277
+ abi: EAS_ABI,
4278
+ functionName: "attest",
4279
+ args: [
4280
+ {
4281
+ schema,
4282
+ data: {
4283
+ recipient,
4284
+ expirationTime: 0n,
4285
+ revocable: true,
4286
+ refUID: ZERO_BYTES322,
4287
+ data,
4288
+ value: 0n
4289
+ }
4290
+ }
4291
+ ]
4292
+ }),
4293
+ value: "0x0",
4294
+ chainId
4295
+ };
4296
+ }
4297
+ function encodeJoinRequest(args, chainId) {
4298
+ const dep = getDeployment(chainId);
4299
+ const eas = dep.eas;
4300
+ if (eas.address === ZERO_ADDRESS4) {
4301
+ throw unsupported(`No EAS coordination path on chain ${chainId} \u2014 join requests require a chain whose attestations are pinned to Base.`);
4302
+ }
4303
+ const easSlug = getDeployment(eas.chainId).slug;
4304
+ const message = formatMessageWithChain(formatMessageWithRef(args.message, args.referrerAgentId), dep.slug, easSlug);
4305
+ const data = encodeAbiParameters10(JOIN_REQUEST_PARAMS, [
4306
+ toBig(args.syndicateId),
4307
+ toBig(args.agentId),
4308
+ getAddress5(args.vault),
4309
+ message
4310
+ ]);
4311
+ return {
4312
+ txs: [
4313
+ buildAttestTx(eas.address, eas.schemas.joinRequest, getAddress5(args.creator), data, eas.chainId)
4314
+ ],
4315
+ preconditions: [],
4316
+ description: `Request to join syndicate ${toBig(args.syndicateId).toString()} (vault ${getAddress5(args.vault)}).`,
4317
+ note: "EAS coordination attestation \u2014 always submitted on Base, even for syndicates on another chain. Revocable; the creator discovers it via the join-request schema."
4318
+ };
4319
+ }
4320
+ function encodeApproveAgent(args, chainId) {
4321
+ const dep = getDeployment(chainId);
4322
+ const eas = dep.eas;
4323
+ if (eas.address === ZERO_ADDRESS4) {
4324
+ throw unsupported(`No EAS coordination path on chain ${chainId} \u2014 approvals require a chain whose attestations are pinned to Base.`);
4325
+ }
4326
+ const registerTx = encodeRegisterAgent({
4327
+ vault: args.vault,
4328
+ agentAddress: args.agentAddress,
4329
+ agentId: toBig(args.agentId)
4330
+ }, chainId).txs[0];
4331
+ const data = encodeAbiParameters10(AGENT_APPROVED_PARAMS, [
4332
+ toBig(args.syndicateId),
4333
+ toBig(args.agentId),
4334
+ getAddress5(args.vault)
4335
+ ]);
4336
+ const attestTx = buildAttestTx(eas.address, eas.schemas.agentApproved, getAddress5(args.agentAddress), data, eas.chainId);
4337
+ return {
4338
+ txs: [registerTx, attestTx],
4339
+ preconditions: [],
4340
+ description: `Approve agent ${toBig(args.agentId).toString()} for syndicate ${toBig(args.syndicateId).toString()}: register on the vault (chain ${chainId}) + AGENT_APPROVED attestation on Base.`,
4341
+ note: "Vault owner only. Two txs on two chains: registerAgent runs on the syndicate's chain; the approval attestation runs on Base."
4342
+ };
4343
+ }
4344
+
4345
+ // ../sdk/dist/encoders/guardian.js
4346
+ import { encodeFunctionData as encodeFunctionData15, getAddress as getAddress6 } from "viem";
4347
+ var ZERO_VALUE4 = "0x0";
4348
+ function registryTx(chainId, data) {
4349
+ const registry2 = getDeployment(chainId).sherwood.guardianRegistry;
4350
+ if (registry2 === "0x0000000000000000000000000000000000000000") {
4351
+ throw unsupported(`Guardian registry is not deployed on chain ${chainId} yet`);
4352
+ }
4353
+ return { to: registry2, data, value: ZERO_VALUE4, chainId };
4354
+ }
4355
+ function parseUint(value, field) {
4356
+ if (typeof value === "bigint") {
4357
+ if (value < 0n)
4358
+ throw usage(`\`${field}\` must be a non-negative integer`);
4359
+ return value;
4360
+ }
4361
+ if (typeof value === "number") {
4362
+ if (!Number.isInteger(value) || value < 0) {
4363
+ throw usage(`\`${field}\` must be a non-negative integer`);
4364
+ }
4365
+ return BigInt(value);
4366
+ }
4367
+ if (!/^\d+$/.test(value)) {
4368
+ throw usage(`\`${field}\` must be a non-negative integer string, got "${value}"`);
4369
+ }
4370
+ return BigInt(value);
4371
+ }
4372
+ function encodeGuardianStake(args, chainId) {
4373
+ if (args.amount <= 0n)
4374
+ throw usage("`amount` must be > 0");
4375
+ const agentId = parseUint(args.agentId, "agentId");
4376
+ const data = encodeFunctionData15({
4377
+ abi: GUARDIAN_REGISTRY_ABI,
4378
+ functionName: "stakeAsGuardian",
4379
+ args: [args.amount, agentId]
4380
+ });
4381
+ const registry2 = getDeployment(chainId).sherwood.guardianRegistry;
4382
+ return {
4383
+ txs: [registryTx(chainId, data)],
4384
+ preconditions: [
4385
+ // Caller must have approved WOOD for the registry; agent should
4386
+ // check this client-side and prepend an ERC20.approve if needed.
4387
+ {
4388
+ type: "allowance",
4389
+ token: registry2,
4390
+ spender: registry2,
4391
+ min: args.amount.toString(),
4392
+ minDecimal: args.amount.toString()
4393
+ }
4394
+ ],
4395
+ description: `Stake ${args.amount.toString()} WOOD as guardian (agent ${agentId.toString()}).`,
4396
+ 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."
4397
+ };
4398
+ }
4399
+ var GUARDIAN_UNSTAKE_FN = {
4400
+ request: "requestUnstakeGuardian",
4401
+ cancel: "cancelUnstakeGuardian",
4402
+ claim: "claimUnstakeGuardian"
4403
+ };
4404
+ function encodeGuardianUnstake(args, chainId) {
4405
+ const fn = GUARDIAN_UNSTAKE_FN[args.action];
4406
+ if (!fn) {
4407
+ throw usage(`\`action\` must be one of: ${Object.keys(GUARDIAN_UNSTAKE_FN).join(", ")}`);
4408
+ }
4409
+ const data = encodeFunctionData15({
4410
+ abi: GUARDIAN_REGISTRY_ABI,
4411
+ functionName: fn,
4412
+ args: []
4413
+ });
4414
+ return {
4415
+ txs: [registryTx(chainId, data)],
4416
+ preconditions: [],
4417
+ description: `Guardian unstake ${args.action}.`,
4418
+ 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."
4419
+ };
4420
+ }
4421
+ function encodeGuardianDelegate(args, chainId) {
4422
+ if (args.amount <= 0n)
4423
+ throw usage("`amount` must be > 0");
4424
+ const delegate = getAddress6(args.delegate);
4425
+ const data = encodeFunctionData15({
4426
+ abi: GUARDIAN_REGISTRY_ABI,
4427
+ functionName: "delegateStake",
4428
+ args: [delegate, args.amount]
4429
+ });
4430
+ return {
4431
+ txs: [registryTx(chainId, data)],
4432
+ preconditions: [],
4433
+ description: `Delegate ${args.amount.toString()} WOOD to ${delegate}.`,
4434
+ 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."
4435
+ };
4436
+ }
4437
+ var DELEGATION_UNSTAKE_FN = {
4438
+ request: "requestUnstakeDelegation",
4439
+ cancel: "cancelUnstakeDelegation",
4440
+ claim: "claimUnstakeDelegation"
4441
+ };
4442
+ function encodeDelegationUnstake(args, chainId) {
4443
+ const fn = DELEGATION_UNSTAKE_FN[args.action];
4444
+ if (!fn) {
4445
+ throw usage(`\`action\` must be one of: ${Object.keys(DELEGATION_UNSTAKE_FN).join(", ")}`);
4446
+ }
4447
+ const delegate = getAddress6(args.delegate);
4448
+ const data = encodeFunctionData15({
4449
+ abi: GUARDIAN_REGISTRY_ABI,
4450
+ functionName: fn,
4451
+ args: [delegate]
4452
+ });
4453
+ return {
4454
+ txs: [registryTx(chainId, data)],
4455
+ preconditions: [],
4456
+ description: `Delegation unstake ${args.action} for delegate ${delegate}.`,
4457
+ 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."
4458
+ };
4459
+ }
4460
+ function encodeSetCommission(args, chainId) {
4461
+ if (!Number.isInteger(args.bps) || args.bps < 0 || args.bps > 5e3) {
4462
+ throw usage("`bps` must be an integer in [0, 5000] (0\u201350%)");
4463
+ }
4464
+ const data = encodeFunctionData15({
4465
+ abi: GUARDIAN_REGISTRY_ABI,
4466
+ functionName: "setCommission",
4467
+ args: [args.bps]
4468
+ });
4469
+ return {
4470
+ txs: [registryTx(chainId, data)],
4471
+ preconditions: [],
4472
+ description: `Set DPoS commission to ${args.bps} bps.`,
4473
+ note: "Raise-rate is capped at 500 bps above the epoch-start rate; first set on a delegate is exempt. Decreases are unbounded."
4474
+ };
4475
+ }
4476
+ function encodeClaimProposalReward(args, chainId) {
4477
+ const proposalId = parseUint(args.proposalId, "proposalId");
4478
+ const data = encodeFunctionData15({
4479
+ abi: GUARDIAN_REGISTRY_ABI,
4480
+ functionName: "claimProposalReward",
4481
+ args: [proposalId]
4482
+ });
4483
+ return {
4484
+ txs: [registryTx(chainId, data)],
4485
+ preconditions: [],
4486
+ description: `Claim guardian-fee reward for proposal ${proposalId.toString()}.`,
4487
+ note: "Approver-side claim. Pulls the DPoS commission slice and seeds the delegator pool atomically."
4488
+ };
4489
+ }
4490
+ function encodeClaimDelegatorProposalReward(args, chainId) {
4491
+ const proposalId = parseUint(args.proposalId, "proposalId");
4492
+ const delegate = getAddress6(args.delegate);
4493
+ const data = encodeFunctionData15({
4494
+ abi: GUARDIAN_REGISTRY_ABI,
4495
+ functionName: "claimDelegatorProposalReward",
4496
+ args: [delegate, proposalId]
4497
+ });
4498
+ return {
4499
+ txs: [registryTx(chainId, data)],
4500
+ preconditions: [],
4501
+ description: `Claim delegator-share reward from delegate ${delegate} for proposal ${proposalId.toString()}.`,
4502
+ note: "Delegator-side claim. The approver must have claimed first to seed the delegator pool."
4503
+ };
4504
+ }
4505
+
4506
+ // ../sdk/dist/reads/syndicates.js
4507
+ import { getAddress as getAddress7 } from "viem";
4508
+
4509
+ // src/commands/identity.ts
4510
+ var IDENTITY_REGISTRY_ABI2 = [
4511
+ {
4512
+ name: "balanceOf",
4513
+ type: "function",
4514
+ stateMutability: "view",
4515
+ inputs: [{ name: "owner", type: "address" }],
4516
+ outputs: [{ name: "", type: "uint256" }]
4517
+ },
4518
+ {
4519
+ name: "ownerOf",
4520
+ type: "function",
4521
+ stateMutability: "view",
4522
+ inputs: [{ name: "tokenId", type: "uint256" }],
4523
+ outputs: [{ name: "", type: "address" }]
4524
+ }
4525
+ ];
4526
+ function getAgent0SDK() {
4527
+ const config = loadConfig();
4528
+ const key = config.privateKey || process.env.PRIVATE_KEY;
4529
+ if (!key) {
4530
+ throw new Error(
4531
+ "Private key not found. Run 'sherwood config set --private-key <key>' or set PRIVATE_KEY env var."
4532
+ );
4533
+ }
4534
+ return new SDK({
4535
+ chainId: getChain().id,
4536
+ rpcUrl: getRpcUrl(),
4537
+ privateKey: key.startsWith("0x") ? key : `0x${key}`
4538
+ });
4539
+ }
4540
+ function registerIdentityCommands(program2) {
4541
+ const identity = program2.command("identity").description("Manage ERC-8004 agent identity (via Agent0 SDK)");
4542
+ identity.command("mint").description("Register a new ERC-8004 agent identity (required before creating/joining syndicates)").requiredOption("--name <name>", "Agent name (e.g. 'Alpha Seeker Agent')").option("--description <desc>", "Agent description", "Sherwood syndicate agent").option("--image <uri>", "Agent image URI (IPFS recommended)").action(async (opts) => {
4543
+ if (isCalldataOnly()) {
4544
+ emitCalldata(
4545
+ encodeIdentityMint(
4546
+ {
4547
+ name: opts.name,
4548
+ description: opts.description,
4549
+ image: opts.image
4550
+ },
4551
+ getChain().id
4552
+ )
4553
+ );
4554
+ return;
4555
+ }
4556
+ const account = getAccount();
4557
+ const existingId = getAgentId();
4558
+ if (existingId) {
4559
+ console.log(chalk4.yellow(`You already have an agent identity saved: #${existingId}`));
4560
+ console.log(chalk4.dim(" Minting a new one anyway. The old ID is not affected."));
4561
+ console.log();
4562
+ }
4563
+ const spinner = ora4("Initializing Agent0 SDK...").start();
4564
+ try {
4565
+ const sdk = getAgent0SDK();
4566
+ spinner.text = "Creating agent profile...";
4567
+ const agent = sdk.createAgent(opts.name, opts.description, opts.image);
4568
+ spinner.text = "Registering on-chain (minting ERC-8004 identity)...";
4569
+ const txHandle = await agent.registerOnChain();
4570
+ spinner.text = "Waiting for confirmation...";
4571
+ await txHandle.waitMined();
4572
+ const agentId = agent.agentId;
4573
+ if (!agentId) {
4574
+ spinner.warn("Identity registered but could not read agentId");
4575
+ console.log(chalk4.dim(" Check the transaction on the explorer."));
4576
+ return;
4577
+ }
4578
+ const tokenId = Number(agentId.includes(":") ? agentId.split(":")[1] : agentId);
4579
+ setAgentId(tokenId);
4580
+ spinner.succeed(`Agent identity registered: #${tokenId}`);
4581
+ console.log(chalk4.dim(` Agent0 ID: ${agentId}`));
4582
+ console.log(chalk4.dim(` Name: ${opts.name}`));
4583
+ console.log(chalk4.dim(` Owner: ${account.address}`));
4584
+ console.log(chalk4.dim(` Saved to ~/.sherwood/config.json`));
4585
+ console.log();
4586
+ console.log(chalk4.green("You can now create syndicates:"));
4587
+ console.log(chalk4.dim(` sherwood syndicate create --agent-id ${tokenId} --subdomain <name> --name <name>`));
4588
+ } catch (err) {
4589
+ spinner.fail("Failed to register identity");
4590
+ console.error(chalk4.red(formatContractError(err)));
4591
+ process.exit(1);
4592
+ }
4593
+ });
4594
+ identity.command("load").description("Load an existing ERC-8004 agent identity into your config").requiredOption("--id <tokenId>", "Agent token ID to load").action(async (opts) => {
4595
+ const account = getAccount();
4596
+ const client = getPublicClient();
4597
+ const registry2 = AGENT_REGISTRY().IDENTITY_REGISTRY;
4598
+ const tokenId = Number(opts.id);
4599
+ const spinner = ora4(`Verifying ownership of agent #${tokenId}...`).start();
4600
+ try {
4601
+ const owner = await client.readContract({
4602
+ address: registry2,
4603
+ abi: IDENTITY_REGISTRY_ABI2,
4604
+ functionName: "ownerOf",
4605
+ args: [BigInt(tokenId)]
4606
+ });
4607
+ if (owner.toLowerCase() !== account.address.toLowerCase()) {
4608
+ spinner.fail(`Agent #${tokenId} is owned by ${owner}, not your wallet`);
4609
+ process.exit(1);
4610
+ }
4611
+ setAgentId(tokenId);
4612
+ spinner.succeed(`Agent #${tokenId} loaded and saved to config`);
4613
+ console.log(chalk4.dim(` Owner: ${account.address}`));
4614
+ console.log(chalk4.dim(` Saved to ~/.sherwood/config.json`));
4615
+ } catch (err) {
4616
+ spinner.fail("Failed to load identity");
4617
+ console.error(chalk4.red(formatContractError(err)));
4618
+ process.exit(1);
4619
+ }
4620
+ });
4621
+ identity.command("find").description("Search ERC-8004 agents by wallet address or name (recover a lost agent ID)").option("--wallet <address>", "Filter by owner / wallet address").option("--name <text>", "Filter by agent name (exact or substring match, depending on registry)").option("--all-chains", "Search across all chains, not just the active one").action(async (opts) => {
4622
+ if (!opts.wallet && !opts.name) {
4623
+ console.error(chalk4.red("Pass --wallet <address> or --name <text> (or both)."));
4624
+ process.exit(1);
4625
+ }
4626
+ const spinner = ora4("Searching ERC-8004 registry...").start();
4627
+ try {
4628
+ const sdk = getAgent0SDK();
4629
+ const filters = {};
4630
+ if (opts.wallet) filters.walletAddress = opts.wallet;
4631
+ if (opts.name) filters.name = opts.name;
4632
+ filters.chains = opts.allChains ? "all" : [getChain().id];
4633
+ const results = await sdk.searchAgents(filters);
4634
+ spinner.stop();
4635
+ if (results.length === 0) {
4636
+ console.log(chalk4.dim("No agents found matching those filters."));
4637
+ console.log(chalk4.dim("If you minted on a different chain, retry with --all-chains."));
3016
4638
  return;
3017
4639
  }
3018
4640
  console.log();
@@ -3048,7 +4670,7 @@ function registerIdentityCommands(program2) {
3048
4670
  try {
3049
4671
  const balance = await client.readContract({
3050
4672
  address: registry2,
3051
- abi: IDENTITY_REGISTRY_ABI,
4673
+ abi: IDENTITY_REGISTRY_ABI2,
3052
4674
  functionName: "balanceOf",
3053
4675
  args: [account.address]
3054
4676
  });
@@ -3064,7 +4686,7 @@ function registerIdentityCommands(program2) {
3064
4686
  try {
3065
4687
  const owner = await client.readContract({
3066
4688
  address: registry2,
3067
- abi: IDENTITY_REGISTRY_ABI,
4689
+ abi: IDENTITY_REGISTRY_ABI2,
3068
4690
  functionName: "ownerOf",
3069
4691
  args: [BigInt(savedId)]
3070
4692
  });
@@ -3126,60 +4748,268 @@ function registerIdentityCommands(program2) {
3126
4748
  console.log(` Saved ID: ${chalk4.dim("none \u2014 run 'sherwood identity mint --name <name>'")}`);
3127
4749
  console.log();
3128
4750
  }
3129
- } catch (err) {
3130
- spinner.fail("Failed to check identity");
3131
- console.error(chalk4.red(formatContractError(err)));
3132
- process.exit(1);
4751
+ } catch (err) {
4752
+ spinner.fail("Failed to check identity");
4753
+ console.error(chalk4.red(formatContractError(err)));
4754
+ process.exit(1);
4755
+ }
4756
+ });
4757
+ }
4758
+
4759
+ // src/commands/proposal.ts
4760
+ import { isAddress as isAddress5, encodeFunctionData as encodeFunctionData17 } from "viem";
4761
+ import chalk6 from "chalk";
4762
+ import ora5 from "ora";
4763
+ import { readFileSync as readFileSync2 } from "fs";
4764
+
4765
+ // src/lib/format.ts
4766
+ function formatDurationShort(seconds) {
4767
+ const s = Number(seconds);
4768
+ if (s >= 86400) return `${(s / 86400).toFixed(s % 86400 === 0 ? 0 : 1)}d`;
4769
+ if (s >= 3600) return `${(s / 3600).toFixed(s % 3600 === 0 ? 0 : 1)}h`;
4770
+ if (s >= 60) return `${(s / 60).toFixed(0)}m`;
4771
+ return `${s}s`;
4772
+ }
4773
+ function formatDurationLong(seconds) {
4774
+ const s = Number(seconds);
4775
+ if (s >= 86400) return `${(s / 86400).toFixed(s % 86400 === 0 ? 0 : 1)} day${s >= 172800 ? "s" : ""}`;
4776
+ if (s >= 3600) return `${(s / 3600).toFixed(s % 3600 === 0 ? 0 : 1)} hour${s >= 7200 ? "s" : ""}`;
4777
+ if (s >= 60) return `${(s / 60).toFixed(0)} min`;
4778
+ return `${s}s`;
4779
+ }
4780
+ function formatShares(raw, decimals = 6) {
4781
+ const num = Number(raw) / 10 ** decimals;
4782
+ return num.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: 2 });
4783
+ }
4784
+ function formatUSDC(raw) {
4785
+ const num = Number(raw) / 1e6;
4786
+ return `$${num.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
4787
+ }
4788
+ function parseBigIntArg(value, name) {
4789
+ if (!/^-?\d+$/.test(value)) {
4790
+ throw new Error(`Invalid ${name}: "${value}" is not a valid integer`);
4791
+ }
4792
+ return BigInt(value);
4793
+ }
4794
+
4795
+ // src/lib/simulate.ts
4796
+ import { decodeFunctionData as decodeFunctionData2, encodeFunctionData as encodeFunctionData16 } from "viem";
4797
+ import chalk5 from "chalk";
4798
+
4799
+ // src/lib/risk.ts
4800
+ import { decodeFunctionData, erc20Abi as erc20Abi2 } from "viem";
4801
+ var MOONWELL_CTOKEN_ABI = [
4802
+ { name: "mint", type: "function", stateMutability: "nonpayable", inputs: [{ name: "mintAmount", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] },
4803
+ { name: "redeem", type: "function", stateMutability: "nonpayable", inputs: [{ name: "redeemTokens", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] },
4804
+ { name: "redeemUnderlying", type: "function", stateMutability: "nonpayable", inputs: [{ name: "redeemAmount", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] },
4805
+ { name: "borrow", type: "function", stateMutability: "nonpayable", inputs: [{ name: "borrowAmount", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] },
4806
+ { name: "repayBorrow", type: "function", stateMutability: "nonpayable", inputs: [{ name: "repayAmount", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] }
4807
+ ];
4808
+ var MOONWELL_COMPTROLLER_ABI = [
4809
+ { name: "enterMarkets", type: "function", stateMutability: "nonpayable", inputs: [{ name: "mTokens", type: "address[]" }], outputs: [{ name: "", type: "uint256[]" }] },
4810
+ { name: "exitMarket", type: "function", stateMutability: "nonpayable", inputs: [{ name: "mToken", type: "address" }], outputs: [{ name: "", type: "uint256" }] }
4811
+ ];
4812
+ var SWAP_ROUTER_SINGLE_ABI = [
4813
+ {
4814
+ name: "exactInputSingle",
4815
+ type: "function",
4816
+ stateMutability: "nonpayable",
4817
+ inputs: [{
4818
+ name: "params",
4819
+ type: "tuple",
4820
+ components: [
4821
+ { name: "tokenIn", type: "address" },
4822
+ { name: "tokenOut", type: "address" },
4823
+ { name: "fee", type: "uint24" },
4824
+ { name: "recipient", type: "address" },
4825
+ { name: "amountIn", type: "uint256" },
4826
+ { name: "amountOutMinimum", type: "uint256" },
4827
+ { name: "sqrtPriceLimitX96", type: "uint160" }
4828
+ ]
4829
+ }],
4830
+ outputs: [{ name: "amountOut", type: "uint256" }]
4831
+ }
4832
+ ];
4833
+ var SWAP_ROUTER_MULTI_ABI = [
4834
+ {
4835
+ name: "exactInput",
4836
+ type: "function",
4837
+ stateMutability: "nonpayable",
4838
+ inputs: [{
4839
+ name: "params",
4840
+ type: "tuple",
4841
+ components: [
4842
+ { name: "path", type: "bytes" },
4843
+ { name: "recipient", type: "address" },
4844
+ { name: "amountIn", type: "uint256" },
4845
+ { name: "amountOutMinimum", type: "uint256" }
4846
+ ]
4847
+ }],
4848
+ outputs: [{ name: "amountOut", type: "uint256" }]
4849
+ }
4850
+ ];
4851
+ function buildLabelMapFromKnown(known) {
4852
+ const map = /* @__PURE__ */ new Map();
4853
+ for (const [label, addr] of Object.entries(known)) {
4854
+ if (addr && addr !== "0x0000000000000000000000000000000000000000") {
4855
+ map.set(addr.toLowerCase(), label);
4856
+ }
4857
+ }
4858
+ return map;
4859
+ }
4860
+ function truncAddr(addr) {
4861
+ return `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`;
4862
+ }
4863
+ function labeledAddr(addr, labels) {
4864
+ const label = labels.get(addr.toLowerCase());
4865
+ return label ? `${label} (${truncAddr(addr)})` : truncAddr(addr);
4866
+ }
4867
+ function tryDecode(abi, data) {
4868
+ try {
4869
+ const decoded = decodeFunctionData({ abi, data });
4870
+ return { functionName: decoded.functionName, args: decoded.args ?? [] };
4871
+ } catch {
4872
+ return null;
4873
+ }
4874
+ }
4875
+ function decodeCallArgs(data) {
4876
+ return tryDecode(erc20Abi2, data) ?? tryDecode(MOONWELL_CTOKEN_ABI, data) ?? tryDecode(MOONWELL_COMPTROLLER_ABI, data) ?? tryDecode(SWAP_ROUTER_SINGLE_ABI, data) ?? // exactInputSingle (single-hop)
4877
+ tryDecode(SWAP_ROUTER_MULTI_ABI, data);
4878
+ }
4879
+ function analyzeRisk(calls, callResults, context) {
4880
+ const risks = [];
4881
+ const labels = buildLabelMapFromKnown(context?.knownAddresses ?? {});
4882
+ const vaultLower = context?.vault?.toLowerCase();
4883
+ const isKnown = (addr) => {
4884
+ const lower = addr.toLowerCase();
4885
+ return labels.has(lower) || lower === vaultLower;
4886
+ };
4887
+ let allTargetsVerified = calls.length > 0;
4888
+ let allCallsDecoded = calls.length > 0;
4889
+ for (let i = 0; i < calls.length; i++) {
4890
+ const call = calls[i];
4891
+ const result = callResults[i];
4892
+ const callNum = i + 1;
4893
+ if (result && !result.success) {
4894
+ risks.push({
4895
+ level: "critical",
4896
+ code: "SIMULATION_FAILED",
4897
+ message: `Call #${callNum} to ${labeledAddr(call.target, labels)} reverted during simulation`,
4898
+ callIndex: i
4899
+ });
4900
+ }
4901
+ const targetKnown = isKnown(call.target);
4902
+ if (!targetKnown) {
4903
+ allTargetsVerified = false;
4904
+ risks.push({
4905
+ level: "critical",
4906
+ code: "UNKNOWN_TARGET",
4907
+ message: `Call #${callNum} targets ${labeledAddr(call.target, labels)} which is not in the known address registry`,
4908
+ callIndex: i
4909
+ });
4910
+ }
4911
+ const decoded = decodeCallArgs(call.data);
4912
+ if (!decoded) {
4913
+ allCallsDecoded = false;
4914
+ if (!targetKnown) {
4915
+ risks.push({
4916
+ level: "critical",
4917
+ code: "UNDECODED_CALLDATA",
4918
+ message: `Call #${callNum} has unrecognized calldata (selector ${call.data.slice(0, 10)}) targeting unknown contract`,
4919
+ callIndex: i
4920
+ });
4921
+ }
4922
+ continue;
4923
+ }
4924
+ if (decoded.functionName === "transfer" && decoded.args.length >= 2) {
4925
+ const to = decoded.args[0];
4926
+ if (typeof to === "string" && to.startsWith("0x") && !isKnown(to)) {
4927
+ risks.push({
4928
+ level: "critical",
4929
+ code: "TRANSFER_TO_UNKNOWN",
4930
+ message: `Call #${callNum} transfer() sends funds to ${truncAddr(to)} which is not a known protocol`,
4931
+ callIndex: i
4932
+ });
4933
+ }
4934
+ }
4935
+ if (decoded.functionName === "transferFrom" && decoded.args.length >= 3) {
4936
+ const to = decoded.args[1];
4937
+ if (typeof to === "string" && to.startsWith("0x") && !isKnown(to)) {
4938
+ risks.push({
4939
+ level: "critical",
4940
+ code: "TRANSFER_FROM_TO_UNKNOWN",
4941
+ message: `Call #${callNum} transferFrom() sends funds to ${truncAddr(to)} which is not a known protocol`,
4942
+ callIndex: i
4943
+ });
4944
+ }
4945
+ }
4946
+ if (decoded.functionName === "approve" && decoded.args.length >= 2) {
4947
+ const spender = decoded.args[0];
4948
+ if (typeof spender === "string" && spender.startsWith("0x") && !isKnown(spender)) {
4949
+ risks.push({
4950
+ level: "critical",
4951
+ code: "APPROVE_TO_UNKNOWN",
4952
+ message: `Call #${callNum} approve() grants allowance to ${truncAddr(spender)} which is not a known protocol`,
4953
+ callIndex: i
4954
+ });
4955
+ }
3133
4956
  }
3134
- });
3135
- }
3136
-
3137
- // src/commands/proposal.ts
3138
- import { isAddress as isAddress4, encodeFunctionData as encodeFunctionData11 } from "viem";
3139
- import chalk6 from "chalk";
3140
- import ora5 from "ora";
3141
- import { readFileSync as readFileSync2 } from "fs";
3142
-
3143
- // src/lib/format.ts
3144
- function formatDurationShort(seconds) {
3145
- const s = Number(seconds);
3146
- if (s >= 86400) return `${(s / 86400).toFixed(s % 86400 === 0 ? 0 : 1)}d`;
3147
- if (s >= 3600) return `${(s / 3600).toFixed(s % 3600 === 0 ? 0 : 1)}h`;
3148
- if (s >= 60) return `${(s / 60).toFixed(0)}m`;
3149
- return `${s}s`;
3150
- }
3151
- function formatDurationLong(seconds) {
3152
- const s = Number(seconds);
3153
- if (s >= 86400) return `${(s / 86400).toFixed(s % 86400 === 0 ? 0 : 1)} day${s >= 172800 ? "s" : ""}`;
3154
- if (s >= 3600) return `${(s / 3600).toFixed(s % 3600 === 0 ? 0 : 1)} hour${s >= 7200 ? "s" : ""}`;
3155
- if (s >= 60) return `${(s / 60).toFixed(0)} min`;
3156
- return `${s}s`;
3157
- }
3158
- function formatShares(raw, decimals = 6) {
3159
- const num = Number(raw) / 10 ** decimals;
3160
- return num.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: 2 });
3161
- }
3162
- function formatUSDC(raw) {
3163
- const num = Number(raw) / 1e6;
3164
- return `$${num.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
3165
- }
3166
- function parseBigIntArg(value, name) {
3167
- if (!/^-?\d+$/.test(value)) {
3168
- throw new Error(`Invalid ${name}: "${value}" is not a valid integer`);
3169
4957
  }
3170
- return BigInt(value);
4958
+ if (context?.performanceFeeBps !== void 0) {
4959
+ const fee = context.performanceFeeBps;
4960
+ const maxFee = context.governorParams?.maxPerformanceFeeBps;
4961
+ if (maxFee !== void 0 && fee > maxFee * 80n / 100n) {
4962
+ risks.push({
4963
+ level: "critical",
4964
+ code: "EXCESSIVE_PERFORMANCE_FEE",
4965
+ message: `Performance fee ${Number(fee) / 100}% is within 20% of the hard cap (${Number(maxFee) / 100}%)`
4966
+ });
4967
+ } else if (fee > 2000n) {
4968
+ risks.push({
4969
+ level: "warning",
4970
+ code: "HIGH_PERFORMANCE_FEE",
4971
+ message: `Performance fee ${Number(fee) / 100}% exceeds 20% threshold`
4972
+ });
4973
+ }
4974
+ }
4975
+ if (context?.strategyDuration !== void 0) {
4976
+ const dur = context.strategyDuration;
4977
+ if (dur < 3600n) {
4978
+ risks.push({
4979
+ level: "warning",
4980
+ code: "SHORT_STRATEGY_DURATION",
4981
+ message: `Strategy duration ${Number(dur)}s is under 1 hour \u2014 flash-loan-style risk`
4982
+ });
4983
+ }
4984
+ if (dur > 2592000n) {
4985
+ risks.push({
4986
+ level: "warning",
4987
+ code: "LONG_STRATEGY_DURATION",
4988
+ message: `Strategy duration ${Number(dur) / 86400} days exceeds 30-day threshold`
4989
+ });
4990
+ }
4991
+ }
4992
+ const hasCritical = risks.some((r) => r.level === "critical");
4993
+ const hasWarning = risks.some((r) => r.level === "warning");
4994
+ if (!hasCritical && !hasWarning) {
4995
+ if (allTargetsVerified) {
4996
+ risks.push({ level: "info", code: "ALL_TARGETS_VERIFIED", message: "All call targets are verified protocol addresses" });
4997
+ }
4998
+ if (allCallsDecoded) {
4999
+ risks.push({ level: "info", code: "ALL_CALLS_DECODED", message: "All calldata successfully decoded" });
5000
+ }
5001
+ }
5002
+ return risks;
3171
5003
  }
3172
5004
 
3173
5005
  // src/lib/simulate.ts
3174
- import { decodeFunctionData, encodeFunctionData as encodeFunctionData10 } from "viem";
3175
- import chalk5 from "chalk";
3176
5006
  var G = chalk5.green;
3177
5007
  var W = chalk5.white;
3178
5008
  var DIM = chalk5.gray;
3179
5009
  var BOLD = chalk5.white.bold;
3180
5010
  var LABEL = chalk5.green.bold;
3181
5011
  var SEP = () => console.log(DIM("\u2500".repeat(60)));
3182
- var MOONWELL_CTOKEN_ABI = [
5012
+ var MOONWELL_CTOKEN_ABI2 = [
3183
5013
  {
3184
5014
  name: "mint",
3185
5015
  type: "function",
@@ -3216,7 +5046,7 @@ var MOONWELL_CTOKEN_ABI = [
3216
5046
  outputs: [{ name: "", type: "uint256" }]
3217
5047
  }
3218
5048
  ];
3219
- var MOONWELL_COMPTROLLER_ABI = [
5049
+ var MOONWELL_COMPTROLLER_ABI2 = [
3220
5050
  {
3221
5051
  name: "enterMarkets",
3222
5052
  type: "function",
@@ -3232,7 +5062,7 @@ var MOONWELL_COMPTROLLER_ABI = [
3232
5062
  outputs: [{ name: "", type: "uint256" }]
3233
5063
  }
3234
5064
  ];
3235
- var SWAP_ROUTER_SINGLE_ABI = [
5065
+ var SWAP_ROUTER_SINGLE_ABI2 = [
3236
5066
  {
3237
5067
  name: "exactInputSingle",
3238
5068
  type: "function",
@@ -3337,10 +5167,7 @@ function getAddressLabel(addr) {
3337
5167
  const labels = buildLabelMap();
3338
5168
  return labels.get(addr.toLowerCase()) ?? null;
3339
5169
  }
3340
- function truncAddr(addr) {
3341
- return `${addr.slice(0, 6)}...${addr.slice(-4)}`;
3342
- }
3343
- function labeledAddr(addr) {
5170
+ function labeledAddr2(addr) {
3344
5171
  const label = buildLabelMap().get(addr.toLowerCase());
3345
5172
  const short = truncAddr(addr);
3346
5173
  return label ? `${short} (${label})` : short;
@@ -3378,21 +5205,21 @@ function decodeCallData(target, data) {
3378
5205
  if (mw) {
3379
5206
  const isMToken = [mw.mUSDC, mw.mWETH, mw.mCbETH, mw.mWstETH, mw.mCbBTC, mw.mDAI, mw.mAERO].some((a) => a.toLowerCase() === targetLower);
3380
5207
  if (isMToken) {
3381
- candidates.push({ abi: MOONWELL_CTOKEN_ABI, context: labels.get(targetLower) || "mToken" });
5208
+ candidates.push({ abi: MOONWELL_CTOKEN_ABI2, context: labels.get(targetLower) || "mToken" });
3382
5209
  }
3383
5210
  if (mw.COMPTROLLER.toLowerCase() === targetLower) {
3384
- candidates.push({ abi: MOONWELL_COMPTROLLER_ABI, context: "Comptroller" });
5211
+ candidates.push({ abi: MOONWELL_COMPTROLLER_ABI2, context: "Comptroller" });
3385
5212
  }
3386
5213
  }
3387
5214
  const uni = safeCall(() => UNISWAP());
3388
5215
  if (uni && uni.SWAP_ROUTER.toLowerCase() === targetLower) {
3389
5216
  candidates.push({ abi: SWAP_ROUTER_ABI, context: "SwapRouter" });
3390
- candidates.push({ abi: SWAP_ROUTER_SINGLE_ABI, context: "SwapRouter" });
5217
+ candidates.push({ abi: SWAP_ROUTER_SINGLE_ABI2, context: "SwapRouter" });
3391
5218
  }
3392
5219
  candidates.push({ abi: ERC20_ABI, context: labels.get(targetLower) || "ERC20" });
3393
5220
  for (const { abi, context } of candidates) {
3394
5221
  try {
3395
- const decoded = decodeFunctionData({
5222
+ const decoded = decodeFunctionData2({
3396
5223
  abi,
3397
5224
  data
3398
5225
  });
@@ -3411,7 +5238,7 @@ function formatArgs(args, functionName, target) {
3411
5238
  if (typeof arg === "bigint") {
3412
5239
  parts.push(formatTokenAmount(arg, target));
3413
5240
  } else if (typeof arg === "string" && arg.startsWith("0x") && arg.length === 42) {
3414
- parts.push(labeledAddr(arg));
5241
+ parts.push(labeledAddr2(arg));
3415
5242
  } else if (typeof arg === "object" && arg !== null && !Array.isArray(arg)) {
3416
5243
  const entries = Object.entries(arg);
3417
5244
  const structParts = [];
@@ -3419,7 +5246,7 @@ function formatArgs(args, functionName, target) {
3419
5246
  if (typeof val === "bigint") {
3420
5247
  structParts.push(`${key}: ${formatTokenAmount(val, target)}`);
3421
5248
  } else if (typeof val === "string" && val.startsWith("0x") && val.length === 42) {
3422
- structParts.push(`${key}: ${labeledAddr(val)}`);
5249
+ structParts.push(`${key}: ${labeledAddr2(val)}`);
3423
5250
  } else if (typeof val === "string" && val.startsWith("0x") && key === "path") {
3424
5251
  structParts.push(`path: ${decodeUniswapPath(val)}`);
3425
5252
  } else {
@@ -3430,7 +5257,7 @@ function formatArgs(args, functionName, target) {
3430
5257
  } else if (Array.isArray(arg)) {
3431
5258
  const arrParts = arg.map((item) => {
3432
5259
  if (typeof item === "string" && item.startsWith("0x") && item.length === 42) {
3433
- return labeledAddr(item);
5260
+ return labeledAddr2(item);
3434
5261
  }
3435
5262
  return String(item);
3436
5263
  });
@@ -3462,168 +5289,9 @@ function decodeUniswapPath(pathHex) {
3462
5289
  function safeCall(fn) {
3463
5290
  try {
3464
5291
  return fn();
3465
- } catch {
3466
- return null;
3467
- }
3468
- }
3469
- function decodeCallArgs(target, data) {
3470
- if (data.length < 10) return null;
3471
- const targetLower = target.toLowerCase();
3472
- const candidates = [];
3473
- const mw = safeCall(() => MOONWELL());
3474
- if (mw) {
3475
- const isMToken = [mw.mUSDC, mw.mWETH, mw.mCbETH, mw.mWstETH, mw.mCbBTC, mw.mDAI, mw.mAERO].some((a) => a.toLowerCase() === targetLower);
3476
- if (isMToken) candidates.push({ abi: MOONWELL_CTOKEN_ABI });
3477
- if (mw.COMPTROLLER.toLowerCase() === targetLower) candidates.push({ abi: MOONWELL_COMPTROLLER_ABI });
3478
- }
3479
- const uni = safeCall(() => UNISWAP());
3480
- if (uni && uni.SWAP_ROUTER.toLowerCase() === targetLower) {
3481
- candidates.push({ abi: SWAP_ROUTER_ABI });
3482
- candidates.push({ abi: SWAP_ROUTER_SINGLE_ABI });
3483
- }
3484
- candidates.push({ abi: ERC20_ABI });
3485
- for (const { abi } of candidates) {
3486
- try {
3487
- const decoded = decodeFunctionData({ abi, data });
3488
- return { functionName: decoded.functionName, args: decoded.args };
3489
- } catch {
3490
- continue;
3491
- }
3492
- }
3493
- return null;
3494
- }
3495
- function analyzeRisk(calls, callResults, context) {
3496
- const risks = [];
3497
- const labels = buildLabelMap();
3498
- const vaultLower = context?.vault?.toLowerCase();
3499
- const isKnown = (addr) => {
3500
- const lower = addr.toLowerCase();
3501
- return labels.has(lower) || lower === vaultLower;
3502
- };
3503
- let allTargetsVerified = true;
3504
- let allCallsDecoded = true;
3505
- for (let i = 0; i < calls.length; i++) {
3506
- const call = calls[i];
3507
- const result = callResults[i];
3508
- const callNum = i + 1;
3509
- if (result && !result.success) {
3510
- risks.push({
3511
- level: "critical",
3512
- code: "SIMULATION_FAILED",
3513
- message: `Call #${callNum} to ${labeledAddr(call.target)} reverted during simulation`,
3514
- callIndex: i
3515
- });
3516
- }
3517
- const targetKnown = isKnown(call.target);
3518
- if (!targetKnown) {
3519
- allTargetsVerified = false;
3520
- risks.push({
3521
- level: "critical",
3522
- code: "UNKNOWN_TARGET",
3523
- message: `Call #${callNum} targets ${labeledAddr(call.target)} which is not in the known address registry`,
3524
- callIndex: i
3525
- });
3526
- }
3527
- const decoded = decodeCallArgs(call.target, call.data);
3528
- if (!decoded) {
3529
- allCallsDecoded = false;
3530
- if (!targetKnown) {
3531
- risks.push({
3532
- level: "critical",
3533
- code: "UNDECODED_CALLDATA",
3534
- message: `Call #${callNum} has unrecognized calldata (selector ${call.data.slice(0, 10)}) targeting unknown contract`,
3535
- callIndex: i
3536
- });
3537
- }
3538
- continue;
3539
- }
3540
- if (decoded.functionName === "transfer" && decoded.args.length >= 2) {
3541
- const to = decoded.args[0];
3542
- if (typeof to === "string" && to.startsWith("0x") && !isKnown(to)) {
3543
- risks.push({
3544
- level: "critical",
3545
- code: "TRANSFER_TO_UNKNOWN",
3546
- message: `Call #${callNum} transfer() sends funds to ${truncAddr(to)} which is not a known protocol`,
3547
- callIndex: i
3548
- });
3549
- }
3550
- }
3551
- if (decoded.functionName === "transferFrom" && decoded.args.length >= 3) {
3552
- const to = decoded.args[1];
3553
- if (typeof to === "string" && to.startsWith("0x") && !isKnown(to)) {
3554
- risks.push({
3555
- level: "critical",
3556
- code: "TRANSFER_FROM_TO_UNKNOWN",
3557
- message: `Call #${callNum} transferFrom() sends funds to ${truncAddr(to)} which is not a known protocol`,
3558
- callIndex: i
3559
- });
3560
- }
3561
- }
3562
- if (decoded.functionName === "approve" && decoded.args.length >= 2) {
3563
- const spender = decoded.args[0];
3564
- if (typeof spender === "string" && spender.startsWith("0x") && !isKnown(spender)) {
3565
- risks.push({
3566
- level: "critical",
3567
- code: "APPROVE_TO_UNKNOWN",
3568
- message: `Call #${callNum} approve() grants allowance to ${truncAddr(spender)} which is not a known protocol`,
3569
- callIndex: i
3570
- });
3571
- }
3572
- }
3573
- }
3574
- if (context?.performanceFeeBps !== void 0) {
3575
- const fee = context.performanceFeeBps;
3576
- const maxFee = context.governorParams?.maxPerformanceFeeBps;
3577
- if (maxFee && fee > maxFee * 80n / 100n) {
3578
- risks.push({
3579
- level: "critical",
3580
- code: "EXCESSIVE_PERFORMANCE_FEE",
3581
- message: `Performance fee ${Number(fee) / 100}% is within 20% of the hard cap (${Number(maxFee) / 100}%)`
3582
- });
3583
- } else if (fee > 2000n) {
3584
- risks.push({
3585
- level: "warning",
3586
- code: "HIGH_PERFORMANCE_FEE",
3587
- message: `Performance fee ${Number(fee) / 100}% exceeds 20% threshold`
3588
- });
3589
- }
3590
- }
3591
- if (context?.strategyDuration !== void 0) {
3592
- const dur = context.strategyDuration;
3593
- if (dur < 3600n) {
3594
- risks.push({
3595
- level: "warning",
3596
- code: "SHORT_STRATEGY_DURATION",
3597
- message: `Strategy duration ${Number(dur)}s is under 1 hour \u2014 flash-loan-style risk`
3598
- });
3599
- }
3600
- if (dur > 2592000n) {
3601
- risks.push({
3602
- level: "warning",
3603
- code: "LONG_STRATEGY_DURATION",
3604
- message: `Strategy duration ${Number(dur) / 86400} days exceeds 30-day threshold`
3605
- });
3606
- }
3607
- }
3608
- const hasCritical = risks.some((r) => r.level === "critical");
3609
- const hasWarning = risks.some((r) => r.level === "warning");
3610
- if (!hasCritical && !hasWarning) {
3611
- if (allTargetsVerified) {
3612
- risks.push({
3613
- level: "info",
3614
- code: "ALL_TARGETS_VERIFIED",
3615
- message: "All call targets are verified protocol addresses"
3616
- });
3617
- }
3618
- if (allCallsDecoded) {
3619
- risks.push({
3620
- level: "info",
3621
- code: "ALL_CALLS_DECODED",
3622
- message: "All calldata successfully decoded"
3623
- });
3624
- }
5292
+ } catch {
5293
+ return null;
3625
5294
  }
3626
- return risks;
3627
5295
  }
3628
5296
  var API_URLS = {
3629
5297
  "base": "https://app.sherwood.sh",
@@ -3674,7 +5342,7 @@ async function simulateViaApi(vault, calls) {
3674
5342
  const warnings = [];
3675
5343
  for (const cr of callResults) {
3676
5344
  if (!cr.success) {
3677
- warnings.push(`Call #${cr.index + 1} to ${labeledAddr(cr.target)} failed`);
5345
+ warnings.push(`Call #${cr.index + 1} to ${labeledAddr2(cr.target)} failed`);
3678
5346
  }
3679
5347
  }
3680
5348
  return {
@@ -3690,7 +5358,7 @@ async function simulateViaApi(vault, calls) {
3690
5358
  async function simulateViaEthCall(vault, calls) {
3691
5359
  const governor = getGovernorAddress();
3692
5360
  const client = getPublicClient();
3693
- const input2 = encodeFunctionData10({
5361
+ const input2 = encodeFunctionData16({
3694
5362
  abi: SYNDICATE_VAULT_ABI,
3695
5363
  functionName: "executeGovernorBatch",
3696
5364
  args: [calls.map((c) => ({ target: c.target, data: c.data, value: c.value }))]
@@ -3743,7 +5411,12 @@ async function simulateBatchCalls(vault, calls, callType, riskContext) {
3743
5411
  console.log(DIM(` Simulation API unavailable (${message}), falling back to eth_call...`));
3744
5412
  result = await simulateViaEthCall(vault, calls);
3745
5413
  }
3746
- const ctx = { vault, ...riskContext };
5414
+ const inlineLabels = buildLabelMap();
5415
+ const knownAddresses = {};
5416
+ for (const [addr, label] of inlineLabels) {
5417
+ knownAddresses[label] = addr;
5418
+ }
5419
+ const ctx = { vault, ...riskContext, knownAddresses };
3747
5420
  result.risks = analyzeRisk(calls, result.callResults, ctx);
3748
5421
  return result;
3749
5422
  }
@@ -3916,10 +5589,10 @@ function parseCallsFile(path) {
3916
5589
  }
3917
5590
  function registerProposalCommands(program2) {
3918
5591
  const proposal = program2.command("proposal").description("Governance proposals \u2014 create, vote, execute, settle");
3919
- proposal.command("create").description("Submit a strategy proposal").requiredOption("--vault <address>", "Vault address the proposal targets").requiredOption("--name <name>", "Strategy name").requiredOption("--description <text>", "Strategy rationale and risk summary").requiredOption("--performance-fee <bps>", "Agent fee in bps (e.g. 1500 = 15%)").requiredOption("--duration <duration>", "Strategy duration (e.g. 7d, 24h, 3600)").requiredOption("--execute-calls <path>", "Path to JSON file with execute Call[] array").requiredOption("--settle-calls <path>", "Path to JSON file with settlement Call[] array").option("--metadata-uri <uri>", "Override \u2014 skip IPFS upload and use this URI directly").option("--simulate", "Simulate calls via Tenderly before submitting").option("--force", "Submit even if simulation fails (requires --simulate)").action(async (opts) => {
5592
+ proposal.command("create").description("Submit a strategy proposal").requiredOption("--vault <address>", "Vault address the proposal targets").requiredOption("--name <name>", "Strategy name").requiredOption("--description <text>", "Strategy rationale and risk summary").requiredOption("--performance-fee <bps>", "Agent fee in bps (e.g. 1500 = 15%)").requiredOption("--duration <duration>", "Strategy duration (e.g. 7d, 24h, 3600)").requiredOption("--execute-calls <path>", "Path to JSON file with execute Call[] array").requiredOption("--settle-calls <path>", "Path to JSON file with settlement Call[] array").option("--strategy <address>", "Live-NAV strategy clone address (default: none \u2014 queue-only)").option("--metadata-uri <uri>", "Override \u2014 skip IPFS upload and use this URI directly").option("--simulate", "Simulate calls via Tenderly before submitting").option("--force", "Submit even if simulation fails (requires --simulate)").action(async (opts) => {
3920
5593
  try {
3921
5594
  const vault = opts.vault;
3922
- if (!isAddress4(vault)) {
5595
+ if (!isAddress5(vault)) {
3923
5596
  console.error(chalk6.red(`Invalid vault address: ${opts.vault}`));
3924
5597
  process.exit(1);
3925
5598
  }
@@ -3927,6 +5600,45 @@ function registerProposalCommands(program2) {
3927
5600
  const strategyDuration = parseDuration(opts.duration);
3928
5601
  const executeCalls = parseCallsFile(opts.executeCalls);
3929
5602
  const settleCalls = parseCallsFile(opts.settleCalls);
5603
+ const ZERO_STRATEGY = "0x0000000000000000000000000000000000000000";
5604
+ let strategy2 = ZERO_STRATEGY;
5605
+ if (opts.strategy) {
5606
+ if (!isAddress5(opts.strategy)) {
5607
+ console.error(chalk6.red(`Invalid strategy address: ${opts.strategy}`));
5608
+ process.exit(1);
5609
+ }
5610
+ strategy2 = opts.strategy;
5611
+ }
5612
+ if (isCalldataOnly()) {
5613
+ if (!opts.metadataUri) {
5614
+ console.error(
5615
+ chalk6.red(
5616
+ "--calldata-only requires --metadata-uri (IPFS metadata upload needs a signing wallet)"
5617
+ )
5618
+ );
5619
+ process.exit(1);
5620
+ }
5621
+ try {
5622
+ emitCalldata(
5623
+ encodePropose(
5624
+ {
5625
+ vault,
5626
+ strategy: strategy2,
5627
+ metadataURI: opts.metadataUri,
5628
+ performanceFeeBps: Number(performanceFeeBps),
5629
+ strategyDuration,
5630
+ executeCalls,
5631
+ settlementCalls: settleCalls
5632
+ },
5633
+ getChain().id
5634
+ )
5635
+ );
5636
+ } catch (err) {
5637
+ console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
5638
+ process.exit(1);
5639
+ }
5640
+ return;
5641
+ }
3930
5642
  let metadataURI = opts.metadataUri || "";
3931
5643
  if (!metadataURI) {
3932
5644
  const spinner2 = ora5({ text: W2("Uploading metadata to IPFS..."), color: "green" }).start();
@@ -4001,7 +5713,7 @@ function registerProposalCommands(program2) {
4001
5713
  const spinner = ora5({ text: W2("Submitting proposal..."), color: "green" }).start();
4002
5714
  const result = await propose(
4003
5715
  vault,
4004
- "0x0000000000000000000000000000000000000000",
5716
+ strategy2,
4005
5717
  metadataURI,
4006
5718
  performanceFeeBps,
4007
5719
  strategyDuration,
@@ -4157,6 +5869,11 @@ function registerProposalCommands(program2) {
4157
5869
  console.error(chalk6.red(`Invalid support value "${opts.support}". Use for|against|abstain.`));
4158
5870
  process.exit(1);
4159
5871
  }
5872
+ if (isCalldataOnly()) {
5873
+ const voteDir = support === VOTE_TYPE.For ? "For" : support === VOTE_TYPE.Against ? "Against" : "Abstain";
5874
+ emitCalldata(encodeVote({ proposalId, vote: voteDir }, getChain().id));
5875
+ return;
5876
+ }
4160
5877
  const account = getAccount();
4161
5878
  const spinner = ora5("Loading proposal...").start();
4162
5879
  const p = await getProposal(proposalId);
@@ -4196,6 +5913,10 @@ function registerProposalCommands(program2) {
4196
5913
  proposal.command("execute").description("Execute an approved proposal").requiredOption("--id <proposalId>", "Proposal ID").option("--dry-run", "Simulate execution without sending a transaction").action(async (opts) => {
4197
5914
  try {
4198
5915
  const proposalId = parseBigIntArg(opts.id, "proposal ID");
5916
+ if (isCalldataOnly()) {
5917
+ emitCalldata(encodeExecute({ proposalId }, getChain().id));
5918
+ return;
5919
+ }
4199
5920
  const spinner = ora5("Loading proposal...").start();
4200
5921
  const state = await getProposalState(proposalId);
4201
5922
  if (state !== PROPOSAL_STATE.Approved) {
@@ -4236,6 +5957,17 @@ function registerProposalCommands(program2) {
4236
5957
  proposal.command("settle").description("Settle an executed proposal (auto-routes settlement path)").requiredOption("--id <proposalId>", "Proposal ID").option("--calls <path>", "Path to JSON file with settle Call[] (for agent/emergency settle)").action(async (opts) => {
4237
5958
  try {
4238
5959
  const proposalId = parseBigIntArg(opts.id, "proposal ID");
5960
+ if (isCalldataOnly()) {
5961
+ if (opts.calls) {
5962
+ const calls = parseCallsFile(opts.calls);
5963
+ emitCalldata(
5964
+ encodeEmergencySettle({ proposalId, calls }, getChain().id)
5965
+ );
5966
+ } else {
5967
+ emitCalldata(encodeSettle({ proposalId }, getChain().id));
5968
+ }
5969
+ return;
5970
+ }
4239
5971
  const account = getAccount();
4240
5972
  const spinner = ora5("Loading proposal...").start();
4241
5973
  const p = await getProposal(proposalId);
@@ -4281,6 +6013,10 @@ function registerProposalCommands(program2) {
4281
6013
  ).action(async (opts) => {
4282
6014
  try {
4283
6015
  const proposalId = parseBigIntArg(opts.id, "proposal ID");
6016
+ if (isCalldataOnly()) {
6017
+ emitCalldata(encodeUnstick({ proposalId }, getChain().id));
6018
+ return;
6019
+ }
4284
6020
  const account = getAccount();
4285
6021
  const spinner = ora5("Loading proposal...").start();
4286
6022
  const p = await getProposal(proposalId);
@@ -4330,7 +6066,7 @@ function registerProposalCommands(program2) {
4330
6066
  process.exit(1);
4331
6067
  }
4332
6068
  const asset = await getAssetAddress();
4333
- const noopCalldata = encodeFunctionData11({
6069
+ const noopCalldata = encodeFunctionData17({
4334
6070
  abi: ERC20_ABI,
4335
6071
  functionName: "balanceOf",
4336
6072
  args: [vault]
@@ -4400,6 +6136,10 @@ function registerProposalCommands(program2) {
4400
6136
  proposal.command("cancel").description("Cancel a proposal (proposer or vault owner)").requiredOption("--id <proposalId>", "Proposal ID").option("--emergency", "Emergency cancel (vault owner only, any non-settled state)").action(async (opts) => {
4401
6137
  try {
4402
6138
  const proposalId = parseBigIntArg(opts.id, "proposal ID");
6139
+ if (isCalldataOnly()) {
6140
+ emitCalldata(encodeCancel({ proposalId }, getChain().id));
6141
+ return;
6142
+ }
4403
6143
  const spinner = ora5("Loading proposal...").start();
4404
6144
  const state = await getProposalState(proposalId);
4405
6145
  if (state === PROPOSAL_STATE.Settled || state === PROPOSAL_STATE.Cancelled) {
@@ -4474,7 +6214,7 @@ function registerProposalCommands(program2) {
4474
6214
  console.log(DIM2(`
4475
6215
  Simulating proposal #${proposalId} on vault ${vault.slice(0, 10)}...`));
4476
6216
  } else if (opts.executeCalls) {
4477
- if (!opts.vault || !isAddress4(opts.vault)) {
6217
+ if (!opts.vault || !isAddress5(opts.vault)) {
4478
6218
  console.error(chalk6.red("\n \u2716 --vault is required when using --execute-calls"));
4479
6219
  process.exit(1);
4480
6220
  }
@@ -4525,6 +6265,33 @@ var DIM3 = chalk7.gray;
4525
6265
  var BOLD3 = chalk7.white.bold;
4526
6266
  var LABEL3 = chalk7.green.bold;
4527
6267
  var SEP3 = () => console.log(DIM3("\u2500".repeat(60)));
6268
+ function registerParamSetter(governor, spec) {
6269
+ governor.command(spec.command).description(spec.description).requiredOption(spec.flag, spec.flagDescription).action(async (opts) => {
6270
+ const rawValue = String(opts[spec.valueLabel]);
6271
+ if (isCalldataOnly()) {
6272
+ try {
6273
+ const value = parseBigIntArg(rawValue, spec.valueLabel);
6274
+ emitCalldata(
6275
+ encodeGovernorParam({ param: spec.param, value }, getChain().id)
6276
+ );
6277
+ } catch (err) {
6278
+ console.error(chalk7.red(formatContractError(err)));
6279
+ process.exit(1);
6280
+ }
6281
+ return;
6282
+ }
6283
+ const spinner = ora6(spec.spinnerText).start();
6284
+ try {
6285
+ const hash = await spec.setter(parseBigIntArg(rawValue, spec.valueLabel));
6286
+ spinner.succeed(G3(spec.succeed(rawValue)));
6287
+ console.log(DIM3(` ${getExplorerUrl(hash)}`));
6288
+ } catch (err) {
6289
+ spinner.fail(spec.failText);
6290
+ console.error(chalk7.red(formatContractError(err)));
6291
+ process.exit(1);
6292
+ }
6293
+ });
6294
+ }
4528
6295
  function registerGovernorCommands(program2) {
4529
6296
  const governor = program2.command("governor").description("Governor parameters and vault management");
4530
6297
  governor.command("info").description("Display current governor parameters and registered vaults").action(async () => {
@@ -4562,96 +6329,96 @@ function registerGovernorCommands(program2) {
4562
6329
  process.exit(1);
4563
6330
  }
4564
6331
  });
4565
- governor.command("set-voting-period").description("Set the voting period (owner only)").requiredOption("--seconds <n>", "New voting period in seconds").action(async (opts) => {
4566
- const spinner = ora6("Setting voting period...").start();
4567
- try {
4568
- const hash = await setVotingPeriod(parseBigIntArg(opts.seconds, "seconds"));
4569
- spinner.succeed(G3(`Voting period change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
4570
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4571
- } catch (err) {
4572
- spinner.fail("Failed to set voting period");
4573
- console.error(chalk7.red(formatContractError(err)));
4574
- process.exit(1);
4575
- }
6332
+ registerParamSetter(governor, {
6333
+ command: "set-voting-period",
6334
+ description: "Set the voting period (owner only)",
6335
+ flag: "--seconds <n>",
6336
+ flagDescription: "New voting period in seconds",
6337
+ valueLabel: "seconds",
6338
+ param: "votingPeriod",
6339
+ setter: setVotingPeriod,
6340
+ spinnerText: "Setting voting period...",
6341
+ succeed: (v) => `Voting period updated to ${v}s (owner-instant \u2014 the owner multisig enforces its own delay).`,
6342
+ failText: "Failed to set voting period"
4576
6343
  });
4577
- governor.command("set-execution-window").description("Set the execution window (owner only)").requiredOption("--seconds <n>", "New execution window in seconds").action(async (opts) => {
4578
- const spinner = ora6("Setting execution window...").start();
4579
- try {
4580
- const hash = await setExecutionWindow(parseBigIntArg(opts.seconds, "seconds"));
4581
- spinner.succeed(G3(`Execution window change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
4582
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4583
- } catch (err) {
4584
- spinner.fail("Failed to set execution window");
4585
- console.error(chalk7.red(formatContractError(err)));
4586
- process.exit(1);
4587
- }
6344
+ registerParamSetter(governor, {
6345
+ command: "set-execution-window",
6346
+ description: "Set the execution window (owner only)",
6347
+ flag: "--seconds <n>",
6348
+ flagDescription: "New execution window in seconds",
6349
+ valueLabel: "seconds",
6350
+ param: "executionWindow",
6351
+ setter: setExecutionWindow,
6352
+ spinnerText: "Setting execution window...",
6353
+ succeed: (v) => `Execution window updated to ${v}s (owner-instant \u2014 the owner multisig enforces its own delay).`,
6354
+ failText: "Failed to set execution window"
4588
6355
  });
4589
- governor.command("set-veto-threshold").description("Set the veto threshold in bps (owner only)").requiredOption("--bps <n>", "New veto threshold in bps (e.g. 4000 = 40%)").action(async (opts) => {
4590
- const spinner = ora6("Setting veto threshold...").start();
4591
- try {
4592
- const hash = await setVetoThresholdBps(parseBigIntArg(opts.bps, "bps"));
4593
- spinner.succeed(G3(`Veto threshold change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
4594
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4595
- } catch (err) {
4596
- spinner.fail("Failed to set veto threshold");
4597
- console.error(chalk7.red(formatContractError(err)));
4598
- process.exit(1);
4599
- }
6356
+ registerParamSetter(governor, {
6357
+ command: "set-veto-threshold",
6358
+ description: "Set the veto threshold in bps (owner only)",
6359
+ flag: "--bps <n>",
6360
+ flagDescription: "New veto threshold in bps (e.g. 4000 = 40%)",
6361
+ valueLabel: "bps",
6362
+ param: "vetoThresholdBps",
6363
+ setter: setVetoThresholdBps,
6364
+ spinnerText: "Setting veto threshold...",
6365
+ succeed: (v) => `Veto threshold updated to ${Number(v) / 100}% (owner-instant \u2014 the owner multisig enforces its own delay).`,
6366
+ failText: "Failed to set veto threshold"
4600
6367
  });
4601
- governor.command("set-max-fee").description("Set the max performance fee in bps (owner only)").requiredOption("--bps <n>", "New max fee in bps (e.g. 3000 = 30%)").action(async (opts) => {
4602
- const spinner = ora6("Setting max fee...").start();
4603
- try {
4604
- const hash = await setMaxPerformanceFeeBps(parseBigIntArg(opts.bps, "bps"));
4605
- spinner.succeed(G3(`Max performance fee change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
4606
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4607
- } catch (err) {
4608
- spinner.fail("Failed to set max fee");
4609
- console.error(chalk7.red(formatContractError(err)));
4610
- process.exit(1);
4611
- }
6368
+ registerParamSetter(governor, {
6369
+ command: "set-max-fee",
6370
+ description: "Set the max performance fee in bps (owner only)",
6371
+ flag: "--bps <n>",
6372
+ flagDescription: "New max fee in bps (e.g. 3000 = 30%)",
6373
+ valueLabel: "bps",
6374
+ param: "maxPerformanceFeeBps",
6375
+ setter: setMaxPerformanceFeeBps,
6376
+ spinnerText: "Setting max fee...",
6377
+ succeed: (v) => `Max performance fee updated to ${Number(v) / 100}% (owner-instant \u2014 the owner multisig enforces its own delay).`,
6378
+ failText: "Failed to set max fee"
4612
6379
  });
4613
- governor.command("set-max-duration").description("Set the max strategy duration in seconds (owner only)").requiredOption("--seconds <n>", "New max duration in seconds").action(async (opts) => {
4614
- const spinner = ora6("Setting max duration...").start();
4615
- try {
4616
- const hash = await setMaxStrategyDuration(parseBigIntArg(opts.seconds, "seconds"));
4617
- spinner.succeed(G3(`Max strategy duration change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
4618
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4619
- } catch (err) {
4620
- spinner.fail("Failed to set max duration");
4621
- console.error(chalk7.red(formatContractError(err)));
4622
- process.exit(1);
4623
- }
6380
+ registerParamSetter(governor, {
6381
+ command: "set-max-duration",
6382
+ description: "Set the max strategy duration in seconds (owner only)",
6383
+ flag: "--seconds <n>",
6384
+ flagDescription: "New max duration in seconds",
6385
+ valueLabel: "seconds",
6386
+ param: "maxStrategyDuration",
6387
+ setter: setMaxStrategyDuration,
6388
+ spinnerText: "Setting max duration...",
6389
+ succeed: (v) => `Max strategy duration updated to ${v}s (owner-instant \u2014 the owner multisig enforces its own delay).`,
6390
+ failText: "Failed to set max duration"
4624
6391
  });
4625
- governor.command("set-cooldown").description("Set the cooldown period in seconds (owner only)").requiredOption("--seconds <n>", "New cooldown in seconds").action(async (opts) => {
4626
- const spinner = ora6("Setting cooldown...").start();
4627
- try {
4628
- const hash = await setCooldownPeriod(parseBigIntArg(opts.seconds, "seconds"));
4629
- spinner.succeed(G3(`Cooldown period change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
4630
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4631
- } catch (err) {
4632
- spinner.fail("Failed to set cooldown");
4633
- console.error(chalk7.red(formatContractError(err)));
4634
- process.exit(1);
4635
- }
6392
+ registerParamSetter(governor, {
6393
+ command: "set-cooldown",
6394
+ description: "Set the cooldown period in seconds (owner only)",
6395
+ flag: "--seconds <n>",
6396
+ flagDescription: "New cooldown in seconds",
6397
+ valueLabel: "seconds",
6398
+ param: "cooldownPeriod",
6399
+ setter: setCooldownPeriod,
6400
+ spinnerText: "Setting cooldown...",
6401
+ succeed: (v) => `Cooldown period updated to ${v}s (owner-instant \u2014 the owner multisig enforces its own delay).`,
6402
+ failText: "Failed to set cooldown"
4636
6403
  });
4637
- governor.command("set-protocol-fee").description("Set the protocol fee in bps (owner only)").requiredOption("--bps <n>", "New protocol fee in bps (e.g. 500 = 5%, max 1000 = 10%)").action(async (opts) => {
4638
- const spinner = ora6("Setting protocol fee...").start();
4639
- try {
4640
- const hash = await setProtocolFeeBps(parseBigIntArg(opts.bps, "bps"));
4641
- spinner.succeed(G3(`Protocol fee change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
4642
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4643
- } catch (err) {
4644
- spinner.fail("Failed to set protocol fee");
4645
- console.error(chalk7.red(formatContractError(err)));
4646
- process.exit(1);
4647
- }
6404
+ registerParamSetter(governor, {
6405
+ command: "set-protocol-fee",
6406
+ description: "Set the protocol fee in bps (owner only)",
6407
+ flag: "--bps <n>",
6408
+ flagDescription: "New protocol fee in bps (e.g. 500 = 5%, max 1000 = 10%)",
6409
+ valueLabel: "bps",
6410
+ param: "protocolFeeBps",
6411
+ setter: setProtocolFeeBps,
6412
+ spinnerText: "Setting protocol fee...",
6413
+ succeed: (v) => `Protocol fee updated to ${Number(v) / 100}% (owner-instant \u2014 the owner multisig enforces its own delay).`,
6414
+ failText: "Failed to set protocol fee"
4648
6415
  });
4649
6416
  }
4650
6417
 
4651
6418
  // src/commands/guardian-delegate.ts
4652
6419
  import chalk8 from "chalk";
4653
6420
  import ora7 from "ora";
4654
- import { formatUnits as formatUnits4, isAddress as isAddress5, parseUnits as parseUnits5 } from "viem";
6421
+ import { formatUnits as formatUnits5, isAddress as isAddress6, parseUnits as parseUnits5 } from "viem";
4655
6422
  var G4 = chalk8.green;
4656
6423
  var W4 = chalk8.white;
4657
6424
  var DIM4 = chalk8.gray;
@@ -4659,7 +6426,7 @@ var BOLD4 = chalk8.white.bold;
4659
6426
  var LABEL4 = chalk8.green.bold;
4660
6427
  var WARN = chalk8.yellow;
4661
6428
  var SEP4 = () => console.log(DIM4("\u2500".repeat(60)));
4662
- var GUARDIAN_REGISTRY_ABI = [
6429
+ var GUARDIAN_REGISTRY_ABI2 = [
4663
6430
  // Guardian stake
4664
6431
  { type: "function", name: "stakeAsGuardian", inputs: [{ type: "uint256" }, { type: "uint256" }], outputs: [], stateMutability: "nonpayable" },
4665
6432
  { type: "function", name: "requestUnstakeGuardian", inputs: [], outputs: [], stateMutability: "nonpayable" },
@@ -4682,7 +6449,7 @@ var GUARDIAN_REGISTRY_ABI = [
4682
6449
  { type: "function", name: "claimProposalReward", inputs: [{ type: "uint256" }], outputs: [], stateMutability: "nonpayable" },
4683
6450
  { type: "function", name: "claimDelegatorProposalReward", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [], stateMutability: "nonpayable" }
4684
6451
  ];
4685
- var ERC20_ABI2 = [
6452
+ var ERC20_ABI3 = [
4686
6453
  { type: "function", name: "approve", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }], stateMutability: "nonpayable" },
4687
6454
  { type: "function", name: "balanceOf", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }], stateMutability: "view" },
4688
6455
  { type: "function", name: "decimals", inputs: [], outputs: [{ type: "uint8" }], stateMutability: "view" }
@@ -4715,7 +6482,7 @@ async function ensureWoodAllowance(amount) {
4715
6482
  try {
4716
6483
  const hash = await wallet.writeContract({
4717
6484
  address: wood(),
4718
- abi: ERC20_ABI2,
6485
+ abi: ERC20_ABI3,
4719
6486
  functionName: "approve",
4720
6487
  args: [registry(), 2n ** 256n - 1n],
4721
6488
  chain: null,
@@ -4734,7 +6501,7 @@ function registerGuardianCommands(program2) {
4734
6501
  const pc = getPublicClient();
4735
6502
  let target;
4736
6503
  if (addressArg) {
4737
- if (!isAddress5(addressArg)) {
6504
+ if (!isAddress6(addressArg)) {
4738
6505
  console.error(chalk8.red("Invalid address"));
4739
6506
  process.exit(1);
4740
6507
  }
@@ -4746,13 +6513,13 @@ function registerGuardianCommands(program2) {
4746
6513
  const spinner = ora7("Loading...").start();
4747
6514
  try {
4748
6515
  const [stake, active, commission] = await Promise.all([
4749
- pc.readContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: "guardianStake", args: [target] }),
4750
- pc.readContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: "isActiveGuardian", args: [target] }),
4751
- pc.readContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: "commissionOf", args: [target] })
6516
+ pc.readContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: "guardianStake", args: [target] }),
6517
+ pc.readContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: "isActiveGuardian", args: [target] }),
6518
+ pc.readContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: "commissionOf", args: [target] })
4752
6519
  ]);
4753
6520
  const delegatedIn = await pc.readContract({
4754
6521
  address: registry(),
4755
- abi: GUARDIAN_REGISTRY_ABI,
6522
+ abi: GUARDIAN_REGISTRY_ABI2,
4756
6523
  functionName: "delegatedInbound",
4757
6524
  args: [target]
4758
6525
  });
@@ -4762,8 +6529,8 @@ function registerGuardianCommands(program2) {
4762
6529
  SEP4();
4763
6530
  console.log(W4(` Address: ${G4(target)}`));
4764
6531
  console.log(W4(` Active guardian: ${active ? G4("yes") : DIM4("no")}`));
4765
- console.log(W4(` Own stake: ${BOLD4(formatUnits4(stake, 18))} WOOD`));
4766
- console.log(W4(` Delegated inbound: ${BOLD4(formatUnits4(delegatedIn, 18))} WOOD`));
6532
+ console.log(W4(` Own stake: ${BOLD4(formatUnits5(stake, 18))} WOOD`));
6533
+ console.log(W4(` Delegated inbound: ${BOLD4(formatUnits5(delegatedIn, 18))} WOOD`));
4767
6534
  console.log(W4(` Commission: ${BOLD4(`${Number(commission) / 100}%`)}`));
4768
6535
  SEP4();
4769
6536
  console.log();
@@ -4776,6 +6543,10 @@ function registerGuardianCommands(program2) {
4776
6543
  guardian.command("stake <amount>").description("Stake as a guardian. Amount in WOOD (min = registry.minGuardianStake).").option("--agent-id <id>", "ERC-8004 agent ID to associate (first-stake only)", "0").action(async (amountStr, opts) => {
4777
6544
  const amount = parseUnits5(amountStr, 18);
4778
6545
  const agentId = BigInt(opts.agentId);
6546
+ if (isCalldataOnly()) {
6547
+ emitCalldata(encodeGuardianStake({ amount, agentId: opts.agentId }, getChain().id));
6548
+ return;
6549
+ }
4779
6550
  await ensureWoodAllowance(amount);
4780
6551
  const wallet = getWalletClient();
4781
6552
  const pc = getPublicClient();
@@ -4783,7 +6554,7 @@ function registerGuardianCommands(program2) {
4783
6554
  try {
4784
6555
  const hash = await wallet.writeContract({
4785
6556
  address: registry(),
4786
- abi: GUARDIAN_REGISTRY_ABI,
6557
+ abi: GUARDIAN_REGISTRY_ABI2,
4787
6558
  functionName: "stakeAsGuardian",
4788
6559
  args: [amount, agentId],
4789
6560
  chain: null,
@@ -4803,11 +6574,15 @@ function registerGuardianCommands(program2) {
4803
6574
  console.error(chalk8.red(`Unknown action '${action}'. Use request | cancel | claim.`));
4804
6575
  process.exit(1);
4805
6576
  }
6577
+ if (isCalldataOnly()) {
6578
+ emitCalldata(encodeGuardianUnstake({ action }, getChain().id));
6579
+ return;
6580
+ }
4806
6581
  const wallet = getWalletClient();
4807
6582
  const pc = getPublicClient();
4808
6583
  const spinner = ora7(`${action}...`).start();
4809
6584
  try {
4810
- const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: fn, args: [], chain: null, account: wallet.account });
6585
+ const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: fn, args: [], chain: null, account: wallet.account });
4811
6586
  await pc.waitForTransactionReceipt({ hash });
4812
6587
  spinner.succeed(`${action} confirmed. Tx: ${hash}`);
4813
6588
  } catch (err) {
@@ -4817,17 +6592,21 @@ function registerGuardianCommands(program2) {
4817
6592
  }
4818
6593
  });
4819
6594
  guardian.command("delegate <delegate> <amount>").description("Delegate WOOD stake to a guardian. Amount in WOOD.").action(async (delegate, amountStr) => {
4820
- if (!isAddress5(delegate)) {
6595
+ if (!isAddress6(delegate)) {
4821
6596
  console.error(chalk8.red("Invalid delegate address"));
4822
6597
  process.exit(1);
4823
6598
  }
4824
6599
  const amount = parseUnits5(amountStr, 18);
6600
+ if (isCalldataOnly()) {
6601
+ emitCalldata(encodeGuardianDelegate({ delegate, amount }, getChain().id));
6602
+ return;
6603
+ }
4825
6604
  await ensureWoodAllowance(amount);
4826
6605
  const wallet = getWalletClient();
4827
6606
  const pc = getPublicClient();
4828
6607
  const spinner = ora7(`Delegating ${amountStr} WOOD to ${delegate}...`).start();
4829
6608
  try {
4830
- const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: "delegateStake", args: [delegate, amount], chain: null, account: wallet.account });
6609
+ const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: "delegateStake", args: [delegate, amount], chain: null, account: wallet.account });
4831
6610
  await pc.waitForTransactionReceipt({ hash });
4832
6611
  spinner.succeed(`Delegated. Tx: ${hash}`);
4833
6612
  } catch (err) {
@@ -4837,7 +6616,7 @@ function registerGuardianCommands(program2) {
4837
6616
  }
4838
6617
  });
4839
6618
  guardian.command("undelegate <delegate> <action>").description("Unstake-delegation flow: 'request' | 'cancel' | 'claim'").action(async (delegate, action) => {
4840
- if (!isAddress5(delegate)) {
6619
+ if (!isAddress6(delegate)) {
4841
6620
  console.error(chalk8.red("Invalid delegate address"));
4842
6621
  process.exit(1);
4843
6622
  }
@@ -4846,11 +6625,15 @@ function registerGuardianCommands(program2) {
4846
6625
  console.error(chalk8.red(`Unknown action '${action}'. Use request | cancel | claim.`));
4847
6626
  process.exit(1);
4848
6627
  }
6628
+ if (isCalldataOnly()) {
6629
+ emitCalldata(encodeDelegationUnstake({ action, delegate }, getChain().id));
6630
+ return;
6631
+ }
4849
6632
  const wallet = getWalletClient();
4850
6633
  const pc = getPublicClient();
4851
6634
  const spinner = ora7(`${action}...`).start();
4852
6635
  try {
4853
- const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: fn, args: [delegate], chain: null, account: wallet.account });
6636
+ const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: fn, args: [delegate], chain: null, account: wallet.account });
4854
6637
  await pc.waitForTransactionReceipt({ hash });
4855
6638
  spinner.succeed(`${action} confirmed. Tx: ${hash}`);
4856
6639
  } catch (err) {
@@ -4865,11 +6648,15 @@ function registerGuardianCommands(program2) {
4865
6648
  console.error(chalk8.red("Commission cannot exceed 5000 bps (50%)"));
4866
6649
  process.exit(1);
4867
6650
  }
6651
+ if (isCalldataOnly()) {
6652
+ emitCalldata(encodeSetCommission({ bps: Number(bps) }, getChain().id));
6653
+ return;
6654
+ }
4868
6655
  const wallet = getWalletClient();
4869
6656
  const pc = getPublicClient();
4870
6657
  const spinner = ora7(`Setting commission to ${Number(bps) / 100}%...`).start();
4871
6658
  try {
4872
- const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: "setCommission", args: [bps], chain: null, account: wallet.account });
6659
+ const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: "setCommission", args: [bps], chain: null, account: wallet.account });
4873
6660
  await pc.waitForTransactionReceipt({ hash });
4874
6661
  spinner.succeed(`Commission set. Tx: ${hash}`);
4875
6662
  } catch (err) {
@@ -4880,11 +6667,15 @@ function registerGuardianCommands(program2) {
4880
6667
  });
4881
6668
  guardian.command("claim-proposal <proposalId>").description("Claim guardian-fee commission for a settled proposal you voted Approve on (vault asset).").action(async (pidStr) => {
4882
6669
  const pid = BigInt(pidStr);
6670
+ if (isCalldataOnly()) {
6671
+ emitCalldata(encodeClaimProposalReward({ proposalId: pid }, getChain().id));
6672
+ return;
6673
+ }
4883
6674
  const wallet = getWalletClient();
4884
6675
  const pc = getPublicClient();
4885
6676
  const spinner = ora7("Claiming...").start();
4886
6677
  try {
4887
- const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: "claimProposalReward", args: [pid], chain: null, account: wallet.account });
6678
+ const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: "claimProposalReward", args: [pid], chain: null, account: wallet.account });
4888
6679
  await pc.waitForTransactionReceipt({ hash });
4889
6680
  spinner.succeed(`Claimed. Tx: ${hash}`);
4890
6681
  } catch (err) {
@@ -4894,16 +6685,20 @@ function registerGuardianCommands(program2) {
4894
6685
  }
4895
6686
  });
4896
6687
  guardian.command("claim-delegator <delegate> <proposalId>").description("Claim your delegator share of a delegate's guardian-fee pool for a specific proposal.").action(async (delegate, pidStr) => {
4897
- if (!isAddress5(delegate)) {
6688
+ if (!isAddress6(delegate)) {
4898
6689
  console.error(chalk8.red("Invalid delegate address"));
4899
6690
  process.exit(1);
4900
6691
  }
4901
6692
  const pid = BigInt(pidStr);
6693
+ if (isCalldataOnly()) {
6694
+ emitCalldata(encodeClaimDelegatorProposalReward({ delegate, proposalId: pid }, getChain().id));
6695
+ return;
6696
+ }
4902
6697
  const wallet = getWalletClient();
4903
6698
  const pc = getPublicClient();
4904
6699
  const spinner = ora7("Claiming delegator share...").start();
4905
6700
  try {
4906
- const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: "claimDelegatorProposalReward", args: [delegate, pid], chain: null, account: wallet.account });
6701
+ const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: "claimDelegatorProposalReward", args: [delegate, pid], chain: null, account: wallet.account });
4907
6702
  await pc.waitForTransactionReceipt({ hash });
4908
6703
  spinner.succeed(`Claimed. Tx: ${hash}`);
4909
6704
  } catch (err) {
@@ -4933,6 +6728,7 @@ function registerGuardianCommands(program2) {
4933
6728
 
4934
6729
  // src/commands/grid.ts
4935
6730
  import chalk14 from "chalk";
6731
+ import { createInterface as createInterface2 } from "readline/promises";
4936
6732
 
4937
6733
  // src/grid/loop.ts
4938
6734
  import chalk13 from "chalk";
@@ -5183,7 +6979,7 @@ var GridManager = class {
5183
6979
  }
5184
6980
  this.portfolio.checkPauseThreshold(state, this.config, prices);
5185
6981
  await this.portfolio.save(state);
5186
- return { fills: totalFills, roundTrips: totalRoundTrips, pnlUsd: totalPnl, paused: false };
6982
+ return { fills: totalFills, roundTrips: totalRoundTrips, pnlUsd: totalPnl, paused: state.paused };
5187
6983
  }
5188
6984
  /** Hourly refresh of `grid.trend` from recent 4h candles. Called from
5189
6985
  * tick() so the downtrend filter sees fresh data between buildGrid runs. */
@@ -5472,7 +7268,8 @@ var DEFAULT_GRID_CONFIG = {
5472
7268
  // was 0.55 — rebalance faster, keep levels near price
5473
7269
  fullRebuildIntervalMs: 12 * 60 * 60 * 1e3,
5474
7270
  // 12h
5475
- tokenSplit: { bitcoin: 0.45, ethereum: 0.3, solana: 0.25 },
7271
+ tokenSplit: { bitcoin: 0.55, ethereum: 0.25, solana: 0.2 },
7272
+ // BTC-tilt — 30d backtest 2026-04-18..05-18 vs prior 0.45/0.30/0.25: net +$464 (+9.28%) vs +$422 (+8.44%) and max DD -$546 vs -$598
5476
7273
  minProfitPerFillUsd: 0.5,
5477
7274
  pauseThresholdPct: 0.2,
5478
7275
  // post leverage-fix calibration: fires at ~4% adverse move on 5x (real dollar terms); old 0.40 required ~8% which essentially never fired before liquidation
@@ -5550,8 +7347,14 @@ var GridHedgeManager = class {
5550
7347
  * @param marketTrend - recent market trend (e.g. BTC's 56h trend) used by
5551
7348
  * `hedgeRatioFor()` to scale hedge size by regime. Pass
5552
7349
  * undefined to use the static default.
7350
+ * @param paused - when true, force desired hedge to zero for all tokens so
7351
+ * the close-path realizes any unrealized PnL into the
7352
+ * ledger. The loop passes `state.paused` so a soft-pause
7353
+ * grid doesn't hold paper-gain on the hedge that erodes
7354
+ * when the underlying mean-reverts toward the resume
7355
+ * threshold.
5553
7356
  */
5554
- async tick(openFills, prices, now = Date.now(), marketTrend) {
7357
+ async tick(openFills, prices, now = Date.now(), marketTrend, paused = false) {
5555
7358
  const state = await this.load(now);
5556
7359
  let adjustments = 0;
5557
7360
  let unrealizedPnl = 0;
@@ -5559,7 +7362,7 @@ var GridHedgeManager = class {
5559
7362
  for (const fill of openFills) {
5560
7363
  const price = prices[fill.token];
5561
7364
  if (!price || price <= 0) continue;
5562
- const desiredShortQty = fill.fillCount >= MIN_FILLS_TO_HEDGE ? fill.totalQuantity * activeRatio : 0;
7365
+ const desiredShortQty = paused ? 0 : fill.fillCount >= MIN_FILLS_TO_HEDGE ? fill.totalQuantity * activeRatio : 0;
5563
7366
  const existing = state.positions.find((p) => p.token === fill.token);
5564
7367
  if (desiredShortQty <= 0) {
5565
7368
  if (existing && existing.quantity > 0) {
@@ -5651,6 +7454,39 @@ var GridHedgeManager = class {
5651
7454
  totalRealizedPnl: state.totalRealizedPnl
5652
7455
  };
5653
7456
  }
7457
+ /**
7458
+ * Close every active hedge position in the ledger at the provided mark
7459
+ * prices, accumulating realized PnL using the same `(entry - price) * qty`
7460
+ * formula `tick()` uses on its auto-close path. Positions whose token is
7461
+ * missing from `prices` are left untouched and reported in `skipped`.
7462
+ *
7463
+ * Bookkeeping-only — this manager does not place perp orders; the actual
7464
+ * HL position (if any) must be closed externally. Use this to zero the
7465
+ * ledger as part of `sherwood grid flatten`.
7466
+ */
7467
+ async flatten(prices, now = Date.now()) {
7468
+ const state = await this.load(now);
7469
+ const closed = [];
7470
+ const skipped = [];
7471
+ for (const pos of state.positions) {
7472
+ if (pos.quantity <= 0) continue;
7473
+ const price = prices[pos.token];
7474
+ if (!price || price <= 0) {
7475
+ skipped.push(pos.token);
7476
+ continue;
7477
+ }
7478
+ const closePnl = (pos.entryPrice - price) * pos.quantity;
7479
+ state.totalRealizedPnl += closePnl;
7480
+ state.todayRealizedPnl += closePnl;
7481
+ pos.realizedPnl += closePnl;
7482
+ closed.push({ token: pos.token, quantity: pos.quantity, realizedPnl: closePnl });
7483
+ pos.quantity = 0;
7484
+ pos.entryPrice = 0;
7485
+ pos.lastAdjustedAt = now;
7486
+ }
7487
+ await this.save();
7488
+ return { closed, skipped, totalRealizedPnl: state.totalRealizedPnl };
7489
+ }
5654
7490
  /** Get current hedge status for display. */
5655
7491
  getStatus() {
5656
7492
  if (!this.state) return null;
@@ -5716,13 +7552,13 @@ var GridExecutor = class {
5716
7552
  import chalk12 from "chalk";
5717
7553
  import { mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "fs/promises";
5718
7554
  import { dirname as dirname3 } from "path";
5719
- import { encodeAbiParameters as encodeAbiParameters11, parseUnits as parseUnits6 } from "viem";
7555
+ import { encodeAbiParameters as encodeAbiParameters12, parseUnits as parseUnits6 } from "viem";
5720
7556
 
5721
7557
  // src/grid/cloid.ts
5722
- import { keccak256, encodeAbiParameters as encodeAbiParameters10 } from "viem";
7558
+ import { keccak256, encodeAbiParameters as encodeAbiParameters11 } from "viem";
5723
7559
  function gridCloid(strategy2, assetIndex, isBuy, levelIndex, nonce) {
5724
7560
  const hash = keccak256(
5725
- encodeAbiParameters10(
7561
+ encodeAbiParameters11(
5726
7562
  [
5727
7563
  { type: "address" },
5728
7564
  { type: "uint32" },
@@ -5782,11 +7618,18 @@ var OnchainGridExecutor = class {
5782
7618
  this.maxOrdersPerTick = Number(value);
5783
7619
  return this.maxOrdersPerTick;
5784
7620
  }
5785
- /** Load persisted state. Call once before the first execute(). */
5786
- async load() {
7621
+ /**
7622
+ * Read persisted state from disk — does NOT touch the network. Use this
7623
+ * when you only need `nonces` + `placedCloids` (e.g. for `flattenAll`'s
7624
+ * cancel-only path). Idempotent: callers may invoke it repeatedly; each
7625
+ * call re-reads the file and clobbers in-memory maps with disk state.
7626
+ */
7627
+ async loadFromDisk() {
5787
7628
  try {
5788
7629
  const raw = await readFile3(this.statePath, "utf-8");
5789
7630
  const state = JSON.parse(raw);
7631
+ this.nonces.clear();
7632
+ this.placedCloids.clear();
5790
7633
  for (const [tok, n] of Object.entries(state.nonces)) this.nonces.set(tok, n);
5791
7634
  for (const [tok, cloids] of Object.entries(state.placedCloids)) {
5792
7635
  this.placedCloids.set(tok, cloids.map((s) => BigInt(s)));
@@ -5803,6 +7646,10 @@ var OnchainGridExecutor = class {
5803
7646
  );
5804
7647
  }
5805
7648
  }
7649
+ }
7650
+ /** Load persisted state + chain meta. Call once before the first execute(). */
7651
+ async load() {
7652
+ await this.loadFromDisk();
5806
7653
  if (!this.meta) this.meta = await hlGetMeta();
5807
7654
  await this.fetchMaxOrdersPerTick();
5808
7655
  }
@@ -5938,7 +7785,7 @@ var OnchainGridExecutor = class {
5938
7785
  for (const chunk of chunks) {
5939
7786
  const nonce = this.bumpNonce(token);
5940
7787
  const { encoded, cloids } = this.encodeOrders(assetIndex, chunk, meta, nonce);
5941
- const data = encodeAbiParameters11(
7788
+ const data = encodeAbiParameters12(
5942
7789
  [{ type: "uint8" }, { type: "tuple[]", components: [...GRID_ORDER_COMPONENTS] }],
5943
7790
  [ACTION_PLACE_GRID, encoded]
5944
7791
  );
@@ -5956,7 +7803,7 @@ var OnchainGridExecutor = class {
5956
7803
  this.bumpNonce(token);
5957
7804
  return "0x";
5958
7805
  }
5959
- const data = encodeAbiParameters11(
7806
+ const data = encodeAbiParameters12(
5960
7807
  [{ type: "uint8" }, { type: "uint32" }, { type: "uint128[]" }],
5961
7808
  [ACTION_CANCEL_ALL, assetIndex, cloids]
5962
7809
  );
@@ -5985,7 +7832,7 @@ var OnchainGridExecutor = class {
5985
7832
  const { encoded, cloids } = this.encodeOrders(assetIndex, chunk, meta, newNonce);
5986
7833
  let data;
5987
7834
  if (i === 0) {
5988
- data = encodeAbiParameters11(
7835
+ data = encodeAbiParameters12(
5989
7836
  [
5990
7837
  { type: "uint8" },
5991
7838
  { type: "uint32" },
@@ -5995,7 +7842,7 @@ var OnchainGridExecutor = class {
5995
7842
  [ACTION_CANCEL_AND_PLACE, assetIndex, oldCloids, encoded]
5996
7843
  );
5997
7844
  } else {
5998
- data = encodeAbiParameters11(
7845
+ data = encodeAbiParameters12(
5999
7846
  [{ type: "uint8" }, { type: "tuple[]", components: [...GRID_ORDER_COMPONENTS] }],
6000
7847
  [ACTION_PLACE_GRID, encoded]
6001
7848
  );
@@ -6008,6 +7855,47 @@ var OnchainGridExecutor = class {
6008
7855
  }
6009
7856
  return txs;
6010
7857
  }
7858
+ /**
7859
+ * Cancel every tracked CLOID across all configured tokens. Used by
7860
+ * `sherwood grid flatten` to wind the strategy down to a no-orders state
7861
+ * without placing anything new.
7862
+ *
7863
+ * Per-token errors are captured and returned rather than thrown — partial
7864
+ * progress is the expected outcome when one token reverts (e.g. nonce
7865
+ * desync after a manual on-chain action). Each successful cancel saves
7866
+ * before moving on, so a crash mid-flatten leaves a consistent on-disk
7867
+ * record of which tokens still hold cloids.
7868
+ */
7869
+ async flattenAll() {
7870
+ if (this.busy) {
7871
+ return { txs: [], cancelled: [], skipped: [], errors: ["flattenAll() called while another execute()/flattenAll() is in flight"] };
7872
+ }
7873
+ this.busy = true;
7874
+ try {
7875
+ await this.loadFromDisk();
7876
+ const txs = [];
7877
+ const cancelled = [];
7878
+ const skipped = [];
7879
+ const errors = [];
7880
+ for (const [token, assetIndex] of Object.entries(this.cfg.assetIndices)) {
7881
+ try {
7882
+ const tx = await this.cancelAll(token, assetIndex);
7883
+ if (tx === "0x") {
7884
+ skipped.push(token);
7885
+ } else {
7886
+ txs.push(tx);
7887
+ cancelled.push(token);
7888
+ }
7889
+ await this.save();
7890
+ } catch (e) {
7891
+ errors.push(`${token}: ${e.message}`);
7892
+ }
7893
+ }
7894
+ return { txs, cancelled, skipped, errors };
7895
+ } finally {
7896
+ this.busy = false;
7897
+ }
7898
+ }
6011
7899
  /**
6012
7900
  * Submit and wait for receipt. Throwing here surfaces to the per-token
6013
7901
  * try/catch in execute(), recording the error and skipping the save() so
@@ -6151,7 +8039,7 @@ var GridLoop = class {
6151
8039
  }
6152
8040
  const openExposure = this.manager.getOpenFillExposure();
6153
8041
  const marketTrend = this.manager.getMarketTrend();
6154
- const hedgeResult = await this.hedge.tick(openExposure, prices, Date.now(), marketTrend);
8042
+ const hedgeResult = await this.hedge.tick(openExposure, prices, Date.now(), marketTrend, result.paused);
6155
8043
  if (hedgeResult.adjustments > 0) {
6156
8044
  console.error(chalk13.magenta(
6157
8045
  ` [hedge] ${hedgeResult.adjustments} adjustment(s), unrealized: $${hedgeResult.unrealizedPnl.toFixed(2)}, total realized: $${hedgeResult.totalRealizedPnl.toFixed(2)}`
@@ -6598,7 +8486,7 @@ async function runBacktest(opts) {
6598
8486
  }
6599
8487
  const exposure = manager.getOpenFillExposure();
6600
8488
  const marketTrend = manager.getMarketTrend();
6601
- const hedgeResult = await hedge.tick(exposure, hedgePrices, t, marketTrend);
8489
+ const hedgeResult = await hedge.tick(exposure, hedgePrices, t, marketTrend, stateAfter?.paused ?? false);
6602
8490
  hedgeAdjustments += hedgeResult.adjustments;
6603
8491
  hedgeRealized = hedgeResult.totalRealizedPnl;
6604
8492
  hedgeUnrealized = hedgeResult.unrealizedPnl;
@@ -6930,6 +8818,9 @@ async function runSweep(opts) {
6930
8818
  }
6931
8819
 
6932
8820
  // src/commands/grid.ts
8821
+ var DEFAULT_PAUSE_REASON = "Manually paused by operator";
8822
+ var DEFAULT_FLATTEN_REASON = "Manually flattened by operator";
8823
+ var CONFIRM_TIMEOUT_SECONDS = 60;
6933
8824
  var DIM5 = chalk14.gray;
6934
8825
  var G5 = chalk14.green;
6935
8826
  var BOLD5 = chalk14.white.bold;
@@ -7022,6 +8913,21 @@ function printSweepSummary(s) {
7022
8913
  console.log(W5(` Saved: ${s.sweepId}/sweep.json + per-run JSONs in ~/.sherwood/grid/sweeps/`));
7023
8914
  console.log();
7024
8915
  }
8916
+ function parseAssetIndices(raw) {
8917
+ const indices = {};
8918
+ for (const pair of raw.split(",")) {
8919
+ const [tok, idx] = pair.split("=");
8920
+ if (!tok || !idx) {
8921
+ throw new Error(`Bad asset-indices pair: '${pair}' (expected token=index)`);
8922
+ }
8923
+ const n = Number(idx.trim());
8924
+ if (!Number.isInteger(n) || n < 0) {
8925
+ throw new Error(`Bad asset-indices value for '${tok}': '${idx}' (must be a non-negative integer)`);
8926
+ }
8927
+ indices[tok.trim()] = n;
8928
+ }
8929
+ return indices;
8930
+ }
7025
8931
  function parseTokenSplit(raw, tokens) {
7026
8932
  const split = {};
7027
8933
  for (const pair of raw.split(",")) {
@@ -7059,12 +8965,7 @@ function registerGridCommand(program2) {
7059
8965
  if (!opts.assetIndices) {
7060
8966
  throw new Error("--asset-indices required when --live (mainnet: --asset-indices bitcoin=0,ethereum=1,solana=5)");
7061
8967
  }
7062
- assetIndices = {};
7063
- for (const pair of opts.assetIndices.split(",")) {
7064
- const [tok, idx] = pair.split("=");
7065
- if (!tok || !idx) throw new Error(`Bad asset-indices pair: ${pair}`);
7066
- assetIndices[tok.trim()] = Number(idx);
7067
- }
8968
+ assetIndices = parseAssetIndices(opts.assetIndices);
7068
8969
  }
7069
8970
  const strategyAddress = opts.strategy;
7070
8971
  if (strategyAddress && !live) {
@@ -7227,7 +9128,7 @@ function registerGridCommand(program2) {
7227
9128
  });
7228
9129
  printSweepSummary(result);
7229
9130
  });
7230
- grid.command("pause").description("Manually pause the live grid (sets state.paused=true)").option("--reason <text>", "Reason for the pause (shown in status output)", "Manually paused by operator").option("--state-dir <path>", "Read/write state from a custom directory (defaults to ~/.sherwood/grid)").action(async (opts) => {
9131
+ grid.command("pause").description("Manually pause the live grid (sets state.paused=true)").option("--reason <text>", "Reason for the pause (shown in status output)", DEFAULT_PAUSE_REASON).option("--state-dir <path>", "Read/write state from a custom directory (defaults to ~/.sherwood/grid)").action(async (opts) => {
7231
9132
  const portfolio = new GridPortfolio(opts.stateDir);
7232
9133
  const state = await portfolio.load();
7233
9134
  if (!state) {
@@ -7273,6 +9174,116 @@ function registerGridCommand(program2) {
7273
9174
  console.log(G5(` Grid resumed. (was paused: ${wasReason || "<unknown>"})`));
7274
9175
  console.log();
7275
9176
  });
9177
+ grid.command("flatten").description("Cancel all open grid orders on-chain, zero the hedge ledger, and pause the grid").option("--state-dir <path>", "Read/write state from a custom directory (defaults to ~/.sherwood/grid)").option("--strategy <address>", "Strategy contract address \u2014 required to cancel on-chain CLOIDs. Omit for hedge-only flatten.").option("--asset-indices <pairs>", "comma-separated token=index pairs (required with --strategy, e.g. bitcoin=3,ethereum=4,solana=5)").option("--reason <text>", "Reason recorded on the paused grid state", DEFAULT_FLATTEN_REASON).option("--dry-run", "Print intended actions and exit without touching state or chain").option("--yes", "Skip the confirmation prompt").action(async (opts) => {
9178
+ const stateDir = opts.stateDir;
9179
+ const portfolio = new GridPortfolio(stateDir);
9180
+ const state = await portfolio.load();
9181
+ if (!state) {
9182
+ console.log(DIM5("\n No grid portfolio found. Nothing to flatten.\n"));
9183
+ return;
9184
+ }
9185
+ const openFills = state.grids.flatMap((g) => g.openFills.filter((f) => !f.closed));
9186
+ const hedgeMgr = new GridHedgeManager(stateDir);
9187
+ const hedgeState = await hedgeMgr.load();
9188
+ const openHedges = hedgeState.positions.filter((p) => p.quantity > 0);
9189
+ let strategyAddress;
9190
+ let assetIndices;
9191
+ if (opts.strategy) {
9192
+ strategyAddress = opts.strategy;
9193
+ if (!opts.assetIndices) {
9194
+ throw new Error("--asset-indices required with --strategy (e.g. --asset-indices bitcoin=3,ethereum=4,solana=5)");
9195
+ }
9196
+ assetIndices = parseAssetIndices(opts.assetIndices);
9197
+ }
9198
+ console.log();
9199
+ console.log(BOLD5(" Flatten plan"));
9200
+ SEP5();
9201
+ console.log(W5(` State dir: ${stateDir ?? "~/.sherwood/grid (default)"}`));
9202
+ console.log(W5(` On-chain: ${strategyAddress ? strategyAddress : DIM5("(skipped \u2014 no --strategy)")}`));
9203
+ console.log(W5(` Open fills: ${openFills.length} (informational \u2014 on-chain CLOIDs cancelled per-token)`));
9204
+ console.log(W5(` Hedge legs: ${openHedges.length}${openHedges.length > 0 ? " (" + openHedges.map((h) => h.token).join(", ") + ")" : ""}`));
9205
+ console.log(W5(` Will pause: yes \u2014 reason: "${opts.reason}"`));
9206
+ SEP5();
9207
+ if (opts.dryRun) {
9208
+ console.log(chalk14.yellow(" --dry-run set \u2014 no state or chain mutation."));
9209
+ console.log();
9210
+ return;
9211
+ }
9212
+ if (!opts.yes) {
9213
+ if (!process.stdin.isTTY) {
9214
+ throw new Error("Refusing to flatten without --yes in a non-interactive context. Pass --yes to confirm.");
9215
+ }
9216
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
9217
+ const ac = new AbortController();
9218
+ const timer = setTimeout(() => ac.abort(), CONFIRM_TIMEOUT_SECONDS * 1e3);
9219
+ let answer;
9220
+ try {
9221
+ answer = (await rl.question(chalk14.yellow(" Proceed with flatten? [y/N] "), { signal: ac.signal })).trim().toLowerCase();
9222
+ } catch (e) {
9223
+ if (e.name === "AbortError") {
9224
+ console.log(DIM5(`
9225
+ Timed out after ${CONFIRM_TIMEOUT_SECONDS}s. Aborted.
9226
+ `));
9227
+ return;
9228
+ }
9229
+ throw e;
9230
+ } finally {
9231
+ clearTimeout(timer);
9232
+ rl.close();
9233
+ }
9234
+ if (answer !== "y" && answer !== "yes") {
9235
+ console.log(DIM5(" Aborted.\n"));
9236
+ return;
9237
+ }
9238
+ }
9239
+ if (strategyAddress && assetIndices) {
9240
+ const executor = new OnchainGridExecutor({ strategyAddress, assetIndices, stateDir });
9241
+ await executor.load();
9242
+ const res = await executor.flattenAll();
9243
+ if (res.cancelled.length > 0) {
9244
+ console.log(G5(` Cancelled CLOIDs for: ${res.cancelled.join(", ")}`));
9245
+ for (const tx of res.txs) console.log(DIM5(` tx ${tx}`));
9246
+ }
9247
+ if (res.skipped.length > 0) {
9248
+ console.log(DIM5(` Skipped (no tracked CLOIDs): ${res.skipped.join(", ")}`));
9249
+ }
9250
+ if (res.errors.length > 0) {
9251
+ for (const err of res.errors) console.log(chalk14.red(` Cancel error: ${err}`));
9252
+ }
9253
+ }
9254
+ if (openHedges.length > 0) {
9255
+ const hl = new HyperliquidProvider();
9256
+ const prices = {};
9257
+ for (const pos of openHedges) {
9258
+ const data = await hl.getHyperliquidData(pos.token);
9259
+ if (data?.markPrice && data.markPrice > 0) prices[pos.token] = data.markPrice;
9260
+ }
9261
+ const hres = await hedgeMgr.flatten(prices);
9262
+ if (hres.closed.length > 0) {
9263
+ for (const c of hres.closed) {
9264
+ const sign = c.realizedPnl >= 0 ? "+" : "-";
9265
+ console.log(G5(` Hedge closed ${c.token}: qty ${c.quantity.toFixed(6)} \u2192 0 (${sign}$${Math.abs(c.realizedPnl).toFixed(2)} realized)`));
9266
+ }
9267
+ }
9268
+ if (hres.skipped.length > 0) {
9269
+ console.log(chalk14.yellow(` Hedge skipped (no mark price): ${hres.skipped.join(", ")}`));
9270
+ }
9271
+ console.log(W5(` Total hedge realized PnL: $${hres.totalRealizedPnl.toFixed(2)}`));
9272
+ } else {
9273
+ console.log(DIM5(" No active hedge legs \u2014 ledger already flat."));
9274
+ }
9275
+ if (!state.paused) {
9276
+ state.paused = true;
9277
+ state.pauseReason = opts.reason;
9278
+ await portfolio.save(state);
9279
+ console.log(chalk14.yellow(` Grid paused. Reason: ${state.pauseReason}`));
9280
+ } else {
9281
+ console.log(DIM5(` Grid was already paused: ${state.pauseReason}`));
9282
+ }
9283
+ console.log();
9284
+ console.log(G5(" Flatten complete. Stop the loop process / systemd unit if still running."));
9285
+ console.log();
9286
+ });
7276
9287
  }
7277
9288
 
7278
9289
  // src/index.ts
@@ -7281,7 +9292,7 @@ try {
7281
9292
  } catch {
7282
9293
  }
7283
9294
  var require2 = createRequire(import.meta.url);
7284
- var { version: CLI_VERSION } = require2("../package.json");
9295
+ var CLI_VERSION = "0.64.1";
7285
9296
  async function loadXmtp() {
7286
9297
  return import("./xmtp-MR6OJ76L.js");
7287
9298
  }
@@ -7295,7 +9306,7 @@ var BOLD6 = chalk15.white.bold;
7295
9306
  var LABEL5 = chalk15.green.bold;
7296
9307
  var SEP6 = () => console.log(DIM6("\u2500".repeat(60)));
7297
9308
  function validateAddress(value, name) {
7298
- if (!isAddress6(value)) {
9309
+ if (!isAddress7(value)) {
7299
9310
  console.error(chalk15.red(`Invalid ${name} address: ${value}`));
7300
9311
  process.exit(1);
7301
9312
  }
@@ -7309,8 +9320,13 @@ function resolveVault(opts) {
7309
9320
  var program = new Command();
7310
9321
  program.name("sherwood").description("CLI for agent-managed investment syndicates").version(CLI_VERSION).addOption(
7311
9322
  new Option("--chain <network>", "Target network").choices(VALID_NETWORKS).default("base")
7312
- ).option("--testnet", "Alias for --chain base-sepolia (deprecated)", false).hook("preAction", (thisCommand) => {
9323
+ ).option("--testnet", "Alias for --chain base-sepolia (deprecated)", false).option(
9324
+ "--calldata-only",
9325
+ "Print unsigned EIP-5792 calldata as JSON instead of signing/sending (for external signers \u2014 no private key required)",
9326
+ false
9327
+ ).hook("preAction", (thisCommand) => {
7313
9328
  const opts = thisCommand.optsWithGlobals();
9329
+ setCalldataOnly(Boolean(opts.calldataOnly));
7314
9330
  let network = opts.chain;
7315
9331
  if (opts.testnet) {
7316
9332
  process.env.ENABLE_TESTNET = "true";
@@ -7333,8 +9349,10 @@ syndicate.command("create").description("Create a new syndicate via the factory
7333
9349
  console.log();
7334
9350
  console.log(LABEL5(" \u25C6 Create Syndicate"));
7335
9351
  SEP6();
7336
- const wallet = getAccount();
7337
- console.log(DIM6(` Wallet: ${wallet.address}`));
9352
+ if (!isCalldataOnly()) {
9353
+ const wallet = getAccount();
9354
+ console.log(DIM6(` Wallet: ${wallet.address}`));
9355
+ }
7338
9356
  console.log(DIM6(` Network: ${getChain().name}`));
7339
9357
  SEP6();
7340
9358
  const savedAgentId = getAgentId();
@@ -7434,6 +9452,23 @@ syndicate.command("create").description("Create a new syndicate via the factory
7434
9452
  metadataURI = `data:application/json;base64,${Buffer.from(json).toString("base64")}`;
7435
9453
  }
7436
9454
  }
9455
+ if (isCalldataOnly()) {
9456
+ emitCalldata(
9457
+ encodeCreateSyndicate(
9458
+ {
9459
+ creatorAgentId: agentIdStr,
9460
+ metadataURI,
9461
+ asset,
9462
+ name,
9463
+ symbol,
9464
+ openDeposits,
9465
+ subdomain
9466
+ },
9467
+ getChain().id
9468
+ )
9469
+ );
9470
+ return;
9471
+ }
7437
9472
  const existingId = await subdomainExists(subdomain);
7438
9473
  if (existingId !== null) {
7439
9474
  const existingInfo = await getSyndicate(existingId);
@@ -7766,9 +9801,18 @@ syndicate.command("update-metadata").description("Update syndicate metadata (cre
7766
9801
  });
7767
9802
  syndicate.command("approve-depositor").description("Approve an address to deposit (owner only)").option("--vault <address>", "Vault address (default: from config)").requiredOption("--depositor <address>", "Address to approve").action(async (opts) => {
7768
9803
  resolveVault(opts);
9804
+ const depositor = validateAddress(opts.depositor, "depositor");
9805
+ if (isCalldataOnly()) {
9806
+ emitCalldata(
9807
+ encodeApproveDepositor(
9808
+ { vault: getVaultAddress(), depositor },
9809
+ getChain().id
9810
+ )
9811
+ );
9812
+ return;
9813
+ }
7769
9814
  const spinner = ora8("Approving depositor...").start();
7770
9815
  try {
7771
- const depositor = validateAddress(opts.depositor, "depositor");
7772
9816
  const hash = await approveDepositor(depositor);
7773
9817
  spinner.succeed(`Depositor approved: ${hash}`);
7774
9818
  console.log(chalk15.dim(` ${getExplorerUrl(hash)}`));
@@ -7793,6 +9837,29 @@ syndicate.command("remove-depositor").description("Remove an address from the de
7793
9837
  }
7794
9838
  });
7795
9839
  syndicate.command("add").description("Register an agent on a syndicate vault (creator only)").option("--vault <address>", "Vault address (default: from config)").option("--agent-id <id>", "Agent's ERC-8004 identity token ID (resolved from wallet if omitted)").requiredOption("--wallet <address>", "Agent wallet address").action(async (opts) => {
9840
+ if (isCalldataOnly()) {
9841
+ resolveVault(opts);
9842
+ const agentWallet = validateAddress(opts.wallet, "wallet");
9843
+ if (!opts.agentId) {
9844
+ console.error(
9845
+ chalk15.red(
9846
+ "--calldata-only requires --agent-id (auto-lookup from wallet is skipped in calldata mode)"
9847
+ )
9848
+ );
9849
+ process.exit(1);
9850
+ }
9851
+ emitCalldata(
9852
+ encodeRegisterAgent(
9853
+ {
9854
+ vault: getVaultAddress(),
9855
+ agentAddress: agentWallet,
9856
+ agentId: BigInt(opts.agentId)
9857
+ },
9858
+ getChain().id
9859
+ )
9860
+ );
9861
+ return;
9862
+ }
7796
9863
  const spinner = ora8("Verifying creator...").start();
7797
9864
  try {
7798
9865
  resolveVault(opts);
@@ -7941,6 +10008,24 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
7941
10008
  }
7942
10009
  process.exit(1);
7943
10010
  }
10011
+ if (isCalldataOnly()) {
10012
+ const referrerAgentId2 = opts.ref ? parseInt(opts.ref, 10) : void 0;
10013
+ spinner.stop();
10014
+ emitCalldata(
10015
+ encodeJoinRequest(
10016
+ {
10017
+ syndicateId: syndicate2.id,
10018
+ agentId: BigInt(agentId),
10019
+ vault: syndicate2.vault,
10020
+ creator: syndicate2.creator,
10021
+ message: opts.message,
10022
+ referrerAgentId: referrerAgentId2
10023
+ },
10024
+ getChain().id
10025
+ )
10026
+ );
10027
+ return;
10028
+ }
7944
10029
  const callerAddress = getAccount().address;
7945
10030
  spinner.text = "Checking membership...";
7946
10031
  setVaultAddress(syndicate2.vault);
@@ -8116,6 +10201,21 @@ syndicate.command("approve").description("Approve an agent join request (registe
8116
10201
  const vaultAddress = getVaultAddress();
8117
10202
  const agentWallet = validateAddress(opts.wallet, "wallet");
8118
10203
  const { creator, subdomain, id: syndicateId } = await resolveVaultSyndicate(vaultAddress);
10204
+ if (isCalldataOnly()) {
10205
+ spinner.stop();
10206
+ emitCalldata(
10207
+ encodeApproveAgent(
10208
+ {
10209
+ syndicateId,
10210
+ agentId: BigInt(opts.agentId),
10211
+ vault: vaultAddress,
10212
+ agentAddress: agentWallet
10213
+ },
10214
+ getChain().id
10215
+ )
10216
+ );
10217
+ return;
10218
+ }
8119
10219
  const callerAddress = getAccount().address.toLowerCase();
8120
10220
  if (creator.toLowerCase() !== callerAddress) {
8121
10221
  spinner.fail("Only the syndicate creator can approve agents");
@@ -8297,10 +10397,57 @@ syndicate.command("set-primary").description("Set the active syndicate for CLI c
8297
10397
  }
8298
10398
  });
8299
10399
  var vaultCmd = program.command("vault");
8300
- vaultCmd.command("deposit").description("Deposit into a vault").option("--vault <address>", "Vault address (default: from config)").requiredOption("--amount <amount>", "Amount to deposit (in asset units)").option("--use-eth", "Auto-wrap ETH \u2192 WETH before depositing (for WETH vaults)").action(async (opts) => {
10400
+ vaultCmd.command("deposit").description("Deposit into a vault").option("--vault <address>", "Vault address (default: from config)").requiredOption("--amount <amount>", "Amount to deposit (in asset units)").option("--use-eth", "Auto-wrap ETH \u2192 WETH before depositing (for WETH vaults)").option("--receiver <address>", "Depositor / share receiver (required with --calldata-only)").action(async (opts) => {
8301
10401
  resolveVault(opts);
8302
10402
  const decimals = await getAssetDecimals();
8303
10403
  const amount = parseUnits7(opts.amount, decimals);
10404
+ if (isCalldataOnly()) {
10405
+ if (!opts.receiver) {
10406
+ console.error(
10407
+ chalk15.red(
10408
+ "--calldata-only requires --receiver <address> (the depositor / share receiver)"
10409
+ )
10410
+ );
10411
+ process.exit(1);
10412
+ }
10413
+ const receiver = validateAddress(opts.receiver, "receiver");
10414
+ const asset = await getAssetAddress();
10415
+ const assetSymbol = await getPublicClient().readContract({
10416
+ address: asset,
10417
+ abi: ERC20_ABI,
10418
+ functionName: "symbol"
10419
+ });
10420
+ let currentWethBalance;
10421
+ if (opts.useEth) {
10422
+ currentWethBalance = await getPublicClient().readContract({
10423
+ address: asset,
10424
+ abi: ERC20_ABI,
10425
+ functionName: "balanceOf",
10426
+ args: [receiver]
10427
+ });
10428
+ }
10429
+ try {
10430
+ emitCalldata(
10431
+ encodeDeposit(
10432
+ {
10433
+ vault: getVaultAddress(),
10434
+ receiver,
10435
+ asset,
10436
+ assetSymbol,
10437
+ assetDecimals: decimals,
10438
+ assets: amount,
10439
+ wrapEth: Boolean(opts.useEth),
10440
+ currentWethBalance
10441
+ },
10442
+ getChain().id
10443
+ )
10444
+ );
10445
+ } catch (err) {
10446
+ console.error(chalk15.red(err instanceof Error ? err.message : String(err)));
10447
+ process.exit(1);
10448
+ }
10449
+ return;
10450
+ }
8304
10451
  try {
8305
10452
  if (!opts.useEth) {
8306
10453
  await preflightDeposit(amount);
@@ -8368,10 +10515,42 @@ vaultCmd.command("balance").description("Show LP share balance and asset value")
8368
10515
  process.exit(1);
8369
10516
  }
8370
10517
  });
8371
- vaultCmd.command("redeem").description("Redeem vault shares for the underlying asset (ERC-4626)").option("--vault <address>", "Vault address (default: from config)").option("--shares <amount>", "Shares to redeem in whole-share units (default: all)").option("--receiver <address>", "Receiver of the underlying asset (default: your wallet)").action(async (opts) => {
10518
+ vaultCmd.command("redeem").description("Redeem vault shares for the underlying asset (ERC-4626)").option("--vault <address>", "Vault address (default: from config)").option("--shares <amount>", "Shares to redeem in whole-share units (default: all)").option("--receiver <address>", "Receiver of the underlying asset (default: your wallet)").option("--owner <address>", "Share owner whose shares burn (required with --calldata-only)").action(async (opts) => {
8372
10519
  resolveVault(opts);
8373
10520
  const assetDecimals = await getAssetDecimals();
8374
10521
  const shareDecimals = assetDecimals * 2;
10522
+ if (isCalldataOnly()) {
10523
+ if (!opts.shares) {
10524
+ console.error(
10525
+ chalk15.red(
10526
+ "--calldata-only requires --shares (auto-'all' needs your on-chain share balance)"
10527
+ )
10528
+ );
10529
+ process.exit(1);
10530
+ }
10531
+ if (!opts.owner) {
10532
+ console.error(
10533
+ chalk15.red(
10534
+ "--calldata-only requires --owner <address> (the share holder whose shares burn)"
10535
+ )
10536
+ );
10537
+ process.exit(1);
10538
+ }
10539
+ const owner = validateAddress(opts.owner, "owner");
10540
+ const receiver = opts.receiver ? validateAddress(opts.receiver, "receiver") : owner;
10541
+ emitCalldata(
10542
+ encodeRedeem(
10543
+ {
10544
+ vault: getVaultAddress(),
10545
+ receiver,
10546
+ owner,
10547
+ shares: parseUnits7(opts.shares, shareDecimals)
10548
+ },
10549
+ getChain().id
10550
+ )
10551
+ );
10552
+ return;
10553
+ }
8375
10554
  let shares;
8376
10555
  try {
8377
10556
  if (opts.shares) {
@@ -8389,7 +10568,7 @@ vaultCmd.command("redeem").description("Redeem vault shares for the underlying a
8389
10568
  `));
8390
10569
  process.exit(1);
8391
10570
  }
8392
- if (opts.receiver && !isAddress6(opts.receiver)) {
10571
+ if (opts.receiver && !isAddress7(opts.receiver)) {
8393
10572
  console.error(chalk15.red(`
8394
10573
  \u2716 Invalid receiver address: ${opts.receiver}
8395
10574
  `));
@@ -8403,7 +10582,7 @@ vaultCmd.command("redeem").description("Redeem vault shares for the underlying a
8403
10582
  `));
8404
10583
  process.exit(1);
8405
10584
  }
8406
- const sharesDisplay = formatUnits5(shares, shareDecimals);
10585
+ const sharesDisplay = formatUnits6(shares, shareDecimals);
8407
10586
  const spinner = ora8(`Redeeming ${sharesDisplay} shares...`).start();
8408
10587
  try {
8409
10588
  const { hash, assetsOut } = await redeem(
@@ -8412,7 +10591,7 @@ vaultCmd.command("redeem").description("Redeem vault shares for the underlying a
8412
10591
  );
8413
10592
  spinner.succeed(`Redeemed: ${hash}`);
8414
10593
  console.log(chalk15.dim(` ${getExplorerUrl(hash)}`));
8415
- console.log(` Assets received: ${formatUnits5(assetsOut, assetDecimals)}`);
10594
+ console.log(` Assets received: ${formatUnits6(assetsOut, assetDecimals)}`);
8416
10595
  } catch (err) {
8417
10596
  spinner.fail("Redeem failed");
8418
10597
  console.error(chalk15.red(formatContractError(err)));