@sherwoodagent/cli 0.19.2 → 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-WWH4GD3N.js → chunk-4Y6Y4SVH.js} +37 -27
- 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-MPDYDPJK.js → eas-X5UIEM6D.js} +14 -8
- package/dist/{governor-J3W67NXA.js → governor-ZKRSAHG4.js} +6 -6
- package/dist/index.js +1594 -391
- 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-LOSOHKOJ.js → research-ZW6XVTGZ.js} +17 -16
- 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-JLQPIA5W.js → trade-OJM5HNHL.js} +133 -70
- 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-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-WWH4GD3N.js.map +0 -1
- package/dist/chunk-YJAMY25R.js.map +0 -1
- package/dist/cron-SKYKVZ6K.js.map +0 -1
- package/dist/research-DZINC4N4.js +0 -14
- package/dist/research-LOSOHKOJ.js.map +0 -1
- package/dist/session-Q2WRYXYF.js.map +0 -1
- package/dist/trade-JLQPIA5W.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-MPDYDPJK.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,7 +1391,7 @@ 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
|
});
|
|
@@ -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,10 +1437,10 @@ ${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-
|
|
1440
|
+
const { createVeniceInferenceAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-X5UIEM6D.js");
|
|
1304
1441
|
const { keccak256, toHex, isAddress: isAddr } = await import("viem");
|
|
1305
|
-
const { getChainContracts: getChainContracts2 } = await import("./config-
|
|
1306
|
-
const { getChain: getActiveChain } = await import("./network-
|
|
1442
|
+
const { getChainContracts: getChainContracts2 } = await import("./config-BGS5MX4D.js");
|
|
1443
|
+
const { getChain: getActiveChain } = await import("./network-UTSOP7EF.js");
|
|
1307
1444
|
const vaultRecipient = opts.vault && isAddr(opts.vault) ? opts.vault : getChainContracts2(getActiveChain().id).vault;
|
|
1308
1445
|
const promptHash = keccak256(toHex(userContent)).slice(0, 18);
|
|
1309
1446
|
const { uid } = await createVeniceInferenceAttestation(
|
|
@@ -1320,7 +1457,7 @@ ${opts.prompt}`;
|
|
|
1320
1457
|
}
|
|
1321
1458
|
} catch (err) {
|
|
1322
1459
|
spinner.fail("Inference failed");
|
|
1323
|
-
console.error(chalk2.red(
|
|
1460
|
+
console.error(chalk2.red(formatContractError(err)));
|
|
1324
1461
|
process.exit(1);
|
|
1325
1462
|
}
|
|
1326
1463
|
});
|
|
@@ -1332,7 +1469,7 @@ import chalk3 from "chalk";
|
|
|
1332
1469
|
import ora3 from "ora";
|
|
1333
1470
|
|
|
1334
1471
|
// src/strategies/allowance-disburse.ts
|
|
1335
|
-
import { encodeFunctionData as
|
|
1472
|
+
import { encodeFunctionData as encodeFunctionData6, parseUnits as parseUnits3 } from "viem";
|
|
1336
1473
|
function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDecimals, minUsdc, swapPath) {
|
|
1337
1474
|
const assetAmount = parseUnits3(config.amount, assetDecimals);
|
|
1338
1475
|
const isUsdc = assetAddress.toLowerCase() === TOKENS().USDC.toLowerCase();
|
|
@@ -1341,7 +1478,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
|
|
|
1341
1478
|
if (!isUsdc) {
|
|
1342
1479
|
calls.push({
|
|
1343
1480
|
target: assetAddress,
|
|
1344
|
-
data:
|
|
1481
|
+
data: encodeFunctionData6({
|
|
1345
1482
|
abi: ERC20_ABI,
|
|
1346
1483
|
functionName: "approve",
|
|
1347
1484
|
args: [UNISWAP().SWAP_ROUTER, assetAmount]
|
|
@@ -1351,7 +1488,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
|
|
|
1351
1488
|
if (isWeth) {
|
|
1352
1489
|
calls.push({
|
|
1353
1490
|
target: UNISWAP().SWAP_ROUTER,
|
|
1354
|
-
data:
|
|
1491
|
+
data: encodeFunctionData6({
|
|
1355
1492
|
abi: SWAP_ROUTER_EXACT_INPUT_SINGLE_ABI,
|
|
1356
1493
|
functionName: "exactInputSingle",
|
|
1357
1494
|
args: [
|
|
@@ -1371,7 +1508,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
|
|
|
1371
1508
|
} else {
|
|
1372
1509
|
calls.push({
|
|
1373
1510
|
target: UNISWAP().SWAP_ROUTER,
|
|
1374
|
-
data:
|
|
1511
|
+
data: encodeFunctionData6({
|
|
1375
1512
|
abi: SWAP_ROUTER_ABI,
|
|
1376
1513
|
functionName: "exactInput",
|
|
1377
1514
|
args: [
|
|
@@ -1391,7 +1528,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
|
|
|
1391
1528
|
for (const agent of agents) {
|
|
1392
1529
|
calls.push({
|
|
1393
1530
|
target: TOKENS().USDC,
|
|
1394
|
-
data:
|
|
1531
|
+
data: encodeFunctionData6({
|
|
1395
1532
|
abi: ERC20_ABI,
|
|
1396
1533
|
functionName: "transfer",
|
|
1397
1534
|
args: [agent, perAgent]
|
|
@@ -1440,7 +1577,7 @@ function registerAllowanceCommands(program2) {
|
|
|
1440
1577
|
spinner.stop();
|
|
1441
1578
|
} catch (err) {
|
|
1442
1579
|
spinner.fail("Failed to read vault state");
|
|
1443
|
-
console.error(chalk3.red(
|
|
1580
|
+
console.error(chalk3.red(formatContractError(err)));
|
|
1444
1581
|
process.exit(1);
|
|
1445
1582
|
}
|
|
1446
1583
|
if (agents.length === 0) {
|
|
@@ -1505,7 +1642,7 @@ function registerAllowanceCommands(program2) {
|
|
|
1505
1642
|
);
|
|
1506
1643
|
} catch (err) {
|
|
1507
1644
|
quoteSpinner.fail("Failed to fetch quote");
|
|
1508
|
-
console.error(chalk3.red(
|
|
1645
|
+
console.error(chalk3.red(formatContractError(err)));
|
|
1509
1646
|
process.exit(1);
|
|
1510
1647
|
}
|
|
1511
1648
|
}
|
|
@@ -1536,7 +1673,7 @@ function registerAllowanceCommands(program2) {
|
|
|
1536
1673
|
console.log(chalk3.dim(` ${getExplorerUrl(txHash)}`));
|
|
1537
1674
|
} catch (err) {
|
|
1538
1675
|
execSpinner.fail("Execution failed");
|
|
1539
|
-
console.error(chalk3.red(
|
|
1676
|
+
console.error(chalk3.red(formatContractError(err)));
|
|
1540
1677
|
process.exit(1);
|
|
1541
1678
|
}
|
|
1542
1679
|
});
|
|
@@ -1590,7 +1727,7 @@ function registerAllowanceCommands(program2) {
|
|
|
1590
1727
|
console.log();
|
|
1591
1728
|
} catch (err) {
|
|
1592
1729
|
spinner.fail("Failed to load status");
|
|
1593
|
-
console.error(chalk3.red(
|
|
1730
|
+
console.error(chalk3.red(formatContractError(err)));
|
|
1594
1731
|
process.exit(1);
|
|
1595
1732
|
}
|
|
1596
1733
|
});
|
|
@@ -1667,7 +1804,7 @@ function registerIdentityCommands(program2) {
|
|
|
1667
1804
|
console.log(chalk4.dim(` sherwood syndicate create --agent-id ${tokenId} --subdomain <name> --name <name>`));
|
|
1668
1805
|
} catch (err) {
|
|
1669
1806
|
spinner.fail("Failed to register identity");
|
|
1670
|
-
console.error(chalk4.red(
|
|
1807
|
+
console.error(chalk4.red(formatContractError(err)));
|
|
1671
1808
|
process.exit(1);
|
|
1672
1809
|
}
|
|
1673
1810
|
});
|
|
@@ -1694,7 +1831,7 @@ function registerIdentityCommands(program2) {
|
|
|
1694
1831
|
console.log(chalk4.dim(` Saved to ~/.sherwood/config.json`));
|
|
1695
1832
|
} catch (err) {
|
|
1696
1833
|
spinner.fail("Failed to load identity");
|
|
1697
|
-
console.error(chalk4.red(
|
|
1834
|
+
console.error(chalk4.red(formatContractError(err)));
|
|
1698
1835
|
process.exit(1);
|
|
1699
1836
|
}
|
|
1700
1837
|
});
|
|
@@ -1747,7 +1884,7 @@ function registerIdentityCommands(program2) {
|
|
|
1747
1884
|
console.log();
|
|
1748
1885
|
} catch (err) {
|
|
1749
1886
|
spinner.fail("Failed to check identity");
|
|
1750
|
-
console.error(chalk4.red(
|
|
1887
|
+
console.error(chalk4.red(formatContractError(err)));
|
|
1751
1888
|
process.exit(1);
|
|
1752
1889
|
}
|
|
1753
1890
|
});
|
|
@@ -1755,7 +1892,7 @@ function registerIdentityCommands(program2) {
|
|
|
1755
1892
|
|
|
1756
1893
|
// src/commands/proposal.ts
|
|
1757
1894
|
import { isAddress as isAddress4 } from "viem";
|
|
1758
|
-
import
|
|
1895
|
+
import chalk6 from "chalk";
|
|
1759
1896
|
import ora5 from "ora";
|
|
1760
1897
|
import { readFileSync as readFileSync2 } from "fs";
|
|
1761
1898
|
|
|
@@ -1789,13 +1926,737 @@ function parseBigIntArg(value, name) {
|
|
|
1789
1926
|
return BigInt(value);
|
|
1790
1927
|
}
|
|
1791
1928
|
|
|
1792
|
-
// src/
|
|
1929
|
+
// src/lib/simulate.ts
|
|
1930
|
+
import { decodeFunctionData, encodeFunctionData as encodeFunctionData7 } from "viem";
|
|
1931
|
+
import chalk5 from "chalk";
|
|
1793
1932
|
var G = chalk5.green;
|
|
1794
1933
|
var W = chalk5.white;
|
|
1795
1934
|
var DIM = chalk5.gray;
|
|
1796
1935
|
var BOLD = chalk5.white.bold;
|
|
1797
1936
|
var LABEL = chalk5.green.bold;
|
|
1798
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)));
|
|
1799
2660
|
function formatTimestamp(ts) {
|
|
1800
2661
|
if (ts === 0n) return "\u2014";
|
|
1801
2662
|
return new Date(Number(ts) * 1e3).toLocaleString();
|
|
@@ -1811,11 +2672,11 @@ function parseCallsFile(path) {
|
|
|
1811
2672
|
}
|
|
1812
2673
|
function registerProposalCommands(program2) {
|
|
1813
2674
|
const proposal = program2.command("proposal").description("Governance proposals \u2014 create, vote, execute, settle");
|
|
1814
|
-
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) => {
|
|
1815
2676
|
try {
|
|
1816
2677
|
const vault = opts.vault;
|
|
1817
2678
|
if (!isAddress4(vault)) {
|
|
1818
|
-
console.error(
|
|
2679
|
+
console.error(chalk6.red(`Invalid vault address: ${opts.vault}`));
|
|
1819
2680
|
process.exit(1);
|
|
1820
2681
|
}
|
|
1821
2682
|
const performanceFeeBps = parseBigIntArg(opts.performanceFee, "performance-fee");
|
|
@@ -1824,7 +2685,7 @@ function registerProposalCommands(program2) {
|
|
|
1824
2685
|
const settleCalls = parseCallsFile(opts.settleCalls);
|
|
1825
2686
|
let metadataURI = opts.metadataUri || "";
|
|
1826
2687
|
if (!metadataURI) {
|
|
1827
|
-
const spinner2 = ora5({ text:
|
|
2688
|
+
const spinner2 = ora5({ text: W2("Uploading metadata to IPFS..."), color: "green" }).start();
|
|
1828
2689
|
try {
|
|
1829
2690
|
const account = getAccount();
|
|
1830
2691
|
const metadata = {
|
|
@@ -1845,36 +2706,67 @@ function registerProposalCommands(program2) {
|
|
|
1845
2706
|
createdAt: Math.floor(Date.now() / 1e3)
|
|
1846
2707
|
};
|
|
1847
2708
|
metadataURI = await uploadMetadata(proposalMeta);
|
|
1848
|
-
spinner2.succeed(
|
|
2709
|
+
spinner2.succeed(G2(`Metadata pinned: ${DIM2(metadataURI)}`));
|
|
1849
2710
|
} catch (err) {
|
|
1850
|
-
spinner2.warn(
|
|
2711
|
+
spinner2.warn(chalk6.yellow("IPFS upload failed \u2014 using inline metadata"));
|
|
1851
2712
|
const json = JSON.stringify({ name: opts.name, description: opts.description, vault });
|
|
1852
2713
|
metadataURI = `data:application/json;base64,${Buffer.from(json).toString("base64")}`;
|
|
1853
2714
|
}
|
|
1854
2715
|
}
|
|
1855
2716
|
console.log();
|
|
1856
|
-
console.log(
|
|
1857
|
-
|
|
1858
|
-
console.log(
|
|
1859
|
-
console.log(
|
|
1860
|
-
console.log(
|
|
1861
|
-
console.log(
|
|
1862
|
-
console.log(
|
|
1863
|
-
console.log(
|
|
1864
|
-
|
|
1865
|
-
|
|
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();
|
|
1866
2758
|
const result = await propose(vault, metadataURI, performanceFeeBps, strategyDuration, executeCalls, settleCalls);
|
|
1867
|
-
spinner.succeed(
|
|
2759
|
+
spinner.succeed(G2("Proposal submitted"));
|
|
1868
2760
|
console.log();
|
|
1869
|
-
console.log(
|
|
1870
|
-
|
|
1871
|
-
console.log(
|
|
1872
|
-
console.log(
|
|
1873
|
-
|
|
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();
|
|
1874
2766
|
console.log();
|
|
1875
2767
|
} catch (err) {
|
|
1876
|
-
console.error(
|
|
1877
|
-
\u2716 ${
|
|
2768
|
+
console.error(chalk6.red(`
|
|
2769
|
+
\u2716 ${formatContractError(err)}`));
|
|
1878
2770
|
process.exit(1);
|
|
1879
2771
|
}
|
|
1880
2772
|
});
|
|
@@ -1884,7 +2776,7 @@ function registerProposalCommands(program2) {
|
|
|
1884
2776
|
const count = await proposalCount();
|
|
1885
2777
|
if (count === 0n) {
|
|
1886
2778
|
spinner.stop();
|
|
1887
|
-
console.log(
|
|
2779
|
+
console.log(DIM2("\n No proposals found.\n"));
|
|
1888
2780
|
return;
|
|
1889
2781
|
}
|
|
1890
2782
|
const vaultFilter = opts.vault ? opts.vault.toLowerCase() : null;
|
|
@@ -1904,16 +2796,16 @@ function registerProposalCommands(program2) {
|
|
|
1904
2796
|
});
|
|
1905
2797
|
spinner.stop();
|
|
1906
2798
|
if (proposals.length === 0) {
|
|
1907
|
-
console.log(
|
|
2799
|
+
console.log(DIM2("\n No matching proposals.\n"));
|
|
1908
2800
|
return;
|
|
1909
2801
|
}
|
|
1910
2802
|
console.log();
|
|
1911
|
-
console.log(
|
|
1912
|
-
console.log(
|
|
2803
|
+
console.log(BOLD2(`Proposals (${proposals.length})`));
|
|
2804
|
+
console.log(DIM2("\u2500".repeat(90)));
|
|
1913
2805
|
console.log(
|
|
1914
|
-
|
|
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")
|
|
1915
2807
|
);
|
|
1916
|
-
console.log(
|
|
2808
|
+
console.log(DIM2("\u2500".repeat(90)));
|
|
1917
2809
|
for (const p of proposals) {
|
|
1918
2810
|
const state = PROPOSAL_STATES[p.computedState] || "Unknown";
|
|
1919
2811
|
const created = p.snapshotTimestamp > 0n ? new Date(Number(p.snapshotTimestamp) * 1e3).toLocaleDateString() : "\u2014";
|
|
@@ -1928,7 +2820,7 @@ function registerProposalCommands(program2) {
|
|
|
1928
2820
|
console.log();
|
|
1929
2821
|
} catch (err) {
|
|
1930
2822
|
spinner.fail("Failed to load proposals");
|
|
1931
|
-
console.error(
|
|
2823
|
+
console.error(chalk6.red(formatContractError(err)));
|
|
1932
2824
|
process.exit(1);
|
|
1933
2825
|
}
|
|
1934
2826
|
});
|
|
@@ -1944,63 +2836,63 @@ function registerProposalCommands(program2) {
|
|
|
1944
2836
|
const totalVotes = p.votesFor + p.votesAgainst;
|
|
1945
2837
|
const quorumNeeded = totalVotes > 0n ? `${Number(params.vetoThresholdBps) / 100}%` : "\u2014";
|
|
1946
2838
|
console.log();
|
|
1947
|
-
console.log(
|
|
1948
|
-
|
|
2839
|
+
console.log(LABEL2(` \u25C6 Proposal #${p.id}`));
|
|
2840
|
+
SEP2();
|
|
1949
2841
|
if (p.metadataURI && p.metadataURI.startsWith("ipfs://")) {
|
|
1950
2842
|
try {
|
|
1951
2843
|
const meta = await fetchMetadata(p.metadataURI);
|
|
1952
|
-
console.log(
|
|
1953
|
-
console.log(
|
|
2844
|
+
console.log(W2(` Name: ${BOLD2(meta.name)}`));
|
|
2845
|
+
console.log(W2(` Description: ${DIM2(meta.description)}`));
|
|
1954
2846
|
} catch {
|
|
1955
|
-
console.log(
|
|
2847
|
+
console.log(W2(` Metadata: ${DIM2(p.metadataURI)}`));
|
|
1956
2848
|
}
|
|
1957
2849
|
} else if (p.metadataURI) {
|
|
1958
|
-
console.log(
|
|
2850
|
+
console.log(W2(` Metadata: ${DIM2(p.metadataURI)}`));
|
|
1959
2851
|
}
|
|
1960
|
-
console.log(
|
|
1961
|
-
console.log(
|
|
1962
|
-
console.log(
|
|
1963
|
-
console.log(
|
|
1964
|
-
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)}`));
|
|
1965
2857
|
console.log();
|
|
1966
|
-
console.log(
|
|
1967
|
-
console.log(
|
|
1968
|
-
console.log(
|
|
1969
|
-
console.log(
|
|
1970
|
-
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)}`));
|
|
1971
2863
|
console.log();
|
|
1972
|
-
console.log(
|
|
1973
|
-
console.log(
|
|
1974
|
-
console.log(
|
|
1975
|
-
console.log(
|
|
1976
|
-
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}`));
|
|
1977
2869
|
if (state === PROPOSAL_STATE.Executed || state === PROPOSAL_STATE.Settled) {
|
|
1978
2870
|
try {
|
|
1979
2871
|
const cap = await getCapitalSnapshot(id);
|
|
1980
2872
|
console.log();
|
|
1981
|
-
console.log(
|
|
1982
|
-
console.log(
|
|
2873
|
+
console.log(LABEL2(" Capital"));
|
|
2874
|
+
console.log(W2(` Snapshot: ${formatUSDC(cap)}`));
|
|
1983
2875
|
} catch {
|
|
1984
2876
|
}
|
|
1985
2877
|
}
|
|
1986
2878
|
const execCalls = await getExecuteCalls(id);
|
|
1987
2879
|
const settlCalls = await getSettlementCalls(id);
|
|
1988
2880
|
console.log();
|
|
1989
|
-
console.log(
|
|
2881
|
+
console.log(LABEL2(` Execute Calls (${execCalls.length})`));
|
|
1990
2882
|
for (let i = 0; i < execCalls.length; i++) {
|
|
1991
|
-
console.log(
|
|
1992
|
-
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}`));
|
|
1993
2885
|
}
|
|
1994
|
-
console.log(
|
|
2886
|
+
console.log(LABEL2(` Settlement Calls (${settlCalls.length})`));
|
|
1995
2887
|
for (let i = 0; i < settlCalls.length; i++) {
|
|
1996
|
-
console.log(
|
|
1997
|
-
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}`));
|
|
1998
2890
|
}
|
|
1999
|
-
|
|
2891
|
+
SEP2();
|
|
2000
2892
|
console.log();
|
|
2001
2893
|
} catch (err) {
|
|
2002
2894
|
spinner.fail("Failed to load proposal");
|
|
2003
|
-
console.error(
|
|
2895
|
+
console.error(chalk6.red(formatContractError(err)));
|
|
2004
2896
|
process.exit(1);
|
|
2005
2897
|
}
|
|
2006
2898
|
});
|
|
@@ -2010,7 +2902,7 @@ function registerProposalCommands(program2) {
|
|
|
2010
2902
|
const supportRaw = String(opts.support).toLowerCase();
|
|
2011
2903
|
const support = supportRaw === "yes" || supportRaw === "for" ? VOTE_TYPE.For : supportRaw === "no" || supportRaw === "against" ? VOTE_TYPE.Against : supportRaw === "abstain" ? VOTE_TYPE.Abstain : null;
|
|
2012
2904
|
if (support === null) {
|
|
2013
|
-
console.error(
|
|
2905
|
+
console.error(chalk6.red(`Invalid support value "${opts.support}". Use for|against|abstain.`));
|
|
2014
2906
|
process.exit(1);
|
|
2015
2907
|
}
|
|
2016
2908
|
const account = getAccount();
|
|
@@ -2029,27 +2921,27 @@ function registerProposalCommands(program2) {
|
|
|
2029
2921
|
const weight = await getVoteWeight(proposalId, account.address);
|
|
2030
2922
|
spinner.stop();
|
|
2031
2923
|
console.log();
|
|
2032
|
-
console.log(
|
|
2033
|
-
|
|
2034
|
-
console.log(
|
|
2035
|
-
console.log(
|
|
2036
|
-
console.log(
|
|
2037
|
-
` 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")}`
|
|
2038
2930
|
));
|
|
2039
|
-
console.log(
|
|
2040
|
-
|
|
2041
|
-
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();
|
|
2042
2934
|
const hash = await vote(proposalId, support);
|
|
2043
|
-
voteSpinner.succeed(
|
|
2044
|
-
console.log(
|
|
2935
|
+
voteSpinner.succeed(G2("Vote cast"));
|
|
2936
|
+
console.log(DIM2(` ${getExplorerUrl(hash)}`));
|
|
2045
2937
|
console.log();
|
|
2046
2938
|
} catch (err) {
|
|
2047
|
-
console.error(
|
|
2048
|
-
\u2716 ${
|
|
2939
|
+
console.error(chalk6.red(`
|
|
2940
|
+
\u2716 ${formatContractError(err)}`));
|
|
2049
2941
|
process.exit(1);
|
|
2050
2942
|
}
|
|
2051
2943
|
});
|
|
2052
|
-
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) => {
|
|
2053
2945
|
try {
|
|
2054
2946
|
const proposalId = parseBigIntArg(opts.id, "proposal ID");
|
|
2055
2947
|
const spinner = ora5("Loading proposal...").start();
|
|
@@ -2058,19 +2950,34 @@ function registerProposalCommands(program2) {
|
|
|
2058
2950
|
spinner.fail(`Proposal is ${PROPOSAL_STATES[state] || "Unknown"}, not Approved`);
|
|
2059
2951
|
process.exit(1);
|
|
2060
2952
|
}
|
|
2061
|
-
|
|
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...");
|
|
2062
2969
|
const hash = await executeProposal(proposalId);
|
|
2063
|
-
spinner.succeed(
|
|
2064
|
-
console.log(
|
|
2970
|
+
spinner.succeed(G2("Proposal executed"));
|
|
2971
|
+
console.log(DIM2(` ${getExplorerUrl(hash)}`));
|
|
2065
2972
|
try {
|
|
2066
2973
|
const cap = await getCapitalSnapshot(proposalId);
|
|
2067
|
-
console.log(
|
|
2974
|
+
console.log(DIM2(` Capital snapshot: ${formatUSDC(cap)}`));
|
|
2068
2975
|
} catch {
|
|
2069
2976
|
}
|
|
2070
2977
|
console.log();
|
|
2071
2978
|
} catch (err) {
|
|
2072
|
-
console.error(
|
|
2073
|
-
\u2716 ${
|
|
2979
|
+
console.error(chalk6.red(`
|
|
2980
|
+
\u2716 ${formatContractError(err)}`));
|
|
2074
2981
|
process.exit(1);
|
|
2075
2982
|
}
|
|
2076
2983
|
});
|
|
@@ -2090,27 +2997,27 @@ function registerProposalCommands(program2) {
|
|
|
2090
2997
|
const durationElapsed = p.executedAt > 0n && now >= p.executedAt + p.strategyDuration;
|
|
2091
2998
|
let hash;
|
|
2092
2999
|
if (isProposer && !durationElapsed) {
|
|
2093
|
-
spinner.text =
|
|
3000
|
+
spinner.text = W2("Settling (proposer)...");
|
|
2094
3001
|
hash = await settleProposal(proposalId);
|
|
2095
|
-
spinner.succeed(
|
|
3002
|
+
spinner.succeed(G2("Settled by proposer"));
|
|
2096
3003
|
} else if (durationElapsed && !opts.calls) {
|
|
2097
|
-
spinner.text =
|
|
3004
|
+
spinner.text = W2("Settling (permissionless)...");
|
|
2098
3005
|
hash = await settleProposal(proposalId);
|
|
2099
|
-
spinner.succeed(
|
|
3006
|
+
spinner.succeed(G2("Settled (permissionless)"));
|
|
2100
3007
|
} else if (durationElapsed && opts.calls) {
|
|
2101
|
-
spinner.text =
|
|
3008
|
+
spinner.text = W2("Emergency settling...");
|
|
2102
3009
|
const calls = parseCallsFile(opts.calls);
|
|
2103
3010
|
hash = await emergencySettle(proposalId, calls);
|
|
2104
|
-
spinner.succeed(
|
|
3011
|
+
spinner.succeed(G2("Emergency settled"));
|
|
2105
3012
|
} else {
|
|
2106
3013
|
spinner.fail("Cannot settle: duration not elapsed and you are not the proposer.");
|
|
2107
3014
|
process.exit(1);
|
|
2108
3015
|
}
|
|
2109
|
-
console.log(
|
|
3016
|
+
console.log(DIM2(` ${getExplorerUrl(hash)}`));
|
|
2110
3017
|
console.log();
|
|
2111
3018
|
} catch (err) {
|
|
2112
|
-
console.error(
|
|
2113
|
-
\u2716 ${
|
|
3019
|
+
console.error(chalk6.red(`
|
|
3020
|
+
\u2716 ${formatContractError(err)}`));
|
|
2114
3021
|
process.exit(1);
|
|
2115
3022
|
}
|
|
2116
3023
|
});
|
|
@@ -2125,22 +3032,90 @@ function registerProposalCommands(program2) {
|
|
|
2125
3032
|
}
|
|
2126
3033
|
let hash;
|
|
2127
3034
|
if (opts.emergency) {
|
|
2128
|
-
spinner.text =
|
|
3035
|
+
spinner.text = W2("Emergency cancelling...");
|
|
2129
3036
|
hash = await emergencyCancel(proposalId);
|
|
2130
|
-
spinner.succeed(
|
|
3037
|
+
spinner.succeed(G2("Emergency cancelled"));
|
|
2131
3038
|
} else {
|
|
2132
3039
|
if (state !== PROPOSAL_STATE.Draft && state !== PROPOSAL_STATE.Pending) {
|
|
2133
3040
|
spinner.fail(`Proposal is ${PROPOSAL_STATES[state] || "Unknown"} \u2014 use --emergency for non-pending/approved`);
|
|
2134
3041
|
process.exit(1);
|
|
2135
3042
|
}
|
|
2136
|
-
spinner.text =
|
|
3043
|
+
spinner.text = W2("Cancelling proposal...");
|
|
2137
3044
|
hash = await cancelProposal(proposalId);
|
|
2138
|
-
spinner.succeed(
|
|
3045
|
+
spinner.succeed(G2("Proposal cancelled"));
|
|
2139
3046
|
}
|
|
2140
|
-
console.log(
|
|
3047
|
+
console.log(DIM2(` ${getExplorerUrl(hash)}`));
|
|
2141
3048
|
console.log();
|
|
2142
3049
|
} catch (err) {
|
|
2143
|
-
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(`
|
|
2144
3119
|
\u2716 ${err instanceof Error ? err.message : String(err)}`));
|
|
2145
3120
|
process.exit(1);
|
|
2146
3121
|
}
|
|
@@ -2148,14 +3123,14 @@ function registerProposalCommands(program2) {
|
|
|
2148
3123
|
}
|
|
2149
3124
|
|
|
2150
3125
|
// src/commands/governor.ts
|
|
2151
|
-
import
|
|
3126
|
+
import chalk7 from "chalk";
|
|
2152
3127
|
import ora6 from "ora";
|
|
2153
|
-
var
|
|
2154
|
-
var
|
|
2155
|
-
var
|
|
2156
|
-
var
|
|
2157
|
-
var
|
|
2158
|
-
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)));
|
|
2159
3134
|
function registerGovernorCommands(program2) {
|
|
2160
3135
|
const governor = program2.command("governor").description("Governor parameters and vault management");
|
|
2161
3136
|
governor.command("info").description("Display current governor parameters and registered vaults").action(async () => {
|
|
@@ -2167,29 +3142,29 @@ function registerGovernorCommands(program2) {
|
|
|
2167
3142
|
]);
|
|
2168
3143
|
spinner.stop();
|
|
2169
3144
|
console.log();
|
|
2170
|
-
console.log(
|
|
2171
|
-
|
|
2172
|
-
console.log(
|
|
2173
|
-
console.log(
|
|
2174
|
-
console.log(
|
|
2175
|
-
console.log(
|
|
2176
|
-
console.log(
|
|
2177
|
-
console.log(
|
|
2178
|
-
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))}`));
|
|
2179
3154
|
console.log();
|
|
2180
|
-
console.log(
|
|
3155
|
+
console.log(LABEL3(` Registered Vaults (${vaults.length})`));
|
|
2181
3156
|
if (vaults.length === 0) {
|
|
2182
|
-
console.log(
|
|
3157
|
+
console.log(DIM3(" (none)"));
|
|
2183
3158
|
} else {
|
|
2184
3159
|
for (const v of vaults) {
|
|
2185
|
-
console.log(
|
|
3160
|
+
console.log(W3(` ${G3(v)}`));
|
|
2186
3161
|
}
|
|
2187
3162
|
}
|
|
2188
|
-
|
|
3163
|
+
SEP3();
|
|
2189
3164
|
console.log();
|
|
2190
3165
|
} catch (err) {
|
|
2191
3166
|
spinner.fail("Failed to load governor info");
|
|
2192
|
-
console.error(
|
|
3167
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2193
3168
|
process.exit(1);
|
|
2194
3169
|
}
|
|
2195
3170
|
});
|
|
@@ -2197,11 +3172,11 @@ function registerGovernorCommands(program2) {
|
|
|
2197
3172
|
const spinner = ora6("Setting voting period...").start();
|
|
2198
3173
|
try {
|
|
2199
3174
|
const hash = await setVotingPeriod(parseBigIntArg(opts.seconds, "seconds"));
|
|
2200
|
-
spinner.succeed(
|
|
2201
|
-
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)}`));
|
|
2202
3177
|
} catch (err) {
|
|
2203
3178
|
spinner.fail("Failed to set voting period");
|
|
2204
|
-
console.error(
|
|
3179
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2205
3180
|
process.exit(1);
|
|
2206
3181
|
}
|
|
2207
3182
|
});
|
|
@@ -2209,11 +3184,11 @@ function registerGovernorCommands(program2) {
|
|
|
2209
3184
|
const spinner = ora6("Setting execution window...").start();
|
|
2210
3185
|
try {
|
|
2211
3186
|
const hash = await setExecutionWindow(parseBigIntArg(opts.seconds, "seconds"));
|
|
2212
|
-
spinner.succeed(
|
|
2213
|
-
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)}`));
|
|
2214
3189
|
} catch (err) {
|
|
2215
3190
|
spinner.fail("Failed to set execution window");
|
|
2216
|
-
console.error(
|
|
3191
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2217
3192
|
process.exit(1);
|
|
2218
3193
|
}
|
|
2219
3194
|
});
|
|
@@ -2221,11 +3196,11 @@ function registerGovernorCommands(program2) {
|
|
|
2221
3196
|
const spinner = ora6("Setting veto threshold...").start();
|
|
2222
3197
|
try {
|
|
2223
3198
|
const hash = await setVetoThresholdBps(parseBigIntArg(opts.bps, "bps"));
|
|
2224
|
-
spinner.succeed(
|
|
2225
|
-
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)}`));
|
|
2226
3201
|
} catch (err) {
|
|
2227
3202
|
spinner.fail("Failed to set veto threshold");
|
|
2228
|
-
console.error(
|
|
3203
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2229
3204
|
process.exit(1);
|
|
2230
3205
|
}
|
|
2231
3206
|
});
|
|
@@ -2233,11 +3208,11 @@ function registerGovernorCommands(program2) {
|
|
|
2233
3208
|
const spinner = ora6("Setting max fee...").start();
|
|
2234
3209
|
try {
|
|
2235
3210
|
const hash = await setMaxPerformanceFeeBps(parseBigIntArg(opts.bps, "bps"));
|
|
2236
|
-
spinner.succeed(
|
|
2237
|
-
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)}`));
|
|
2238
3213
|
} catch (err) {
|
|
2239
3214
|
spinner.fail("Failed to set max fee");
|
|
2240
|
-
console.error(
|
|
3215
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2241
3216
|
process.exit(1);
|
|
2242
3217
|
}
|
|
2243
3218
|
});
|
|
@@ -2245,11 +3220,11 @@ function registerGovernorCommands(program2) {
|
|
|
2245
3220
|
const spinner = ora6("Setting max duration...").start();
|
|
2246
3221
|
try {
|
|
2247
3222
|
const hash = await setMaxStrategyDuration(parseBigIntArg(opts.seconds, "seconds"));
|
|
2248
|
-
spinner.succeed(
|
|
2249
|
-
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)}`));
|
|
2250
3225
|
} catch (err) {
|
|
2251
3226
|
spinner.fail("Failed to set max duration");
|
|
2252
|
-
console.error(
|
|
3227
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2253
3228
|
process.exit(1);
|
|
2254
3229
|
}
|
|
2255
3230
|
});
|
|
@@ -2257,11 +3232,11 @@ function registerGovernorCommands(program2) {
|
|
|
2257
3232
|
const spinner = ora6("Setting cooldown...").start();
|
|
2258
3233
|
try {
|
|
2259
3234
|
const hash = await setCooldownPeriod(parseBigIntArg(opts.seconds, "seconds"));
|
|
2260
|
-
spinner.succeed(
|
|
2261
|
-
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)}`));
|
|
2262
3237
|
} catch (err) {
|
|
2263
3238
|
spinner.fail("Failed to set cooldown");
|
|
2264
|
-
console.error(
|
|
3239
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2265
3240
|
process.exit(1);
|
|
2266
3241
|
}
|
|
2267
3242
|
});
|
|
@@ -2269,11 +3244,11 @@ function registerGovernorCommands(program2) {
|
|
|
2269
3244
|
const spinner = ora6("Setting protocol fee...").start();
|
|
2270
3245
|
try {
|
|
2271
3246
|
const hash = await setProtocolFeeBps(parseBigIntArg(opts.bps, "bps"));
|
|
2272
|
-
spinner.succeed(
|
|
2273
|
-
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)}`));
|
|
2274
3249
|
} catch (err) {
|
|
2275
3250
|
spinner.fail("Failed to set protocol fee");
|
|
2276
|
-
console.error(
|
|
3251
|
+
console.error(chalk7.red(formatContractError(err)));
|
|
2277
3252
|
process.exit(1);
|
|
2278
3253
|
}
|
|
2279
3254
|
});
|
|
@@ -2287,20 +3262,20 @@ try {
|
|
|
2287
3262
|
var require2 = createRequire(import.meta.url);
|
|
2288
3263
|
var { version: CLI_VERSION } = require2("../package.json");
|
|
2289
3264
|
async function loadXmtp() {
|
|
2290
|
-
return import("./xmtp-
|
|
3265
|
+
return import("./xmtp-XKHXATPQ.js");
|
|
2291
3266
|
}
|
|
2292
3267
|
async function loadCron() {
|
|
2293
|
-
return import("./cron-
|
|
3268
|
+
return import("./cron-EOULYO3R.js");
|
|
2294
3269
|
}
|
|
2295
|
-
var
|
|
2296
|
-
var
|
|
2297
|
-
var
|
|
2298
|
-
var
|
|
2299
|
-
var
|
|
2300
|
-
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)));
|
|
2301
3276
|
function validateAddress(value, name) {
|
|
2302
3277
|
if (!isAddress5(value)) {
|
|
2303
|
-
console.error(
|
|
3278
|
+
console.error(chalk8.red(`Invalid ${name} address: ${value}`));
|
|
2304
3279
|
process.exit(1);
|
|
2305
3280
|
}
|
|
2306
3281
|
return value;
|
|
@@ -2320,7 +3295,7 @@ program.name("sherwood").description("CLI for agent-managed investment syndicate
|
|
|
2320
3295
|
process.env.ENABLE_TESTNET = "true";
|
|
2321
3296
|
if (network !== "base") {
|
|
2322
3297
|
console.warn(
|
|
2323
|
-
|
|
3298
|
+
chalk8.yellow("[warn] --testnet ignored, --chain takes precedence")
|
|
2324
3299
|
);
|
|
2325
3300
|
} else {
|
|
2326
3301
|
network = "base-sepolia";
|
|
@@ -2328,39 +3303,39 @@ program.name("sherwood").description("CLI for agent-managed investment syndicate
|
|
|
2328
3303
|
}
|
|
2329
3304
|
setNetwork(network);
|
|
2330
3305
|
if (getNetwork() !== "base") {
|
|
2331
|
-
console.log(
|
|
3306
|
+
console.log(chalk8.yellow(`[${getNetwork()}]`));
|
|
2332
3307
|
}
|
|
2333
3308
|
});
|
|
2334
3309
|
var syndicate = program.command("syndicate");
|
|
2335
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) => {
|
|
2336
3311
|
try {
|
|
2337
3312
|
console.log();
|
|
2338
|
-
console.log(
|
|
2339
|
-
|
|
3313
|
+
console.log(LABEL4(" \u25C6 Create Syndicate"));
|
|
3314
|
+
SEP4();
|
|
2340
3315
|
const wallet = getAccount();
|
|
2341
|
-
console.log(
|
|
2342
|
-
console.log(
|
|
2343
|
-
|
|
3316
|
+
console.log(DIM4(` Wallet: ${wallet.address}`));
|
|
3317
|
+
console.log(DIM4(` Network: ${getChain().name}`));
|
|
3318
|
+
SEP4();
|
|
2344
3319
|
const savedAgentId = getAgentId();
|
|
2345
3320
|
const nonInteractive = opts.yes;
|
|
2346
3321
|
const name = opts.name || (nonInteractive ? (() => {
|
|
2347
3322
|
throw new Error("--name is required in non-interactive mode (-y)");
|
|
2348
3323
|
})() : await input({
|
|
2349
|
-
message:
|
|
3324
|
+
message: G4("Syndicate name"),
|
|
2350
3325
|
validate: (v) => v.length > 0 || "Name is required"
|
|
2351
3326
|
}));
|
|
2352
3327
|
const subdomain = opts.subdomain || (nonInteractive ? name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "") : await input({
|
|
2353
|
-
message:
|
|
3328
|
+
message: G4("ENS subdomain"),
|
|
2354
3329
|
default: name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, ""),
|
|
2355
3330
|
validate: (v) => v.length >= 3 || "Must be at least 3 characters"
|
|
2356
3331
|
}));
|
|
2357
3332
|
const description = opts.description || (nonInteractive ? `${name} \u2014 a Sherwood syndicate` : await input({
|
|
2358
|
-
message:
|
|
3333
|
+
message: G4("Description"),
|
|
2359
3334
|
default: `${name} \u2014 a Sherwood syndicate`
|
|
2360
3335
|
}));
|
|
2361
|
-
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" }));
|
|
2362
3337
|
const openDeposits = opts.openDeposits !== void 0 ? opts.openDeposits : nonInteractive ? true : await confirm({
|
|
2363
|
-
message:
|
|
3338
|
+
message: G4("Open deposits? (anyone can deposit)"),
|
|
2364
3339
|
default: true
|
|
2365
3340
|
});
|
|
2366
3341
|
const ASSET_SYMBOLS = {
|
|
@@ -2376,14 +3351,14 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
2376
3351
|
asset = opts.asset;
|
|
2377
3352
|
} else {
|
|
2378
3353
|
const supported = Object.keys(ASSET_SYMBOLS).join(", ");
|
|
2379
|
-
console.error(
|
|
3354
|
+
console.error(chalk8.red(` Unknown asset "${opts.asset}". Use a symbol (${supported}) or a 0x address.`));
|
|
2380
3355
|
process.exit(1);
|
|
2381
3356
|
}
|
|
2382
3357
|
} else if (nonInteractive) {
|
|
2383
3358
|
asset = TOKENS().USDC !== "0x0000000000000000000000000000000000000000" ? ASSET_SYMBOLS.USDC : ASSET_SYMBOLS.WETH;
|
|
2384
3359
|
} else {
|
|
2385
3360
|
const assetChoice = await select({
|
|
2386
|
-
message:
|
|
3361
|
+
message: G4("Vault asset (what token do depositors provide?)"),
|
|
2387
3362
|
choices: [
|
|
2388
3363
|
{ name: "USDC", value: "USDC", description: "USD Coin (6 decimals)" },
|
|
2389
3364
|
{ name: "WETH", value: "WETH", description: "Wrapped Ether (18 decimals)" }
|
|
@@ -2398,26 +3373,26 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
2398
3373
|
]);
|
|
2399
3374
|
const symbol = `sw${assetSymbol}`;
|
|
2400
3375
|
console.log();
|
|
2401
|
-
console.log(
|
|
2402
|
-
|
|
2403
|
-
console.log(
|
|
2404
|
-
console.log(
|
|
2405
|
-
console.log(
|
|
2406
|
-
console.log(
|
|
2407
|
-
console.log(
|
|
2408
|
-
console.log(
|
|
2409
|
-
console.log(
|
|
2410
|
-
|
|
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();
|
|
2411
3386
|
if (!nonInteractive) {
|
|
2412
|
-
const go = await confirm({ message:
|
|
3387
|
+
const go = await confirm({ message: G4("Deploy syndicate?"), default: true });
|
|
2413
3388
|
if (!go) {
|
|
2414
|
-
console.log(
|
|
3389
|
+
console.log(DIM4(" Cancelled."));
|
|
2415
3390
|
return;
|
|
2416
3391
|
}
|
|
2417
3392
|
}
|
|
2418
3393
|
let metadataURI = opts.metadataUri || "";
|
|
2419
3394
|
if (!metadataURI) {
|
|
2420
|
-
const spinner2 = ora7({ text:
|
|
3395
|
+
const spinner2 = ora7({ text: W4("Uploading metadata to IPFS..."), color: "green" }).start();
|
|
2421
3396
|
try {
|
|
2422
3397
|
const metadata = {
|
|
2423
3398
|
schema: "sherwood/syndicate/v1",
|
|
@@ -2431,14 +3406,77 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
2431
3406
|
links: {}
|
|
2432
3407
|
};
|
|
2433
3408
|
metadataURI = await uploadMetadata(metadata);
|
|
2434
|
-
spinner2.succeed(
|
|
3409
|
+
spinner2.succeed(G4(`Metadata pinned: ${DIM4(metadataURI)}`));
|
|
2435
3410
|
} catch (err) {
|
|
2436
|
-
spinner2.warn(
|
|
3411
|
+
spinner2.warn(chalk8.yellow(`IPFS upload failed \u2014 using inline metadata`));
|
|
2437
3412
|
const json = JSON.stringify({ name, description, subdomain, asset: assetSymbol, openDeposits, createdBy: "@sherwoodagent/cli" });
|
|
2438
3413
|
metadataURI = `data:application/json;base64,${Buffer.from(json).toString("base64")}`;
|
|
2439
3414
|
}
|
|
2440
3415
|
}
|
|
2441
|
-
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();
|
|
2442
3480
|
const result = await createSyndicate({
|
|
2443
3481
|
creatorAgentId: BigInt(agentIdStr),
|
|
2444
3482
|
metadataURI,
|
|
@@ -2448,9 +3486,12 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
2448
3486
|
openDeposits,
|
|
2449
3487
|
subdomain
|
|
2450
3488
|
});
|
|
2451
|
-
|
|
3489
|
+
const chainId = getChain().id;
|
|
3490
|
+
setChainContract(chainId, "vault", result.vault);
|
|
3491
|
+
addSyndicate(chainId, { subdomain, vault: result.vault, role: "creator" });
|
|
3492
|
+
setPrimarySyndicate(chainId, subdomain);
|
|
2452
3493
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
2453
|
-
spinner.text =
|
|
3494
|
+
spinner.text = W4("Registering creator as agent...");
|
|
2454
3495
|
try {
|
|
2455
3496
|
setVaultAddress(result.vault);
|
|
2456
3497
|
const creatorAddress = getAccount().address;
|
|
@@ -2460,9 +3501,9 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
2460
3501
|
// agentAddress = creator EOA (direct execution)
|
|
2461
3502
|
);
|
|
2462
3503
|
} catch (regErr) {
|
|
2463
|
-
console.warn(
|
|
3504
|
+
console.warn(chalk8.yellow("\n \u26A0 Could not auto-register creator as agent \u2014 register manually with `syndicate add`"));
|
|
2464
3505
|
}
|
|
2465
|
-
spinner.text =
|
|
3506
|
+
spinner.text = W4("Setting up chat...");
|
|
2466
3507
|
try {
|
|
2467
3508
|
const xmtp = await loadXmtp();
|
|
2468
3509
|
const xmtpClient = await xmtp.getXmtpClient();
|
|
@@ -2470,35 +3511,37 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
2470
3511
|
await setTextRecord(subdomain, "xmtpGroupId", groupId, result.vault);
|
|
2471
3512
|
cacheGroupId(subdomain, groupId);
|
|
2472
3513
|
} catch {
|
|
2473
|
-
console.warn(
|
|
2474
|
-
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`));
|
|
2475
3516
|
}
|
|
2476
3517
|
try {
|
|
2477
3518
|
const cron = await loadCron();
|
|
2478
3519
|
const cronResult = cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
|
|
2479
3520
|
if (cronResult.isOpenClaw && cronResult.registered) {
|
|
2480
|
-
console.log(
|
|
3521
|
+
console.log(G4(" \u2713 Participation crons registered (15m check + hourly summary)"));
|
|
2481
3522
|
} else if (!cronResult.isOpenClaw) {
|
|
2482
|
-
console.log(
|
|
3523
|
+
console.log(DIM4(" Tip: Set up a scheduled process to run `sherwood session check " + subdomain + "` periodically"));
|
|
2483
3524
|
}
|
|
2484
3525
|
} catch {
|
|
2485
3526
|
}
|
|
2486
3527
|
spinner.stop();
|
|
2487
3528
|
console.log();
|
|
2488
|
-
console.log(
|
|
2489
|
-
|
|
2490
|
-
console.log(
|
|
2491
|
-
console.log(
|
|
2492
|
-
console.log(
|
|
2493
|
-
console.log(
|
|
2494
|
-
console.log(
|
|
2495
|
-
console.log(
|
|
2496
|
-
|
|
2497
|
-
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)"));
|
|
2498
3541
|
console.log();
|
|
2499
3542
|
} catch (err) {
|
|
2500
|
-
console.error(
|
|
2501
|
-
\u2716 ${
|
|
3543
|
+
console.error(chalk8.red(`
|
|
3544
|
+
\u2716 ${formatContractError(err)}`));
|
|
2502
3545
|
process.exit(1);
|
|
2503
3546
|
}
|
|
2504
3547
|
});
|
|
@@ -2522,34 +3565,34 @@ syndicate.command("list").description("List active syndicates (queries subgraph,
|
|
|
2522
3565
|
}
|
|
2523
3566
|
spinner.stop();
|
|
2524
3567
|
if (syndicates.length === 0) {
|
|
2525
|
-
console.log(
|
|
3568
|
+
console.log(chalk8.dim("No active syndicates found."));
|
|
2526
3569
|
return;
|
|
2527
3570
|
}
|
|
2528
3571
|
console.log();
|
|
2529
|
-
console.log(
|
|
3572
|
+
console.log(chalk8.bold(`Active Syndicates (${syndicates.length})`));
|
|
2530
3573
|
if (!process.env.SUBGRAPH_URL) {
|
|
2531
|
-
console.log(
|
|
3574
|
+
console.log(chalk8.dim(" (Set SUBGRAPH_URL for faster indexed queries)"));
|
|
2532
3575
|
}
|
|
2533
|
-
console.log(
|
|
3576
|
+
console.log(chalk8.dim("\u2500".repeat(70)));
|
|
2534
3577
|
for (const s of syndicates) {
|
|
2535
3578
|
const ts = typeof s.createdAt === "string" ? Number(s.createdAt) : Number(s.createdAt);
|
|
2536
3579
|
const date = new Date(ts * 1e3).toLocaleDateString();
|
|
2537
3580
|
const ensName = s.subdomain ? `${s.subdomain}.sherwoodagent.eth` : "";
|
|
2538
|
-
console.log(` #${s.id} ${
|
|
2539
|
-
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))}`);
|
|
2540
3583
|
console.log(` Creator: ${s.creator}`);
|
|
2541
3584
|
console.log(` Created: ${date}`);
|
|
2542
3585
|
if (s.totalDeposits) {
|
|
2543
3586
|
console.log(` Deposits: ${s.totalDeposits} USDC`);
|
|
2544
3587
|
}
|
|
2545
3588
|
if (s.metadataURI) {
|
|
2546
|
-
console.log(` Metadata: ${
|
|
3589
|
+
console.log(` Metadata: ${chalk8.dim(s.metadataURI)}`);
|
|
2547
3590
|
}
|
|
2548
3591
|
console.log();
|
|
2549
3592
|
}
|
|
2550
3593
|
} catch (err) {
|
|
2551
3594
|
spinner.fail("Failed to load syndicates");
|
|
2552
|
-
console.error(
|
|
3595
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2553
3596
|
process.exit(1);
|
|
2554
3597
|
}
|
|
2555
3598
|
});
|
|
@@ -2560,27 +3603,27 @@ syndicate.command("info").description("Display syndicate details by ID").argumen
|
|
|
2560
3603
|
const info = await getSyndicate(id);
|
|
2561
3604
|
spinner.stop();
|
|
2562
3605
|
if (!info.vault || info.vault === "0x0000000000000000000000000000000000000000") {
|
|
2563
|
-
console.log(
|
|
3606
|
+
console.log(chalk8.red(`Syndicate #${id} not found.`));
|
|
2564
3607
|
process.exit(1);
|
|
2565
3608
|
}
|
|
2566
3609
|
const date = new Date(Number(info.createdAt) * 1e3).toLocaleDateString();
|
|
2567
3610
|
console.log();
|
|
2568
|
-
console.log(
|
|
2569
|
-
console.log(
|
|
3611
|
+
console.log(chalk8.bold(`Syndicate #${info.id}`));
|
|
3612
|
+
console.log(chalk8.dim("\u2500".repeat(40)));
|
|
2570
3613
|
if (info.subdomain) {
|
|
2571
|
-
console.log(` ENS: ${
|
|
3614
|
+
console.log(` ENS: ${chalk8.bold(`${info.subdomain}.sherwoodagent.eth`)}`);
|
|
2572
3615
|
}
|
|
2573
|
-
console.log(` Vault: ${
|
|
3616
|
+
console.log(` Vault: ${chalk8.cyan(info.vault)}`);
|
|
2574
3617
|
console.log(` Creator: ${info.creator}`);
|
|
2575
3618
|
console.log(` Created: ${date}`);
|
|
2576
|
-
console.log(` Active: ${info.active ?
|
|
3619
|
+
console.log(` Active: ${info.active ? chalk8.green("yes") : chalk8.red("no")}`);
|
|
2577
3620
|
if (info.metadataURI) {
|
|
2578
|
-
console.log(` Metadata: ${
|
|
3621
|
+
console.log(` Metadata: ${chalk8.dim(info.metadataURI)}`);
|
|
2579
3622
|
}
|
|
2580
3623
|
setVaultAddress(info.vault);
|
|
2581
3624
|
const vaultInfo = await getVaultInfo();
|
|
2582
3625
|
console.log();
|
|
2583
|
-
console.log(
|
|
3626
|
+
console.log(chalk8.bold(" Vault Stats"));
|
|
2584
3627
|
console.log(` Total Assets: ${vaultInfo.totalAssets}`);
|
|
2585
3628
|
console.log(` Agent Count: ${vaultInfo.agentCount}`);
|
|
2586
3629
|
console.log(` Redemptions Locked: ${vaultInfo.redemptionsLocked}`);
|
|
@@ -2588,12 +3631,12 @@ syndicate.command("info").description("Display syndicate details by ID").argumen
|
|
|
2588
3631
|
console.log();
|
|
2589
3632
|
} catch (err) {
|
|
2590
3633
|
spinner.fail("Failed to load syndicate info");
|
|
2591
|
-
console.error(
|
|
3634
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2592
3635
|
process.exit(1);
|
|
2593
3636
|
}
|
|
2594
3637
|
});
|
|
2595
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) => {
|
|
2596
|
-
const spinner = ora7({ text:
|
|
3639
|
+
const spinner = ora7({ text: W4("Loading syndicate..."), color: "green" }).start();
|
|
2597
3640
|
try {
|
|
2598
3641
|
const syndicateId = BigInt(opts.id);
|
|
2599
3642
|
let metadataURI = opts.uri;
|
|
@@ -2605,7 +3648,7 @@ syndicate.command("update-metadata").description("Update syndicate metadata (cre
|
|
|
2605
3648
|
}
|
|
2606
3649
|
const name = opts.name || info.subdomain;
|
|
2607
3650
|
const description = opts.description || `${name} \u2014 a Sherwood syndicate on ${info.subdomain}.sherwoodagent.eth`;
|
|
2608
|
-
spinner.text =
|
|
3651
|
+
spinner.text = W4("Uploading metadata to IPFS...");
|
|
2609
3652
|
const metadata = {
|
|
2610
3653
|
schema: "sherwood/syndicate/v1",
|
|
2611
3654
|
name,
|
|
@@ -2616,15 +3659,15 @@ syndicate.command("update-metadata").description("Update syndicate metadata (cre
|
|
|
2616
3659
|
links: {}
|
|
2617
3660
|
};
|
|
2618
3661
|
metadataURI = await uploadMetadata(metadata);
|
|
2619
|
-
spinner.text =
|
|
3662
|
+
spinner.text = W4("Updating on-chain metadata...");
|
|
2620
3663
|
}
|
|
2621
3664
|
const hash = await updateMetadata(syndicateId, metadataURI);
|
|
2622
|
-
spinner.succeed(
|
|
2623
|
-
console.log(
|
|
2624
|
-
console.log(
|
|
3665
|
+
spinner.succeed(G4(`Metadata updated`));
|
|
3666
|
+
console.log(DIM4(` IPFS: ${metadataURI}`));
|
|
3667
|
+
console.log(DIM4(` ${getExplorerUrl(hash)}`));
|
|
2625
3668
|
} catch (err) {
|
|
2626
3669
|
spinner.fail("Metadata update failed");
|
|
2627
|
-
console.error(
|
|
3670
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2628
3671
|
process.exit(1);
|
|
2629
3672
|
}
|
|
2630
3673
|
});
|
|
@@ -2635,10 +3678,10 @@ syndicate.command("approve-depositor").description("Approve an address to deposi
|
|
|
2635
3678
|
const depositor = validateAddress(opts.depositor, "depositor");
|
|
2636
3679
|
const hash = await approveDepositor(depositor);
|
|
2637
3680
|
spinner.succeed(`Depositor approved: ${hash}`);
|
|
2638
|
-
console.log(
|
|
3681
|
+
console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
|
|
2639
3682
|
} catch (err) {
|
|
2640
3683
|
spinner.fail("Approval failed");
|
|
2641
|
-
console.error(
|
|
3684
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2642
3685
|
process.exit(1);
|
|
2643
3686
|
}
|
|
2644
3687
|
});
|
|
@@ -2649,10 +3692,10 @@ syndicate.command("remove-depositor").description("Remove an address from the de
|
|
|
2649
3692
|
const depositor = validateAddress(opts.depositor, "depositor");
|
|
2650
3693
|
const hash = await removeDepositor(depositor);
|
|
2651
3694
|
spinner.succeed(`Depositor removed: ${hash}`);
|
|
2652
|
-
console.log(
|
|
3695
|
+
console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
|
|
2653
3696
|
} catch (err) {
|
|
2654
3697
|
spinner.fail("Removal failed");
|
|
2655
|
-
console.error(
|
|
3698
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2656
3699
|
process.exit(1);
|
|
2657
3700
|
}
|
|
2658
3701
|
});
|
|
@@ -2674,7 +3717,7 @@ syndicate.command("add").description("Register an agent on a syndicate vault (cr
|
|
|
2674
3717
|
agentWallet
|
|
2675
3718
|
);
|
|
2676
3719
|
spinner.succeed(`Agent registered: ${hash}`);
|
|
2677
|
-
console.log(
|
|
3720
|
+
console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
|
|
2678
3721
|
try {
|
|
2679
3722
|
const xmtp = await loadXmtp();
|
|
2680
3723
|
const xmtpClient = await xmtp.getXmtpClient();
|
|
@@ -2686,26 +3729,66 @@ syndicate.command("add").description("Register an agent on a syndicate vault (cr
|
|
|
2686
3729
|
syndicate: subdomain,
|
|
2687
3730
|
timestamp: Math.floor(Date.now() / 1e3)
|
|
2688
3731
|
});
|
|
2689
|
-
console.log(
|
|
3732
|
+
console.log(chalk8.dim(` Added to chat: ${subdomain}`));
|
|
2690
3733
|
} catch {
|
|
2691
|
-
console.warn(
|
|
2692
|
-
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`));
|
|
2693
3736
|
}
|
|
2694
3737
|
} catch (err) {
|
|
2695
3738
|
spinner.fail("Registration failed");
|
|
2696
|
-
console.error(
|
|
3739
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2697
3740
|
process.exit(1);
|
|
2698
3741
|
}
|
|
2699
3742
|
});
|
|
2700
|
-
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) => {
|
|
2701
3779
|
const spinner = ora7("Resolving syndicate...").start();
|
|
2702
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
|
+
}
|
|
2703
3786
|
const agentId = getAgentId();
|
|
2704
3787
|
if (!agentId) {
|
|
2705
3788
|
spinner.fail("No agent identity found. Run 'sherwood identity mint' first.");
|
|
2706
3789
|
process.exit(1);
|
|
2707
3790
|
}
|
|
2708
|
-
const syndicate2 = await resolveSyndicate(
|
|
3791
|
+
const syndicate2 = await resolveSyndicate(subdomain);
|
|
2709
3792
|
const callerAddress = getAccount().address;
|
|
2710
3793
|
spinner.text = "Checking membership...";
|
|
2711
3794
|
setVaultAddress(syndicate2.vault);
|
|
@@ -2715,17 +3798,17 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
|
|
|
2715
3798
|
try {
|
|
2716
3799
|
const xmtp = await loadXmtp();
|
|
2717
3800
|
await xmtp.getXmtpClient();
|
|
2718
|
-
console.log(
|
|
3801
|
+
console.log(chalk8.dim(" XMTP identity ready"));
|
|
2719
3802
|
} catch {
|
|
2720
|
-
console.warn(
|
|
3803
|
+
console.warn(chalk8.yellow(" \u26A0 Could not initialize XMTP identity"));
|
|
2721
3804
|
}
|
|
2722
3805
|
try {
|
|
2723
3806
|
const cron = await loadCron();
|
|
2724
|
-
const cronResult = cron.registerSyndicateCrons(
|
|
3807
|
+
const cronResult = cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
|
|
2725
3808
|
if (cronResult.isOpenClaw && cronResult.registered) {
|
|
2726
|
-
console.log(
|
|
3809
|
+
console.log(chalk8.green(" \u2713 Participation crons registered"));
|
|
2727
3810
|
} else if (!cronResult.isOpenClaw) {
|
|
2728
|
-
console.log(
|
|
3811
|
+
console.log(chalk8.dim(" Tip: Set up a scheduled process to run `sherwood session check " + subdomain + "` periodically"));
|
|
2729
3812
|
}
|
|
2730
3813
|
} catch {
|
|
2731
3814
|
}
|
|
@@ -2738,24 +3821,26 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
|
|
|
2738
3821
|
);
|
|
2739
3822
|
if (existingRequest) {
|
|
2740
3823
|
spinner.succeed("You already have a pending join request for this syndicate");
|
|
2741
|
-
console.log(
|
|
2742
|
-
console.log(
|
|
3824
|
+
console.log(chalk8.dim(` Attestation: ${existingRequest.uid}`));
|
|
3825
|
+
console.log(chalk8.dim(` Submitted: ${new Date(existingRequest.time * 1e3).toLocaleString()}`));
|
|
2743
3826
|
try {
|
|
2744
3827
|
const xmtp = await loadXmtp();
|
|
2745
3828
|
await xmtp.getXmtpClient();
|
|
2746
|
-
console.log(
|
|
3829
|
+
console.log(chalk8.dim(" XMTP identity ready"));
|
|
2747
3830
|
} catch {
|
|
2748
|
-
console.warn(
|
|
3831
|
+
console.warn(chalk8.yellow(" \u26A0 Could not initialize XMTP identity"));
|
|
2749
3832
|
}
|
|
2750
3833
|
return;
|
|
2751
3834
|
}
|
|
2752
3835
|
spinner.text = "Creating join request attestation...";
|
|
3836
|
+
const referrerAgentId = opts.ref ? parseInt(opts.ref, 10) : void 0;
|
|
2753
3837
|
const { uid, hash } = await createJoinRequest(
|
|
2754
3838
|
syndicate2.id,
|
|
2755
3839
|
BigInt(agentId),
|
|
2756
3840
|
syndicate2.vault,
|
|
2757
3841
|
syndicate2.creator,
|
|
2758
|
-
opts.message
|
|
3842
|
+
opts.message,
|
|
3843
|
+
referrerAgentId
|
|
2759
3844
|
);
|
|
2760
3845
|
try {
|
|
2761
3846
|
spinner.text = "Registering XMTP identity...";
|
|
@@ -2764,34 +3849,37 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
|
|
|
2764
3849
|
spinner.succeed("Join request created (XMTP identity ready)");
|
|
2765
3850
|
} catch {
|
|
2766
3851
|
spinner.succeed("Join request created");
|
|
2767
|
-
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"));
|
|
2768
3853
|
}
|
|
3854
|
+
addSyndicate(getChain().id, { subdomain, vault: syndicate2.vault, role: "agent" });
|
|
3855
|
+
setPrimarySyndicate(getChain().id, subdomain);
|
|
2769
3856
|
try {
|
|
2770
3857
|
const cron = await loadCron();
|
|
2771
|
-
const cronResult = cron.registerSyndicateCrons(
|
|
3858
|
+
const cronResult = cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
|
|
2772
3859
|
if (cronResult.isOpenClaw && cronResult.registered) {
|
|
2773
|
-
console.log(
|
|
3860
|
+
console.log(G4(" \u2713 Participation crons registered (will activate after approval)"));
|
|
2774
3861
|
} else if (!cronResult.isOpenClaw) {
|
|
2775
|
-
console.log(
|
|
3862
|
+
console.log(DIM4(" Tip: Set up a scheduled process to run `sherwood session check " + subdomain + "` periodically"));
|
|
2776
3863
|
}
|
|
2777
3864
|
} catch {
|
|
2778
3865
|
}
|
|
2779
3866
|
console.log();
|
|
2780
|
-
console.log(
|
|
2781
|
-
|
|
2782
|
-
console.log(
|
|
2783
|
-
console.log(
|
|
2784
|
-
console.log(
|
|
2785
|
-
console.log(
|
|
2786
|
-
console.log(
|
|
2787
|
-
console.log(
|
|
2788
|
-
|
|
2789
|
-
console.log(
|
|
2790
|
-
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}`));
|
|
2791
3879
|
console.log();
|
|
2792
3880
|
} catch (err) {
|
|
2793
3881
|
spinner.fail("Join request failed");
|
|
2794
|
-
console.error(
|
|
3882
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2795
3883
|
process.exit(1);
|
|
2796
3884
|
}
|
|
2797
3885
|
});
|
|
@@ -2801,10 +3889,11 @@ syndicate.command("requests").description("View pending join requests for a synd
|
|
|
2801
3889
|
let creatorAddress;
|
|
2802
3890
|
let subdomain;
|
|
2803
3891
|
let syndicateVault;
|
|
2804
|
-
|
|
2805
|
-
|
|
3892
|
+
const resolvedSubdomain = opts.subdomain || getPrimarySyndicate(getChain().id)?.subdomain;
|
|
3893
|
+
if (resolvedSubdomain) {
|
|
3894
|
+
const syndicateInfo = await resolveSyndicate(resolvedSubdomain);
|
|
2806
3895
|
creatorAddress = syndicateInfo.creator;
|
|
2807
|
-
subdomain =
|
|
3896
|
+
subdomain = resolvedSubdomain;
|
|
2808
3897
|
syndicateVault = syndicateInfo.vault;
|
|
2809
3898
|
} else {
|
|
2810
3899
|
resolveVault(opts);
|
|
@@ -2831,37 +3920,43 @@ syndicate.command("requests").description("View pending join requests for a synd
|
|
|
2831
3920
|
);
|
|
2832
3921
|
spinner.stop();
|
|
2833
3922
|
if (requests.length === 0) {
|
|
2834
|
-
console.log(
|
|
3923
|
+
console.log(DIM4("\n No pending join requests.\n"));
|
|
2835
3924
|
return;
|
|
2836
3925
|
}
|
|
2837
3926
|
console.log();
|
|
2838
|
-
console.log(
|
|
2839
|
-
|
|
3927
|
+
console.log(LABEL4(` \u25C6 Pending Join Requests (${requests.length})`));
|
|
3928
|
+
SEP4();
|
|
2840
3929
|
for (let i = 0; i < requests.length; i++) {
|
|
2841
3930
|
const req = requests[i];
|
|
2842
3931
|
const date = new Date(req.time * 1e3).toLocaleString();
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
console.log(
|
|
2846
|
-
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}`));
|
|
2847
3941
|
console.log();
|
|
2848
3942
|
}
|
|
2849
|
-
console.log(
|
|
2850
|
-
console.log(
|
|
2851
|
-
console.log(
|
|
2852
|
-
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>`));
|
|
2853
3947
|
console.log();
|
|
2854
3948
|
} catch (err) {
|
|
2855
3949
|
spinner.fail("Failed to load requests");
|
|
2856
|
-
console.error(
|
|
3950
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2857
3951
|
process.exit(1);
|
|
2858
3952
|
}
|
|
2859
3953
|
});
|
|
2860
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) => {
|
|
2861
3955
|
const spinner = ora7("Verifying creator...").start();
|
|
2862
3956
|
try {
|
|
2863
|
-
|
|
2864
|
-
|
|
3957
|
+
const approveSubdomain = opts.subdomain || getPrimarySyndicate(getChain().id)?.subdomain;
|
|
3958
|
+
if (approveSubdomain && !opts.vault) {
|
|
3959
|
+
const syndicateInfo = await resolveSyndicate(approveSubdomain);
|
|
2865
3960
|
setVaultAddress(syndicateInfo.vault);
|
|
2866
3961
|
} else {
|
|
2867
3962
|
resolveVault(opts);
|
|
@@ -2882,11 +3977,11 @@ syndicate.command("approve").description("Approve an agent join request (registe
|
|
|
2882
3977
|
agentWallet
|
|
2883
3978
|
);
|
|
2884
3979
|
agentWasRegistered = true;
|
|
2885
|
-
console.log(
|
|
3980
|
+
console.log(DIM4(` Agent registered: ${getExplorerUrl(regHash)}`));
|
|
2886
3981
|
} catch (regErr) {
|
|
2887
3982
|
const msg = regErr instanceof Error ? regErr.message : String(regErr);
|
|
2888
3983
|
if (msg.includes("0xe098d3ee") || msg.includes("AgentAlreadyRegistered")) {
|
|
2889
|
-
console.log(
|
|
3984
|
+
console.log(DIM4(" Agent already registered on vault \u2014 skipping"));
|
|
2890
3985
|
} else {
|
|
2891
3986
|
throw regErr;
|
|
2892
3987
|
}
|
|
@@ -2902,7 +3997,7 @@ syndicate.command("approve").description("Approve an agent join request (registe
|
|
|
2902
3997
|
let approvalUid;
|
|
2903
3998
|
if (alreadyApproved) {
|
|
2904
3999
|
approvalUid = alreadyApproved.uid;
|
|
2905
|
-
console.log(
|
|
4000
|
+
console.log(DIM4(` Approval attestation already exists \u2014 skipping`));
|
|
2906
4001
|
} else {
|
|
2907
4002
|
spinner.text = "Creating approval attestation...";
|
|
2908
4003
|
const result = await createApproval(
|
|
@@ -2925,23 +4020,23 @@ syndicate.command("approve").description("Approve an agent join request (registe
|
|
|
2925
4020
|
syndicate: subdomain,
|
|
2926
4021
|
timestamp: Math.floor(Date.now() / 1e3)
|
|
2927
4022
|
});
|
|
2928
|
-
console.log(
|
|
4023
|
+
console.log(DIM4(` Added to chat: ${subdomain}`));
|
|
2929
4024
|
} catch {
|
|
2930
|
-
console.warn(
|
|
2931
|
-
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`));
|
|
2932
4027
|
}
|
|
2933
4028
|
spinner.succeed("Agent approved and registered");
|
|
2934
4029
|
console.log();
|
|
2935
|
-
console.log(
|
|
2936
|
-
|
|
2937
|
-
console.log(
|
|
2938
|
-
console.log(
|
|
2939
|
-
console.log(
|
|
2940
|
-
console.log(
|
|
2941
|
-
|
|
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();
|
|
2942
4037
|
} catch (err) {
|
|
2943
4038
|
spinner.fail("Approval failed");
|
|
2944
|
-
console.error(
|
|
4039
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2945
4040
|
process.exit(1);
|
|
2946
4041
|
}
|
|
2947
4042
|
});
|
|
@@ -2953,26 +4048,134 @@ syndicate.command("reject").description("Reject a join request by revoking its a
|
|
|
2953
4048
|
opts.attestation
|
|
2954
4049
|
);
|
|
2955
4050
|
spinner.succeed("Join request rejected");
|
|
2956
|
-
console.log(
|
|
4051
|
+
console.log(DIM4(` ${getExplorerUrl(hash)}`));
|
|
2957
4052
|
} catch (err) {
|
|
2958
4053
|
spinner.fail("Rejection failed");
|
|
2959
|
-
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)));
|
|
2960
4147
|
process.exit(1);
|
|
2961
4148
|
}
|
|
2962
4149
|
});
|
|
2963
4150
|
var vaultCmd = program.command("vault");
|
|
2964
|
-
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) => {
|
|
2965
4152
|
resolveVault(opts);
|
|
2966
4153
|
const decimals = await getAssetDecimals();
|
|
2967
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
|
+
}
|
|
2968
4165
|
const spinner = ora7(`Depositing ${opts.amount}...`).start();
|
|
2969
4166
|
try {
|
|
2970
|
-
|
|
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
|
+
}
|
|
2971
4174
|
spinner.succeed(`Deposited: ${hash}`);
|
|
2972
|
-
console.log(
|
|
4175
|
+
console.log(chalk8.dim(` ${getExplorerUrl(hash)}`));
|
|
2973
4176
|
} catch (err) {
|
|
2974
4177
|
spinner.fail("Deposit failed");
|
|
2975
|
-
console.error(
|
|
4178
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2976
4179
|
process.exit(1);
|
|
2977
4180
|
}
|
|
2978
4181
|
});
|
|
@@ -2983,8 +4186,8 @@ vaultCmd.command("info").description("Display vault state").option("--vault <add
|
|
|
2983
4186
|
const info = await getVaultInfo();
|
|
2984
4187
|
spinner.stop();
|
|
2985
4188
|
console.log();
|
|
2986
|
-
console.log(
|
|
2987
|
-
console.log(
|
|
4189
|
+
console.log(chalk8.bold("Vault Info"));
|
|
4190
|
+
console.log(chalk8.dim("\u2500".repeat(40)));
|
|
2988
4191
|
console.log(` Address: ${info.address}`);
|
|
2989
4192
|
console.log(` Total Assets: ${info.totalAssets}`);
|
|
2990
4193
|
console.log(` Agent Count: ${info.agentCount}`);
|
|
@@ -2993,7 +4196,7 @@ vaultCmd.command("info").description("Display vault state").option("--vault <add
|
|
|
2993
4196
|
console.log();
|
|
2994
4197
|
} catch (err) {
|
|
2995
4198
|
spinner.fail("Failed to load vault info");
|
|
2996
|
-
console.error(
|
|
4199
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
2997
4200
|
process.exit(1);
|
|
2998
4201
|
}
|
|
2999
4202
|
});
|
|
@@ -3004,22 +4207,22 @@ vaultCmd.command("balance").description("Show LP share balance and asset value")
|
|
|
3004
4207
|
const balance = await getBalance(opts.address);
|
|
3005
4208
|
spinner.stop();
|
|
3006
4209
|
console.log();
|
|
3007
|
-
console.log(
|
|
3008
|
-
console.log(
|
|
4210
|
+
console.log(chalk8.bold("LP Position"));
|
|
4211
|
+
console.log(chalk8.dim("\u2500".repeat(40)));
|
|
3009
4212
|
console.log(` Shares: ${balance.shares.toString()}`);
|
|
3010
4213
|
console.log(` Asset Value: ${balance.assetsValue}`);
|
|
3011
4214
|
console.log(` % of Vault: ${balance.percentOfVault}`);
|
|
3012
4215
|
console.log();
|
|
3013
4216
|
} catch (err) {
|
|
3014
4217
|
spinner.fail("Failed to load balance");
|
|
3015
|
-
console.error(
|
|
4218
|
+
console.error(chalk8.red(formatContractError(err)));
|
|
3016
4219
|
process.exit(1);
|
|
3017
4220
|
}
|
|
3018
4221
|
});
|
|
3019
4222
|
var strategy = program.command("strategy").description("Strategy templates \u2014 list, clone, propose");
|
|
3020
4223
|
registerStrategyTemplateCommands(strategy);
|
|
3021
4224
|
program.command("providers").description("List available DeFi providers").action(async () => {
|
|
3022
|
-
const { MessariProvider, NansenProvider } = await import("./research-
|
|
4225
|
+
const { MessariProvider, NansenProvider } = await import("./research-SJ6D3L2B.js");
|
|
3023
4226
|
const providers = [new MoonwellProvider(), new UniswapProvider(), new MessariProvider(), new NansenProvider()];
|
|
3024
4227
|
for (const p of providers) {
|
|
3025
4228
|
const info = p.info();
|
|
@@ -3030,26 +4233,26 @@ ${info.name} (${info.type})`);
|
|
|
3030
4233
|
}
|
|
3031
4234
|
});
|
|
3032
4235
|
try {
|
|
3033
|
-
const { registerChatCommands } = await import("./chat-
|
|
4236
|
+
const { registerChatCommands } = await import("./chat-JYAJMP4D.js");
|
|
3034
4237
|
registerChatCommands(program);
|
|
3035
4238
|
} catch {
|
|
3036
4239
|
program.command("chat <name> [action] [actionArgs...]").description("Syndicate chat (XMTP) \u2014 requires @xmtp/cli").action(() => {
|
|
3037
|
-
console.error(
|
|
3038
|
-
console.error(
|
|
3039
|
-
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"));
|
|
3040
4243
|
process.exit(1);
|
|
3041
4244
|
});
|
|
3042
4245
|
}
|
|
3043
|
-
var { registerSessionCommands } = await import("./session-
|
|
4246
|
+
var { registerSessionCommands } = await import("./session-XZNKLK3I.js");
|
|
3044
4247
|
registerSessionCommands(program);
|
|
3045
4248
|
registerVeniceCommands(program);
|
|
3046
4249
|
registerAllowanceCommands(program);
|
|
3047
4250
|
registerIdentityCommands(program);
|
|
3048
4251
|
registerProposalCommands(program);
|
|
3049
4252
|
registerGovernorCommands(program);
|
|
3050
|
-
var { registerResearchCommands } = await import("./research-
|
|
4253
|
+
var { registerResearchCommands } = await import("./research-ZW6XVTGZ.js");
|
|
3051
4254
|
registerResearchCommands(program);
|
|
3052
|
-
var { registerTradeCommands } = await import("./trade-
|
|
4255
|
+
var { registerTradeCommands } = await import("./trade-OJM5HNHL.js");
|
|
3053
4256
|
registerTradeCommands(program);
|
|
3054
4257
|
var configCmd = program.command("config");
|
|
3055
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) => {
|
|
@@ -3057,42 +4260,42 @@ configCmd.command("set").description("Save settings to ~/.sherwood/config.json (
|
|
|
3057
4260
|
if (opts.privateKey) {
|
|
3058
4261
|
setPrivateKey(opts.privateKey);
|
|
3059
4262
|
const account = getAccount();
|
|
3060
|
-
console.log(
|
|
3061
|
-
console.log(
|
|
4263
|
+
console.log(chalk8.green("Private key saved to ~/.sherwood/config.json"));
|
|
4264
|
+
console.log(chalk8.dim(` Wallet: ${account.address}`));
|
|
3062
4265
|
saved = true;
|
|
3063
4266
|
}
|
|
3064
4267
|
if (opts.vault) {
|
|
3065
4268
|
const chainId = getChain().id;
|
|
3066
4269
|
setChainContract(chainId, "vault", opts.vault);
|
|
3067
|
-
console.log(
|
|
3068
|
-
console.log(
|
|
4270
|
+
console.log(chalk8.green(`Vault saved to ~/.sherwood/config.json (chain ${chainId})`));
|
|
4271
|
+
console.log(chalk8.dim(` Vault: ${opts.vault}`));
|
|
3069
4272
|
saved = true;
|
|
3070
4273
|
}
|
|
3071
4274
|
if (opts.rpc) {
|
|
3072
4275
|
const network = getNetwork();
|
|
3073
4276
|
setConfigRpcUrl(network, opts.rpc);
|
|
3074
|
-
console.log(
|
|
3075
|
-
console.log(
|
|
4277
|
+
console.log(chalk8.green(`RPC URL saved for ${network}`));
|
|
4278
|
+
console.log(chalk8.dim(` RPC: ${opts.rpc}`));
|
|
3076
4279
|
saved = true;
|
|
3077
4280
|
}
|
|
3078
4281
|
if (opts.notifyTo) {
|
|
3079
4282
|
setNotifyTo(opts.notifyTo);
|
|
3080
|
-
console.log(
|
|
3081
|
-
console.log(
|
|
4283
|
+
console.log(chalk8.green("Notify destination saved to ~/.sherwood/config.json"));
|
|
4284
|
+
console.log(chalk8.dim(` Notify to: ${opts.notifyTo}`));
|
|
3082
4285
|
saved = true;
|
|
3083
4286
|
}
|
|
3084
4287
|
if (opts.uniswapApiKey) {
|
|
3085
4288
|
setUniswapApiKey(opts.uniswapApiKey);
|
|
3086
|
-
console.log(
|
|
4289
|
+
console.log(chalk8.green("Uniswap API key saved to ~/.sherwood/config.json"));
|
|
3087
4290
|
saved = true;
|
|
3088
4291
|
}
|
|
3089
4292
|
if (opts.veniceApiKey) {
|
|
3090
4293
|
setVeniceApiKey(opts.veniceApiKey);
|
|
3091
|
-
console.log(
|
|
4294
|
+
console.log(chalk8.green("Venice API key saved to ~/.sherwood/config.json"));
|
|
3092
4295
|
saved = true;
|
|
3093
4296
|
}
|
|
3094
4297
|
if (!saved) {
|
|
3095
|
-
console.log(
|
|
4298
|
+
console.log(chalk8.red("Provide at least one of: --private-key, --vault, --rpc, --notify-to, --uniswap-api-key, --venice-api-key"));
|
|
3096
4299
|
process.exit(1);
|
|
3097
4300
|
}
|
|
3098
4301
|
});
|
|
@@ -3103,17 +4306,17 @@ configCmd.command("show").description("Display current config for the active net
|
|
|
3103
4306
|
const config = loadConfig();
|
|
3104
4307
|
const customRpc = config.rpc?.[network];
|
|
3105
4308
|
console.log();
|
|
3106
|
-
console.log(
|
|
3107
|
-
console.log(
|
|
3108
|
-
console.log(` Network: ${
|
|
3109
|
-
console.log(` RPC: ${customRpc ?
|
|
3110
|
-
console.log(` Wallet: ${config.privateKey ?
|
|
3111
|
-
console.log(` Agent ID: ${config.agentId ??
|
|
3112
|
-
console.log(` Vault: ${contracts.vault ??
|
|
3113
|
-
console.log(` Uniswap: ${getUniswapApiKey() ?
|
|
3114
|
-
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")}`);
|
|
3115
4318
|
console.log();
|
|
3116
|
-
console.log(
|
|
4319
|
+
console.log(chalk8.dim(" Config file: ~/.sherwood/config.json"));
|
|
3117
4320
|
console.log();
|
|
3118
4321
|
});
|
|
3119
4322
|
program.parse();
|