@sherwoodagent/cli 0.19.1 → 0.22.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chat-WKOSUZXO.js → chat-JYAJMP4D.js} +42 -16
- package/dist/chat-JYAJMP4D.js.map +1 -0
- package/dist/{chunk-CTLEZZO7.js → chunk-4Y6Y4SVH.js} +43 -33
- package/dist/chunk-4Y6Y4SVH.js.map +1 -0
- package/dist/chunk-ASJTFFZH.js +485 -0
- package/dist/chunk-ASJTFFZH.js.map +1 -0
- package/dist/{chunk-SZ5QQJGA.js → chunk-B42ZCCZ7.js} +54 -2
- package/dist/chunk-B42ZCCZ7.js.map +1 -0
- package/dist/{chunk-QT7BUKWR.js → chunk-G7JQRLQE.js} +141 -37
- package/dist/chunk-G7JQRLQE.js.map +1 -0
- package/dist/{chunk-LHDWONBI.js → chunk-JR5K5WVR.js} +45 -6
- package/dist/chunk-JR5K5WVR.js.map +1 -0
- package/dist/{chunk-TAAEMX3L.js → chunk-LVPBJ7MI.js} +2 -2
- package/dist/{chunk-XR2ZUO4R.js → chunk-QGZUMIVO.js} +2 -2
- package/dist/{chunk-P4J6FKAP.js → chunk-VO6DCRWV.js} +36 -64
- package/dist/chunk-VO6DCRWV.js.map +1 -0
- package/dist/chunk-W2ZUPR3W.js +64 -0
- package/dist/chunk-W2ZUPR3W.js.map +1 -0
- package/dist/{chunk-YJAMY25R.js → chunk-Y7LS6DEF.js} +26 -39
- package/dist/chunk-Y7LS6DEF.js.map +1 -0
- package/dist/{client-3AAXAX4C.js → client-SCIY6SUP.js} +12 -4
- package/dist/{config-IDAHD7S3.js → config-BGS5MX4D.js} +10 -2
- package/dist/{cron-SKYKVZ6K.js → cron-EOULYO3R.js} +8 -3
- package/dist/cron-EOULYO3R.js.map +1 -0
- package/dist/{eas-JE3NQCXE.js → eas-X5UIEM6D.js} +14 -8
- package/dist/{governor-J3W67NXA.js → governor-ZKRSAHG4.js} +6 -6
- package/dist/index.js +1599 -392
- package/dist/index.js.map +1 -1
- package/dist/{network-DBUZ7GRF.js → network-UTSOP7EF.js} +3 -3
- package/dist/research-SJ6D3L2B.js +14 -0
- package/dist/{research-47YMVW3N.js → research-ZW6XVTGZ.js} +20 -15
- package/dist/research-ZW6XVTGZ.js.map +1 -0
- package/dist/session-XAW5UIXP.js +17 -0
- package/dist/session-XAW5UIXP.js.map +1 -0
- package/dist/{session-Q2WRYXYF.js → session-XZNKLK3I.js} +17 -66
- package/dist/session-XZNKLK3I.js.map +1 -0
- package/dist/{trade-D6VT5MLQ.js → trade-OJM5HNHL.js} +139 -68
- package/dist/trade-OJM5HNHL.js.map +1 -0
- package/dist/xmtp-XKHXATPQ.js +278 -0
- package/dist/xmtp-XKHXATPQ.js.map +1 -0
- package/package.json +7 -2
- package/dist/chat-WKOSUZXO.js.map +0 -1
- package/dist/chunk-CTLEZZO7.js.map +0 -1
- package/dist/chunk-I7CKBBOG.js +0 -129
- package/dist/chunk-I7CKBBOG.js.map +0 -1
- package/dist/chunk-LHDWONBI.js.map +0 -1
- package/dist/chunk-P4J6FKAP.js.map +0 -1
- package/dist/chunk-QT7BUKWR.js.map +0 -1
- package/dist/chunk-SZ5QQJGA.js.map +0 -1
- package/dist/chunk-YJAMY25R.js.map +0 -1
- package/dist/cron-SKYKVZ6K.js.map +0 -1
- package/dist/research-47YMVW3N.js.map +0 -1
- package/dist/research-DZINC4N4.js +0 -14
- package/dist/session-Q2WRYXYF.js.map +0 -1
- package/dist/trade-D6VT5MLQ.js.map +0 -1
- package/dist/xmtp-7DF7UG66.js +0 -367
- package/dist/xmtp-7DF7UG66.js.map +0 -1
- /package/dist/{chunk-TAAEMX3L.js.map → chunk-LVPBJ7MI.js.map} +0 -0
- /package/dist/{chunk-XR2ZUO4R.js.map → chunk-QGZUMIVO.js.map} +0 -0
- /package/dist/{client-3AAXAX4C.js.map → client-SCIY6SUP.js.map} +0 -0
- /package/dist/{config-IDAHD7S3.js.map → config-BGS5MX4D.js.map} +0 -0
- /package/dist/{eas-JE3NQCXE.js.map → eas-X5UIEM6D.js.map} +0 -0
- /package/dist/{governor-J3W67NXA.js.map → governor-ZKRSAHG4.js.map} +0 -0
- /package/dist/{network-DBUZ7GRF.js.map → network-UTSOP7EF.js.map} +0 -0
- /package/dist/{research-DZINC4N4.js.map → research-SJ6D3L2B.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
getQuote,
|
|
10
10
|
listModels,
|
|
11
11
|
provisionApiKey
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-Y7LS6DEF.js";
|
|
13
13
|
import {
|
|
14
14
|
PROPOSAL_STATE,
|
|
15
15
|
PROPOSAL_STATES,
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
setVotingPeriod,
|
|
41
41
|
settleProposal,
|
|
42
42
|
vote
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-VO6DCRWV.js";
|
|
44
44
|
import {
|
|
45
45
|
fetchMetadata,
|
|
46
46
|
uploadMetadata
|
|
@@ -49,26 +49,30 @@ import {
|
|
|
49
49
|
createApproval,
|
|
50
50
|
createJoinRequest,
|
|
51
51
|
getEasScanUrl,
|
|
52
|
+
parseReferrer,
|
|
52
53
|
queryApprovals,
|
|
53
54
|
queryJoinRequests,
|
|
54
|
-
revokeAttestation
|
|
55
|
-
|
|
55
|
+
revokeAttestation,
|
|
56
|
+
stripReferrerPrefix
|
|
57
|
+
} from "./chunk-4Y6Y4SVH.js";
|
|
56
58
|
import {
|
|
57
59
|
approveDepositor,
|
|
58
60
|
deposit,
|
|
61
|
+
depositWithEthWrap,
|
|
59
62
|
executeBatch,
|
|
60
63
|
getAssetDecimals,
|
|
61
64
|
getBalance,
|
|
62
65
|
getVaultAddress,
|
|
63
66
|
getVaultInfo,
|
|
64
67
|
isAgent,
|
|
68
|
+
preflightDeposit,
|
|
65
69
|
registerAgent,
|
|
66
70
|
removeDepositor,
|
|
67
71
|
resolveSyndicate,
|
|
68
72
|
resolveVaultSyndicate,
|
|
69
73
|
setTextRecord,
|
|
70
74
|
setVaultAddress
|
|
71
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-G7JQRLQE.js";
|
|
72
76
|
import {
|
|
73
77
|
AERODROME,
|
|
74
78
|
AGENT_REGISTRY,
|
|
@@ -86,14 +90,15 @@ import {
|
|
|
86
90
|
UNISWAP,
|
|
87
91
|
VENICE,
|
|
88
92
|
VENICE_STAKING_ABI
|
|
89
|
-
} from "./chunk-
|
|
93
|
+
} from "./chunk-JR5K5WVR.js";
|
|
90
94
|
import {
|
|
95
|
+
formatContractError,
|
|
91
96
|
getAccount,
|
|
92
97
|
getPublicClient,
|
|
93
|
-
getWalletClient,
|
|
94
98
|
sendTxWithRetry,
|
|
99
|
+
waitForReceipt,
|
|
95
100
|
writeContractWithRetry
|
|
96
|
-
} from "./chunk-
|
|
101
|
+
} from "./chunk-ASJTFFZH.js";
|
|
97
102
|
import {
|
|
98
103
|
VALID_NETWORKS,
|
|
99
104
|
getChain,
|
|
@@ -102,12 +107,15 @@ import {
|
|
|
102
107
|
getRpcUrl,
|
|
103
108
|
isTestnet,
|
|
104
109
|
setNetwork
|
|
105
|
-
} from "./chunk-
|
|
110
|
+
} from "./chunk-LVPBJ7MI.js";
|
|
106
111
|
import {
|
|
112
|
+
addSyndicate,
|
|
107
113
|
cacheGroupId,
|
|
108
114
|
getAgentId,
|
|
109
115
|
getChainContracts,
|
|
110
116
|
getNotifyTo,
|
|
117
|
+
getPrimarySyndicate,
|
|
118
|
+
getSyndicates,
|
|
111
119
|
getUniswapApiKey,
|
|
112
120
|
getVeniceApiKey,
|
|
113
121
|
loadConfig,
|
|
@@ -115,17 +123,18 @@ import {
|
|
|
115
123
|
setChainContract,
|
|
116
124
|
setConfigRpcUrl,
|
|
117
125
|
setNotifyTo,
|
|
126
|
+
setPrimarySyndicate,
|
|
118
127
|
setPrivateKey,
|
|
119
128
|
setUniswapApiKey,
|
|
120
129
|
setVeniceApiKey
|
|
121
|
-
} from "./chunk-
|
|
130
|
+
} from "./chunk-B42ZCCZ7.js";
|
|
122
131
|
|
|
123
132
|
// src/index.ts
|
|
124
133
|
import { config as loadDotenv } from "dotenv";
|
|
125
134
|
import { createRequire } from "module";
|
|
126
135
|
import { Command, Option } from "commander";
|
|
127
136
|
import { parseUnits as parseUnits5, isAddress as isAddress5 } from "viem";
|
|
128
|
-
import
|
|
137
|
+
import chalk8 from "chalk";
|
|
129
138
|
import ora7 from "ora";
|
|
130
139
|
import { input, confirm, select } from "@inquirer/prompts";
|
|
131
140
|
|
|
@@ -186,7 +195,7 @@ async function cloneTemplate(template) {
|
|
|
186
195
|
data: creationCode,
|
|
187
196
|
value: 0n
|
|
188
197
|
});
|
|
189
|
-
const receipt = await
|
|
198
|
+
const receipt = await waitForReceipt(hash);
|
|
190
199
|
if (!receipt.contractAddress) {
|
|
191
200
|
throw new Error(`Clone deployment failed \u2014 no contract address in receipt (tx: ${hash})`);
|
|
192
201
|
}
|
|
@@ -428,6 +437,52 @@ function buildSettleCalls4(clone) {
|
|
|
428
437
|
];
|
|
429
438
|
}
|
|
430
439
|
|
|
440
|
+
// src/strategies/mamo-yield-template.ts
|
|
441
|
+
import { encodeAbiParameters as encodeAbiParameters5, encodeFunctionData as encodeFunctionData5 } from "viem";
|
|
442
|
+
function buildInitData5(underlying, mamoFactory, minRedeemAmount) {
|
|
443
|
+
return encodeAbiParameters5(
|
|
444
|
+
[
|
|
445
|
+
{ type: "address" },
|
|
446
|
+
{ type: "address" },
|
|
447
|
+
{ type: "uint256" }
|
|
448
|
+
],
|
|
449
|
+
[underlying, mamoFactory, minRedeemAmount]
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
function buildExecuteCalls5(clone, underlying, amount) {
|
|
453
|
+
return [
|
|
454
|
+
{
|
|
455
|
+
target: underlying,
|
|
456
|
+
data: encodeFunctionData5({
|
|
457
|
+
abi: ERC20_ABI,
|
|
458
|
+
functionName: "approve",
|
|
459
|
+
args: [clone, amount]
|
|
460
|
+
}),
|
|
461
|
+
value: 0n
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
target: clone,
|
|
465
|
+
data: encodeFunctionData5({
|
|
466
|
+
abi: BASE_STRATEGY_ABI,
|
|
467
|
+
functionName: "execute"
|
|
468
|
+
}),
|
|
469
|
+
value: 0n
|
|
470
|
+
}
|
|
471
|
+
];
|
|
472
|
+
}
|
|
473
|
+
function buildSettleCalls5(clone) {
|
|
474
|
+
return [
|
|
475
|
+
{
|
|
476
|
+
target: clone,
|
|
477
|
+
data: encodeFunctionData5({
|
|
478
|
+
abi: BASE_STRATEGY_ABI,
|
|
479
|
+
functionName: "settle"
|
|
480
|
+
}),
|
|
481
|
+
value: 0n
|
|
482
|
+
}
|
|
483
|
+
];
|
|
484
|
+
}
|
|
485
|
+
|
|
431
486
|
// src/commands/strategy-template.ts
|
|
432
487
|
var ZERO = "0x0000000000000000000000000000000000000000";
|
|
433
488
|
var TEMPLATES = [
|
|
@@ -454,6 +509,12 @@ var TEMPLATES = [
|
|
|
454
509
|
key: "wsteth-moonwell",
|
|
455
510
|
description: "WETH \u2192 wstETH \u2192 Moonwell \u2014 stack Lido + lending yield",
|
|
456
511
|
addressKey: "WSTETH_MOONWELL"
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
name: "Mamo Yield",
|
|
515
|
+
key: "mamo-yield",
|
|
516
|
+
description: "Deposit into Mamo for optimized yield across Moonwell + Morpho vaults",
|
|
517
|
+
addressKey: "MAMO_YIELD"
|
|
457
518
|
}
|
|
458
519
|
];
|
|
459
520
|
function resolveTemplate(key) {
|
|
@@ -500,6 +561,48 @@ async function buildInitDataForTemplate(templateKey, opts, vault) {
|
|
|
500
561
|
const decimals = assetSymbol.toUpperCase() === "USDC" ? 6 : 18;
|
|
501
562
|
const assetAmount = parseUnits(opts.amount, decimals);
|
|
502
563
|
const agent = opts.agent || getAccount().address;
|
|
564
|
+
const isSingleHop = !!opts.singleHop;
|
|
565
|
+
let minVVV = 0n;
|
|
566
|
+
if (!isDirect) {
|
|
567
|
+
if (opts.minVvv) {
|
|
568
|
+
minVVV = parseUnits(opts.minVvv, 18);
|
|
569
|
+
} else {
|
|
570
|
+
const publicClient = getPublicClient();
|
|
571
|
+
const aeroRouterAbi = [{
|
|
572
|
+
name: "getAmountsOut",
|
|
573
|
+
type: "function",
|
|
574
|
+
stateMutability: "view",
|
|
575
|
+
inputs: [
|
|
576
|
+
{ name: "amountIn", type: "uint256" },
|
|
577
|
+
{ name: "routes", type: "tuple[]", components: [
|
|
578
|
+
{ name: "from", type: "address" },
|
|
579
|
+
{ name: "to", type: "address" },
|
|
580
|
+
{ name: "stable", type: "bool" },
|
|
581
|
+
{ name: "factory", type: "address" }
|
|
582
|
+
] }
|
|
583
|
+
],
|
|
584
|
+
outputs: [{ name: "amounts", type: "uint256[]" }]
|
|
585
|
+
}];
|
|
586
|
+
const routes = isSingleHop ? [{ from: asset, to: vvv, stable: false, factory: AERODROME().FACTORY }] : [
|
|
587
|
+
{ from: asset, to: TOKENS().WETH, stable: false, factory: AERODROME().FACTORY },
|
|
588
|
+
{ from: TOKENS().WETH, to: vvv, stable: false, factory: AERODROME().FACTORY }
|
|
589
|
+
];
|
|
590
|
+
try {
|
|
591
|
+
const amounts = await publicClient.readContract({
|
|
592
|
+
address: AERODROME().ROUTER,
|
|
593
|
+
abi: aeroRouterAbi,
|
|
594
|
+
functionName: "getAmountsOut",
|
|
595
|
+
args: [assetAmount, routes]
|
|
596
|
+
});
|
|
597
|
+
const expectedVVV = amounts[amounts.length - 1];
|
|
598
|
+
minVVV = expectedVVV * 95n / 100n;
|
|
599
|
+
console.log(chalk.dim(` Auto-quoted minVVV: ${(Number(minVVV) / 1e18).toFixed(4)} VVV (5% slippage on ~${(Number(expectedVVV) / 1e18).toFixed(4)} VVV)`));
|
|
600
|
+
} catch {
|
|
601
|
+
console.error(chalk.red("Could not quote USDC\u2192VVV price. Pass --min-vvv <amount> manually."));
|
|
602
|
+
process.exit(1);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
503
606
|
const params = {
|
|
504
607
|
asset,
|
|
505
608
|
weth: isDirect ? ZERO : TOKENS().WETH,
|
|
@@ -509,9 +612,9 @@ async function buildInitDataForTemplate(templateKey, opts, vault) {
|
|
|
509
612
|
aeroFactory: isDirect ? ZERO : AERODROME().FACTORY,
|
|
510
613
|
agent,
|
|
511
614
|
assetAmount,
|
|
512
|
-
minVVV
|
|
615
|
+
minVVV,
|
|
513
616
|
deadlineOffset: 300n,
|
|
514
|
-
singleHop:
|
|
617
|
+
singleHop: isSingleHop
|
|
515
618
|
};
|
|
516
619
|
return {
|
|
517
620
|
initData: buildInitData2(params),
|
|
@@ -586,6 +689,26 @@ async function buildInitDataForTemplate(templateKey, opts, vault) {
|
|
|
586
689
|
assetAmount: supplyAmount
|
|
587
690
|
};
|
|
588
691
|
}
|
|
692
|
+
if (templateKey === "mamo-yield") {
|
|
693
|
+
if (!opts.amount) {
|
|
694
|
+
console.error(chalk.red("--amount is required for mamo-yield template"));
|
|
695
|
+
process.exit(1);
|
|
696
|
+
}
|
|
697
|
+
if (!opts.mamoFactory) {
|
|
698
|
+
console.error(chalk.red("--mamo-factory is required for mamo-yield template"));
|
|
699
|
+
process.exit(1);
|
|
700
|
+
}
|
|
701
|
+
const token = opts.token || "USDC";
|
|
702
|
+
const underlying = resolveToken(token);
|
|
703
|
+
const decimals = token.toUpperCase() === "USDC" ? 6 : 18;
|
|
704
|
+
const amount = parseUnits(opts.amount, decimals);
|
|
705
|
+
const minRedeemAmount = parseUnits(opts.minRedeem || opts.amount, decimals);
|
|
706
|
+
return {
|
|
707
|
+
initData: buildInitData5(underlying, opts.mamoFactory, minRedeemAmount),
|
|
708
|
+
asset: underlying,
|
|
709
|
+
assetAmount: amount
|
|
710
|
+
};
|
|
711
|
+
}
|
|
589
712
|
throw new Error(`No init builder for template: ${templateKey}`);
|
|
590
713
|
}
|
|
591
714
|
function buildCallsForTemplate(templateKey, clone, asset, assetAmount, extraApprovals) {
|
|
@@ -615,6 +738,12 @@ function buildCallsForTemplate(templateKey, clone, asset, assetAmount, extraAppr
|
|
|
615
738
|
settleCalls: buildSettleCalls4(clone)
|
|
616
739
|
};
|
|
617
740
|
}
|
|
741
|
+
if (templateKey === "mamo-yield") {
|
|
742
|
+
return {
|
|
743
|
+
executeCalls: buildExecuteCalls5(clone, asset, assetAmount),
|
|
744
|
+
settleCalls: buildSettleCalls5(clone)
|
|
745
|
+
};
|
|
746
|
+
}
|
|
618
747
|
throw new Error(`No call builder for template: ${templateKey}`);
|
|
619
748
|
}
|
|
620
749
|
function resolveToken(symbolOrAddress) {
|
|
@@ -679,7 +808,7 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
679
808
|
console.log(chalk.dim("Full proposal: sherwood strategy propose <template> --vault <addr> ..."));
|
|
680
809
|
console.log();
|
|
681
810
|
});
|
|
682
|
-
strategy2.command("clone").description("Clone a strategy template and initialize it").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell").requiredOption("--vault <address>", "Vault address").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").action(async (templateKey, opts) => {
|
|
811
|
+
strategy2.command("clone").description("Clone a strategy template and initialize it").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell, mamo-yield").requiredOption("--vault <address>", "Vault address").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").option("--mamo-factory <address>", "Mamo StrategyFactory address (Mamo)").action(async (templateKey, opts) => {
|
|
683
812
|
const vault = opts.vault;
|
|
684
813
|
if (!isAddress(vault)) {
|
|
685
814
|
console.error(chalk.red("Invalid vault address"));
|
|
@@ -697,7 +826,7 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
697
826
|
console.log(chalk.dim(` Tx: ${getExplorerUrl(cloneHash)}`));
|
|
698
827
|
} catch (err) {
|
|
699
828
|
cloneSpinner.fail("Clone failed");
|
|
700
|
-
console.error(chalk.red(
|
|
829
|
+
console.error(chalk.red(formatContractError(err)));
|
|
701
830
|
process.exit(1);
|
|
702
831
|
}
|
|
703
832
|
const initSpinner = ora("Initializing strategy...").start();
|
|
@@ -712,14 +841,14 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
712
841
|
functionName: "initialize",
|
|
713
842
|
args: [vault, account.address, initData]
|
|
714
843
|
});
|
|
715
|
-
const receipt = await
|
|
844
|
+
const receipt = await waitForReceipt(initHash);
|
|
716
845
|
if (receipt.status === "reverted") {
|
|
717
846
|
throw new Error("Initialize transaction reverted on-chain");
|
|
718
847
|
}
|
|
719
848
|
initSpinner.succeed("Initialized");
|
|
720
849
|
} catch (err) {
|
|
721
850
|
initSpinner.fail("Initialize failed");
|
|
722
|
-
console.error(chalk.red(
|
|
851
|
+
console.error(chalk.red(formatContractError(err)));
|
|
723
852
|
process.exit(1);
|
|
724
853
|
}
|
|
725
854
|
console.log();
|
|
@@ -727,7 +856,7 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
727
856
|
console.log(chalk.dim("Use this address in your proposal batch calls."));
|
|
728
857
|
console.log();
|
|
729
858
|
});
|
|
730
|
-
strategy2.command("init").description("Initialize an already-deployed but uninitialized strategy clone").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell").requiredOption("--clone <address>", "Clone address to initialize").requiredOption("--vault <address>", "Vault address").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").action(async (templateKey, opts) => {
|
|
859
|
+
strategy2.command("init").description("Initialize an already-deployed but uninitialized strategy clone").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell, mamo-yield").requiredOption("--clone <address>", "Clone address to initialize").requiredOption("--vault <address>", "Vault address").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").option("--mamo-factory <address>", "Mamo StrategyFactory address (Mamo)").action(async (templateKey, opts) => {
|
|
731
860
|
const clone = opts.clone;
|
|
732
861
|
const vault = opts.vault;
|
|
733
862
|
if (!isAddress(clone)) {
|
|
@@ -761,7 +890,7 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
761
890
|
functionName: "initialize",
|
|
762
891
|
args: [vault, account.address, initData]
|
|
763
892
|
});
|
|
764
|
-
const receipt = await
|
|
893
|
+
const receipt = await waitForReceipt(initHash);
|
|
765
894
|
if (receipt.status === "reverted") {
|
|
766
895
|
throw new Error("Initialize transaction reverted on-chain");
|
|
767
896
|
}
|
|
@@ -769,7 +898,7 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
769
898
|
console.log(chalk.dim(` Tx: ${getExplorerUrl(initHash)}`));
|
|
770
899
|
} catch (err) {
|
|
771
900
|
initSpinner.fail("Initialize failed");
|
|
772
|
-
console.error(chalk.red(
|
|
901
|
+
console.error(chalk.red(formatContractError(err)));
|
|
773
902
|
process.exit(1);
|
|
774
903
|
}
|
|
775
904
|
const verifiedVault = await publicClient.readContract({
|
|
@@ -783,7 +912,7 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
783
912
|
console.log(` Proposer: ${chalk.green(getAccount().address)}`);
|
|
784
913
|
console.log();
|
|
785
914
|
});
|
|
786
|
-
strategy2.command("propose").description("Clone + init + build calls + submit governance proposal (all-in-one)").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell").requiredOption("--vault <address>", "Vault address").option("--write-calls <dir>", "Write execute/settle JSON to directory (skip proposal submission)").option("--name <name>", "Proposal name").option("--description <text>", "Proposal description").option("--performance-fee <bps>", "Agent fee in bps").option("--duration <duration>", "Strategy duration (7d, 24h, etc.)").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").action(async (templateKey, opts) => {
|
|
915
|
+
strategy2.command("propose").description("Clone + init + build calls + submit governance proposal (all-in-one)").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell, mamo-yield").requiredOption("--vault <address>", "Vault address").option("--write-calls <dir>", "Write execute/settle JSON to directory (skip proposal submission)").option("--name <name>", "Proposal name").option("--description <text>", "Proposal description").option("--performance-fee <bps>", "Agent fee in bps").option("--duration <duration>", "Strategy duration (7d, 24h, etc.)").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").option("--mamo-factory <address>", "Mamo StrategyFactory address (Mamo)").action(async (templateKey, opts) => {
|
|
787
916
|
const vault = opts.vault;
|
|
788
917
|
if (!isAddress(vault)) {
|
|
789
918
|
console.error(chalk.red("Invalid vault address"));
|
|
@@ -799,7 +928,7 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
799
928
|
console.log(chalk.dim(` Tx: ${getExplorerUrl(result.hash)}`));
|
|
800
929
|
} catch (err) {
|
|
801
930
|
cloneSpinner.fail("Clone failed");
|
|
802
|
-
console.error(chalk.red(
|
|
931
|
+
console.error(chalk.red(formatContractError(err)));
|
|
803
932
|
process.exit(1);
|
|
804
933
|
}
|
|
805
934
|
const initSpinner = ora("Initializing strategy...").start();
|
|
@@ -820,14 +949,14 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
820
949
|
functionName: "initialize",
|
|
821
950
|
args: [vault, account2.address, built.initData]
|
|
822
951
|
});
|
|
823
|
-
const initReceipt = await
|
|
952
|
+
const initReceipt = await waitForReceipt(initHash);
|
|
824
953
|
if (initReceipt.status === "reverted") {
|
|
825
954
|
throw new Error("Initialize transaction reverted on-chain");
|
|
826
955
|
}
|
|
827
956
|
initSpinner.succeed("Initialized");
|
|
828
957
|
} catch (err) {
|
|
829
958
|
initSpinner.fail("Initialize failed");
|
|
830
|
-
console.error(chalk.red(
|
|
959
|
+
console.error(chalk.red(formatContractError(err)));
|
|
831
960
|
process.exit(1);
|
|
832
961
|
}
|
|
833
962
|
const { executeCalls, settleCalls } = buildCallsForTemplate(
|
|
@@ -874,9 +1003,9 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
874
1003
|
console.error(chalk.red("Missing --name, --performance-fee, or --duration. Use --write-calls to skip proposal submission."));
|
|
875
1004
|
process.exit(1);
|
|
876
1005
|
}
|
|
877
|
-
const { propose: propose2 } = await import("./governor-
|
|
1006
|
+
const { propose: propose2 } = await import("./governor-ZKRSAHG4.js");
|
|
878
1007
|
const { pinJSON } = await import("./ipfs-22YLNQ2C.js");
|
|
879
|
-
const { parseDuration: parseDuration2 } = await import("./governor-
|
|
1008
|
+
const { parseDuration: parseDuration2 } = await import("./governor-ZKRSAHG4.js");
|
|
880
1009
|
const performanceFeeBps = BigInt(opts.performanceFee);
|
|
881
1010
|
if (performanceFeeBps < 0n || performanceFeeBps > 10000n) {
|
|
882
1011
|
console.error(chalk.red("--performance-fee must be 0-10000 (basis points)"));
|
|
@@ -902,7 +1031,7 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
902
1031
|
metaSpinner.succeed(`Metadata pinned: ${metadataURI}`);
|
|
903
1032
|
} catch (err) {
|
|
904
1033
|
metaSpinner.fail("IPFS pin failed");
|
|
905
|
-
console.error(chalk.red(
|
|
1034
|
+
console.error(chalk.red(formatContractError(err)));
|
|
906
1035
|
process.exit(1);
|
|
907
1036
|
}
|
|
908
1037
|
const proposeSpinner = ora("Submitting proposal...").start();
|
|
@@ -916,11 +1045,12 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
916
1045
|
settleCalls
|
|
917
1046
|
);
|
|
918
1047
|
proposeSpinner.succeed(`Proposal #${proposalId} created`);
|
|
1048
|
+
console.log(`Proposal #${proposalId}`);
|
|
919
1049
|
console.log(chalk.dim(` Tx: ${getExplorerUrl(hash)}`));
|
|
920
1050
|
console.log(chalk.dim(` Clone: ${clone}`));
|
|
921
1051
|
} catch (err) {
|
|
922
1052
|
proposeSpinner.fail("Proposal failed");
|
|
923
|
-
console.error(chalk.red(
|
|
1053
|
+
console.error(chalk.red(formatContractError(err)));
|
|
924
1054
|
process.exit(1);
|
|
925
1055
|
}
|
|
926
1056
|
if (templateKey === "venice-inference") {
|
|
@@ -941,9 +1071,8 @@ function getFactoryAddress() {
|
|
|
941
1071
|
return SHERWOOD().FACTORY;
|
|
942
1072
|
}
|
|
943
1073
|
async function createSyndicate(params) {
|
|
944
|
-
const wallet = getWalletClient();
|
|
945
1074
|
const client = getPublicClient();
|
|
946
|
-
const hash = await
|
|
1075
|
+
const hash = await writeContractWithRetry({
|
|
947
1076
|
account: getAccount(),
|
|
948
1077
|
chain: getChain(),
|
|
949
1078
|
address: getFactoryAddress(),
|
|
@@ -961,7 +1090,7 @@ async function createSyndicate(params) {
|
|
|
961
1090
|
}
|
|
962
1091
|
]
|
|
963
1092
|
});
|
|
964
|
-
const receipt = await
|
|
1093
|
+
const receipt = await waitForReceipt(hash);
|
|
965
1094
|
for (const log of receipt.logs) {
|
|
966
1095
|
try {
|
|
967
1096
|
const event = decodeEventLog({
|
|
@@ -988,6 +1117,16 @@ async function createSyndicate(params) {
|
|
|
988
1117
|
vault: info.vault
|
|
989
1118
|
};
|
|
990
1119
|
}
|
|
1120
|
+
async function subdomainExists(subdomain) {
|
|
1121
|
+
const client = getPublicClient();
|
|
1122
|
+
const id = await client.readContract({
|
|
1123
|
+
address: getFactoryAddress(),
|
|
1124
|
+
abi: SYNDICATE_FACTORY_ABI,
|
|
1125
|
+
functionName: "subdomainToSyndicate",
|
|
1126
|
+
args: [subdomain]
|
|
1127
|
+
});
|
|
1128
|
+
return id > 0n ? id : null;
|
|
1129
|
+
}
|
|
991
1130
|
async function getSyndicate(id) {
|
|
992
1131
|
const client = getPublicClient();
|
|
993
1132
|
const result = await client.readContract({
|
|
@@ -1032,9 +1171,7 @@ async function getActiveSyndicates() {
|
|
|
1032
1171
|
}));
|
|
1033
1172
|
}
|
|
1034
1173
|
async function updateMetadata(syndicateId, metadataURI) {
|
|
1035
|
-
const
|
|
1036
|
-
const client = getPublicClient();
|
|
1037
|
-
const hash = await wallet.writeContract({
|
|
1174
|
+
const hash = await writeContractWithRetry({
|
|
1038
1175
|
account: getAccount(),
|
|
1039
1176
|
chain: getChain(),
|
|
1040
1177
|
address: getFactoryAddress(),
|
|
@@ -1042,7 +1179,7 @@ async function updateMetadata(syndicateId, metadataURI) {
|
|
|
1042
1179
|
functionName: "updateMetadata",
|
|
1043
1180
|
args: [syndicateId, metadataURI]
|
|
1044
1181
|
});
|
|
1045
|
-
await
|
|
1182
|
+
await waitForReceipt(hash);
|
|
1046
1183
|
return hash;
|
|
1047
1184
|
}
|
|
1048
1185
|
|
|
@@ -1139,7 +1276,7 @@ function registerVeniceCommands(program2) {
|
|
|
1139
1276
|
checkSpinner.succeed(`sVVV balance: ${formatUnits2(sVvvBalance, 18)}`);
|
|
1140
1277
|
} catch (err) {
|
|
1141
1278
|
checkSpinner.fail("Failed to check sVVV balance");
|
|
1142
|
-
console.error(chalk2.red(
|
|
1279
|
+
console.error(chalk2.red(formatContractError(err)));
|
|
1143
1280
|
process.exit(1);
|
|
1144
1281
|
}
|
|
1145
1282
|
const keySpinner = ora2("Provisioning Venice API key...").start();
|
|
@@ -1149,7 +1286,7 @@ function registerVeniceCommands(program2) {
|
|
|
1149
1286
|
console.log(chalk2.dim(` Key: ${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`));
|
|
1150
1287
|
console.log(chalk2.dim(" Saved to ~/.sherwood/config.json"));
|
|
1151
1288
|
try {
|
|
1152
|
-
const { createVeniceProvisionAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-
|
|
1289
|
+
const { createVeniceProvisionAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-X5UIEM6D.js");
|
|
1153
1290
|
const { uid } = await createVeniceProvisionAttestation(account.address);
|
|
1154
1291
|
if (uid !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
1155
1292
|
console.log(chalk2.dim(` Attested: ${getEasScanUrl2(uid)}`));
|
|
@@ -1158,7 +1295,7 @@ function registerVeniceCommands(program2) {
|
|
|
1158
1295
|
}
|
|
1159
1296
|
} catch (err) {
|
|
1160
1297
|
keySpinner.fail("Failed to provision API key");
|
|
1161
|
-
console.error(chalk2.red(
|
|
1298
|
+
console.error(chalk2.red(formatContractError(err)));
|
|
1162
1299
|
process.exit(1);
|
|
1163
1300
|
}
|
|
1164
1301
|
});
|
|
@@ -1238,7 +1375,7 @@ function registerVeniceCommands(program2) {
|
|
|
1238
1375
|
console.log();
|
|
1239
1376
|
} catch (err) {
|
|
1240
1377
|
spinner.fail("Failed to load status");
|
|
1241
|
-
console.error(chalk2.red(
|
|
1378
|
+
console.error(chalk2.red(formatContractError(err)));
|
|
1242
1379
|
process.exit(1);
|
|
1243
1380
|
}
|
|
1244
1381
|
});
|
|
@@ -1254,11 +1391,11 @@ function registerVeniceCommands(program2) {
|
|
|
1254
1391
|
console.log();
|
|
1255
1392
|
} catch (err) {
|
|
1256
1393
|
spinner.fail("Failed to list models");
|
|
1257
|
-
console.error(chalk2.red(
|
|
1394
|
+
console.error(chalk2.red(formatContractError(err)));
|
|
1258
1395
|
process.exit(1);
|
|
1259
1396
|
}
|
|
1260
1397
|
});
|
|
1261
|
-
venice.command("infer").description("Run private inference via Venice chat completions").requiredOption("--prompt <text>", "User prompt").requiredOption("--model <id>", "Venice model ID (use 'venice models' to list)").option("--system <text>", "System prompt").option("--data <path>", "Path to data file \u2014 contents prepended to prompt as context").option("--web-search", "Enable Venice web search", false).option("--no-thinking", "Disable chain-of-thought reasoning").option("--temperature <n>", "Sampling temperature (0-2)").option("--max-tokens <n>", "Maximum completion tokens").option("--json", "Output raw JSON response", false).action(async (opts) => {
|
|
1398
|
+
venice.command("infer").description("Run private inference via Venice chat completions").requiredOption("--prompt <text>", "User prompt").requiredOption("--model <id>", "Venice model ID (use 'venice models' to list)").option("--system <text>", "System prompt").option("--data <path>", "Path to data file \u2014 contents prepended to prompt as context").option("--web-search", "Enable Venice web search", false).option("--no-thinking", "Disable chain-of-thought reasoning").option("--temperature <n>", "Sampling temperature (0-2)").option("--max-tokens <n>", "Maximum completion tokens").option("--json", "Output raw JSON response", false).option("--vault <address>", "Vault address \u2014 attestation recipient (defaults to config vault)").action(async (opts) => {
|
|
1262
1399
|
const messages = [];
|
|
1263
1400
|
if (opts.system) {
|
|
1264
1401
|
messages.push({ role: "system", content: opts.system });
|
|
@@ -1275,7 +1412,7 @@ ${data}
|
|
|
1275
1412
|
${opts.prompt}`;
|
|
1276
1413
|
} catch (err) {
|
|
1277
1414
|
console.error(chalk2.red(`Failed to read data file: ${opts.data}`));
|
|
1278
|
-
console.error(chalk2.red(
|
|
1415
|
+
console.error(chalk2.red(formatContractError(err)));
|
|
1279
1416
|
process.exit(1);
|
|
1280
1417
|
}
|
|
1281
1418
|
}
|
|
@@ -1300,14 +1437,18 @@ ${opts.prompt}`;
|
|
|
1300
1437
|
console.log(chalk2.dim(`Model: ${result.model} | Tokens: ${result.usage.promptTokens} in, ${result.usage.completionTokens} out, ${result.usage.totalTokens} total`));
|
|
1301
1438
|
}
|
|
1302
1439
|
try {
|
|
1303
|
-
const { createVeniceInferenceAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-
|
|
1304
|
-
const { keccak256, toHex } = await import("viem");
|
|
1440
|
+
const { createVeniceInferenceAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-X5UIEM6D.js");
|
|
1441
|
+
const { keccak256, toHex, isAddress: isAddr } = await import("viem");
|
|
1442
|
+
const { getChainContracts: getChainContracts2 } = await import("./config-BGS5MX4D.js");
|
|
1443
|
+
const { getChain: getActiveChain } = await import("./network-UTSOP7EF.js");
|
|
1444
|
+
const vaultRecipient = opts.vault && isAddr(opts.vault) ? opts.vault : getChainContracts2(getActiveChain().id).vault;
|
|
1305
1445
|
const promptHash = keccak256(toHex(userContent)).slice(0, 18);
|
|
1306
1446
|
const { uid } = await createVeniceInferenceAttestation(
|
|
1307
1447
|
result.model,
|
|
1308
1448
|
result.usage.promptTokens,
|
|
1309
1449
|
result.usage.completionTokens,
|
|
1310
|
-
promptHash
|
|
1450
|
+
promptHash,
|
|
1451
|
+
vaultRecipient
|
|
1311
1452
|
);
|
|
1312
1453
|
if (uid !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
1313
1454
|
console.log(chalk2.dim(`Attested: ${getEasScanUrl2(uid)}`));
|
|
@@ -1316,7 +1457,7 @@ ${opts.prompt}`;
|
|
|
1316
1457
|
}
|
|
1317
1458
|
} catch (err) {
|
|
1318
1459
|
spinner.fail("Inference failed");
|
|
1319
|
-
console.error(chalk2.red(
|
|
1460
|
+
console.error(chalk2.red(formatContractError(err)));
|
|
1320
1461
|
process.exit(1);
|
|
1321
1462
|
}
|
|
1322
1463
|
});
|
|
@@ -1328,7 +1469,7 @@ import chalk3 from "chalk";
|
|
|
1328
1469
|
import ora3 from "ora";
|
|
1329
1470
|
|
|
1330
1471
|
// src/strategies/allowance-disburse.ts
|
|
1331
|
-
import { encodeFunctionData as
|
|
1472
|
+
import { encodeFunctionData as encodeFunctionData6, parseUnits as parseUnits3 } from "viem";
|
|
1332
1473
|
function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDecimals, minUsdc, swapPath) {
|
|
1333
1474
|
const assetAmount = parseUnits3(config.amount, assetDecimals);
|
|
1334
1475
|
const isUsdc = assetAddress.toLowerCase() === TOKENS().USDC.toLowerCase();
|
|
@@ -1337,7 +1478,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
|
|
|
1337
1478
|
if (!isUsdc) {
|
|
1338
1479
|
calls.push({
|
|
1339
1480
|
target: assetAddress,
|
|
1340
|
-
data:
|
|
1481
|
+
data: encodeFunctionData6({
|
|
1341
1482
|
abi: ERC20_ABI,
|
|
1342
1483
|
functionName: "approve",
|
|
1343
1484
|
args: [UNISWAP().SWAP_ROUTER, assetAmount]
|
|
@@ -1347,7 +1488,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
|
|
|
1347
1488
|
if (isWeth) {
|
|
1348
1489
|
calls.push({
|
|
1349
1490
|
target: UNISWAP().SWAP_ROUTER,
|
|
1350
|
-
data:
|
|
1491
|
+
data: encodeFunctionData6({
|
|
1351
1492
|
abi: SWAP_ROUTER_EXACT_INPUT_SINGLE_ABI,
|
|
1352
1493
|
functionName: "exactInputSingle",
|
|
1353
1494
|
args: [
|
|
@@ -1367,7 +1508,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
|
|
|
1367
1508
|
} else {
|
|
1368
1509
|
calls.push({
|
|
1369
1510
|
target: UNISWAP().SWAP_ROUTER,
|
|
1370
|
-
data:
|
|
1511
|
+
data: encodeFunctionData6({
|
|
1371
1512
|
abi: SWAP_ROUTER_ABI,
|
|
1372
1513
|
functionName: "exactInput",
|
|
1373
1514
|
args: [
|
|
@@ -1387,7 +1528,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
|
|
|
1387
1528
|
for (const agent of agents) {
|
|
1388
1529
|
calls.push({
|
|
1389
1530
|
target: TOKENS().USDC,
|
|
1390
|
-
data:
|
|
1531
|
+
data: encodeFunctionData6({
|
|
1391
1532
|
abi: ERC20_ABI,
|
|
1392
1533
|
functionName: "transfer",
|
|
1393
1534
|
args: [agent, perAgent]
|
|
@@ -1436,7 +1577,7 @@ function registerAllowanceCommands(program2) {
|
|
|
1436
1577
|
spinner.stop();
|
|
1437
1578
|
} catch (err) {
|
|
1438
1579
|
spinner.fail("Failed to read vault state");
|
|
1439
|
-
console.error(chalk3.red(
|
|
1580
|
+
console.error(chalk3.red(formatContractError(err)));
|
|
1440
1581
|
process.exit(1);
|
|
1441
1582
|
}
|
|
1442
1583
|
if (agents.length === 0) {
|
|
@@ -1501,7 +1642,7 @@ function registerAllowanceCommands(program2) {
|
|
|
1501
1642
|
);
|
|
1502
1643
|
} catch (err) {
|
|
1503
1644
|
quoteSpinner.fail("Failed to fetch quote");
|
|
1504
|
-
console.error(chalk3.red(
|
|
1645
|
+
console.error(chalk3.red(formatContractError(err)));
|
|
1505
1646
|
process.exit(1);
|
|
1506
1647
|
}
|
|
1507
1648
|
}
|
|
@@ -1532,7 +1673,7 @@ function registerAllowanceCommands(program2) {
|
|
|
1532
1673
|
console.log(chalk3.dim(` ${getExplorerUrl(txHash)}`));
|
|
1533
1674
|
} catch (err) {
|
|
1534
1675
|
execSpinner.fail("Execution failed");
|
|
1535
|
-
console.error(chalk3.red(
|
|
1676
|
+
console.error(chalk3.red(formatContractError(err)));
|
|
1536
1677
|
process.exit(1);
|
|
1537
1678
|
}
|
|
1538
1679
|
});
|
|
@@ -1586,7 +1727,7 @@ function registerAllowanceCommands(program2) {
|
|
|
1586
1727
|
console.log();
|
|
1587
1728
|
} catch (err) {
|
|
1588
1729
|
spinner.fail("Failed to load status");
|
|
1589
|
-
console.error(chalk3.red(
|
|
1730
|
+
console.error(chalk3.red(formatContractError(err)));
|
|
1590
1731
|
process.exit(1);
|
|
1591
1732
|
}
|
|
1592
1733
|
});
|
|
@@ -1663,7 +1804,7 @@ function registerIdentityCommands(program2) {
|
|
|
1663
1804
|
console.log(chalk4.dim(` sherwood syndicate create --agent-id ${tokenId} --subdomain <name> --name <name>`));
|
|
1664
1805
|
} catch (err) {
|
|
1665
1806
|
spinner.fail("Failed to register identity");
|
|
1666
|
-
console.error(chalk4.red(
|
|
1807
|
+
console.error(chalk4.red(formatContractError(err)));
|
|
1667
1808
|
process.exit(1);
|
|
1668
1809
|
}
|
|
1669
1810
|
});
|
|
@@ -1690,7 +1831,7 @@ function registerIdentityCommands(program2) {
|
|
|
1690
1831
|
console.log(chalk4.dim(` Saved to ~/.sherwood/config.json`));
|
|
1691
1832
|
} catch (err) {
|
|
1692
1833
|
spinner.fail("Failed to load identity");
|
|
1693
|
-
console.error(chalk4.red(
|
|
1834
|
+
console.error(chalk4.red(formatContractError(err)));
|
|
1694
1835
|
process.exit(1);
|
|
1695
1836
|
}
|
|
1696
1837
|
});
|
|
@@ -1743,7 +1884,7 @@ function registerIdentityCommands(program2) {
|
|
|
1743
1884
|
console.log();
|
|
1744
1885
|
} catch (err) {
|
|
1745
1886
|
spinner.fail("Failed to check identity");
|
|
1746
|
-
console.error(chalk4.red(
|
|
1887
|
+
console.error(chalk4.red(formatContractError(err)));
|
|
1747
1888
|
process.exit(1);
|
|
1748
1889
|
}
|
|
1749
1890
|
});
|
|
@@ -1751,7 +1892,7 @@ function registerIdentityCommands(program2) {
|
|
|
1751
1892
|
|
|
1752
1893
|
// src/commands/proposal.ts
|
|
1753
1894
|
import { isAddress as isAddress4 } from "viem";
|
|
1754
|
-
import
|
|
1895
|
+
import chalk6 from "chalk";
|
|
1755
1896
|
import ora5 from "ora";
|
|
1756
1897
|
import { readFileSync as readFileSync2 } from "fs";
|
|
1757
1898
|
|
|
@@ -1785,13 +1926,737 @@ function parseBigIntArg(value, name) {
|
|
|
1785
1926
|
return BigInt(value);
|
|
1786
1927
|
}
|
|
1787
1928
|
|
|
1788
|
-
// src/
|
|
1929
|
+
// src/lib/simulate.ts
|
|
1930
|
+
import { decodeFunctionData, encodeFunctionData as encodeFunctionData7 } from "viem";
|
|
1931
|
+
import chalk5 from "chalk";
|
|
1789
1932
|
var G = chalk5.green;
|
|
1790
1933
|
var W = chalk5.white;
|
|
1791
1934
|
var DIM = chalk5.gray;
|
|
1792
1935
|
var BOLD = chalk5.white.bold;
|
|
1793
1936
|
var LABEL = chalk5.green.bold;
|
|
1794
1937
|
var SEP = () => console.log(DIM("\u2500".repeat(60)));
|
|
1938
|
+
var MOONWELL_CTOKEN_ABI = [
|
|
1939
|
+
{
|
|
1940
|
+
name: "mint",
|
|
1941
|
+
type: "function",
|
|
1942
|
+
stateMutability: "nonpayable",
|
|
1943
|
+
inputs: [{ name: "mintAmount", type: "uint256" }],
|
|
1944
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
1945
|
+
},
|
|
1946
|
+
{
|
|
1947
|
+
name: "redeem",
|
|
1948
|
+
type: "function",
|
|
1949
|
+
stateMutability: "nonpayable",
|
|
1950
|
+
inputs: [{ name: "redeemTokens", type: "uint256" }],
|
|
1951
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
1952
|
+
},
|
|
1953
|
+
{
|
|
1954
|
+
name: "redeemUnderlying",
|
|
1955
|
+
type: "function",
|
|
1956
|
+
stateMutability: "nonpayable",
|
|
1957
|
+
inputs: [{ name: "redeemAmount", type: "uint256" }],
|
|
1958
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
1959
|
+
},
|
|
1960
|
+
{
|
|
1961
|
+
name: "borrow",
|
|
1962
|
+
type: "function",
|
|
1963
|
+
stateMutability: "nonpayable",
|
|
1964
|
+
inputs: [{ name: "borrowAmount", type: "uint256" }],
|
|
1965
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
1966
|
+
},
|
|
1967
|
+
{
|
|
1968
|
+
name: "repayBorrow",
|
|
1969
|
+
type: "function",
|
|
1970
|
+
stateMutability: "nonpayable",
|
|
1971
|
+
inputs: [{ name: "repayAmount", type: "uint256" }],
|
|
1972
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
1973
|
+
}
|
|
1974
|
+
];
|
|
1975
|
+
var MOONWELL_COMPTROLLER_ABI = [
|
|
1976
|
+
{
|
|
1977
|
+
name: "enterMarkets",
|
|
1978
|
+
type: "function",
|
|
1979
|
+
stateMutability: "nonpayable",
|
|
1980
|
+
inputs: [{ name: "mTokens", type: "address[]" }],
|
|
1981
|
+
outputs: [{ name: "", type: "uint256[]" }]
|
|
1982
|
+
},
|
|
1983
|
+
{
|
|
1984
|
+
name: "exitMarket",
|
|
1985
|
+
type: "function",
|
|
1986
|
+
stateMutability: "nonpayable",
|
|
1987
|
+
inputs: [{ name: "mToken", type: "address" }],
|
|
1988
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
1989
|
+
}
|
|
1990
|
+
];
|
|
1991
|
+
var SWAP_ROUTER_SINGLE_ABI = [
|
|
1992
|
+
{
|
|
1993
|
+
name: "exactInputSingle",
|
|
1994
|
+
type: "function",
|
|
1995
|
+
stateMutability: "nonpayable",
|
|
1996
|
+
inputs: [
|
|
1997
|
+
{
|
|
1998
|
+
name: "params",
|
|
1999
|
+
type: "tuple",
|
|
2000
|
+
components: [
|
|
2001
|
+
{ name: "tokenIn", type: "address" },
|
|
2002
|
+
{ name: "tokenOut", type: "address" },
|
|
2003
|
+
{ name: "fee", type: "uint24" },
|
|
2004
|
+
{ name: "recipient", type: "address" },
|
|
2005
|
+
{ name: "amountIn", type: "uint256" },
|
|
2006
|
+
{ name: "amountOutMinimum", type: "uint256" },
|
|
2007
|
+
{ name: "sqrtPriceLimitX96", type: "uint160" }
|
|
2008
|
+
]
|
|
2009
|
+
}
|
|
2010
|
+
],
|
|
2011
|
+
outputs: [{ name: "amountOut", type: "uint256" }]
|
|
2012
|
+
}
|
|
2013
|
+
];
|
|
2014
|
+
function buildLabelMap() {
|
|
2015
|
+
const map = /* @__PURE__ */ new Map();
|
|
2016
|
+
const add = (addr, label) => {
|
|
2017
|
+
if (addr !== "0x0000000000000000000000000000000000000000") {
|
|
2018
|
+
map.set(addr.toLowerCase(), label);
|
|
2019
|
+
}
|
|
2020
|
+
};
|
|
2021
|
+
try {
|
|
2022
|
+
const tokens = TOKENS();
|
|
2023
|
+
add(tokens.USDC, "USDC");
|
|
2024
|
+
add(tokens.WETH, "WETH");
|
|
2025
|
+
add(tokens.cbETH, "cbETH");
|
|
2026
|
+
add(tokens.wstETH, "wstETH");
|
|
2027
|
+
add(tokens.cbBTC, "cbBTC");
|
|
2028
|
+
add(tokens.DAI, "DAI");
|
|
2029
|
+
add(tokens.AERO, "AERO");
|
|
2030
|
+
const mw = MOONWELL();
|
|
2031
|
+
add(mw.COMPTROLLER, "Moonwell Comptroller");
|
|
2032
|
+
add(mw.mUSDC, "mUSDC");
|
|
2033
|
+
add(mw.mWETH, "mWETH");
|
|
2034
|
+
add(mw.mCbETH, "mCbETH");
|
|
2035
|
+
add(mw.mWstETH, "mWstETH");
|
|
2036
|
+
add(mw.mCbBTC, "mCbBTC");
|
|
2037
|
+
add(mw.mDAI, "mDAI");
|
|
2038
|
+
add(mw.mAERO, "mAERO");
|
|
2039
|
+
const uni = UNISWAP();
|
|
2040
|
+
add(uni.SWAP_ROUTER, "SwapRouter");
|
|
2041
|
+
add(uni.QUOTER_V2, "QuoterV2");
|
|
2042
|
+
const sw = SHERWOOD();
|
|
2043
|
+
add(sw.FACTORY, "SyndicateFactory");
|
|
2044
|
+
add(sw.GOVERNOR, "SyndicateGovernor");
|
|
2045
|
+
const aero = AERODROME();
|
|
2046
|
+
add(aero.ROUTER, "Aerodrome Router");
|
|
2047
|
+
add(aero.FACTORY, "Aerodrome Factory");
|
|
2048
|
+
const venice = VENICE();
|
|
2049
|
+
add(venice.VVV, "VVV");
|
|
2050
|
+
add(venice.STAKING, "sVVV Staking");
|
|
2051
|
+
add(venice.DIEM, "DIEM");
|
|
2052
|
+
const strats = STRATEGY_TEMPLATES();
|
|
2053
|
+
add(strats.MOONWELL_SUPPLY, "MoonwellSupplyStrategy");
|
|
2054
|
+
add(strats.AERODROME_LP, "AerodromeLPStrategy");
|
|
2055
|
+
add(strats.VENICE_INFERENCE, "VeniceInferenceStrategy");
|
|
2056
|
+
add(strats.WSTETH_MOONWELL, "WstETHMoonwellStrategy");
|
|
2057
|
+
} catch {
|
|
2058
|
+
}
|
|
2059
|
+
return map;
|
|
2060
|
+
}
|
|
2061
|
+
function buildDecimalsMap() {
|
|
2062
|
+
const map = /* @__PURE__ */ new Map();
|
|
2063
|
+
try {
|
|
2064
|
+
const tokens = TOKENS();
|
|
2065
|
+
map.set(tokens.USDC.toLowerCase(), { decimals: 6, symbol: "USDC" });
|
|
2066
|
+
map.set(tokens.WETH.toLowerCase(), { decimals: 18, symbol: "WETH" });
|
|
2067
|
+
map.set(tokens.cbETH.toLowerCase(), { decimals: 18, symbol: "cbETH" });
|
|
2068
|
+
map.set(tokens.wstETH.toLowerCase(), { decimals: 18, symbol: "wstETH" });
|
|
2069
|
+
map.set(tokens.cbBTC.toLowerCase(), { decimals: 8, symbol: "cbBTC" });
|
|
2070
|
+
map.set(tokens.DAI.toLowerCase(), { decimals: 18, symbol: "DAI" });
|
|
2071
|
+
map.set(tokens.AERO.toLowerCase(), { decimals: 18, symbol: "AERO" });
|
|
2072
|
+
} catch {
|
|
2073
|
+
}
|
|
2074
|
+
return map;
|
|
2075
|
+
}
|
|
2076
|
+
function buildMTokenToUnderlying() {
|
|
2077
|
+
const map = /* @__PURE__ */ new Map();
|
|
2078
|
+
try {
|
|
2079
|
+
const mw = MOONWELL();
|
|
2080
|
+
const tokens = TOKENS();
|
|
2081
|
+
map.set(mw.mUSDC.toLowerCase(), tokens.USDC.toLowerCase());
|
|
2082
|
+
map.set(mw.mWETH.toLowerCase(), tokens.WETH.toLowerCase());
|
|
2083
|
+
map.set(mw.mCbETH.toLowerCase(), tokens.cbETH.toLowerCase());
|
|
2084
|
+
map.set(mw.mWstETH.toLowerCase(), tokens.wstETH.toLowerCase());
|
|
2085
|
+
map.set(mw.mCbBTC.toLowerCase(), tokens.cbBTC.toLowerCase());
|
|
2086
|
+
map.set(mw.mDAI.toLowerCase(), tokens.DAI.toLowerCase());
|
|
2087
|
+
map.set(mw.mAERO.toLowerCase(), tokens.AERO.toLowerCase());
|
|
2088
|
+
} catch {
|
|
2089
|
+
}
|
|
2090
|
+
return map;
|
|
2091
|
+
}
|
|
2092
|
+
function getAddressLabel(addr) {
|
|
2093
|
+
const labels = buildLabelMap();
|
|
2094
|
+
return labels.get(addr.toLowerCase()) ?? null;
|
|
2095
|
+
}
|
|
2096
|
+
function truncAddr(addr) {
|
|
2097
|
+
return `${addr.slice(0, 6)}...${addr.slice(-4)}`;
|
|
2098
|
+
}
|
|
2099
|
+
function labeledAddr(addr) {
|
|
2100
|
+
const label = buildLabelMap().get(addr.toLowerCase());
|
|
2101
|
+
const short = truncAddr(addr);
|
|
2102
|
+
return label ? `${short} (${label})` : short;
|
|
2103
|
+
}
|
|
2104
|
+
function formatTokenAmount(amount, target) {
|
|
2105
|
+
const decimalsMap = buildDecimalsMap();
|
|
2106
|
+
const tokenInfo = decimalsMap.get(target.toLowerCase());
|
|
2107
|
+
if (tokenInfo) {
|
|
2108
|
+
return formatWithDecimals(amount, tokenInfo.decimals, tokenInfo.symbol);
|
|
2109
|
+
}
|
|
2110
|
+
const mTokenMap = buildMTokenToUnderlying();
|
|
2111
|
+
const underlying = mTokenMap.get(target.toLowerCase());
|
|
2112
|
+
if (underlying) {
|
|
2113
|
+
const underlyingInfo = decimalsMap.get(underlying);
|
|
2114
|
+
if (underlyingInfo) {
|
|
2115
|
+
return formatWithDecimals(amount, underlyingInfo.decimals, underlyingInfo.symbol);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
return amount.toString();
|
|
2119
|
+
}
|
|
2120
|
+
function formatWithDecimals(amount, decimals, symbol) {
|
|
2121
|
+
const num = Number(amount) / 10 ** decimals;
|
|
2122
|
+
const formatted = num.toLocaleString("en-US", {
|
|
2123
|
+
minimumFractionDigits: 2,
|
|
2124
|
+
maximumFractionDigits: decimals <= 8 ? 2 : 4
|
|
2125
|
+
});
|
|
2126
|
+
return `${formatted} ${symbol}`;
|
|
2127
|
+
}
|
|
2128
|
+
function decodeCallData(target, data) {
|
|
2129
|
+
if (data.length < 10) return null;
|
|
2130
|
+
const labels = buildLabelMap();
|
|
2131
|
+
const targetLower = target.toLowerCase();
|
|
2132
|
+
const candidates = [];
|
|
2133
|
+
const mw = safeCall(() => MOONWELL());
|
|
2134
|
+
if (mw) {
|
|
2135
|
+
const isMToken = [mw.mUSDC, mw.mWETH, mw.mCbETH, mw.mWstETH, mw.mCbBTC, mw.mDAI, mw.mAERO].some((a) => a.toLowerCase() === targetLower);
|
|
2136
|
+
if (isMToken) {
|
|
2137
|
+
candidates.push({ abi: MOONWELL_CTOKEN_ABI, context: labels.get(targetLower) || "mToken" });
|
|
2138
|
+
}
|
|
2139
|
+
if (mw.COMPTROLLER.toLowerCase() === targetLower) {
|
|
2140
|
+
candidates.push({ abi: MOONWELL_COMPTROLLER_ABI, context: "Comptroller" });
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
const uni = safeCall(() => UNISWAP());
|
|
2144
|
+
if (uni && uni.SWAP_ROUTER.toLowerCase() === targetLower) {
|
|
2145
|
+
candidates.push({ abi: SWAP_ROUTER_ABI, context: "SwapRouter" });
|
|
2146
|
+
candidates.push({ abi: SWAP_ROUTER_SINGLE_ABI, context: "SwapRouter" });
|
|
2147
|
+
}
|
|
2148
|
+
candidates.push({ abi: ERC20_ABI, context: labels.get(targetLower) || "ERC20" });
|
|
2149
|
+
for (const { abi, context } of candidates) {
|
|
2150
|
+
try {
|
|
2151
|
+
const decoded = decodeFunctionData({
|
|
2152
|
+
abi,
|
|
2153
|
+
data
|
|
2154
|
+
});
|
|
2155
|
+
const args = formatArgs(decoded.args, decoded.functionName, target);
|
|
2156
|
+
return `${decoded.functionName}(${args})`;
|
|
2157
|
+
} catch {
|
|
2158
|
+
continue;
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
return null;
|
|
2162
|
+
}
|
|
2163
|
+
function formatArgs(args, functionName, target) {
|
|
2164
|
+
if (!args || args.length === 0) return "";
|
|
2165
|
+
const parts = [];
|
|
2166
|
+
for (const arg of args) {
|
|
2167
|
+
if (typeof arg === "bigint") {
|
|
2168
|
+
parts.push(formatTokenAmount(arg, target));
|
|
2169
|
+
} else if (typeof arg === "string" && arg.startsWith("0x") && arg.length === 42) {
|
|
2170
|
+
parts.push(labeledAddr(arg));
|
|
2171
|
+
} else if (typeof arg === "object" && arg !== null && !Array.isArray(arg)) {
|
|
2172
|
+
const entries = Object.entries(arg);
|
|
2173
|
+
const structParts = [];
|
|
2174
|
+
for (const [key, val] of entries) {
|
|
2175
|
+
if (typeof val === "bigint") {
|
|
2176
|
+
structParts.push(`${key}: ${formatTokenAmount(val, target)}`);
|
|
2177
|
+
} else if (typeof val === "string" && val.startsWith("0x") && val.length === 42) {
|
|
2178
|
+
structParts.push(`${key}: ${labeledAddr(val)}`);
|
|
2179
|
+
} else if (typeof val === "string" && val.startsWith("0x") && key === "path") {
|
|
2180
|
+
structParts.push(`path: ${decodeUniswapPath(val)}`);
|
|
2181
|
+
} else {
|
|
2182
|
+
structParts.push(`${key}: ${String(val)}`);
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
parts.push(`{${structParts.join(", ")}}`);
|
|
2186
|
+
} else if (Array.isArray(arg)) {
|
|
2187
|
+
const arrParts = arg.map((item) => {
|
|
2188
|
+
if (typeof item === "string" && item.startsWith("0x") && item.length === 42) {
|
|
2189
|
+
return labeledAddr(item);
|
|
2190
|
+
}
|
|
2191
|
+
return String(item);
|
|
2192
|
+
});
|
|
2193
|
+
parts.push(`[${arrParts.join(", ")}]`);
|
|
2194
|
+
} else {
|
|
2195
|
+
parts.push(String(arg));
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
return parts.join(", ");
|
|
2199
|
+
}
|
|
2200
|
+
function decodeUniswapPath(pathHex) {
|
|
2201
|
+
const labels = buildLabelMap();
|
|
2202
|
+
const path = pathHex.startsWith("0x") ? pathHex.slice(2) : pathHex;
|
|
2203
|
+
const tokens = [];
|
|
2204
|
+
let offset = 0;
|
|
2205
|
+
while (offset + 40 <= path.length) {
|
|
2206
|
+
const addr = `0x${path.slice(offset, offset + 40)}`;
|
|
2207
|
+
const label = labels.get(addr.toLowerCase());
|
|
2208
|
+
tokens.push(label || truncAddr(addr));
|
|
2209
|
+
offset += 40;
|
|
2210
|
+
if (offset + 6 <= path.length) {
|
|
2211
|
+
offset += 6;
|
|
2212
|
+
} else {
|
|
2213
|
+
break;
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
return tokens.join(" \u2192 ");
|
|
2217
|
+
}
|
|
2218
|
+
function safeCall(fn) {
|
|
2219
|
+
try {
|
|
2220
|
+
return fn();
|
|
2221
|
+
} catch {
|
|
2222
|
+
return null;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
function decodeCallArgs(target, data) {
|
|
2226
|
+
if (data.length < 10) return null;
|
|
2227
|
+
const targetLower = target.toLowerCase();
|
|
2228
|
+
const candidates = [];
|
|
2229
|
+
const mw = safeCall(() => MOONWELL());
|
|
2230
|
+
if (mw) {
|
|
2231
|
+
const isMToken = [mw.mUSDC, mw.mWETH, mw.mCbETH, mw.mWstETH, mw.mCbBTC, mw.mDAI, mw.mAERO].some((a) => a.toLowerCase() === targetLower);
|
|
2232
|
+
if (isMToken) candidates.push({ abi: MOONWELL_CTOKEN_ABI });
|
|
2233
|
+
if (mw.COMPTROLLER.toLowerCase() === targetLower) candidates.push({ abi: MOONWELL_COMPTROLLER_ABI });
|
|
2234
|
+
}
|
|
2235
|
+
const uni = safeCall(() => UNISWAP());
|
|
2236
|
+
if (uni && uni.SWAP_ROUTER.toLowerCase() === targetLower) {
|
|
2237
|
+
candidates.push({ abi: SWAP_ROUTER_ABI });
|
|
2238
|
+
candidates.push({ abi: SWAP_ROUTER_SINGLE_ABI });
|
|
2239
|
+
}
|
|
2240
|
+
candidates.push({ abi: ERC20_ABI });
|
|
2241
|
+
for (const { abi } of candidates) {
|
|
2242
|
+
try {
|
|
2243
|
+
const decoded = decodeFunctionData({ abi, data });
|
|
2244
|
+
return { functionName: decoded.functionName, args: decoded.args };
|
|
2245
|
+
} catch {
|
|
2246
|
+
continue;
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
return null;
|
|
2250
|
+
}
|
|
2251
|
+
function analyzeRisk(calls, callResults, context) {
|
|
2252
|
+
const risks = [];
|
|
2253
|
+
const labels = buildLabelMap();
|
|
2254
|
+
const vaultLower = context?.vault?.toLowerCase();
|
|
2255
|
+
const isKnown = (addr) => {
|
|
2256
|
+
const lower = addr.toLowerCase();
|
|
2257
|
+
return labels.has(lower) || lower === vaultLower;
|
|
2258
|
+
};
|
|
2259
|
+
let allTargetsVerified = true;
|
|
2260
|
+
let allCallsDecoded = true;
|
|
2261
|
+
for (let i = 0; i < calls.length; i++) {
|
|
2262
|
+
const call = calls[i];
|
|
2263
|
+
const result = callResults[i];
|
|
2264
|
+
const callNum = i + 1;
|
|
2265
|
+
if (result && !result.success) {
|
|
2266
|
+
risks.push({
|
|
2267
|
+
level: "critical",
|
|
2268
|
+
code: "SIMULATION_FAILED",
|
|
2269
|
+
message: `Call #${callNum} to ${labeledAddr(call.target)} reverted during simulation`,
|
|
2270
|
+
callIndex: i
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
2273
|
+
const targetKnown = isKnown(call.target);
|
|
2274
|
+
if (!targetKnown) {
|
|
2275
|
+
allTargetsVerified = false;
|
|
2276
|
+
risks.push({
|
|
2277
|
+
level: "critical",
|
|
2278
|
+
code: "UNKNOWN_TARGET",
|
|
2279
|
+
message: `Call #${callNum} targets ${labeledAddr(call.target)} which is not in the known address registry`,
|
|
2280
|
+
callIndex: i
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
const decoded = decodeCallArgs(call.target, call.data);
|
|
2284
|
+
if (!decoded) {
|
|
2285
|
+
allCallsDecoded = false;
|
|
2286
|
+
if (!targetKnown) {
|
|
2287
|
+
risks.push({
|
|
2288
|
+
level: "critical",
|
|
2289
|
+
code: "UNDECODED_CALLDATA",
|
|
2290
|
+
message: `Call #${callNum} has unrecognized calldata (selector ${call.data.slice(0, 10)}) targeting unknown contract`,
|
|
2291
|
+
callIndex: i
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
continue;
|
|
2295
|
+
}
|
|
2296
|
+
if (decoded.functionName === "transfer" && decoded.args.length >= 2) {
|
|
2297
|
+
const to = decoded.args[0];
|
|
2298
|
+
if (typeof to === "string" && to.startsWith("0x") && !isKnown(to)) {
|
|
2299
|
+
risks.push({
|
|
2300
|
+
level: "critical",
|
|
2301
|
+
code: "TRANSFER_TO_UNKNOWN",
|
|
2302
|
+
message: `Call #${callNum} transfer() sends funds to ${truncAddr(to)} which is not a known protocol`,
|
|
2303
|
+
callIndex: i
|
|
2304
|
+
});
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
if (decoded.functionName === "transferFrom" && decoded.args.length >= 3) {
|
|
2308
|
+
const to = decoded.args[1];
|
|
2309
|
+
if (typeof to === "string" && to.startsWith("0x") && !isKnown(to)) {
|
|
2310
|
+
risks.push({
|
|
2311
|
+
level: "critical",
|
|
2312
|
+
code: "TRANSFER_FROM_TO_UNKNOWN",
|
|
2313
|
+
message: `Call #${callNum} transferFrom() sends funds to ${truncAddr(to)} which is not a known protocol`,
|
|
2314
|
+
callIndex: i
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
if (decoded.functionName === "approve" && decoded.args.length >= 2) {
|
|
2319
|
+
const spender = decoded.args[0];
|
|
2320
|
+
if (typeof spender === "string" && spender.startsWith("0x") && !isKnown(spender)) {
|
|
2321
|
+
risks.push({
|
|
2322
|
+
level: "critical",
|
|
2323
|
+
code: "APPROVE_TO_UNKNOWN",
|
|
2324
|
+
message: `Call #${callNum} approve() grants allowance to ${truncAddr(spender)} which is not a known protocol`,
|
|
2325
|
+
callIndex: i
|
|
2326
|
+
});
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
if (context?.performanceFeeBps !== void 0) {
|
|
2331
|
+
const fee = context.performanceFeeBps;
|
|
2332
|
+
const maxFee = context.governorParams?.maxPerformanceFeeBps;
|
|
2333
|
+
if (maxFee && fee > maxFee * 80n / 100n) {
|
|
2334
|
+
risks.push({
|
|
2335
|
+
level: "critical",
|
|
2336
|
+
code: "EXCESSIVE_PERFORMANCE_FEE",
|
|
2337
|
+
message: `Performance fee ${Number(fee) / 100}% is within 20% of the hard cap (${Number(maxFee) / 100}%)`
|
|
2338
|
+
});
|
|
2339
|
+
} else if (fee > 2000n) {
|
|
2340
|
+
risks.push({
|
|
2341
|
+
level: "warning",
|
|
2342
|
+
code: "HIGH_PERFORMANCE_FEE",
|
|
2343
|
+
message: `Performance fee ${Number(fee) / 100}% exceeds 20% threshold`
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
if (context?.strategyDuration !== void 0) {
|
|
2348
|
+
const dur = context.strategyDuration;
|
|
2349
|
+
if (dur < 3600n) {
|
|
2350
|
+
risks.push({
|
|
2351
|
+
level: "warning",
|
|
2352
|
+
code: "SHORT_STRATEGY_DURATION",
|
|
2353
|
+
message: `Strategy duration ${Number(dur)}s is under 1 hour \u2014 flash-loan-style risk`
|
|
2354
|
+
});
|
|
2355
|
+
}
|
|
2356
|
+
if (dur > 2592000n) {
|
|
2357
|
+
risks.push({
|
|
2358
|
+
level: "warning",
|
|
2359
|
+
code: "LONG_STRATEGY_DURATION",
|
|
2360
|
+
message: `Strategy duration ${Number(dur) / 86400} days exceeds 30-day threshold`
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
const hasCritical = risks.some((r) => r.level === "critical");
|
|
2365
|
+
const hasWarning = risks.some((r) => r.level === "warning");
|
|
2366
|
+
if (!hasCritical && !hasWarning) {
|
|
2367
|
+
if (allTargetsVerified) {
|
|
2368
|
+
risks.push({
|
|
2369
|
+
level: "info",
|
|
2370
|
+
code: "ALL_TARGETS_VERIFIED",
|
|
2371
|
+
message: "All call targets are verified protocol addresses"
|
|
2372
|
+
});
|
|
2373
|
+
}
|
|
2374
|
+
if (allCallsDecoded) {
|
|
2375
|
+
risks.push({
|
|
2376
|
+
level: "info",
|
|
2377
|
+
code: "ALL_CALLS_DECODED",
|
|
2378
|
+
message: "All calldata successfully decoded"
|
|
2379
|
+
});
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
return risks;
|
|
2383
|
+
}
|
|
2384
|
+
var API_URLS = {
|
|
2385
|
+
"base": "https://app.sherwood.sh",
|
|
2386
|
+
"base-sepolia": "https://testnet.app.sherwood.sh"
|
|
2387
|
+
};
|
|
2388
|
+
function getApiBaseUrl() {
|
|
2389
|
+
if (process.env.SHERWOOD_API_URL) return process.env.SHERWOOD_API_URL;
|
|
2390
|
+
const chain = getChain();
|
|
2391
|
+
return API_URLS[chain.testnet ? "base-sepolia" : "base"] || API_URLS["base"];
|
|
2392
|
+
}
|
|
2393
|
+
async function simulateViaApi(vault, calls) {
|
|
2394
|
+
const baseUrl = getApiBaseUrl();
|
|
2395
|
+
const chainId = getChain().id;
|
|
2396
|
+
const response = await fetch(`${baseUrl}/api/simulate`, {
|
|
2397
|
+
method: "POST",
|
|
2398
|
+
headers: { "Content-Type": "application/json" },
|
|
2399
|
+
body: JSON.stringify({
|
|
2400
|
+
vault,
|
|
2401
|
+
chainId,
|
|
2402
|
+
calls: calls.map((c) => ({
|
|
2403
|
+
target: c.target,
|
|
2404
|
+
data: c.data,
|
|
2405
|
+
value: c.value.toString()
|
|
2406
|
+
}))
|
|
2407
|
+
})
|
|
2408
|
+
});
|
|
2409
|
+
if (!response.ok) {
|
|
2410
|
+
const body = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
2411
|
+
throw new Error(body.error || `Simulation API returned ${response.status}`);
|
|
2412
|
+
}
|
|
2413
|
+
const result = await response.json();
|
|
2414
|
+
const callResults = result.calls.map((c, i) => ({
|
|
2415
|
+
index: c.index,
|
|
2416
|
+
target: calls[i]?.target || c.target,
|
|
2417
|
+
decodedFunction: calls[i] ? decodeCallData(calls[i].target, calls[i].data) : null,
|
|
2418
|
+
selector: calls[i]?.data.slice(0, 10) || "0x",
|
|
2419
|
+
success: c.success,
|
|
2420
|
+
gasUsed: c.gasUsed
|
|
2421
|
+
}));
|
|
2422
|
+
for (const cr of callResults) {
|
|
2423
|
+
if (!cr.decodedFunction) {
|
|
2424
|
+
const matchingCall = calls[cr.index];
|
|
2425
|
+
if (matchingCall) {
|
|
2426
|
+
cr.decodedFunction = decodeCallData(matchingCall.target, matchingCall.data);
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
const warnings = [];
|
|
2431
|
+
for (const cr of callResults) {
|
|
2432
|
+
if (!cr.success) {
|
|
2433
|
+
warnings.push(`Call #${cr.index + 1} to ${labeledAddr(cr.target)} failed`);
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
return {
|
|
2437
|
+
success: result.success,
|
|
2438
|
+
totalGasUsed: result.totalGasUsed,
|
|
2439
|
+
callResults,
|
|
2440
|
+
warnings,
|
|
2441
|
+
risks: [],
|
|
2442
|
+
source: "api",
|
|
2443
|
+
errorMessage: result.errorMessage ?? void 0
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
async function simulateViaEthCall(vault, calls) {
|
|
2447
|
+
const governor = getGovernorAddress();
|
|
2448
|
+
const client = getPublicClient();
|
|
2449
|
+
const input2 = encodeFunctionData7({
|
|
2450
|
+
abi: SYNDICATE_VAULT_ABI,
|
|
2451
|
+
functionName: "executeGovernorBatch",
|
|
2452
|
+
args: [calls.map((c) => ({ target: c.target, data: c.data, value: c.value }))]
|
|
2453
|
+
});
|
|
2454
|
+
const callResults = calls.map((c, i) => ({
|
|
2455
|
+
index: i,
|
|
2456
|
+
target: c.target,
|
|
2457
|
+
decodedFunction: decodeCallData(c.target, c.data),
|
|
2458
|
+
selector: c.data.slice(0, 10),
|
|
2459
|
+
success: true,
|
|
2460
|
+
// will be set after simulation
|
|
2461
|
+
gasUsed: 0
|
|
2462
|
+
}));
|
|
2463
|
+
try {
|
|
2464
|
+
await client.call({
|
|
2465
|
+
account: governor,
|
|
2466
|
+
to: vault,
|
|
2467
|
+
data: input2
|
|
2468
|
+
});
|
|
2469
|
+
return {
|
|
2470
|
+
success: true,
|
|
2471
|
+
totalGasUsed: 0,
|
|
2472
|
+
callResults,
|
|
2473
|
+
warnings: [],
|
|
2474
|
+
risks: [],
|
|
2475
|
+
source: "eth_call"
|
|
2476
|
+
};
|
|
2477
|
+
} catch (err) {
|
|
2478
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2479
|
+
for (const cr of callResults) {
|
|
2480
|
+
cr.success = false;
|
|
2481
|
+
}
|
|
2482
|
+
return {
|
|
2483
|
+
success: false,
|
|
2484
|
+
totalGasUsed: 0,
|
|
2485
|
+
callResults,
|
|
2486
|
+
warnings: [`Batch reverted: ${message.slice(0, 200)}`],
|
|
2487
|
+
risks: [],
|
|
2488
|
+
source: "eth_call",
|
|
2489
|
+
errorMessage: message
|
|
2490
|
+
};
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
async function simulateBatchCalls(vault, calls, callType, riskContext) {
|
|
2494
|
+
let result;
|
|
2495
|
+
try {
|
|
2496
|
+
result = await simulateViaApi(vault, calls);
|
|
2497
|
+
} catch (err) {
|
|
2498
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2499
|
+
console.log(DIM(` Simulation API unavailable (${message}), falling back to eth_call...`));
|
|
2500
|
+
result = await simulateViaEthCall(vault, calls);
|
|
2501
|
+
}
|
|
2502
|
+
const ctx = { vault, ...riskContext };
|
|
2503
|
+
result.risks = analyzeRisk(calls, result.callResults, ctx);
|
|
2504
|
+
return result;
|
|
2505
|
+
}
|
|
2506
|
+
function printSimulationResult(result, callType) {
|
|
2507
|
+
console.log();
|
|
2508
|
+
console.log(LABEL(` \u25C6 Simulation \u2014 ${callType} calls`));
|
|
2509
|
+
SEP();
|
|
2510
|
+
const sourceLabel = result.source === "api" ? "Tenderly (via API)" : `eth_call ${DIM("(configure Tenderly for detailed results)")}`;
|
|
2511
|
+
console.log(W(` Source: ${sourceLabel}`));
|
|
2512
|
+
const statusIcon = result.success ? G("\u2713 PASS") : chalk5.red("\u2716 FAIL");
|
|
2513
|
+
console.log(W(` Status: ${statusIcon}`));
|
|
2514
|
+
if (result.totalGasUsed > 0) {
|
|
2515
|
+
console.log(W(` Total Gas: ${result.totalGasUsed.toLocaleString()}`));
|
|
2516
|
+
}
|
|
2517
|
+
if (result.errorMessage && !result.success) {
|
|
2518
|
+
console.log(chalk5.red(` Error: ${result.errorMessage.slice(0, 120)}`));
|
|
2519
|
+
}
|
|
2520
|
+
console.log();
|
|
2521
|
+
for (const cr of result.callResults) {
|
|
2522
|
+
const icon = cr.success ? G("\u2713") : chalk5.red("\u2716");
|
|
2523
|
+
const funcName = cr.decodedFunction || `${cr.selector}... ${DIM("(unknown)")}`;
|
|
2524
|
+
console.log(W(` [${cr.index + 1}] ${icon} ${funcName}`));
|
|
2525
|
+
const label = getAddressLabel(cr.target);
|
|
2526
|
+
const targetDisplay = label ? `${cr.target} (${label})` : cr.target;
|
|
2527
|
+
console.log(DIM(` Target: ${targetDisplay}`));
|
|
2528
|
+
if (cr.gasUsed > 0) {
|
|
2529
|
+
console.log(DIM(` Gas: ${cr.gasUsed.toLocaleString()}`));
|
|
2530
|
+
}
|
|
2531
|
+
console.log();
|
|
2532
|
+
}
|
|
2533
|
+
if (result.risks.length > 0) {
|
|
2534
|
+
const criticals = result.risks.filter((r) => r.level === "critical");
|
|
2535
|
+
const warnings = result.risks.filter((r) => r.level === "warning");
|
|
2536
|
+
const infos = result.risks.filter((r) => r.level === "info");
|
|
2537
|
+
if (criticals.length > 0) {
|
|
2538
|
+
console.log(chalk5.red.bold(` \u2716 CRITICAL RISKS (${criticals.length})`));
|
|
2539
|
+
for (const r of criticals) {
|
|
2540
|
+
const ref = r.callIndex !== void 0 ? ` [call #${r.callIndex + 1}]` : "";
|
|
2541
|
+
console.log(chalk5.red(` \u2716${ref} ${r.code} \u2014 ${r.message}`));
|
|
2542
|
+
}
|
|
2543
|
+
console.log();
|
|
2544
|
+
}
|
|
2545
|
+
if (warnings.length > 0) {
|
|
2546
|
+
console.log(chalk5.yellow.bold(` \u26A0 WARNINGS (${warnings.length})`));
|
|
2547
|
+
for (const r of warnings) {
|
|
2548
|
+
const ref = r.callIndex !== void 0 ? ` [call #${r.callIndex + 1}]` : "";
|
|
2549
|
+
console.log(chalk5.yellow(` \u26A0${ref} ${r.code} \u2014 ${r.message}`));
|
|
2550
|
+
}
|
|
2551
|
+
console.log();
|
|
2552
|
+
}
|
|
2553
|
+
if (criticals.length === 0 && warnings.length === 0 && infos.length > 0) {
|
|
2554
|
+
console.log(G.bold(` \u2713 RISK ASSESSMENT: CLEAN`));
|
|
2555
|
+
for (const r of infos) {
|
|
2556
|
+
console.log(G(` \u2713 ${r.message}`));
|
|
2557
|
+
}
|
|
2558
|
+
console.log();
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
if (result.warnings.length > 0) {
|
|
2562
|
+
console.log(chalk5.yellow(` \u26A0 Simulation Warnings`));
|
|
2563
|
+
for (const w of result.warnings) {
|
|
2564
|
+
console.log(chalk5.yellow(` - ${w}`));
|
|
2565
|
+
}
|
|
2566
|
+
console.log();
|
|
2567
|
+
}
|
|
2568
|
+
SEP();
|
|
2569
|
+
}
|
|
2570
|
+
async function sendSimulationAlert(subdomain, proposalId, vault, execResult, settleResult) {
|
|
2571
|
+
try {
|
|
2572
|
+
const xmtp = await import("./xmtp-XKHXATPQ.js");
|
|
2573
|
+
const { getAccount: getAccount2 } = await import("./client-SCIY6SUP.js");
|
|
2574
|
+
const group = await xmtp.getGroup("", subdomain);
|
|
2575
|
+
const allRisks = [...execResult.risks, ...settleResult?.risks ?? []];
|
|
2576
|
+
const criticals = allRisks.filter((r) => r.level === "critical");
|
|
2577
|
+
const warnings = allRisks.filter((r) => r.level === "warning");
|
|
2578
|
+
const highestLevel = criticals.length > 0 ? "critical" : warnings.length > 0 ? "warning" : "info";
|
|
2579
|
+
const lines = [];
|
|
2580
|
+
const statusEmoji = highestLevel === "critical" ? "\u{1F6A8}" : highestLevel === "warning" ? "\u26A0\uFE0F" : "\u2705";
|
|
2581
|
+
const statusText = highestLevel === "critical" ? "RISKS DETECTED" : highestLevel === "warning" ? "WARNINGS" : "CLEAN";
|
|
2582
|
+
lines.push(`## ${statusEmoji} Proposal #${proposalId} \u2014 Simulation Report`);
|
|
2583
|
+
lines.push(`**Vault**: \`${truncAddr(vault)}\``);
|
|
2584
|
+
lines.push(`**Status**: ${statusText}`);
|
|
2585
|
+
lines.push(`**Source**: ${execResult.source === "api" ? "Tenderly" : "eth_call"}`);
|
|
2586
|
+
lines.push("");
|
|
2587
|
+
lines.push(`### Execute Calls ${execResult.success ? "\u2713" : "\u2716"}`);
|
|
2588
|
+
for (const cr of execResult.callResults) {
|
|
2589
|
+
const icon = cr.success ? "\u2713" : "\u2716";
|
|
2590
|
+
const fn = cr.decodedFunction || `${cr.selector}...`;
|
|
2591
|
+
const flag = allRisks.find((r) => r.callIndex === cr.index);
|
|
2592
|
+
const suffix = flag && flag.level === "critical" ? " \u2190 **" + flag.code + "**" : "";
|
|
2593
|
+
lines.push(`${cr.index + 1}. ${icon} \`${fn}\`${suffix}`);
|
|
2594
|
+
}
|
|
2595
|
+
lines.push("");
|
|
2596
|
+
if (settleResult && settleResult.callResults.length > 0) {
|
|
2597
|
+
lines.push(`### Settlement Calls ${settleResult.success ? "\u2713" : "\u2716"}`);
|
|
2598
|
+
for (const cr of settleResult.callResults) {
|
|
2599
|
+
const icon = cr.success ? "\u2713" : "\u2716";
|
|
2600
|
+
const fn = cr.decodedFunction || `${cr.selector}...`;
|
|
2601
|
+
lines.push(`${cr.index + 1}. ${icon} \`${fn}\``);
|
|
2602
|
+
}
|
|
2603
|
+
lines.push("");
|
|
2604
|
+
}
|
|
2605
|
+
if (criticals.length > 0) {
|
|
2606
|
+
lines.push(`### \u{1F6A8} Critical Risks`);
|
|
2607
|
+
for (const r of criticals) {
|
|
2608
|
+
const ref = r.callIndex !== void 0 ? ` [call #${r.callIndex + 1}]` : "";
|
|
2609
|
+
lines.push(`- **${r.code}**${ref}: ${r.message}`);
|
|
2610
|
+
}
|
|
2611
|
+
lines.push("");
|
|
2612
|
+
}
|
|
2613
|
+
if (warnings.length > 0) {
|
|
2614
|
+
lines.push(`### \u26A0\uFE0F Warnings`);
|
|
2615
|
+
for (const r of warnings) {
|
|
2616
|
+
const ref = r.callIndex !== void 0 ? ` [call #${r.callIndex + 1}]` : "";
|
|
2617
|
+
lines.push(`- **${r.code}**${ref}: ${r.message}`);
|
|
2618
|
+
}
|
|
2619
|
+
lines.push("");
|
|
2620
|
+
}
|
|
2621
|
+
if (highestLevel === "critical") {
|
|
2622
|
+
lines.push(`### Action Required`);
|
|
2623
|
+
lines.push(`Recommend **VETO** \u2014 critical risks detected.`);
|
|
2624
|
+
} else if (highestLevel === "warning") {
|
|
2625
|
+
lines.push(`### Review Required`);
|
|
2626
|
+
lines.push(`Warnings detected \u2014 manual review recommended before execution.`);
|
|
2627
|
+
} else {
|
|
2628
|
+
lines.push(`### \u2705 No Risks Detected`);
|
|
2629
|
+
lines.push(`All call targets verified. All calldata decoded. Safe to proceed.`);
|
|
2630
|
+
}
|
|
2631
|
+
const markdown = lines.join("\n");
|
|
2632
|
+
const messageType = highestLevel === "critical" ? "RISK_ALERT" : "POSITION_UPDATE";
|
|
2633
|
+
await xmtp.sendEnvelope(group, {
|
|
2634
|
+
type: messageType,
|
|
2635
|
+
from: getAccount2().address,
|
|
2636
|
+
text: markdown,
|
|
2637
|
+
data: {
|
|
2638
|
+
format: "markdown",
|
|
2639
|
+
proposalId: Number(proposalId),
|
|
2640
|
+
vault,
|
|
2641
|
+
riskLevel: highestLevel,
|
|
2642
|
+
riskCodes: allRisks.filter((r) => r.level !== "info").map((r) => r.code),
|
|
2643
|
+
simulationSuccess: execResult.success && (settleResult?.success ?? true)
|
|
2644
|
+
},
|
|
2645
|
+
timestamp: Math.floor(Date.now() / 1e3)
|
|
2646
|
+
});
|
|
2647
|
+
} catch (err) {
|
|
2648
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2649
|
+
console.log(chalk5.yellow(` \u26A0 Failed to send XMTP alert: ${message.slice(0, 100)}`));
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
// src/commands/proposal.ts
|
|
2654
|
+
var G2 = chalk6.green;
|
|
2655
|
+
var W2 = chalk6.white;
|
|
2656
|
+
var DIM2 = chalk6.gray;
|
|
2657
|
+
var BOLD2 = chalk6.white.bold;
|
|
2658
|
+
var LABEL2 = chalk6.green.bold;
|
|
2659
|
+
var SEP2 = () => console.log(DIM2("\u2500".repeat(60)));
|
|
1795
2660
|
function formatTimestamp(ts) {
|
|
1796
2661
|
if (ts === 0n) return "\u2014";
|
|
1797
2662
|
return new Date(Number(ts) * 1e3).toLocaleString();
|
|
@@ -1807,11 +2672,11 @@ function parseCallsFile(path) {
|
|
|
1807
2672
|
}
|
|
1808
2673
|
function registerProposalCommands(program2) {
|
|
1809
2674
|
const proposal = program2.command("proposal").description("Governance proposals \u2014 create, vote, execute, settle");
|
|
1810
|
-
proposal.command("create").description("Submit a strategy proposal").requiredOption("--vault <address>", "Vault address the proposal targets").requiredOption("--name <name>", "Strategy name").requiredOption("--description <text>", "Strategy rationale and risk summary").requiredOption("--performance-fee <bps>", "Agent fee in bps (e.g. 1500 = 15%)").requiredOption("--duration <duration>", "Strategy duration (e.g. 7d, 24h, 3600)").requiredOption("--execute-calls <path>", "Path to JSON file with execute Call[] array").requiredOption("--settle-calls <path>", "Path to JSON file with settlement Call[] array").option("--metadata-uri <uri>", "Override \u2014 skip IPFS upload and use this URI directly").action(async (opts) => {
|
|
2675
|
+
proposal.command("create").description("Submit a strategy proposal").requiredOption("--vault <address>", "Vault address the proposal targets").requiredOption("--name <name>", "Strategy name").requiredOption("--description <text>", "Strategy rationale and risk summary").requiredOption("--performance-fee <bps>", "Agent fee in bps (e.g. 1500 = 15%)").requiredOption("--duration <duration>", "Strategy duration (e.g. 7d, 24h, 3600)").requiredOption("--execute-calls <path>", "Path to JSON file with execute Call[] array").requiredOption("--settle-calls <path>", "Path to JSON file with settlement Call[] array").option("--metadata-uri <uri>", "Override \u2014 skip IPFS upload and use this URI directly").option("--simulate", "Simulate calls via Tenderly before submitting").option("--force", "Submit even if simulation fails (requires --simulate)").action(async (opts) => {
|
|
1811
2676
|
try {
|
|
1812
2677
|
const vault = opts.vault;
|
|
1813
2678
|
if (!isAddress4(vault)) {
|
|
1814
|
-
console.error(
|
|
2679
|
+
console.error(chalk6.red(`Invalid vault address: ${opts.vault}`));
|
|
1815
2680
|
process.exit(1);
|
|
1816
2681
|
}
|
|
1817
2682
|
const performanceFeeBps = parseBigIntArg(opts.performanceFee, "performance-fee");
|
|
@@ -1820,7 +2685,7 @@ function registerProposalCommands(program2) {
|
|
|
1820
2685
|
const settleCalls = parseCallsFile(opts.settleCalls);
|
|
1821
2686
|
let metadataURI = opts.metadataUri || "";
|
|
1822
2687
|
if (!metadataURI) {
|
|
1823
|
-
const spinner2 = ora5({ text:
|
|
2688
|
+
const spinner2 = ora5({ text: W2("Uploading metadata to IPFS..."), color: "green" }).start();
|
|
1824
2689
|
try {
|
|
1825
2690
|
const account = getAccount();
|
|
1826
2691
|
const metadata = {
|
|
@@ -1841,36 +2706,67 @@ function registerProposalCommands(program2) {
|
|
|
1841
2706
|
createdAt: Math.floor(Date.now() / 1e3)
|
|
1842
2707
|
};
|
|
1843
2708
|
metadataURI = await uploadMetadata(proposalMeta);
|
|
1844
|
-
spinner2.succeed(
|
|
2709
|
+
spinner2.succeed(G2(`Metadata pinned: ${DIM2(metadataURI)}`));
|
|
1845
2710
|
} catch (err) {
|
|
1846
|
-
spinner2.warn(
|
|
2711
|
+
spinner2.warn(chalk6.yellow("IPFS upload failed \u2014 using inline metadata"));
|
|
1847
2712
|
const json = JSON.stringify({ name: opts.name, description: opts.description, vault });
|
|
1848
2713
|
metadataURI = `data:application/json;base64,${Buffer.from(json).toString("base64")}`;
|
|
1849
2714
|
}
|
|
1850
2715
|
}
|
|
1851
2716
|
console.log();
|
|
1852
|
-
console.log(
|
|
1853
|
-
|
|
1854
|
-
console.log(
|
|
1855
|
-
console.log(
|
|
1856
|
-
console.log(
|
|
1857
|
-
console.log(
|
|
1858
|
-
console.log(
|
|
1859
|
-
console.log(
|
|
1860
|
-
|
|
1861
|
-
|
|
2717
|
+
console.log(LABEL2(" \u25C6 Proposal Summary"));
|
|
2718
|
+
SEP2();
|
|
2719
|
+
console.log(W2(` Name: ${BOLD2(opts.name)}`));
|
|
2720
|
+
console.log(W2(` Vault: ${G2(vault)}`));
|
|
2721
|
+
console.log(W2(` Performance Fee: ${Number(performanceFeeBps) / 100}%`));
|
|
2722
|
+
console.log(W2(` Duration: ${formatDurationShort(strategyDuration)}`));
|
|
2723
|
+
console.log(W2(` Calls: ${executeCalls.length} execute + ${settleCalls.length} settle`));
|
|
2724
|
+
console.log(W2(` Metadata: ${DIM2(metadataURI.length > 50 ? metadataURI.slice(0, 50) + "..." : metadataURI)}`));
|
|
2725
|
+
SEP2();
|
|
2726
|
+
if (opts.simulate) {
|
|
2727
|
+
let createRiskContext = { vault, performanceFeeBps, strategyDuration };
|
|
2728
|
+
try {
|
|
2729
|
+
const govParams = await getGovernorParams();
|
|
2730
|
+
createRiskContext = { ...createRiskContext, governorParams: govParams };
|
|
2731
|
+
} catch {
|
|
2732
|
+
}
|
|
2733
|
+
const simSpinner = ora5({ text: W2("Simulating execute calls..."), color: "green" }).start();
|
|
2734
|
+
const execResult = await simulateBatchCalls(vault, executeCalls, "execute", createRiskContext);
|
|
2735
|
+
simSpinner.stop();
|
|
2736
|
+
printSimulationResult(execResult, "execute");
|
|
2737
|
+
if (settleCalls.length > 0) {
|
|
2738
|
+
const settleSpinner = ora5({ text: W2("Simulating settlement calls..."), color: "green" }).start();
|
|
2739
|
+
const settleResult = await simulateBatchCalls(vault, settleCalls, "settlement", createRiskContext);
|
|
2740
|
+
settleSpinner.stop();
|
|
2741
|
+
printSimulationResult(settleResult, "settlement");
|
|
2742
|
+
if (!settleResult.success && !opts.force) {
|
|
2743
|
+
console.error(chalk6.red("\n \u2716 Settlement simulation failed. Use --force to submit anyway."));
|
|
2744
|
+
process.exit(1);
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
if (!execResult.success && !opts.force) {
|
|
2748
|
+
console.error(chalk6.red("\n \u2716 Execution simulation failed. Use --force to submit anyway."));
|
|
2749
|
+
process.exit(1);
|
|
2750
|
+
}
|
|
2751
|
+
const hasCriticalRisks = execResult.risks.some((r) => r.level === "critical");
|
|
2752
|
+
if (hasCriticalRisks && !opts.force) {
|
|
2753
|
+
console.error(chalk6.red("\n \u2716 Critical risks detected. Use --force to submit anyway."));
|
|
2754
|
+
process.exit(1);
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
const spinner = ora5({ text: W2("Submitting proposal..."), color: "green" }).start();
|
|
1862
2758
|
const result = await propose(vault, metadataURI, performanceFeeBps, strategyDuration, executeCalls, settleCalls);
|
|
1863
|
-
spinner.succeed(
|
|
2759
|
+
spinner.succeed(G2("Proposal submitted"));
|
|
1864
2760
|
console.log();
|
|
1865
|
-
console.log(
|
|
1866
|
-
|
|
1867
|
-
console.log(
|
|
1868
|
-
console.log(
|
|
1869
|
-
|
|
2761
|
+
console.log(LABEL2(" \u25C6 Proposal Created"));
|
|
2762
|
+
SEP2();
|
|
2763
|
+
console.log(W2(` Proposal ID: ${G2(`#${result.proposalId}`)}`));
|
|
2764
|
+
console.log(W2(` Tx: ${DIM2(getExplorerUrl(result.hash))}`));
|
|
2765
|
+
SEP2();
|
|
1870
2766
|
console.log();
|
|
1871
2767
|
} catch (err) {
|
|
1872
|
-
console.error(
|
|
1873
|
-
\u2716 ${
|
|
2768
|
+
console.error(chalk6.red(`
|
|
2769
|
+
\u2716 ${formatContractError(err)}`));
|
|
1874
2770
|
process.exit(1);
|
|
1875
2771
|
}
|
|
1876
2772
|
});
|
|
@@ -1880,7 +2776,7 @@ function registerProposalCommands(program2) {
|
|
|
1880
2776
|
const count = await proposalCount();
|
|
1881
2777
|
if (count === 0n) {
|
|
1882
2778
|
spinner.stop();
|
|
1883
|
-
console.log(
|
|
2779
|
+
console.log(DIM2("\n No proposals found.\n"));
|
|
1884
2780
|
return;
|
|
1885
2781
|
}
|
|
1886
2782
|
const vaultFilter = opts.vault ? opts.vault.toLowerCase() : null;
|
|
@@ -1900,16 +2796,16 @@ function registerProposalCommands(program2) {
|
|
|
1900
2796
|
});
|
|
1901
2797
|
spinner.stop();
|
|
1902
2798
|
if (proposals.length === 0) {
|
|
1903
|
-
console.log(
|
|
2799
|
+
console.log(DIM2("\n No matching proposals.\n"));
|
|
1904
2800
|
return;
|
|
1905
2801
|
}
|
|
1906
2802
|
console.log();
|
|
1907
|
-
console.log(
|
|
1908
|
-
console.log(
|
|
2803
|
+
console.log(BOLD2(`Proposals (${proposals.length})`));
|
|
2804
|
+
console.log(DIM2("\u2500".repeat(90)));
|
|
1909
2805
|
console.log(
|
|
1910
|
-
|
|
2806
|
+
DIM2(" ID ") + DIM2("Agent".padEnd(14)) + DIM2("State".padEnd(12)) + DIM2("Votes (For/Against)".padEnd(22)) + DIM2("Fee".padEnd(8)) + DIM2("Duration".padEnd(10)) + DIM2("Created")
|
|
1911
2807
|
);
|
|
1912
|
-
console.log(
|
|
2808
|
+
console.log(DIM2("\u2500".repeat(90)));
|
|
1913
2809
|
for (const p of proposals) {
|
|
1914
2810
|
const state = PROPOSAL_STATES[p.computedState] || "Unknown";
|
|
1915
2811
|
const created = p.snapshotTimestamp > 0n ? new Date(Number(p.snapshotTimestamp) * 1e3).toLocaleDateString() : "\u2014";
|
|
@@ -1924,7 +2820,7 @@ function registerProposalCommands(program2) {
|
|
|
1924
2820
|
console.log();
|
|
1925
2821
|
} catch (err) {
|
|
1926
2822
|
spinner.fail("Failed to load proposals");
|
|
1927
|
-
console.error(
|
|
2823
|
+
console.error(chalk6.red(formatContractError(err)));
|
|
1928
2824
|
process.exit(1);
|
|
1929
2825
|
}
|
|
1930
2826
|
});
|
|
@@ -1940,63 +2836,63 @@ function registerProposalCommands(program2) {
|
|
|
1940
2836
|
const totalVotes = p.votesFor + p.votesAgainst;
|
|
1941
2837
|
const quorumNeeded = totalVotes > 0n ? `${Number(params.vetoThresholdBps) / 100}%` : "\u2014";
|
|
1942
2838
|
console.log();
|
|
1943
|
-
console.log(
|
|
1944
|
-
|
|
2839
|
+
console.log(LABEL2(` \u25C6 Proposal #${p.id}`));
|
|
2840
|
+
SEP2();
|
|
1945
2841
|
if (p.metadataURI && p.metadataURI.startsWith("ipfs://")) {
|
|
1946
2842
|
try {
|
|
1947
2843
|
const meta = await fetchMetadata(p.metadataURI);
|
|
1948
|
-
console.log(
|
|
1949
|
-
console.log(
|
|
2844
|
+
console.log(W2(` Name: ${BOLD2(meta.name)}`));
|
|
2845
|
+
console.log(W2(` Description: ${DIM2(meta.description)}`));
|
|
1950
2846
|
} catch {
|
|
1951
|
-
console.log(
|
|
2847
|
+
console.log(W2(` Metadata: ${DIM2(p.metadataURI)}`));
|
|
1952
2848
|
}
|
|
1953
2849
|
} else if (p.metadataURI) {
|
|
1954
|
-
console.log(
|
|
2850
|
+
console.log(W2(` Metadata: ${DIM2(p.metadataURI)}`));
|
|
1955
2851
|
}
|
|
1956
|
-
console.log(
|
|
1957
|
-
console.log(
|
|
1958
|
-
console.log(
|
|
1959
|
-
console.log(
|
|
1960
|
-
console.log(
|
|
2852
|
+
console.log(W2(` State: ${BOLD2(stateLabel)}`));
|
|
2853
|
+
console.log(W2(` Proposer: ${G2(p.proposer)}`));
|
|
2854
|
+
console.log(W2(` Vault: ${G2(p.vault)}`));
|
|
2855
|
+
console.log(W2(` Performance Fee: ${Number(p.performanceFeeBps) / 100}%`));
|
|
2856
|
+
console.log(W2(` Duration: ${formatDurationShort(p.strategyDuration)}`));
|
|
1961
2857
|
console.log();
|
|
1962
|
-
console.log(
|
|
1963
|
-
console.log(
|
|
1964
|
-
console.log(
|
|
1965
|
-
console.log(
|
|
1966
|
-
console.log(
|
|
2858
|
+
console.log(LABEL2(" Timestamps"));
|
|
2859
|
+
console.log(W2(` Snapshot: ${formatTimestamp(p.snapshotTimestamp)}`));
|
|
2860
|
+
console.log(W2(` Vote End: ${formatTimestamp(p.voteEnd)}`));
|
|
2861
|
+
console.log(W2(` Execute By: ${formatTimestamp(p.executeBy)}`));
|
|
2862
|
+
console.log(W2(` Executed At: ${formatTimestamp(p.executedAt)}`));
|
|
1967
2863
|
console.log();
|
|
1968
|
-
console.log(
|
|
1969
|
-
console.log(
|
|
1970
|
-
console.log(
|
|
1971
|
-
console.log(
|
|
1972
|
-
console.log(
|
|
2864
|
+
console.log(LABEL2(" Votes"));
|
|
2865
|
+
console.log(W2(` For: ${formatShares(p.votesFor)}`));
|
|
2866
|
+
console.log(W2(` Against: ${formatShares(p.votesAgainst)}`));
|
|
2867
|
+
console.log(W2(` Abstain: ${formatShares(p.votesAbstain)}`));
|
|
2868
|
+
console.log(W2(` Veto Threshold: ${quorumNeeded}`));
|
|
1973
2869
|
if (state === PROPOSAL_STATE.Executed || state === PROPOSAL_STATE.Settled) {
|
|
1974
2870
|
try {
|
|
1975
2871
|
const cap = await getCapitalSnapshot(id);
|
|
1976
2872
|
console.log();
|
|
1977
|
-
console.log(
|
|
1978
|
-
console.log(
|
|
2873
|
+
console.log(LABEL2(" Capital"));
|
|
2874
|
+
console.log(W2(` Snapshot: ${formatUSDC(cap)}`));
|
|
1979
2875
|
} catch {
|
|
1980
2876
|
}
|
|
1981
2877
|
}
|
|
1982
2878
|
const execCalls = await getExecuteCalls(id);
|
|
1983
2879
|
const settlCalls = await getSettlementCalls(id);
|
|
1984
2880
|
console.log();
|
|
1985
|
-
console.log(
|
|
2881
|
+
console.log(LABEL2(` Execute Calls (${execCalls.length})`));
|
|
1986
2882
|
for (let i = 0; i < execCalls.length; i++) {
|
|
1987
|
-
console.log(
|
|
1988
|
-
console.log(
|
|
2883
|
+
console.log(DIM2(` [${i}] target=${execCalls[i].target}`));
|
|
2884
|
+
console.log(DIM2(` data=${execCalls[i].data.slice(0, 20)}... value=${execCalls[i].value}`));
|
|
1989
2885
|
}
|
|
1990
|
-
console.log(
|
|
2886
|
+
console.log(LABEL2(` Settlement Calls (${settlCalls.length})`));
|
|
1991
2887
|
for (let i = 0; i < settlCalls.length; i++) {
|
|
1992
|
-
console.log(
|
|
1993
|
-
console.log(
|
|
2888
|
+
console.log(DIM2(` [${i}] target=${settlCalls[i].target}`));
|
|
2889
|
+
console.log(DIM2(` data=${settlCalls[i].data.slice(0, 20)}... value=${settlCalls[i].value}`));
|
|
1994
2890
|
}
|
|
1995
|
-
|
|
2891
|
+
SEP2();
|
|
1996
2892
|
console.log();
|
|
1997
2893
|
} catch (err) {
|
|
1998
2894
|
spinner.fail("Failed to load proposal");
|
|
1999
|
-
console.error(
|
|
2895
|
+
console.error(chalk6.red(formatContractError(err)));
|
|
2000
2896
|
process.exit(1);
|
|
2001
2897
|
}
|
|
2002
2898
|
});
|
|
@@ -2006,7 +2902,7 @@ function registerProposalCommands(program2) {
|
|
|
2006
2902
|
const supportRaw = String(opts.support).toLowerCase();
|
|
2007
2903
|
const support = supportRaw === "yes" || supportRaw === "for" ? VOTE_TYPE.For : supportRaw === "no" || supportRaw === "against" ? VOTE_TYPE.Against : supportRaw === "abstain" ? VOTE_TYPE.Abstain : null;
|
|
2008
2904
|
if (support === null) {
|
|
2009
|
-
console.error(
|
|
2905
|
+
console.error(chalk6.red(`Invalid support value "${opts.support}". Use for|against|abstain.`));
|
|
2010
2906
|
process.exit(1);
|
|
2011
2907
|
}
|
|
2012
2908
|
const account = getAccount();
|
|
@@ -2025,27 +2921,27 @@ function registerProposalCommands(program2) {
|
|
|
2025
2921
|
const weight = await getVoteWeight(proposalId, account.address);
|
|
2026
2922
|
spinner.stop();
|
|
2027
2923
|
console.log();
|
|
2028
|
-
console.log(
|
|
2029
|
-
|
|
2030
|
-
console.log(
|
|
2031
|
-
console.log(
|
|
2032
|
-
console.log(
|
|
2033
|
-
` Support: ${support === VOTE_TYPE.For ?
|
|
2924
|
+
console.log(LABEL2(" \u25C6 Cast Vote"));
|
|
2925
|
+
SEP2();
|
|
2926
|
+
console.log(W2(` Proposal: #${proposalId}`));
|
|
2927
|
+
console.log(W2(` Vault: ${G2(p.vault)}`));
|
|
2928
|
+
console.log(W2(
|
|
2929
|
+
` Support: ${support === VOTE_TYPE.For ? G2("FOR") : support === VOTE_TYPE.Against ? chalk6.red("AGAINST") : DIM2("ABSTAIN")}`
|
|
2034
2930
|
));
|
|
2035
|
-
console.log(
|
|
2036
|
-
|
|
2037
|
-
const voteSpinner = ora5({ text:
|
|
2931
|
+
console.log(W2(` Weight: ${formatShares(weight)} shares`));
|
|
2932
|
+
SEP2();
|
|
2933
|
+
const voteSpinner = ora5({ text: W2("Submitting vote..."), color: "green" }).start();
|
|
2038
2934
|
const hash = await vote(proposalId, support);
|
|
2039
|
-
voteSpinner.succeed(
|
|
2040
|
-
console.log(
|
|
2935
|
+
voteSpinner.succeed(G2("Vote cast"));
|
|
2936
|
+
console.log(DIM2(` ${getExplorerUrl(hash)}`));
|
|
2041
2937
|
console.log();
|
|
2042
2938
|
} catch (err) {
|
|
2043
|
-
console.error(
|
|
2044
|
-
\u2716 ${
|
|
2939
|
+
console.error(chalk6.red(`
|
|
2940
|
+
\u2716 ${formatContractError(err)}`));
|
|
2045
2941
|
process.exit(1);
|
|
2046
2942
|
}
|
|
2047
2943
|
});
|
|
2048
|
-
proposal.command("execute").description("Execute an approved proposal").requiredOption("--id <proposalId>", "Proposal ID").action(async (opts) => {
|
|
2944
|
+
proposal.command("execute").description("Execute an approved proposal").requiredOption("--id <proposalId>", "Proposal ID").option("--dry-run", "Simulate execution without sending a transaction").action(async (opts) => {
|
|
2049
2945
|
try {
|
|
2050
2946
|
const proposalId = parseBigIntArg(opts.id, "proposal ID");
|
|
2051
2947
|
const spinner = ora5("Loading proposal...").start();
|
|
@@ -2054,19 +2950,34 @@ function registerProposalCommands(program2) {
|
|
|
2054
2950
|
spinner.fail(`Proposal is ${PROPOSAL_STATES[state] || "Unknown"}, not Approved`);
|
|
2055
2951
|
process.exit(1);
|
|
2056
2952
|
}
|
|
2057
|
-
|
|
2953
|
+
if (opts.dryRun) {
|
|
2954
|
+
const p = await getProposal(proposalId);
|
|
2955
|
+
const execCalls = await getExecuteCalls(proposalId);
|
|
2956
|
+
const dryRunContext = {
|
|
2957
|
+
vault: p.vault,
|
|
2958
|
+
performanceFeeBps: p.performanceFeeBps,
|
|
2959
|
+
strategyDuration: p.strategyDuration
|
|
2960
|
+
};
|
|
2961
|
+
spinner.text = W2("Simulating execution...");
|
|
2962
|
+
const result = await simulateBatchCalls(p.vault, execCalls, "execute", dryRunContext);
|
|
2963
|
+
spinner.stop();
|
|
2964
|
+
printSimulationResult(result, "execute");
|
|
2965
|
+
const hasCritical = result.risks.some((r) => r.level === "critical");
|
|
2966
|
+
process.exit(result.success && !hasCritical ? 0 : 1);
|
|
2967
|
+
}
|
|
2968
|
+
spinner.text = W2("Executing proposal...");
|
|
2058
2969
|
const hash = await executeProposal(proposalId);
|
|
2059
|
-
spinner.succeed(
|
|
2060
|
-
console.log(
|
|
2970
|
+
spinner.succeed(G2("Proposal executed"));
|
|
2971
|
+
console.log(DIM2(` ${getExplorerUrl(hash)}`));
|
|
2061
2972
|
try {
|
|
2062
2973
|
const cap = await getCapitalSnapshot(proposalId);
|
|
2063
|
-
console.log(
|
|
2974
|
+
console.log(DIM2(` Capital snapshot: ${formatUSDC(cap)}`));
|
|
2064
2975
|
} catch {
|
|
2065
2976
|
}
|
|
2066
2977
|
console.log();
|
|
2067
2978
|
} catch (err) {
|
|
2068
|
-
console.error(
|
|
2069
|
-
\u2716 ${
|
|
2979
|
+
console.error(chalk6.red(`
|
|
2980
|
+
\u2716 ${formatContractError(err)}`));
|
|
2070
2981
|
process.exit(1);
|
|
2071
2982
|
}
|
|
2072
2983
|
});
|
|
@@ -2086,27 +2997,27 @@ function registerProposalCommands(program2) {
|
|
|
2086
2997
|
const durationElapsed = p.executedAt > 0n && now >= p.executedAt + p.strategyDuration;
|
|
2087
2998
|
let hash;
|
|
2088
2999
|
if (isProposer && !durationElapsed) {
|
|
2089
|
-
spinner.text =
|
|
3000
|
+
spinner.text = W2("Settling (proposer)...");
|
|
2090
3001
|
hash = await settleProposal(proposalId);
|
|
2091
|
-
spinner.succeed(
|
|
3002
|
+
spinner.succeed(G2("Settled by proposer"));
|
|
2092
3003
|
} else if (durationElapsed && !opts.calls) {
|
|
2093
|
-
spinner.text =
|
|
3004
|
+
spinner.text = W2("Settling (permissionless)...");
|
|
2094
3005
|
hash = await settleProposal(proposalId);
|
|
2095
|
-
spinner.succeed(
|
|
3006
|
+
spinner.succeed(G2("Settled (permissionless)"));
|
|
2096
3007
|
} else if (durationElapsed && opts.calls) {
|
|
2097
|
-
spinner.text =
|
|
3008
|
+
spinner.text = W2("Emergency settling...");
|
|
2098
3009
|
const calls = parseCallsFile(opts.calls);
|
|
2099
3010
|
hash = await emergencySettle(proposalId, calls);
|
|
2100
|
-
spinner.succeed(
|
|
3011
|
+
spinner.succeed(G2("Emergency settled"));
|
|
2101
3012
|
} else {
|
|
2102
3013
|
spinner.fail("Cannot settle: duration not elapsed and you are not the proposer.");
|
|
2103
3014
|
process.exit(1);
|
|
2104
3015
|
}
|
|
2105
|
-
console.log(
|
|
3016
|
+
console.log(DIM2(` ${getExplorerUrl(hash)}`));
|
|
2106
3017
|
console.log();
|
|
2107
3018
|
} catch (err) {
|
|
2108
|
-
console.error(
|
|
2109
|
-
\u2716 ${
|
|
3019
|
+
console.error(chalk6.red(`
|
|
3020
|
+
\u2716 ${formatContractError(err)}`));
|
|
2110
3021
|
process.exit(1);
|
|
2111
3022
|
}
|
|
2112
3023
|
});
|
|
@@ -2121,22 +3032,90 @@ function registerProposalCommands(program2) {
|
|
|
2121
3032
|
}
|
|
2122
3033
|
let hash;
|
|
2123
3034
|
if (opts.emergency) {
|
|
2124
|
-
spinner.text =
|
|
3035
|
+
spinner.text = W2("Emergency cancelling...");
|
|
2125
3036
|
hash = await emergencyCancel(proposalId);
|
|
2126
|
-
spinner.succeed(
|
|
3037
|
+
spinner.succeed(G2("Emergency cancelled"));
|
|
2127
3038
|
} else {
|
|
2128
3039
|
if (state !== PROPOSAL_STATE.Draft && state !== PROPOSAL_STATE.Pending) {
|
|
2129
3040
|
spinner.fail(`Proposal is ${PROPOSAL_STATES[state] || "Unknown"} \u2014 use --emergency for non-pending/approved`);
|
|
2130
3041
|
process.exit(1);
|
|
2131
3042
|
}
|
|
2132
|
-
spinner.text =
|
|
3043
|
+
spinner.text = W2("Cancelling proposal...");
|
|
2133
3044
|
hash = await cancelProposal(proposalId);
|
|
2134
|
-
spinner.succeed(
|
|
3045
|
+
spinner.succeed(G2("Proposal cancelled"));
|
|
2135
3046
|
}
|
|
2136
|
-
console.log(
|
|
3047
|
+
console.log(DIM2(` ${getExplorerUrl(hash)}`));
|
|
2137
3048
|
console.log();
|
|
2138
3049
|
} catch (err) {
|
|
2139
|
-
console.error(
|
|
3050
|
+
console.error(chalk6.red(`
|
|
3051
|
+
\u2716 ${formatContractError(err)}`));
|
|
3052
|
+
process.exit(1);
|
|
3053
|
+
}
|
|
3054
|
+
});
|
|
3055
|
+
proposal.command("simulate").description("Simulate proposal calls via Tenderly (or eth_call fallback)").option("--id <proposalId>", "Proposal ID to simulate").option("--vault <address>", "Vault address (required with --execute-calls)").option("--execute-calls <path>", "Path to JSON execute calls file").option("--settle-calls <path>", "Path to JSON settlement calls file").option("--notify <subdomain>", "Send results to syndicate XMTP chat").action(async (opts) => {
|
|
3056
|
+
try {
|
|
3057
|
+
let vault;
|
|
3058
|
+
let executeCalls;
|
|
3059
|
+
let settlementCalls = [];
|
|
3060
|
+
let riskContext = {};
|
|
3061
|
+
let proposalId;
|
|
3062
|
+
if (opts.id) {
|
|
3063
|
+
proposalId = parseBigIntArg(opts.id, "proposal ID");
|
|
3064
|
+
const spinner2 = ora5("Loading proposal...").start();
|
|
3065
|
+
const p = await getProposal(proposalId);
|
|
3066
|
+
vault = p.vault;
|
|
3067
|
+
executeCalls = await getExecuteCalls(proposalId);
|
|
3068
|
+
settlementCalls = await getSettlementCalls(proposalId);
|
|
3069
|
+
riskContext = {
|
|
3070
|
+
vault,
|
|
3071
|
+
performanceFeeBps: p.performanceFeeBps,
|
|
3072
|
+
strategyDuration: p.strategyDuration
|
|
3073
|
+
};
|
|
3074
|
+
try {
|
|
3075
|
+
const govParams = await getGovernorParams();
|
|
3076
|
+
riskContext.governorParams = govParams;
|
|
3077
|
+
} catch {
|
|
3078
|
+
}
|
|
3079
|
+
spinner2.stop();
|
|
3080
|
+
console.log(DIM2(`
|
|
3081
|
+
Simulating proposal #${proposalId} on vault ${vault.slice(0, 10)}...`));
|
|
3082
|
+
} else if (opts.executeCalls) {
|
|
3083
|
+
if (!opts.vault || !isAddress4(opts.vault)) {
|
|
3084
|
+
console.error(chalk6.red("\n \u2716 --vault is required when using --execute-calls"));
|
|
3085
|
+
process.exit(1);
|
|
3086
|
+
}
|
|
3087
|
+
vault = opts.vault;
|
|
3088
|
+
executeCalls = parseCallsFile(opts.executeCalls);
|
|
3089
|
+
if (opts.settleCalls) {
|
|
3090
|
+
settlementCalls = parseCallsFile(opts.settleCalls);
|
|
3091
|
+
}
|
|
3092
|
+
riskContext = { vault };
|
|
3093
|
+
} else {
|
|
3094
|
+
console.error(chalk6.red("\n \u2716 Provide --id <proposalId> or --execute-calls <path>"));
|
|
3095
|
+
process.exit(1);
|
|
3096
|
+
}
|
|
3097
|
+
const spinner = ora5({ text: W2("Simulating execute calls..."), color: "green" }).start();
|
|
3098
|
+
const execResult = await simulateBatchCalls(vault, executeCalls, "execute", riskContext);
|
|
3099
|
+
spinner.stop();
|
|
3100
|
+
printSimulationResult(execResult, "execute");
|
|
3101
|
+
let settleResult;
|
|
3102
|
+
if (settlementCalls.length > 0) {
|
|
3103
|
+
const settleSpinner = ora5({ text: W2("Simulating settlement calls..."), color: "green" }).start();
|
|
3104
|
+
settleResult = await simulateBatchCalls(vault, settlementCalls, "settlement", riskContext);
|
|
3105
|
+
settleSpinner.stop();
|
|
3106
|
+
printSimulationResult(settleResult, "settlement");
|
|
3107
|
+
}
|
|
3108
|
+
if (opts.notify && proposalId !== void 0) {
|
|
3109
|
+
const notifySpinner = ora5({ text: W2("Sending XMTP alert..."), color: "green" }).start();
|
|
3110
|
+
await sendSimulationAlert(opts.notify, proposalId, vault, execResult, settleResult);
|
|
3111
|
+
notifySpinner.succeed(G2("Risk report sent to chat"));
|
|
3112
|
+
}
|
|
3113
|
+
const hasCritical = execResult.risks.some((r) => r.level === "critical") || (settleResult?.risks.some((r) => r.level === "critical") ?? false);
|
|
3114
|
+
if (!execResult.success || settleResult && !settleResult.success || hasCritical) {
|
|
3115
|
+
process.exit(1);
|
|
3116
|
+
}
|
|
3117
|
+
} catch (err) {
|
|
3118
|
+
console.error(chalk6.red(`
|
|
2140
3119
|
\u2716 ${err instanceof Error ? err.message : String(err)}`));
|
|
2141
3120
|
process.exit(1);
|
|
2142
3121
|
}
|
|
@@ -2144,14 +3123,14 @@ function registerProposalCommands(program2) {
|
|
|
2144
3123
|
}
|
|
2145
3124
|
|
|
2146
3125
|
// src/commands/governor.ts
|
|
2147
|
-
import
|
|
3126
|
+
import chalk7 from "chalk";
|
|
2148
3127
|
import ora6 from "ora";
|
|
2149
|
-
var
|
|
2150
|
-
var
|
|
2151
|
-
var
|
|
2152
|
-
var
|
|
2153
|
-
var
|
|
2154
|
-
var
|
|
3128
|
+
var G3 = chalk7.green;
|
|
3129
|
+
var W3 = chalk7.white;
|
|
3130
|
+
var DIM3 = chalk7.gray;
|
|
3131
|
+
var BOLD3 = chalk7.white.bold;
|
|
3132
|
+
var LABEL3 = chalk7.green.bold;
|
|
3133
|
+
var SEP3 = () => console.log(DIM3("\u2500".repeat(60)));
|
|
2155
3134
|
function registerGovernorCommands(program2) {
|
|
2156
3135
|
const governor = program2.command("governor").description("Governor parameters and vault management");
|
|
2157
3136
|
governor.command("info").description("Display current governor parameters and registered vaults").action(async () => {
|
|
@@ -2163,29 +3142,29 @@ function registerGovernorCommands(program2) {
|
|
|
2163
3142
|
]);
|
|
2164
3143
|
spinner.stop();
|
|
2165
3144
|
console.log();
|
|
2166
|
-
console.log(
|
|
2167
|
-
|
|
2168
|
-
console.log(
|
|
2169
|
-
console.log(
|
|
2170
|
-
console.log(
|
|
2171
|
-
console.log(
|
|
2172
|
-
console.log(
|
|
2173
|
-
console.log(
|
|
2174
|
-
console.log(
|
|
3145
|
+
console.log(LABEL3(" \u25C6 Governor Parameters"));
|
|
3146
|
+
SEP3();
|
|
3147
|
+
console.log(W3(` Address: ${G3(getGovernorAddress())}`));
|
|
3148
|
+
console.log(W3(` Voting Period: ${BOLD3(formatDurationLong(params.votingPeriod))}`));
|
|
3149
|
+
console.log(W3(` Execution Window: ${BOLD3(formatDurationLong(params.executionWindow))}`));
|
|
3150
|
+
console.log(W3(` Veto Threshold: ${BOLD3(`${Number(params.vetoThresholdBps) / 100}%`)}`));
|
|
3151
|
+
console.log(W3(` Max Performance Fee: ${BOLD3(`${Number(params.maxPerformanceFeeBps) / 100}%`)}`));
|
|
3152
|
+
console.log(W3(` Max Strategy Duration:${BOLD3(` ${formatDurationLong(params.maxStrategyDuration)}`)}`));
|
|
3153
|
+
console.log(W3(` Cooldown Period: ${BOLD3(formatDurationLong(params.cooldownPeriod))}`));
|
|
2175
3154
|
console.log();
|
|
2176
|
-
console.log(
|
|
3155
|
+
console.log(LABEL3(` Registered Vaults (${vaults.length})`));
|
|
2177
3156
|
if (vaults.length === 0) {
|
|
2178
|
-
console.log(
|
|
3157
|
+
console.log(DIM3(" (none)"));
|
|
2179
3158
|
} else {
|
|
2180
3159
|
for (const v of vaults) {
|
|
2181
|
-
console.log(
|
|
3160
|
+
console.log(W3(` ${G3(v)}`));
|
|
2182
3161
|
}
|
|
2183
3162
|
}
|
|
2184
|
-
|
|
3163
|
+
SEP3();
|
|
2185
3164
|
console.log();
|
|
2186
3165
|
} catch (err) {
|
|
2187
3166
|
spinner.fail("Failed to load governor info");
|
|
2188
|
-
console.error(
|
|
3167
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2189
3168
|
process.exit(1);
|
|
2190
3169
|
}
|
|
2191
3170
|
});
|
|
@@ -2193,11 +3172,11 @@ function registerGovernorCommands(program2) {
|
|
|
2193
3172
|
const spinner = ora6("Setting voting period...").start();
|
|
2194
3173
|
try {
|
|
2195
3174
|
const hash = await setVotingPeriod(parseBigIntArg(opts.seconds, "seconds"));
|
|
2196
|
-
spinner.succeed(
|
|
2197
|
-
console.log(
|
|
3175
|
+
spinner.succeed(G3(`Voting period change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
|
|
3176
|
+
console.log(DIM3(` ${getExplorerUrl(hash)}`));
|
|
2198
3177
|
} catch (err) {
|
|
2199
3178
|
spinner.fail("Failed to set voting period");
|
|
2200
|
-
console.error(
|
|
3179
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2201
3180
|
process.exit(1);
|
|
2202
3181
|
}
|
|
2203
3182
|
});
|
|
@@ -2205,11 +3184,11 @@ function registerGovernorCommands(program2) {
|
|
|
2205
3184
|
const spinner = ora6("Setting execution window...").start();
|
|
2206
3185
|
try {
|
|
2207
3186
|
const hash = await setExecutionWindow(parseBigIntArg(opts.seconds, "seconds"));
|
|
2208
|
-
spinner.succeed(
|
|
2209
|
-
console.log(
|
|
3187
|
+
spinner.succeed(G3(`Execution window change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
|
|
3188
|
+
console.log(DIM3(` ${getExplorerUrl(hash)}`));
|
|
2210
3189
|
} catch (err) {
|
|
2211
3190
|
spinner.fail("Failed to set execution window");
|
|
2212
|
-
console.error(
|
|
3191
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2213
3192
|
process.exit(1);
|
|
2214
3193
|
}
|
|
2215
3194
|
});
|
|
@@ -2217,11 +3196,11 @@ function registerGovernorCommands(program2) {
|
|
|
2217
3196
|
const spinner = ora6("Setting veto threshold...").start();
|
|
2218
3197
|
try {
|
|
2219
3198
|
const hash = await setVetoThresholdBps(parseBigIntArg(opts.bps, "bps"));
|
|
2220
|
-
spinner.succeed(
|
|
2221
|
-
console.log(
|
|
3199
|
+
spinner.succeed(G3(`Veto threshold change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
|
|
3200
|
+
console.log(DIM3(` ${getExplorerUrl(hash)}`));
|
|
2222
3201
|
} catch (err) {
|
|
2223
3202
|
spinner.fail("Failed to set veto threshold");
|
|
2224
|
-
console.error(
|
|
3203
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2225
3204
|
process.exit(1);
|
|
2226
3205
|
}
|
|
2227
3206
|
});
|
|
@@ -2229,11 +3208,11 @@ function registerGovernorCommands(program2) {
|
|
|
2229
3208
|
const spinner = ora6("Setting max fee...").start();
|
|
2230
3209
|
try {
|
|
2231
3210
|
const hash = await setMaxPerformanceFeeBps(parseBigIntArg(opts.bps, "bps"));
|
|
2232
|
-
spinner.succeed(
|
|
2233
|
-
console.log(
|
|
3211
|
+
spinner.succeed(G3(`Max performance fee change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
|
|
3212
|
+
console.log(DIM3(` ${getExplorerUrl(hash)}`));
|
|
2234
3213
|
} catch (err) {
|
|
2235
3214
|
spinner.fail("Failed to set max fee");
|
|
2236
|
-
console.error(
|
|
3215
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2237
3216
|
process.exit(1);
|
|
2238
3217
|
}
|
|
2239
3218
|
});
|
|
@@ -2241,11 +3220,11 @@ function registerGovernorCommands(program2) {
|
|
|
2241
3220
|
const spinner = ora6("Setting max duration...").start();
|
|
2242
3221
|
try {
|
|
2243
3222
|
const hash = await setMaxStrategyDuration(parseBigIntArg(opts.seconds, "seconds"));
|
|
2244
|
-
spinner.succeed(
|
|
2245
|
-
console.log(
|
|
3223
|
+
spinner.succeed(G3(`Max strategy duration change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
|
|
3224
|
+
console.log(DIM3(` ${getExplorerUrl(hash)}`));
|
|
2246
3225
|
} catch (err) {
|
|
2247
3226
|
spinner.fail("Failed to set max duration");
|
|
2248
|
-
console.error(
|
|
3227
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2249
3228
|
process.exit(1);
|
|
2250
3229
|
}
|
|
2251
3230
|
});
|
|
@@ -2253,11 +3232,11 @@ function registerGovernorCommands(program2) {
|
|
|
2253
3232
|
const spinner = ora6("Setting cooldown...").start();
|
|
2254
3233
|
try {
|
|
2255
3234
|
const hash = await setCooldownPeriod(parseBigIntArg(opts.seconds, "seconds"));
|
|
2256
|
-
spinner.succeed(
|
|
2257
|
-
console.log(
|
|
3235
|
+
spinner.succeed(G3(`Cooldown period change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
|
|
3236
|
+
console.log(DIM3(` ${getExplorerUrl(hash)}`));
|
|
2258
3237
|
} catch (err) {
|
|
2259
3238
|
spinner.fail("Failed to set cooldown");
|
|
2260
|
-
console.error(
|
|
3239
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2261
3240
|
process.exit(1);
|
|
2262
3241
|
}
|
|
2263
3242
|
});
|
|
@@ -2265,11 +3244,11 @@ function registerGovernorCommands(program2) {
|
|
|
2265
3244
|
const spinner = ora6("Setting protocol fee...").start();
|
|
2266
3245
|
try {
|
|
2267
3246
|
const hash = await setProtocolFeeBps(parseBigIntArg(opts.bps, "bps"));
|
|
2268
|
-
spinner.succeed(
|
|
2269
|
-
console.log(
|
|
3247
|
+
spinner.succeed(G3(`Protocol fee change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
|
|
3248
|
+
console.log(DIM3(` ${getExplorerUrl(hash)}`));
|
|
2270
3249
|
} catch (err) {
|
|
2271
3250
|
spinner.fail("Failed to set protocol fee");
|
|
2272
|
-
console.error(
|
|
3251
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2273
3252
|
process.exit(1);
|
|
2274
3253
|
}
|
|
2275
3254
|
});
|
|
@@ -2283,20 +3262,20 @@ try {
|
|
|
2283
3262
|
var require2 = createRequire(import.meta.url);
|
|
2284
3263
|
var { version: CLI_VERSION } = require2("../package.json");
|
|
2285
3264
|
async function loadXmtp() {
|
|
2286
|
-
return import("./xmtp-
|
|
3265
|
+
return import("./xmtp-XKHXATPQ.js");
|
|
2287
3266
|
}
|
|
2288
3267
|
async function loadCron() {
|
|
2289
|
-
return import("./cron-
|
|
3268
|
+
return import("./cron-EOULYO3R.js");
|
|
2290
3269
|
}
|
|
2291
|
-
var
|
|
2292
|
-
var
|
|
2293
|
-
var
|
|
2294
|
-
var
|
|
2295
|
-
var
|
|
2296
|
-
var
|
|
3270
|
+
var G4 = chalk8.green;
|
|
3271
|
+
var W4 = chalk8.white;
|
|
3272
|
+
var DIM4 = chalk8.gray;
|
|
3273
|
+
var BOLD4 = chalk8.white.bold;
|
|
3274
|
+
var LABEL4 = chalk8.green.bold;
|
|
3275
|
+
var SEP4 = () => console.log(DIM4("\u2500".repeat(60)));
|
|
2297
3276
|
function validateAddress(value, name) {
|
|
2298
3277
|
if (!isAddress5(value)) {
|
|
2299
|
-
console.error(
|
|
3278
|
+
console.error(chalk8.red(`Invalid ${name} address: ${value}`));
|
|
2300
3279
|
process.exit(1);
|
|
2301
3280
|
}
|
|
2302
3281
|
return value;
|
|
@@ -2316,7 +3295,7 @@ program.name("sherwood").description("CLI for agent-managed investment syndicate
|
|
|
2316
3295
|
process.env.ENABLE_TESTNET = "true";
|
|
2317
3296
|
if (network !== "base") {
|
|
2318
3297
|
console.warn(
|
|
2319
|
-
|
|
3298
|
+
chalk8.yellow("[warn] --testnet ignored, --chain takes precedence")
|
|
2320
3299
|
);
|
|
2321
3300
|
} else {
|
|
2322
3301
|
network = "base-sepolia";
|
|
@@ -2324,39 +3303,39 @@ program.name("sherwood").description("CLI for agent-managed investment syndicate
|
|
|
2324
3303
|
}
|
|
2325
3304
|
setNetwork(network);
|
|
2326
3305
|
if (getNetwork() !== "base") {
|
|
2327
|
-
console.log(
|
|
3306
|
+
console.log(chalk8.yellow(`[${getNetwork()}]`));
|
|
2328
3307
|
}
|
|
2329
3308
|
});
|
|
2330
3309
|
var syndicate = program.command("syndicate");
|
|
2331
3310
|
syndicate.command("create").description("Create a new syndicate via the factory (interactive)").option("--subdomain <name>", "ENS subdomain (skip prompt)").option("--name <name>", "Syndicate name (skip prompt)").option("--agent-id <id>", "ERC-8004 agent identity token ID (skip prompt)").option("--asset <symbol-or-address>", "Vault asset: USDC, WETH, or a token address").option("--description <text>", "Short description").option("--metadata-uri <uri>", "Override metadata URI (skip IPFS upload)").option("--open-deposits", "Allow anyone to deposit (no whitelist)").option("--public-chat", "Enable dashboard spectator mode", false).option("-y, --yes", "Skip confirmation prompt (non-interactive mode)", false).action(async (opts) => {
|
|
2332
3311
|
try {
|
|
2333
3312
|
console.log();
|
|
2334
|
-
console.log(
|
|
2335
|
-
|
|
3313
|
+
console.log(LABEL4(" \u25C6 Create Syndicate"));
|
|
3314
|
+
SEP4();
|
|
2336
3315
|
const wallet = getAccount();
|
|
2337
|
-
console.log(
|
|
2338
|
-
console.log(
|
|
2339
|
-
|
|
3316
|
+
console.log(DIM4(` Wallet: ${wallet.address}`));
|
|
3317
|
+
console.log(DIM4(` Network: ${getChain().name}`));
|
|
3318
|
+
SEP4();
|
|
2340
3319
|
const savedAgentId = getAgentId();
|
|
2341
3320
|
const nonInteractive = opts.yes;
|
|
2342
3321
|
const name = opts.name || (nonInteractive ? (() => {
|
|
2343
3322
|
throw new Error("--name is required in non-interactive mode (-y)");
|
|
2344
3323
|
})() : await input({
|
|
2345
|
-
message:
|
|
3324
|
+
message: G4("Syndicate name"),
|
|
2346
3325
|
validate: (v) => v.length > 0 || "Name is required"
|
|
2347
3326
|
}));
|
|
2348
3327
|
const subdomain = opts.subdomain || (nonInteractive ? name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "") : await input({
|
|
2349
|
-
message:
|
|
3328
|
+
message: G4("ENS subdomain"),
|
|
2350
3329
|
default: name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, ""),
|
|
2351
3330
|
validate: (v) => v.length >= 3 || "Must be at least 3 characters"
|
|
2352
3331
|
}));
|
|
2353
3332
|
const description = opts.description || (nonInteractive ? `${name} \u2014 a Sherwood syndicate` : await input({
|
|
2354
|
-
message:
|
|
3333
|
+
message: G4("Description"),
|
|
2355
3334
|
default: `${name} \u2014 a Sherwood syndicate`
|
|
2356
3335
|
}));
|
|
2357
|
-
const agentIdStr = opts.agentId || (savedAgentId ? nonInteractive ? String(savedAgentId) : await input({ message:
|
|
3336
|
+
const agentIdStr = opts.agentId || (savedAgentId ? nonInteractive ? String(savedAgentId) : await input({ message: G4("Agent ID (ERC-8004)"), default: String(savedAgentId) }) : nonInteractive ? "0" : await input({ message: G4("Agent ID (ERC-8004)"), validate: (v) => /^\d+$/.test(v) || "Must be a number" }));
|
|
2358
3337
|
const openDeposits = opts.openDeposits !== void 0 ? opts.openDeposits : nonInteractive ? true : await confirm({
|
|
2359
|
-
message:
|
|
3338
|
+
message: G4("Open deposits? (anyone can deposit)"),
|
|
2360
3339
|
default: true
|
|
2361
3340
|
});
|
|
2362
3341
|
const ASSET_SYMBOLS = {
|
|
@@ -2372,14 +3351,14 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
2372
3351
|
asset = opts.asset;
|
|
2373
3352
|
} else {
|
|
2374
3353
|
const supported = Object.keys(ASSET_SYMBOLS).join(", ");
|
|
2375
|
-
console.error(
|
|
3354
|
+
console.error(chalk8.red(` Unknown asset "${opts.asset}". Use a symbol (${supported}) or a 0x address.`));
|
|
2376
3355
|
process.exit(1);
|
|
2377
3356
|
}
|
|
2378
3357
|
} else if (nonInteractive) {
|
|
2379
3358
|
asset = TOKENS().USDC !== "0x0000000000000000000000000000000000000000" ? ASSET_SYMBOLS.USDC : ASSET_SYMBOLS.WETH;
|
|
2380
3359
|
} else {
|
|
2381
3360
|
const assetChoice = await select({
|
|
2382
|
-
message:
|
|
3361
|
+
message: G4("Vault asset (what token do depositors provide?)"),
|
|
2383
3362
|
choices: [
|
|
2384
3363
|
{ name: "USDC", value: "USDC", description: "USD Coin (6 decimals)" },
|
|
2385
3364
|
{ name: "WETH", value: "WETH", description: "Wrapped Ether (18 decimals)" }
|
|
@@ -2394,26 +3373,26 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
2394
3373
|
]);
|
|
2395
3374
|
const symbol = `sw${assetSymbol}`;
|
|
2396
3375
|
console.log();
|
|
2397
|
-
console.log(
|
|
2398
|
-
|
|
2399
|
-
console.log(
|
|
2400
|
-
console.log(
|
|
2401
|
-
console.log(
|
|
2402
|
-
console.log(
|
|
2403
|
-
console.log(
|
|
2404
|
-
console.log(
|
|
2405
|
-
console.log(
|
|
2406
|
-
|
|
3376
|
+
console.log(LABEL4(" \u25C6 Review"));
|
|
3377
|
+
SEP4();
|
|
3378
|
+
console.log(W4(` Name: ${BOLD4(name)}`));
|
|
3379
|
+
console.log(W4(` ENS: ${G4(`${subdomain}.sherwoodagent.eth`)}`));
|
|
3380
|
+
console.log(W4(` Description: ${DIM4(description)}`));
|
|
3381
|
+
console.log(W4(` Agent ID: #${agentIdStr}`));
|
|
3382
|
+
console.log(W4(` Asset: ${assetSymbol} (${asset.slice(0, 10)}...)`));
|
|
3383
|
+
console.log(W4(` Share token: ${symbol}`));
|
|
3384
|
+
console.log(W4(` Open deposits: ${openDeposits ? G4("yes") : chalk8.red("no (whitelist)")}`));
|
|
3385
|
+
SEP4();
|
|
2407
3386
|
if (!nonInteractive) {
|
|
2408
|
-
const go = await confirm({ message:
|
|
3387
|
+
const go = await confirm({ message: G4("Deploy syndicate?"), default: true });
|
|
2409
3388
|
if (!go) {
|
|
2410
|
-
console.log(
|
|
3389
|
+
console.log(DIM4(" Cancelled."));
|
|
2411
3390
|
return;
|
|
2412
3391
|
}
|
|
2413
3392
|
}
|
|
2414
3393
|
let metadataURI = opts.metadataUri || "";
|
|
2415
3394
|
if (!metadataURI) {
|
|
2416
|
-
const spinner2 = ora7({ text:
|
|
3395
|
+
const spinner2 = ora7({ text: W4("Uploading metadata to IPFS..."), color: "green" }).start();
|
|
2417
3396
|
try {
|
|
2418
3397
|
const metadata = {
|
|
2419
3398
|
schema: "sherwood/syndicate/v1",
|
|
@@ -2427,14 +3406,77 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
2427
3406
|
links: {}
|
|
2428
3407
|
};
|
|
2429
3408
|
metadataURI = await uploadMetadata(metadata);
|
|
2430
|
-
spinner2.succeed(
|
|
3409
|
+
spinner2.succeed(G4(`Metadata pinned: ${DIM4(metadataURI)}`));
|
|
2431
3410
|
} catch (err) {
|
|
2432
|
-
spinner2.warn(
|
|
3411
|
+
spinner2.warn(chalk8.yellow(`IPFS upload failed \u2014 using inline metadata`));
|
|
2433
3412
|
const json = JSON.stringify({ name, description, subdomain, asset: assetSymbol, openDeposits, createdBy: "@sherwoodagent/cli" });
|
|
2434
3413
|
metadataURI = `data:application/json;base64,${Buffer.from(json).toString("base64")}`;
|
|
2435
3414
|
}
|
|
2436
3415
|
}
|
|
2437
|
-
const
|
|
3416
|
+
const existingId = await subdomainExists(subdomain);
|
|
3417
|
+
if (existingId !== null) {
|
|
3418
|
+
const existingInfo = await getSyndicate(existingId);
|
|
3419
|
+
const callerAddress = getAccount().address.toLowerCase();
|
|
3420
|
+
if (existingInfo.creator.toLowerCase() !== callerAddress) {
|
|
3421
|
+
console.error(chalk8.red(`
|
|
3422
|
+
\u2716 Subdomain '${subdomain}' is already taken by another syndicate.
|
|
3423
|
+
`));
|
|
3424
|
+
process.exit(1);
|
|
3425
|
+
}
|
|
3426
|
+
console.log(chalk8.yellow(`
|
|
3427
|
+
\u26A0 Syndicate '${subdomain}' already exists \u2014 resuming setup...
|
|
3428
|
+
`));
|
|
3429
|
+
const spinner2 = ora7({ text: W4("Resuming setup..."), color: "green" }).start();
|
|
3430
|
+
const chainId2 = getChain().id;
|
|
3431
|
+
setChainContract(chainId2, "vault", existingInfo.vault);
|
|
3432
|
+
addSyndicate(chainId2, { subdomain, vault: existingInfo.vault, role: "creator" });
|
|
3433
|
+
setPrimarySyndicate(chainId2, subdomain);
|
|
3434
|
+
spinner2.text = W4("Checking agent registration...");
|
|
3435
|
+
try {
|
|
3436
|
+
setVaultAddress(existingInfo.vault);
|
|
3437
|
+
const isAlreadyAgent = await isAgent(getAccount().address);
|
|
3438
|
+
if (!isAlreadyAgent) {
|
|
3439
|
+
spinner2.text = W4("Registering creator as agent...");
|
|
3440
|
+
await registerAgent(BigInt(agentIdStr), getAccount().address);
|
|
3441
|
+
}
|
|
3442
|
+
} catch {
|
|
3443
|
+
}
|
|
3444
|
+
spinner2.text = W4("Checking chat group...");
|
|
3445
|
+
try {
|
|
3446
|
+
const xmtp = await loadXmtp();
|
|
3447
|
+
const xmtpClient = await xmtp.getXmtpClient();
|
|
3448
|
+
try {
|
|
3449
|
+
await xmtp.getGroup(xmtpClient, subdomain);
|
|
3450
|
+
} catch {
|
|
3451
|
+
spinner2.text = W4("Creating chat group...");
|
|
3452
|
+
const groupId = await xmtp.createSyndicateGroup(xmtpClient, subdomain, opts.publicChat);
|
|
3453
|
+
await setTextRecord(subdomain, "xmtpGroupId", groupId, existingInfo.vault);
|
|
3454
|
+
cacheGroupId(subdomain, groupId);
|
|
3455
|
+
}
|
|
3456
|
+
} catch {
|
|
3457
|
+
console.warn(chalk8.yellow("\n \u26A0 Could not set up XMTP chat group"));
|
|
3458
|
+
}
|
|
3459
|
+
try {
|
|
3460
|
+
const cron = await loadCron();
|
|
3461
|
+
cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
|
|
3462
|
+
} catch {
|
|
3463
|
+
}
|
|
3464
|
+
spinner2.stop();
|
|
3465
|
+
console.log();
|
|
3466
|
+
console.log(LABEL4(" \u25C6 Syndicate Setup Resumed"));
|
|
3467
|
+
SEP4();
|
|
3468
|
+
console.log(W4(` ID: ${G4(`#${existingId}`)}`));
|
|
3469
|
+
console.log(W4(` Vault: ${G4(existingInfo.vault)}`));
|
|
3470
|
+
console.log(W4(` ENS: ${G4(`${subdomain}.sherwoodagent.eth`)}`));
|
|
3471
|
+
console.log(W4(` Chat: ${DIM4(`sherwood chat ${subdomain}`)}`));
|
|
3472
|
+
const refParam2 = getAgentId() ? `?ref=${getAgentId()}` : "";
|
|
3473
|
+
console.log(W4(` Share: ${G4(`https://sherwood.sh/syndicate/${subdomain}${refParam2}`)}`));
|
|
3474
|
+
SEP4();
|
|
3475
|
+
console.log(G4(" \u2713 Syndicate saved to ~/.sherwood/config.json (set as primary)"));
|
|
3476
|
+
console.log();
|
|
3477
|
+
return;
|
|
3478
|
+
}
|
|
3479
|
+
const spinner = ora7({ text: W4("Deploying vault via factory..."), color: "green" }).start();
|
|
2438
3480
|
const result = await createSyndicate({
|
|
2439
3481
|
creatorAgentId: BigInt(agentIdStr),
|
|
2440
3482
|
metadataURI,
|
|
@@ -2444,9 +3486,12 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
2444
3486
|
openDeposits,
|
|
2445
3487
|
subdomain
|
|
2446
3488
|
});
|
|
2447
|
-
|
|
3489
|
+
const chainId = getChain().id;
|
|
3490
|
+
setChainContract(chainId, "vault", result.vault);
|
|
3491
|
+
addSyndicate(chainId, { subdomain, vault: result.vault, role: "creator" });
|
|
3492
|
+
setPrimarySyndicate(chainId, subdomain);
|
|
2448
3493
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
2449
|
-
spinner.text =
|
|
3494
|
+
spinner.text = W4("Registering creator as agent...");
|
|
2450
3495
|
try {
|
|
2451
3496
|
setVaultAddress(result.vault);
|
|
2452
3497
|
const creatorAddress = getAccount().address;
|
|
@@ -2456,9 +3501,9 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
2456
3501
|
// agentAddress = creator EOA (direct execution)
|
|
2457
3502
|
);
|
|
2458
3503
|
} catch (regErr) {
|
|
2459
|
-
console.warn(
|
|
3504
|
+
console.warn(chalk8.yellow("\n \u26A0 Could not auto-register creator as agent \u2014 register manually with `syndicate add`"));
|
|
2460
3505
|
}
|
|
2461
|
-
spinner.text =
|
|
3506
|
+
spinner.text = W4("Setting up chat...");
|
|
2462
3507
|
try {
|
|
2463
3508
|
const xmtp = await loadXmtp();
|
|
2464
3509
|
const xmtpClient = await xmtp.getXmtpClient();
|
|
@@ -2466,35 +3511,37 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
2466
3511
|
await setTextRecord(subdomain, "xmtpGroupId", groupId, result.vault);
|
|
2467
3512
|
cacheGroupId(subdomain, groupId);
|
|
2468
3513
|
} catch {
|
|
2469
|
-
console.warn(
|
|
2470
|
-
console.warn(
|
|
3514
|
+
console.warn(chalk8.yellow("\n \u26A0 Could not create XMTP chat group"));
|
|
3515
|
+
console.warn(chalk8.dim(` Recover later with: sherwood chat ${subdomain} init`));
|
|
2471
3516
|
}
|
|
2472
3517
|
try {
|
|
2473
3518
|
const cron = await loadCron();
|
|
2474
3519
|
const cronResult = cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
|
|
2475
3520
|
if (cronResult.isOpenClaw && cronResult.registered) {
|
|
2476
|
-
console.log(
|
|
3521
|
+
console.log(G4(" \u2713 Participation crons registered (15m check + hourly summary)"));
|
|
2477
3522
|
} else if (!cronResult.isOpenClaw) {
|
|
2478
|
-
console.log(
|
|
3523
|
+
console.log(DIM4(" Tip: Set up a scheduled process to run `sherwood session check " + subdomain + "` periodically"));
|
|
2479
3524
|
}
|
|
2480
3525
|
} catch {
|
|
2481
3526
|
}
|
|
2482
3527
|
spinner.stop();
|
|
2483
3528
|
console.log();
|
|
2484
|
-
console.log(
|
|
2485
|
-
|
|
2486
|
-
console.log(
|
|
2487
|
-
console.log(
|
|
2488
|
-
console.log(
|
|
2489
|
-
console.log(
|
|
2490
|
-
console.log(
|
|
2491
|
-
console.log(
|
|
2492
|
-
|
|
2493
|
-
console.log(
|
|
3529
|
+
console.log(LABEL4(" \u25C6 Syndicate Created"));
|
|
3530
|
+
SEP4();
|
|
3531
|
+
console.log(W4(` ID: ${G4(`#${result.syndicateId}`)}`));
|
|
3532
|
+
console.log(W4(` Vault: ${G4(result.vault)}`));
|
|
3533
|
+
console.log(W4(` ENS: ${G4(`${subdomain}.sherwoodagent.eth`)}`));
|
|
3534
|
+
console.log(W4(` Metadata: ${DIM4(metadataURI.length > 50 ? metadataURI.slice(0, 50) + "..." : metadataURI)}`));
|
|
3535
|
+
console.log(W4(` Explorer: ${DIM4(getExplorerUrl(result.hash))}`));
|
|
3536
|
+
console.log(W4(` Chat: ${DIM4(`sherwood chat ${subdomain}`)}`));
|
|
3537
|
+
const refParam = getAgentId() ? `?ref=${getAgentId()}` : "";
|
|
3538
|
+
console.log(W4(` Share: ${G4(`https://sherwood.sh/syndicate/${subdomain}${refParam}`)}`));
|
|
3539
|
+
SEP4();
|
|
3540
|
+
console.log(G4(" \u2713 Syndicate saved to ~/.sherwood/config.json (set as primary)"));
|
|
2494
3541
|
console.log();
|
|
2495
3542
|
} catch (err) {
|
|
2496
|
-
console.error(
|
|
2497
|
-
\u2716 ${
|
|
3543
|
+
console.error(chalk8.red(`
|
|
3544
|
+
\u2716 ${formatContractError(err)}`));
|
|
2498
3545
|
process.exit(1);
|
|
2499
3546
|
}
|
|
2500
3547
|
});
|
|
@@ -2518,34 +3565,34 @@ syndicate.command("list").description("List active syndicates (queries subgraph,
|
|
|
2518
3565
|
}
|
|
2519
3566
|
spinner.stop();
|
|
2520
3567
|
if (syndicates.length === 0) {
|
|
2521
|
-
console.log(
|
|
3568
|
+
console.log(chalk8.dim("No active syndicates found."));
|
|
2522
3569
|
return;
|
|
2523
3570
|
}
|
|
2524
3571
|
console.log();
|
|
2525
|
-
console.log(
|
|
3572
|
+
console.log(chalk8.bold(`Active Syndicates (${syndicates.length})`));
|
|
2526
3573
|
if (!process.env.SUBGRAPH_URL) {
|
|
2527
|
-
console.log(
|
|
3574
|
+
console.log(chalk8.dim(" (Set SUBGRAPH_URL for faster indexed queries)"));
|
|
2528
3575
|
}
|
|
2529
|
-
console.log(
|
|
3576
|
+
console.log(chalk8.dim("\u2500".repeat(70)));
|
|
2530
3577
|
for (const s of syndicates) {
|
|
2531
3578
|
const ts = typeof s.createdAt === "string" ? Number(s.createdAt) : Number(s.createdAt);
|
|
2532
3579
|
const date = new Date(ts * 1e3).toLocaleDateString();
|
|
2533
3580
|
const ensName = s.subdomain ? `${s.subdomain}.sherwoodagent.eth` : "";
|
|
2534
|
-
console.log(` #${s.id} ${
|
|
2535
|
-
if (ensName) console.log(` Vault: ${
|
|
3581
|
+
console.log(` #${s.id} ${chalk8.bold(ensName || String(s.vault))}`);
|
|
3582
|
+
if (ensName) console.log(` Vault: ${chalk8.cyan(String(s.vault))}`);
|
|
2536
3583
|
console.log(` Creator: ${s.creator}`);
|
|
2537
3584
|
console.log(` Created: ${date}`);
|
|
2538
3585
|
if (s.totalDeposits) {
|
|
2539
3586
|
console.log(` Deposits: ${s.totalDeposits} USDC`);
|
|
2540
3587
|
}
|
|
2541
3588
|
if (s.metadataURI) {
|
|
2542
|
-
console.log(` Metadata: ${
|
|
3589
|
+
console.log(` Metadata: ${chalk8.dim(s.metadataURI)}`);
|
|
2543
3590
|
}
|
|
2544
3591
|
console.log();
|
|
2545
3592
|
}
|
|
2546
3593
|
} catch (err) {
|
|
2547
3594
|
spinner.fail("Failed to load syndicates");
|
|
2548
|
-
console.error(
|
|
3595
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2549
3596
|
process.exit(1);
|
|
2550
3597
|
}
|
|
2551
3598
|
});
|
|
@@ -2556,27 +3603,27 @@ syndicate.command("info").description("Display syndicate details by ID").argumen
|
|
|
2556
3603
|
const info = await getSyndicate(id);
|
|
2557
3604
|
spinner.stop();
|
|
2558
3605
|
if (!info.vault || info.vault === "0x0000000000000000000000000000000000000000") {
|
|
2559
|
-
console.log(
|
|
3606
|
+
console.log(chalk8.red(`Syndicate #${id} not found.`));
|
|
2560
3607
|
process.exit(1);
|
|
2561
3608
|
}
|
|
2562
3609
|
const date = new Date(Number(info.createdAt) * 1e3).toLocaleDateString();
|
|
2563
3610
|
console.log();
|
|
2564
|
-
console.log(
|
|
2565
|
-
console.log(
|
|
3611
|
+
console.log(chalk8.bold(`Syndicate #${info.id}`));
|
|
3612
|
+
console.log(chalk8.dim("\u2500".repeat(40)));
|
|
2566
3613
|
if (info.subdomain) {
|
|
2567
|
-
console.log(` ENS: ${
|
|
3614
|
+
console.log(` ENS: ${chalk8.bold(`${info.subdomain}.sherwoodagent.eth`)}`);
|
|
2568
3615
|
}
|
|
2569
|
-
console.log(` Vault: ${
|
|
3616
|
+
console.log(` Vault: ${chalk8.cyan(info.vault)}`);
|
|
2570
3617
|
console.log(` Creator: ${info.creator}`);
|
|
2571
3618
|
console.log(` Created: ${date}`);
|
|
2572
|
-
console.log(` Active: ${info.active ?
|
|
3619
|
+
console.log(` Active: ${info.active ? chalk8.green("yes") : chalk8.red("no")}`);
|
|
2573
3620
|
if (info.metadataURI) {
|
|
2574
|
-
console.log(` Metadata: ${
|
|
3621
|
+
console.log(` Metadata: ${chalk8.dim(info.metadataURI)}`);
|
|
2575
3622
|
}
|
|
2576
3623
|
setVaultAddress(info.vault);
|
|
2577
3624
|
const vaultInfo = await getVaultInfo();
|
|
2578
3625
|
console.log();
|
|
2579
|
-
console.log(
|
|
3626
|
+
console.log(chalk8.bold(" Vault Stats"));
|
|
2580
3627
|
console.log(` Total Assets: ${vaultInfo.totalAssets}`);
|
|
2581
3628
|
console.log(` Agent Count: ${vaultInfo.agentCount}`);
|
|
2582
3629
|
console.log(` Redemptions Locked: ${vaultInfo.redemptionsLocked}`);
|
|
@@ -2584,12 +3631,12 @@ syndicate.command("info").description("Display syndicate details by ID").argumen
|
|
|
2584
3631
|
console.log();
|
|
2585
3632
|
} catch (err) {
|
|
2586
3633
|
spinner.fail("Failed to load syndicate info");
|
|
2587
|
-
console.error(
|
|
3634
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2588
3635
|
process.exit(1);
|
|
2589
3636
|
}
|
|
2590
3637
|
});
|
|
2591
3638
|
syndicate.command("update-metadata").description("Update syndicate metadata (creator only)").requiredOption("--id <id>", "Syndicate ID").option("--name <name>", "Syndicate name").option("--description <text>", "Short description").option("--uri <uri>", "Direct metadata URI (skips IPFS upload)").action(async (opts) => {
|
|
2592
|
-
const spinner = ora7({ text:
|
|
3639
|
+
const spinner = ora7({ text: W4("Loading syndicate..."), color: "green" }).start();
|
|
2593
3640
|
try {
|
|
2594
3641
|
const syndicateId = BigInt(opts.id);
|
|
2595
3642
|
let metadataURI = opts.uri;
|
|
@@ -2601,7 +3648,7 @@ syndicate.command("update-metadata").description("Update syndicate metadata (cre
|
|
|
2601
3648
|
}
|
|
2602
3649
|
const name = opts.name || info.subdomain;
|
|
2603
3650
|
const description = opts.description || `${name} \u2014 a Sherwood syndicate on ${info.subdomain}.sherwoodagent.eth`;
|
|
2604
|
-
spinner.text =
|
|
3651
|
+
spinner.text = W4("Uploading metadata to IPFS...");
|
|
2605
3652
|
const metadata = {
|
|
2606
3653
|
schema: "sherwood/syndicate/v1",
|
|
2607
3654
|
name,
|
|
@@ -2612,15 +3659,15 @@ syndicate.command("update-metadata").description("Update syndicate metadata (cre
|
|
|
2612
3659
|
links: {}
|
|
2613
3660
|
};
|
|
2614
3661
|
metadataURI = await uploadMetadata(metadata);
|
|
2615
|
-
spinner.text =
|
|
3662
|
+
spinner.text = W4("Updating on-chain metadata...");
|
|
2616
3663
|
}
|
|
2617
3664
|
const hash = await updateMetadata(syndicateId, metadataURI);
|
|
2618
|
-
spinner.succeed(
|
|
2619
|
-
console.log(
|
|
2620
|
-
console.log(
|
|
3665
|
+
spinner.succeed(G4(`Metadata updated`));
|
|
3666
|
+
console.log(DIM4(` IPFS: ${metadataURI}`));
|
|
3667
|
+
console.log(DIM4(` ${getExplorerUrl(hash)}`));
|
|
2621
3668
|
} catch (err) {
|
|
2622
3669
|
spinner.fail("Metadata update failed");
|
|
2623
|
-
console.error(
|
|
3670
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2624
3671
|
process.exit(1);
|
|
2625
3672
|
}
|
|
2626
3673
|
});
|
|
@@ -2631,10 +3678,10 @@ syndicate.command("approve-depositor").description("Approve an address to deposi
|
|
|
2631
3678
|
const depositor = validateAddress(opts.depositor, "depositor");
|
|
2632
3679
|
const hash = await approveDepositor(depositor);
|
|
2633
3680
|
spinner.succeed(`Depositor approved: ${hash}`);
|
|
2634
|
-
console.log(
|
|
3681
|
+
console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
|
|
2635
3682
|
} catch (err) {
|
|
2636
3683
|
spinner.fail("Approval failed");
|
|
2637
|
-
console.error(
|
|
3684
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2638
3685
|
process.exit(1);
|
|
2639
3686
|
}
|
|
2640
3687
|
});
|
|
@@ -2645,10 +3692,10 @@ syndicate.command("remove-depositor").description("Remove an address from the de
|
|
|
2645
3692
|
const depositor = validateAddress(opts.depositor, "depositor");
|
|
2646
3693
|
const hash = await removeDepositor(depositor);
|
|
2647
3694
|
spinner.succeed(`Depositor removed: ${hash}`);
|
|
2648
|
-
console.log(
|
|
3695
|
+
console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
|
|
2649
3696
|
} catch (err) {
|
|
2650
3697
|
spinner.fail("Removal failed");
|
|
2651
|
-
console.error(
|
|
3698
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2652
3699
|
process.exit(1);
|
|
2653
3700
|
}
|
|
2654
3701
|
});
|
|
@@ -2670,7 +3717,7 @@ syndicate.command("add").description("Register an agent on a syndicate vault (cr
|
|
|
2670
3717
|
agentWallet
|
|
2671
3718
|
);
|
|
2672
3719
|
spinner.succeed(`Agent registered: ${hash}`);
|
|
2673
|
-
console.log(
|
|
3720
|
+
console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
|
|
2674
3721
|
try {
|
|
2675
3722
|
const xmtp = await loadXmtp();
|
|
2676
3723
|
const xmtpClient = await xmtp.getXmtpClient();
|
|
@@ -2682,26 +3729,66 @@ syndicate.command("add").description("Register an agent on a syndicate vault (cr
|
|
|
2682
3729
|
syndicate: subdomain,
|
|
2683
3730
|
timestamp: Math.floor(Date.now() / 1e3)
|
|
2684
3731
|
});
|
|
2685
|
-
console.log(
|
|
3732
|
+
console.log(chalk8.dim(` Added to chat: ${subdomain}`));
|
|
2686
3733
|
} catch {
|
|
2687
|
-
console.warn(
|
|
2688
|
-
console.warn(
|
|
3734
|
+
console.warn(chalk8.yellow(" \u26A0 Could not add agent to chat group"));
|
|
3735
|
+
console.warn(chalk8.dim(` If no group exists, run: sherwood chat ${subdomain} init`));
|
|
2689
3736
|
}
|
|
2690
3737
|
} catch (err) {
|
|
2691
3738
|
spinner.fail("Registration failed");
|
|
2692
|
-
console.error(
|
|
3739
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2693
3740
|
process.exit(1);
|
|
2694
3741
|
}
|
|
2695
3742
|
});
|
|
2696
|
-
syndicate.command("
|
|
3743
|
+
syndicate.command("share").description("Print the shareable dashboard URL for your syndicate").option("--subdomain <name>", "Syndicate subdomain (default: primary)").action(async (opts) => {
|
|
3744
|
+
try {
|
|
3745
|
+
const subdomain = opts.subdomain || getPrimarySyndicate(getChain().id)?.subdomain;
|
|
3746
|
+
if (!subdomain) {
|
|
3747
|
+
console.error(chalk8.red(" No syndicate configured. Pass --subdomain <name> or run 'syndicate create/join' first."));
|
|
3748
|
+
process.exit(1);
|
|
3749
|
+
}
|
|
3750
|
+
const syndicateInfo = await resolveSyndicate(subdomain);
|
|
3751
|
+
const agentId = getAgentId();
|
|
3752
|
+
const refParam = agentId ? `?ref=${agentId}` : "";
|
|
3753
|
+
const dashboardUrl = `https://sherwood.sh/syndicate/${subdomain}${refParam}`;
|
|
3754
|
+
console.log();
|
|
3755
|
+
console.log(LABEL4(" \u25C6 Share Your Syndicate"));
|
|
3756
|
+
SEP4();
|
|
3757
|
+
console.log(W4(` Dashboard: ${G4(dashboardUrl)}`));
|
|
3758
|
+
console.log(W4(` ENS: ${DIM4(`${subdomain}.sherwoodagent.eth`)}`));
|
|
3759
|
+
console.log(W4(` Vault: ${DIM4(syndicateInfo.vault)}`));
|
|
3760
|
+
console.log(W4(` Join CLI: ${DIM4(`sherwood syndicate join --subdomain ${subdomain}`)}`));
|
|
3761
|
+
SEP4();
|
|
3762
|
+
try {
|
|
3763
|
+
const { exec } = await import("child_process");
|
|
3764
|
+
const platform = (await import("os")).platform();
|
|
3765
|
+
const cmd = platform === "darwin" ? "pbcopy" : "xclip -selection clipboard";
|
|
3766
|
+
const proc = exec(cmd);
|
|
3767
|
+
proc.stdin?.write(dashboardUrl);
|
|
3768
|
+
proc.stdin?.end();
|
|
3769
|
+
console.log(G4(" \u2713 Dashboard URL copied to clipboard"));
|
|
3770
|
+
} catch {
|
|
3771
|
+
}
|
|
3772
|
+
console.log();
|
|
3773
|
+
} catch (err) {
|
|
3774
|
+
console.error(chalk8.red(` \u2716 ${formatContractError(err)}`));
|
|
3775
|
+
process.exit(1);
|
|
3776
|
+
}
|
|
3777
|
+
});
|
|
3778
|
+
syndicate.command("join").description("Request to join a syndicate (creates an EAS attestation)").option("--subdomain <name>", "Syndicate subdomain to join").option("--ref <agentId>", "Referrer agent ID (from invite link)").option("--message <text>", "Message to the creator", "Requesting to join your syndicate").action(async (opts) => {
|
|
2697
3779
|
const spinner = ora7("Resolving syndicate...").start();
|
|
2698
3780
|
try {
|
|
3781
|
+
const subdomain = opts.subdomain || getPrimarySyndicate(getChain().id)?.subdomain;
|
|
3782
|
+
if (!subdomain) {
|
|
3783
|
+
spinner.fail("No syndicate configured. Pass --subdomain <name> or run 'syndicate create' first.");
|
|
3784
|
+
process.exit(1);
|
|
3785
|
+
}
|
|
2699
3786
|
const agentId = getAgentId();
|
|
2700
3787
|
if (!agentId) {
|
|
2701
3788
|
spinner.fail("No agent identity found. Run 'sherwood identity mint' first.");
|
|
2702
3789
|
process.exit(1);
|
|
2703
3790
|
}
|
|
2704
|
-
const syndicate2 = await resolveSyndicate(
|
|
3791
|
+
const syndicate2 = await resolveSyndicate(subdomain);
|
|
2705
3792
|
const callerAddress = getAccount().address;
|
|
2706
3793
|
spinner.text = "Checking membership...";
|
|
2707
3794
|
setVaultAddress(syndicate2.vault);
|
|
@@ -2711,17 +3798,17 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
|
|
|
2711
3798
|
try {
|
|
2712
3799
|
const xmtp = await loadXmtp();
|
|
2713
3800
|
await xmtp.getXmtpClient();
|
|
2714
|
-
console.log(
|
|
3801
|
+
console.log(chalk8.dim(" XMTP identity ready"));
|
|
2715
3802
|
} catch {
|
|
2716
|
-
console.warn(
|
|
3803
|
+
console.warn(chalk8.yellow(" \u26A0 Could not initialize XMTP identity"));
|
|
2717
3804
|
}
|
|
2718
3805
|
try {
|
|
2719
3806
|
const cron = await loadCron();
|
|
2720
|
-
const cronResult = cron.registerSyndicateCrons(
|
|
3807
|
+
const cronResult = cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
|
|
2721
3808
|
if (cronResult.isOpenClaw && cronResult.registered) {
|
|
2722
|
-
console.log(
|
|
3809
|
+
console.log(chalk8.green(" \u2713 Participation crons registered"));
|
|
2723
3810
|
} else if (!cronResult.isOpenClaw) {
|
|
2724
|
-
console.log(
|
|
3811
|
+
console.log(chalk8.dim(" Tip: Set up a scheduled process to run `sherwood session check " + subdomain + "` periodically"));
|
|
2725
3812
|
}
|
|
2726
3813
|
} catch {
|
|
2727
3814
|
}
|
|
@@ -2734,24 +3821,26 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
|
|
|
2734
3821
|
);
|
|
2735
3822
|
if (existingRequest) {
|
|
2736
3823
|
spinner.succeed("You already have a pending join request for this syndicate");
|
|
2737
|
-
console.log(
|
|
2738
|
-
console.log(
|
|
3824
|
+
console.log(chalk8.dim(` Attestation: ${existingRequest.uid}`));
|
|
3825
|
+
console.log(chalk8.dim(` Submitted: ${new Date(existingRequest.time * 1e3).toLocaleString()}`));
|
|
2739
3826
|
try {
|
|
2740
3827
|
const xmtp = await loadXmtp();
|
|
2741
3828
|
await xmtp.getXmtpClient();
|
|
2742
|
-
console.log(
|
|
3829
|
+
console.log(chalk8.dim(" XMTP identity ready"));
|
|
2743
3830
|
} catch {
|
|
2744
|
-
console.warn(
|
|
3831
|
+
console.warn(chalk8.yellow(" \u26A0 Could not initialize XMTP identity"));
|
|
2745
3832
|
}
|
|
2746
3833
|
return;
|
|
2747
3834
|
}
|
|
2748
3835
|
spinner.text = "Creating join request attestation...";
|
|
3836
|
+
const referrerAgentId = opts.ref ? parseInt(opts.ref, 10) : void 0;
|
|
2749
3837
|
const { uid, hash } = await createJoinRequest(
|
|
2750
3838
|
syndicate2.id,
|
|
2751
3839
|
BigInt(agentId),
|
|
2752
3840
|
syndicate2.vault,
|
|
2753
3841
|
syndicate2.creator,
|
|
2754
|
-
opts.message
|
|
3842
|
+
opts.message,
|
|
3843
|
+
referrerAgentId
|
|
2755
3844
|
);
|
|
2756
3845
|
try {
|
|
2757
3846
|
spinner.text = "Registering XMTP identity...";
|
|
@@ -2760,34 +3849,37 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
|
|
|
2760
3849
|
spinner.succeed("Join request created (XMTP identity ready)");
|
|
2761
3850
|
} catch {
|
|
2762
3851
|
spinner.succeed("Join request created");
|
|
2763
|
-
console.warn(
|
|
3852
|
+
console.warn(chalk8.yellow(" \u26A0 Could not initialize XMTP identity \u2014 creator may not be able to auto-add you to chat"));
|
|
2764
3853
|
}
|
|
3854
|
+
addSyndicate(getChain().id, { subdomain, vault: syndicate2.vault, role: "agent" });
|
|
3855
|
+
setPrimarySyndicate(getChain().id, subdomain);
|
|
2765
3856
|
try {
|
|
2766
3857
|
const cron = await loadCron();
|
|
2767
|
-
const cronResult = cron.registerSyndicateCrons(
|
|
3858
|
+
const cronResult = cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
|
|
2768
3859
|
if (cronResult.isOpenClaw && cronResult.registered) {
|
|
2769
|
-
console.log(
|
|
3860
|
+
console.log(G4(" \u2713 Participation crons registered (will activate after approval)"));
|
|
2770
3861
|
} else if (!cronResult.isOpenClaw) {
|
|
2771
|
-
console.log(
|
|
3862
|
+
console.log(DIM4(" Tip: Set up a scheduled process to run `sherwood session check " + subdomain + "` periodically"));
|
|
2772
3863
|
}
|
|
2773
3864
|
} catch {
|
|
2774
3865
|
}
|
|
2775
3866
|
console.log();
|
|
2776
|
-
console.log(
|
|
2777
|
-
|
|
2778
|
-
console.log(
|
|
2779
|
-
console.log(
|
|
2780
|
-
console.log(
|
|
2781
|
-
console.log(
|
|
2782
|
-
console.log(
|
|
2783
|
-
console.log(
|
|
2784
|
-
|
|
2785
|
-
console.log(
|
|
2786
|
-
console.log(
|
|
3867
|
+
console.log(LABEL4(" \u25C6 Join Request Submitted"));
|
|
3868
|
+
SEP4();
|
|
3869
|
+
console.log(W4(` Syndicate: ${G4(`${subdomain}.sherwoodagent.eth`)}`));
|
|
3870
|
+
console.log(W4(` Agent ID: #${agentId}`));
|
|
3871
|
+
console.log(W4(` Creator: ${DIM4(syndicate2.creator)}`));
|
|
3872
|
+
console.log(W4(` Attestation: ${DIM4(uid)}`));
|
|
3873
|
+
console.log(W4(` EAS Scan: ${DIM4(getEasScanUrl(uid))}`));
|
|
3874
|
+
console.log(W4(` Explorer: ${DIM4(getExplorerUrl(hash))}`));
|
|
3875
|
+
SEP4();
|
|
3876
|
+
console.log(G4(" \u2713 Syndicate saved to ~/.sherwood/config.json (set as primary)"));
|
|
3877
|
+
console.log(G4(" \u2713 The creator can review with:"));
|
|
3878
|
+
console.log(DIM4(` sherwood syndicate requests --subdomain ${subdomain}`));
|
|
2787
3879
|
console.log();
|
|
2788
3880
|
} catch (err) {
|
|
2789
3881
|
spinner.fail("Join request failed");
|
|
2790
|
-
console.error(
|
|
3882
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2791
3883
|
process.exit(1);
|
|
2792
3884
|
}
|
|
2793
3885
|
});
|
|
@@ -2797,10 +3889,11 @@ syndicate.command("requests").description("View pending join requests for a synd
|
|
|
2797
3889
|
let creatorAddress;
|
|
2798
3890
|
let subdomain;
|
|
2799
3891
|
let syndicateVault;
|
|
2800
|
-
|
|
2801
|
-
|
|
3892
|
+
const resolvedSubdomain = opts.subdomain || getPrimarySyndicate(getChain().id)?.subdomain;
|
|
3893
|
+
if (resolvedSubdomain) {
|
|
3894
|
+
const syndicateInfo = await resolveSyndicate(resolvedSubdomain);
|
|
2802
3895
|
creatorAddress = syndicateInfo.creator;
|
|
2803
|
-
subdomain =
|
|
3896
|
+
subdomain = resolvedSubdomain;
|
|
2804
3897
|
syndicateVault = syndicateInfo.vault;
|
|
2805
3898
|
} else {
|
|
2806
3899
|
resolveVault(opts);
|
|
@@ -2827,37 +3920,43 @@ syndicate.command("requests").description("View pending join requests for a synd
|
|
|
2827
3920
|
);
|
|
2828
3921
|
spinner.stop();
|
|
2829
3922
|
if (requests.length === 0) {
|
|
2830
|
-
console.log(
|
|
3923
|
+
console.log(DIM4("\n No pending join requests.\n"));
|
|
2831
3924
|
return;
|
|
2832
3925
|
}
|
|
2833
3926
|
console.log();
|
|
2834
|
-
console.log(
|
|
2835
|
-
|
|
3927
|
+
console.log(LABEL4(` \u25C6 Pending Join Requests (${requests.length})`));
|
|
3928
|
+
SEP4();
|
|
2836
3929
|
for (let i = 0; i < requests.length; i++) {
|
|
2837
3930
|
const req = requests[i];
|
|
2838
3931
|
const date = new Date(req.time * 1e3).toLocaleString();
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
console.log(
|
|
2842
|
-
console.log(
|
|
3932
|
+
const referrer = parseReferrer(req.decoded.message);
|
|
3933
|
+
const cleanMessage = stripReferrerPrefix(req.decoded.message);
|
|
3934
|
+
console.log(W4(` ${i + 1}. Agent #${req.decoded.agentId} ${DIM4(`(${req.attester})`)}`));
|
|
3935
|
+
console.log(DIM4(` Message: "${cleanMessage}"`));
|
|
3936
|
+
if (referrer !== null) {
|
|
3937
|
+
console.log(G4(` Referred by: Agent #${referrer}`));
|
|
3938
|
+
}
|
|
3939
|
+
console.log(DIM4(` Requested: ${date}`));
|
|
3940
|
+
console.log(DIM4(` Attestation: ${req.uid}`));
|
|
2843
3941
|
console.log();
|
|
2844
3942
|
}
|
|
2845
|
-
console.log(
|
|
2846
|
-
console.log(
|
|
2847
|
-
console.log(
|
|
2848
|
-
console.log(
|
|
3943
|
+
console.log(G4(" To approve:"));
|
|
3944
|
+
console.log(DIM4(` sherwood syndicate approve --agent-id <id> --wallet <addr>`));
|
|
3945
|
+
console.log(G4(" To reject:"));
|
|
3946
|
+
console.log(DIM4(` sherwood syndicate reject --attestation <uid>`));
|
|
2849
3947
|
console.log();
|
|
2850
3948
|
} catch (err) {
|
|
2851
3949
|
spinner.fail("Failed to load requests");
|
|
2852
|
-
console.error(
|
|
3950
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2853
3951
|
process.exit(1);
|
|
2854
3952
|
}
|
|
2855
3953
|
});
|
|
2856
3954
|
syndicate.command("approve").description("Approve an agent join request (registers agent + creates EAS approval)").option("--vault <address>", "Vault address (default: from config)").option("--subdomain <name>", "Syndicate subdomain (alternative to --vault)").requiredOption("--agent-id <id>", "Agent's ERC-8004 identity token ID").requiredOption("--wallet <address>", "Agent wallet address").action(async (opts) => {
|
|
2857
3955
|
const spinner = ora7("Verifying creator...").start();
|
|
2858
3956
|
try {
|
|
2859
|
-
|
|
2860
|
-
|
|
3957
|
+
const approveSubdomain = opts.subdomain || getPrimarySyndicate(getChain().id)?.subdomain;
|
|
3958
|
+
if (approveSubdomain && !opts.vault) {
|
|
3959
|
+
const syndicateInfo = await resolveSyndicate(approveSubdomain);
|
|
2861
3960
|
setVaultAddress(syndicateInfo.vault);
|
|
2862
3961
|
} else {
|
|
2863
3962
|
resolveVault(opts);
|
|
@@ -2878,11 +3977,11 @@ syndicate.command("approve").description("Approve an agent join request (registe
|
|
|
2878
3977
|
agentWallet
|
|
2879
3978
|
);
|
|
2880
3979
|
agentWasRegistered = true;
|
|
2881
|
-
console.log(
|
|
3980
|
+
console.log(DIM4(` Agent registered: ${getExplorerUrl(regHash)}`));
|
|
2882
3981
|
} catch (regErr) {
|
|
2883
3982
|
const msg = regErr instanceof Error ? regErr.message : String(regErr);
|
|
2884
3983
|
if (msg.includes("0xe098d3ee") || msg.includes("AgentAlreadyRegistered")) {
|
|
2885
|
-
console.log(
|
|
3984
|
+
console.log(DIM4(" Agent already registered on vault \u2014 skipping"));
|
|
2886
3985
|
} else {
|
|
2887
3986
|
throw regErr;
|
|
2888
3987
|
}
|
|
@@ -2898,7 +3997,7 @@ syndicate.command("approve").description("Approve an agent join request (registe
|
|
|
2898
3997
|
let approvalUid;
|
|
2899
3998
|
if (alreadyApproved) {
|
|
2900
3999
|
approvalUid = alreadyApproved.uid;
|
|
2901
|
-
console.log(
|
|
4000
|
+
console.log(DIM4(` Approval attestation already exists \u2014 skipping`));
|
|
2902
4001
|
} else {
|
|
2903
4002
|
spinner.text = "Creating approval attestation...";
|
|
2904
4003
|
const result = await createApproval(
|
|
@@ -2921,23 +4020,23 @@ syndicate.command("approve").description("Approve an agent join request (registe
|
|
|
2921
4020
|
syndicate: subdomain,
|
|
2922
4021
|
timestamp: Math.floor(Date.now() / 1e3)
|
|
2923
4022
|
});
|
|
2924
|
-
console.log(
|
|
4023
|
+
console.log(DIM4(` Added to chat: ${subdomain}`));
|
|
2925
4024
|
} catch {
|
|
2926
|
-
console.warn(
|
|
2927
|
-
console.warn(
|
|
4025
|
+
console.warn(chalk8.yellow(" \u26A0 Could not add agent to chat group"));
|
|
4026
|
+
console.warn(chalk8.dim(` If no group exists, run: sherwood chat ${subdomain} init`));
|
|
2928
4027
|
}
|
|
2929
4028
|
spinner.succeed("Agent approved and registered");
|
|
2930
4029
|
console.log();
|
|
2931
|
-
console.log(
|
|
2932
|
-
|
|
2933
|
-
console.log(
|
|
2934
|
-
console.log(
|
|
2935
|
-
console.log(
|
|
2936
|
-
console.log(
|
|
2937
|
-
|
|
4030
|
+
console.log(LABEL4(" \u25C6 Agent Approved"));
|
|
4031
|
+
SEP4();
|
|
4032
|
+
console.log(W4(` Agent ID: #${opts.agentId}`));
|
|
4033
|
+
console.log(W4(` Wallet: ${G4(opts.wallet)}`));
|
|
4034
|
+
console.log(W4(` Approval: ${DIM4(approvalUid)}`));
|
|
4035
|
+
console.log(W4(` EAS Scan: ${DIM4(getEasScanUrl(approvalUid))}`));
|
|
4036
|
+
SEP4();
|
|
2938
4037
|
} catch (err) {
|
|
2939
4038
|
spinner.fail("Approval failed");
|
|
2940
|
-
console.error(
|
|
4039
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2941
4040
|
process.exit(1);
|
|
2942
4041
|
}
|
|
2943
4042
|
});
|
|
@@ -2949,26 +4048,134 @@ syndicate.command("reject").description("Reject a join request by revoking its a
|
|
|
2949
4048
|
opts.attestation
|
|
2950
4049
|
);
|
|
2951
4050
|
spinner.succeed("Join request rejected");
|
|
2952
|
-
console.log(
|
|
4051
|
+
console.log(DIM4(` ${getExplorerUrl(hash)}`));
|
|
2953
4052
|
} catch (err) {
|
|
2954
4053
|
spinner.fail("Rejection failed");
|
|
2955
|
-
console.error(
|
|
4054
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
4055
|
+
process.exit(1);
|
|
4056
|
+
}
|
|
4057
|
+
});
|
|
4058
|
+
syndicate.command("leave").description("Leave a syndicate \u2014 removes participation crons and session state").option("--subdomain <name>", "Syndicate subdomain to leave").action(async (opts) => {
|
|
4059
|
+
const subdomain = opts.subdomain || getPrimarySyndicate(getChain().id)?.subdomain;
|
|
4060
|
+
if (!subdomain) {
|
|
4061
|
+
console.error(chalk8.red(" No syndicate configured. Pass --subdomain <name>."));
|
|
4062
|
+
process.exit(1);
|
|
4063
|
+
}
|
|
4064
|
+
const spinner = ora7("Cleaning up...").start();
|
|
4065
|
+
try {
|
|
4066
|
+
let cronsRemoved = false;
|
|
4067
|
+
try {
|
|
4068
|
+
const cron = await loadCron();
|
|
4069
|
+
const result = cron.unregisterSyndicateCrons(subdomain, isTestnet());
|
|
4070
|
+
cronsRemoved = result.removed;
|
|
4071
|
+
} catch {
|
|
4072
|
+
}
|
|
4073
|
+
const { resetSession } = await import("./session-XAW5UIXP.js");
|
|
4074
|
+
resetSession(subdomain);
|
|
4075
|
+
spinner.succeed("Left syndicate");
|
|
4076
|
+
if (cronsRemoved) {
|
|
4077
|
+
console.log(G4(" \u2713 Participation crons removed"));
|
|
4078
|
+
}
|
|
4079
|
+
console.log(G4(" \u2713 Session state cleared"));
|
|
4080
|
+
console.log();
|
|
4081
|
+
console.log(chalk8.dim(" Note: This does not remove you on-chain. To exit your position:"));
|
|
4082
|
+
console.log(chalk8.dim(" sherwood vault balance \u2014 check your LP share balance"));
|
|
4083
|
+
console.log(chalk8.dim(" Redeem shares via the vault contract or dashboard"));
|
|
4084
|
+
console.log();
|
|
4085
|
+
} catch (err) {
|
|
4086
|
+
spinner.fail("Leave failed");
|
|
4087
|
+
console.error(chalk8.red(err instanceof Error ? err.message : String(err)));
|
|
4088
|
+
process.exit(1);
|
|
4089
|
+
}
|
|
4090
|
+
});
|
|
4091
|
+
syndicate.command("set-primary").description("Set the active syndicate for CLI commands (auto-used when --subdomain is omitted)").option("--subdomain <name>", "Syndicate subdomain").option("--vault <address>", "Vault address").action(async (opts) => {
|
|
4092
|
+
try {
|
|
4093
|
+
const chainId = getChain().id;
|
|
4094
|
+
const memberships = getSyndicates(chainId);
|
|
4095
|
+
if (!opts.subdomain && !opts.vault) {
|
|
4096
|
+
const primary = getPrimarySyndicate(chainId);
|
|
4097
|
+
if (memberships.length === 0) {
|
|
4098
|
+
console.log(DIM4("\n No syndicates saved. Create or join one first.\n"));
|
|
4099
|
+
return;
|
|
4100
|
+
}
|
|
4101
|
+
console.log();
|
|
4102
|
+
console.log(LABEL4(" \u25C6 Your Syndicates"));
|
|
4103
|
+
SEP4();
|
|
4104
|
+
for (const m of memberships) {
|
|
4105
|
+
const isPrimary = primary?.subdomain === m.subdomain;
|
|
4106
|
+
const marker = isPrimary ? G4(" \u2605") : "";
|
|
4107
|
+
console.log(W4(` ${m.subdomain}.sherwoodagent.eth${marker} ${DIM4(`(${m.role})`)}`));
|
|
4108
|
+
console.log(DIM4(` vault: ${m.vault}`));
|
|
4109
|
+
}
|
|
4110
|
+
SEP4();
|
|
4111
|
+
if (memberships.length > 1) {
|
|
4112
|
+
console.log(DIM4(" Switch with: sherwood syndicate set-primary --subdomain <name>"));
|
|
4113
|
+
}
|
|
4114
|
+
console.log();
|
|
4115
|
+
return;
|
|
4116
|
+
}
|
|
4117
|
+
let target;
|
|
4118
|
+
if (opts.subdomain) {
|
|
4119
|
+
const found = memberships.find((m) => m.subdomain === opts.subdomain);
|
|
4120
|
+
if (!found) {
|
|
4121
|
+
const syndicateInfo = await resolveSyndicate(opts.subdomain);
|
|
4122
|
+
addSyndicate(chainId, { subdomain: opts.subdomain, vault: syndicateInfo.vault, role: "agent" });
|
|
4123
|
+
}
|
|
4124
|
+
target = opts.subdomain;
|
|
4125
|
+
} else if (opts.vault) {
|
|
4126
|
+
const found = memberships.find(
|
|
4127
|
+
(m) => m.vault.toLowerCase() === opts.vault.toLowerCase()
|
|
4128
|
+
);
|
|
4129
|
+
if (found) {
|
|
4130
|
+
target = found.subdomain;
|
|
4131
|
+
} else {
|
|
4132
|
+
const syndicateInfo = await resolveVaultSyndicate(validateAddress(opts.vault, "vault"));
|
|
4133
|
+
addSyndicate(chainId, { subdomain: syndicateInfo.subdomain, vault: opts.vault, role: "agent" });
|
|
4134
|
+
target = syndicateInfo.subdomain;
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
if (!target) {
|
|
4138
|
+
console.error(chalk8.red(" Could not resolve syndicate. Pass --subdomain or --vault."));
|
|
4139
|
+
process.exit(1);
|
|
4140
|
+
}
|
|
4141
|
+
setPrimarySyndicate(chainId, target);
|
|
4142
|
+
console.log(G4(`
|
|
4143
|
+
\u2713 Primary syndicate set to ${target}.sherwoodagent.eth
|
|
4144
|
+
`));
|
|
4145
|
+
} catch (err) {
|
|
4146
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2956
4147
|
process.exit(1);
|
|
2957
4148
|
}
|
|
2958
4149
|
});
|
|
2959
4150
|
var vaultCmd = program.command("vault");
|
|
2960
|
-
vaultCmd.command("deposit").description("Deposit into a vault").option("--vault <address>", "Vault address (default: from config)").requiredOption("--amount <amount>", "Amount to deposit (in asset units)").action(async (opts) => {
|
|
4151
|
+
vaultCmd.command("deposit").description("Deposit into a vault").option("--vault <address>", "Vault address (default: from config)").requiredOption("--amount <amount>", "Amount to deposit (in asset units)").option("--use-eth", "Auto-wrap ETH \u2192 WETH before depositing (for WETH vaults)").action(async (opts) => {
|
|
2961
4152
|
resolveVault(opts);
|
|
2962
4153
|
const decimals = await getAssetDecimals();
|
|
2963
4154
|
const amount = parseUnits5(opts.amount, decimals);
|
|
4155
|
+
try {
|
|
4156
|
+
if (!opts.useEth) {
|
|
4157
|
+
await preflightDeposit(amount);
|
|
4158
|
+
}
|
|
4159
|
+
} catch (err) {
|
|
4160
|
+
console.error(chalk8.red(`
|
|
4161
|
+
\u2716 ${err instanceof Error ? err.message : String(err)}
|
|
4162
|
+
`));
|
|
4163
|
+
process.exit(1);
|
|
4164
|
+
}
|
|
2964
4165
|
const spinner = ora7(`Depositing ${opts.amount}...`).start();
|
|
2965
4166
|
try {
|
|
2966
|
-
|
|
4167
|
+
let hash;
|
|
4168
|
+
if (opts.useEth) {
|
|
4169
|
+
spinner.text = `Wrapping ETH + depositing ${opts.amount}...`;
|
|
4170
|
+
hash = await depositWithEthWrap(amount);
|
|
4171
|
+
} else {
|
|
4172
|
+
hash = await deposit(amount);
|
|
4173
|
+
}
|
|
2967
4174
|
spinner.succeed(`Deposited: ${hash}`);
|
|
2968
|
-
console.log(
|
|
4175
|
+
console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
|
|
2969
4176
|
} catch (err) {
|
|
2970
4177
|
spinner.fail("Deposit failed");
|
|
2971
|
-
console.error(
|
|
4178
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2972
4179
|
process.exit(1);
|
|
2973
4180
|
}
|
|
2974
4181
|
});
|
|
@@ -2979,8 +4186,8 @@ vaultCmd.command("info").description("Display vault state").option("--vault <add
|
|
|
2979
4186
|
const info = await getVaultInfo();
|
|
2980
4187
|
spinner.stop();
|
|
2981
4188
|
console.log();
|
|
2982
|
-
console.log(
|
|
2983
|
-
console.log(
|
|
4189
|
+
console.log(chalk8.bold("Vault Info"));
|
|
4190
|
+
console.log(chalk8.dim("\u2500".repeat(40)));
|
|
2984
4191
|
console.log(` Address: ${info.address}`);
|
|
2985
4192
|
console.log(` Total Assets: ${info.totalAssets}`);
|
|
2986
4193
|
console.log(` Agent Count: ${info.agentCount}`);
|
|
@@ -2989,7 +4196,7 @@ vaultCmd.command("info").description("Display vault state").option("--vault <add
|
|
|
2989
4196
|
console.log();
|
|
2990
4197
|
} catch (err) {
|
|
2991
4198
|
spinner.fail("Failed to load vault info");
|
|
2992
|
-
console.error(
|
|
4199
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2993
4200
|
process.exit(1);
|
|
2994
4201
|
}
|
|
2995
4202
|
});
|
|
@@ -3000,22 +4207,22 @@ vaultCmd.command("balance").description("Show LP share balance and asset value")
|
|
|
3000
4207
|
const balance = await getBalance(opts.address);
|
|
3001
4208
|
spinner.stop();
|
|
3002
4209
|
console.log();
|
|
3003
|
-
console.log(
|
|
3004
|
-
console.log(
|
|
4210
|
+
console.log(chalk8.bold("LP Position"));
|
|
4211
|
+
console.log(chalk8.dim("\u2500".repeat(40)));
|
|
3005
4212
|
console.log(` Shares: ${balance.shares.toString()}`);
|
|
3006
4213
|
console.log(` Asset Value: ${balance.assetsValue}`);
|
|
3007
4214
|
console.log(` % of Vault: ${balance.percentOfVault}`);
|
|
3008
4215
|
console.log();
|
|
3009
4216
|
} catch (err) {
|
|
3010
4217
|
spinner.fail("Failed to load balance");
|
|
3011
|
-
console.error(
|
|
4218
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
3012
4219
|
process.exit(1);
|
|
3013
4220
|
}
|
|
3014
4221
|
});
|
|
3015
4222
|
var strategy = program.command("strategy").description("Strategy templates \u2014 list, clone, propose");
|
|
3016
4223
|
registerStrategyTemplateCommands(strategy);
|
|
3017
4224
|
program.command("providers").description("List available DeFi providers").action(async () => {
|
|
3018
|
-
const { MessariProvider, NansenProvider } = await import("./research-
|
|
4225
|
+
const { MessariProvider, NansenProvider } = await import("./research-SJ6D3L2B.js");
|
|
3019
4226
|
const providers = [new MoonwellProvider(), new UniswapProvider(), new MessariProvider(), new NansenProvider()];
|
|
3020
4227
|
for (const p of providers) {
|
|
3021
4228
|
const info = p.info();
|
|
@@ -3026,26 +4233,26 @@ ${info.name} (${info.type})`);
|
|
|
3026
4233
|
}
|
|
3027
4234
|
});
|
|
3028
4235
|
try {
|
|
3029
|
-
const { registerChatCommands } = await import("./chat-
|
|
4236
|
+
const { registerChatCommands } = await import("./chat-JYAJMP4D.js");
|
|
3030
4237
|
registerChatCommands(program);
|
|
3031
4238
|
} catch {
|
|
3032
4239
|
program.command("chat <name> [action] [actionArgs...]").description("Syndicate chat (XMTP) \u2014 requires @xmtp/cli").action(() => {
|
|
3033
|
-
console.error(
|
|
3034
|
-
console.error(
|
|
3035
|
-
console.error(
|
|
4240
|
+
console.error(chalk8.red("XMTP CLI not available."));
|
|
4241
|
+
console.error(chalk8.dim("Install with: npm install -g @xmtp/cli"));
|
|
4242
|
+
console.error(chalk8.dim("Or reinstall: npm i -g @sherwoodagent/cli"));
|
|
3036
4243
|
process.exit(1);
|
|
3037
4244
|
});
|
|
3038
4245
|
}
|
|
3039
|
-
var { registerSessionCommands } = await import("./session-
|
|
4246
|
+
var { registerSessionCommands } = await import("./session-XZNKLK3I.js");
|
|
3040
4247
|
registerSessionCommands(program);
|
|
3041
4248
|
registerVeniceCommands(program);
|
|
3042
4249
|
registerAllowanceCommands(program);
|
|
3043
4250
|
registerIdentityCommands(program);
|
|
3044
4251
|
registerProposalCommands(program);
|
|
3045
4252
|
registerGovernorCommands(program);
|
|
3046
|
-
var { registerResearchCommands } = await import("./research-
|
|
4253
|
+
var { registerResearchCommands } = await import("./research-ZW6XVTGZ.js");
|
|
3047
4254
|
registerResearchCommands(program);
|
|
3048
|
-
var { registerTradeCommands } = await import("./trade-
|
|
4255
|
+
var { registerTradeCommands } = await import("./trade-OJM5HNHL.js");
|
|
3049
4256
|
registerTradeCommands(program);
|
|
3050
4257
|
var configCmd = program.command("config");
|
|
3051
4258
|
configCmd.command("set").description("Save settings to ~/.sherwood/config.json (persists across sessions)").option("--private-key <key>", "Wallet private key (0x-prefixed)").option("--vault <address>", "Default SyndicateVault address").option("--rpc <url>", "Custom RPC URL for the active --chain network").option("--notify-to <id>", "Destination for cron summaries (Telegram chat ID, phone, etc.)").option("--uniswap-api-key <key>", "Uniswap Trading API key (from developers.uniswap.org)").option("--venice-api-key <key>", "Venice AI inference API key").action((opts) => {
|
|
@@ -3053,42 +4260,42 @@ configCmd.command("set").description("Save settings to ~/.sherwood/config.json (
|
|
|
3053
4260
|
if (opts.privateKey) {
|
|
3054
4261
|
setPrivateKey(opts.privateKey);
|
|
3055
4262
|
const account = getAccount();
|
|
3056
|
-
console.log(
|
|
3057
|
-
console.log(
|
|
4263
|
+
console.log(chalk8.green("Private key saved to ~/.sherwood/config.json"));
|
|
4264
|
+
console.log(chalk8.dim(` Wallet: ${account.address}`));
|
|
3058
4265
|
saved = true;
|
|
3059
4266
|
}
|
|
3060
4267
|
if (opts.vault) {
|
|
3061
4268
|
const chainId = getChain().id;
|
|
3062
4269
|
setChainContract(chainId, "vault", opts.vault);
|
|
3063
|
-
console.log(
|
|
3064
|
-
console.log(
|
|
4270
|
+
console.log(chalk8.green(`Vault saved to ~/.sherwood/config.json (chain ${chainId})`));
|
|
4271
|
+
console.log(chalk8.dim(` Vault: ${opts.vault}`));
|
|
3065
4272
|
saved = true;
|
|
3066
4273
|
}
|
|
3067
4274
|
if (opts.rpc) {
|
|
3068
4275
|
const network = getNetwork();
|
|
3069
4276
|
setConfigRpcUrl(network, opts.rpc);
|
|
3070
|
-
console.log(
|
|
3071
|
-
console.log(
|
|
4277
|
+
console.log(chalk8.green(`RPC URL saved for ${network}`));
|
|
4278
|
+
console.log(chalk8.dim(` RPC: ${opts.rpc}`));
|
|
3072
4279
|
saved = true;
|
|
3073
4280
|
}
|
|
3074
4281
|
if (opts.notifyTo) {
|
|
3075
4282
|
setNotifyTo(opts.notifyTo);
|
|
3076
|
-
console.log(
|
|
3077
|
-
console.log(
|
|
4283
|
+
console.log(chalk8.green("Notify destination saved to ~/.sherwood/config.json"));
|
|
4284
|
+
console.log(chalk8.dim(` Notify to: ${opts.notifyTo}`));
|
|
3078
4285
|
saved = true;
|
|
3079
4286
|
}
|
|
3080
4287
|
if (opts.uniswapApiKey) {
|
|
3081
4288
|
setUniswapApiKey(opts.uniswapApiKey);
|
|
3082
|
-
console.log(
|
|
4289
|
+
console.log(chalk8.green("Uniswap API key saved to ~/.sherwood/config.json"));
|
|
3083
4290
|
saved = true;
|
|
3084
4291
|
}
|
|
3085
4292
|
if (opts.veniceApiKey) {
|
|
3086
4293
|
setVeniceApiKey(opts.veniceApiKey);
|
|
3087
|
-
console.log(
|
|
4294
|
+
console.log(chalk8.green("Venice API key saved to ~/.sherwood/config.json"));
|
|
3088
4295
|
saved = true;
|
|
3089
4296
|
}
|
|
3090
4297
|
if (!saved) {
|
|
3091
|
-
console.log(
|
|
4298
|
+
console.log(chalk8.red("Provide at least one of: --private-key, --vault, --rpc, --notify-to, --uniswap-api-key, --venice-api-key"));
|
|
3092
4299
|
process.exit(1);
|
|
3093
4300
|
}
|
|
3094
4301
|
});
|
|
@@ -3099,17 +4306,17 @@ configCmd.command("show").description("Display current config for the active net
|
|
|
3099
4306
|
const config = loadConfig();
|
|
3100
4307
|
const customRpc = config.rpc?.[network];
|
|
3101
4308
|
console.log();
|
|
3102
|
-
console.log(
|
|
3103
|
-
console.log(
|
|
3104
|
-
console.log(` Network: ${
|
|
3105
|
-
console.log(` RPC: ${customRpc ?
|
|
3106
|
-
console.log(` Wallet: ${config.privateKey ?
|
|
3107
|
-
console.log(` Agent ID: ${config.agentId ??
|
|
3108
|
-
console.log(` Vault: ${contracts.vault ??
|
|
3109
|
-
console.log(` Uniswap: ${getUniswapApiKey() ?
|
|
3110
|
-
console.log(` Venice: ${getVeniceApiKey() ?
|
|
4309
|
+
console.log(chalk8.bold(`Sherwood Config`));
|
|
4310
|
+
console.log(chalk8.dim("\u2500".repeat(50)));
|
|
4311
|
+
console.log(` Network: ${chalk8.cyan(network)} (chain ${chainId})`);
|
|
4312
|
+
console.log(` RPC: ${customRpc ? chalk8.green(customRpc) : chalk8.dim("default")}`);
|
|
4313
|
+
console.log(` Wallet: ${config.privateKey ? chalk8.green("configured") : chalk8.dim("not set")}`);
|
|
4314
|
+
console.log(` Agent ID: ${config.agentId ?? chalk8.dim("not set")}`);
|
|
4315
|
+
console.log(` Vault: ${contracts.vault ?? chalk8.dim("not set")}`);
|
|
4316
|
+
console.log(` Uniswap: ${getUniswapApiKey() ? chalk8.green("API key configured") : chalk8.dim("not set")}`);
|
|
4317
|
+
console.log(` Venice: ${getVeniceApiKey() ? chalk8.green("API key configured") : chalk8.dim("not set")}`);
|
|
3111
4318
|
console.log();
|
|
3112
|
-
console.log(
|
|
4319
|
+
console.log(chalk8.dim(" Config file: ~/.sherwood/config.json"));
|
|
3113
4320
|
console.log();
|
|
3114
4321
|
});
|
|
3115
4322
|
program.parse();
|