@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 +2642 -463
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
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
|
|
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
|
|
1387
|
-
if (
|
|
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
|
|
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
|
-
|
|
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: "
|
|
3030
|
+
inputs: [{ name: "account", type: "address" }],
|
|
2907
3031
|
outputs: [{ name: "", type: "uint256" }]
|
|
2908
3032
|
},
|
|
2909
3033
|
{
|
|
2910
|
-
name: "
|
|
3034
|
+
name: "owner",
|
|
2911
3035
|
type: "function",
|
|
2912
3036
|
stateMutability: "view",
|
|
2913
|
-
inputs: [
|
|
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
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
}
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
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
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
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
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
}
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
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(
|
|
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}: ${
|
|
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
|
|
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 ${
|
|
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 =
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
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 =
|
|
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 || !
|
|
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
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
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
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
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
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
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
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
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
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
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
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
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
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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 (!
|
|
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:
|
|
4750
|
-
pc.readContract({ address: registry(), abi:
|
|
4751
|
-
pc.readContract({ address: registry(), abi:
|
|
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:
|
|
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(
|
|
4766
|
-
console.log(W4(` Delegated inbound: ${BOLD4(
|
|
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:
|
|
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:
|
|
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 (!
|
|
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:
|
|
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 (!
|
|
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:
|
|
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:
|
|
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:
|
|
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 (!
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
|
7555
|
+
import { encodeAbiParameters as encodeAbiParameters12, parseUnits as parseUnits6 } from "viem";
|
|
5720
7556
|
|
|
5721
7557
|
// src/grid/cloid.ts
|
|
5722
|
-
import { keccak256, encodeAbiParameters as
|
|
7558
|
+
import { keccak256, encodeAbiParameters as encodeAbiParameters11 } from "viem";
|
|
5723
7559
|
function gridCloid(strategy2, assetIndex, isBuy, levelIndex, nonce) {
|
|
5724
7560
|
const hash = keccak256(
|
|
5725
|
-
|
|
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
|
-
/**
|
|
5786
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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)",
|
|
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
|
|
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 (!
|
|
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).
|
|
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
|
-
|
|
7337
|
-
|
|
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 && !
|
|
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 =
|
|
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: ${
|
|
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)));
|