@sherwoodagent/cli 0.22.8 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chat-JYAJMP4D.js → chat-52PWNH7Y.js} +7 -7
- package/dist/{chunk-4YIE7GHL.js → chunk-66VFSLPS.js} +2 -2
- package/dist/{chunk-VO6DCRWV.js → chunk-7PCI3WHQ.js} +4 -4
- package/dist/{chunk-LVPBJ7MI.js → chunk-GWIW6DDG.js} +2 -2
- package/dist/{chunk-4Y6Y4SVH.js → chunk-JEVBC7DV.js} +4 -4
- package/dist/{chunk-G7JQRLQE.js → chunk-QBNWBWD3.js} +5 -5
- package/dist/{chunk-B42ZCCZ7.js → chunk-RGOTBJIN.js} +28 -3
- package/dist/chunk-RGOTBJIN.js.map +1 -0
- package/dist/{chunk-ASJTFFZH.js → chunk-SBVHGUQJ.js} +3 -3
- package/dist/{chunk-Y7LS6DEF.js → chunk-VQIFAXUY.js} +5 -5
- package/dist/{chunk-JR5K5WVR.js → chunk-ZPLUJT4V.js} +169 -13
- package/dist/chunk-ZPLUJT4V.js.map +1 -0
- package/dist/{client-SCIY6SUP.js → client-CWQFYZ5Z.js} +4 -4
- package/dist/{config-BGS5MX4D.js → config-YXZKPSZB.js} +8 -2
- package/dist/{eas-X5UIEM6D.js → eas-YZIHYQAI.js} +6 -6
- package/dist/{governor-ZKRSAHG4.js → governor-YPDAOT3S.js} +6 -6
- package/dist/index.js +741 -74
- package/dist/index.js.map +1 -1
- package/dist/{network-UTSOP7EF.js → network-QEMP3DSP.js} +3 -3
- package/dist/price-SWNEGWO4.js +156 -0
- package/dist/price-SWNEGWO4.js.map +1 -0
- package/dist/{research-EWH43HFN.js → research-PH77VSE4.js} +9 -9
- package/dist/research-ZX7T2ZIJ.js +14 -0
- package/dist/{session-XZNKLK3I.js → session-NMA4RLKC.js} +12 -12
- package/dist/{trade-VKSXEHWW.js → trade-N2A74UPX.js} +16 -16
- package/dist/{xmtp-XKHXATPQ.js → xmtp-YNOGIZ4G.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-B42ZCCZ7.js.map +0 -1
- package/dist/chunk-JR5K5WVR.js.map +0 -1
- package/dist/research-W3M3VZBD.js +0 -14
- /package/dist/{chat-JYAJMP4D.js.map → chat-52PWNH7Y.js.map} +0 -0
- /package/dist/{chunk-4YIE7GHL.js.map → chunk-66VFSLPS.js.map} +0 -0
- /package/dist/{chunk-VO6DCRWV.js.map → chunk-7PCI3WHQ.js.map} +0 -0
- /package/dist/{chunk-LVPBJ7MI.js.map → chunk-GWIW6DDG.js.map} +0 -0
- /package/dist/{chunk-4Y6Y4SVH.js.map → chunk-JEVBC7DV.js.map} +0 -0
- /package/dist/{chunk-G7JQRLQE.js.map → chunk-QBNWBWD3.js.map} +0 -0
- /package/dist/{chunk-ASJTFFZH.js.map → chunk-SBVHGUQJ.js.map} +0 -0
- /package/dist/{chunk-Y7LS6DEF.js.map → chunk-VQIFAXUY.js.map} +0 -0
- /package/dist/{client-SCIY6SUP.js.map → client-CWQFYZ5Z.js.map} +0 -0
- /package/dist/{config-BGS5MX4D.js.map → config-YXZKPSZB.js.map} +0 -0
- /package/dist/{eas-X5UIEM6D.js.map → eas-YZIHYQAI.js.map} +0 -0
- /package/dist/{governor-ZKRSAHG4.js.map → governor-YPDAOT3S.js.map} +0 -0
- /package/dist/{network-UTSOP7EF.js.map → network-QEMP3DSP.js.map} +0 -0
- /package/dist/{research-EWH43HFN.js.map → research-PH77VSE4.js.map} +0 -0
- /package/dist/{research-W3M3VZBD.js.map → research-ZX7T2ZIJ.js.map} +0 -0
- /package/dist/{session-XZNKLK3I.js.map → session-NMA4RLKC.js.map} +0 -0
- /package/dist/{trade-VKSXEHWW.js.map → trade-N2A74UPX.js.map} +0 -0
- /package/dist/{xmtp-XKHXATPQ.js.map → xmtp-YNOGIZ4G.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
approveDepositor,
|
|
4
|
+
deposit,
|
|
5
|
+
depositWithEthWrap,
|
|
6
|
+
executeBatch,
|
|
7
|
+
getAssetDecimals,
|
|
8
|
+
getBalance,
|
|
9
|
+
getVaultAddress,
|
|
10
|
+
getVaultInfo,
|
|
11
|
+
isAgent,
|
|
12
|
+
preflightDeposit,
|
|
13
|
+
registerAgent,
|
|
14
|
+
removeDepositor,
|
|
15
|
+
resolveSyndicate,
|
|
16
|
+
resolveVaultSyndicate,
|
|
17
|
+
setTextRecord,
|
|
18
|
+
setVaultAddress
|
|
19
|
+
} from "./chunk-QBNWBWD3.js";
|
|
2
20
|
import {
|
|
3
21
|
UniswapProvider,
|
|
4
22
|
applySlippage,
|
|
@@ -9,7 +27,7 @@ import {
|
|
|
9
27
|
getQuote,
|
|
10
28
|
listModels,
|
|
11
29
|
provisionApiKey
|
|
12
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-VQIFAXUY.js";
|
|
13
31
|
import {
|
|
14
32
|
PROPOSAL_STATE,
|
|
15
33
|
PROPOSAL_STATES,
|
|
@@ -40,7 +58,7 @@ import {
|
|
|
40
58
|
setVotingPeriod,
|
|
41
59
|
settleProposal,
|
|
42
60
|
vote
|
|
43
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-7PCI3WHQ.js";
|
|
44
62
|
import {
|
|
45
63
|
fetchMetadata,
|
|
46
64
|
uploadMetadata
|
|
@@ -54,43 +72,28 @@ import {
|
|
|
54
72
|
queryJoinRequests,
|
|
55
73
|
revokeAttestation,
|
|
56
74
|
stripReferrerPrefix
|
|
57
|
-
} from "./chunk-
|
|
58
|
-
import {
|
|
59
|
-
approveDepositor,
|
|
60
|
-
deposit,
|
|
61
|
-
depositWithEthWrap,
|
|
62
|
-
executeBatch,
|
|
63
|
-
getAssetDecimals,
|
|
64
|
-
getBalance,
|
|
65
|
-
getVaultAddress,
|
|
66
|
-
getVaultInfo,
|
|
67
|
-
isAgent,
|
|
68
|
-
preflightDeposit,
|
|
69
|
-
registerAgent,
|
|
70
|
-
removeDepositor,
|
|
71
|
-
resolveSyndicate,
|
|
72
|
-
resolveVaultSyndicate,
|
|
73
|
-
setTextRecord,
|
|
74
|
-
setVaultAddress
|
|
75
|
-
} from "./chunk-G7JQRLQE.js";
|
|
75
|
+
} from "./chunk-JEVBC7DV.js";
|
|
76
76
|
import {
|
|
77
77
|
AERODROME,
|
|
78
78
|
AGENT_REGISTRY,
|
|
79
79
|
BASE_STRATEGY_ABI,
|
|
80
|
+
CHAINLINK,
|
|
80
81
|
EAS_SCHEMAS,
|
|
81
82
|
ERC20_ABI,
|
|
82
83
|
MOONWELL,
|
|
84
|
+
PORTFOLIO_STRATEGY_ABI,
|
|
83
85
|
SHERWOOD,
|
|
84
86
|
STRATEGY_TEMPLATES,
|
|
85
87
|
SWAP_ROUTER_ABI,
|
|
86
88
|
SWAP_ROUTER_EXACT_INPUT_SINGLE_ABI,
|
|
87
89
|
SYNDICATE_FACTORY_ABI,
|
|
88
90
|
SYNDICATE_VAULT_ABI,
|
|
91
|
+
SYNTHRA,
|
|
89
92
|
TOKENS,
|
|
90
93
|
UNISWAP,
|
|
91
94
|
VENICE,
|
|
92
95
|
VENICE_STAKING_ABI
|
|
93
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-ZPLUJT4V.js";
|
|
94
97
|
import {
|
|
95
98
|
formatContractError,
|
|
96
99
|
getAccount,
|
|
@@ -98,8 +101,9 @@ import {
|
|
|
98
101
|
sendTxWithRetry,
|
|
99
102
|
waitForReceipt,
|
|
100
103
|
writeContractWithRetry
|
|
101
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-SBVHGUQJ.js";
|
|
102
105
|
import {
|
|
106
|
+
CHAIN_REGISTRY,
|
|
103
107
|
VALID_NETWORKS,
|
|
104
108
|
getChain,
|
|
105
109
|
getExplorerUrl,
|
|
@@ -107,11 +111,13 @@ import {
|
|
|
107
111
|
getRpcUrl,
|
|
108
112
|
isTestnet,
|
|
109
113
|
setNetwork
|
|
110
|
-
} from "./chunk-
|
|
114
|
+
} from "./chunk-GWIW6DDG.js";
|
|
111
115
|
import {
|
|
112
116
|
addSyndicate,
|
|
113
117
|
cacheGroupId,
|
|
118
|
+
cacheSwapRoute,
|
|
114
119
|
getAgentId,
|
|
120
|
+
getCachedSwapRoute,
|
|
115
121
|
getChainContracts,
|
|
116
122
|
getNotifyTo,
|
|
117
123
|
getPrimarySyndicate,
|
|
@@ -127,7 +133,7 @@ import {
|
|
|
127
133
|
setPrivateKey,
|
|
128
134
|
setUniswapApiKey,
|
|
129
135
|
setVeniceApiKey
|
|
130
|
-
} from "./chunk-
|
|
136
|
+
} from "./chunk-RGOTBJIN.js";
|
|
131
137
|
|
|
132
138
|
// src/index.ts
|
|
133
139
|
import { config as loadDotenv } from "dotenv";
|
|
@@ -173,7 +179,7 @@ var MoonwellProvider = class {
|
|
|
173
179
|
};
|
|
174
180
|
|
|
175
181
|
// src/commands/strategy-template.ts
|
|
176
|
-
import { parseUnits, isAddress, erc20Abi } from "viem";
|
|
182
|
+
import { parseUnits, formatUnits, isAddress, erc20Abi, encodeAbiParameters as encodeAbiParameters7, decodeAbiParameters } from "viem";
|
|
177
183
|
import chalk from "chalk";
|
|
178
184
|
import ora from "ora";
|
|
179
185
|
import { writeFileSync, mkdirSync } from "fs";
|
|
@@ -483,8 +489,189 @@ function buildSettleCalls5(clone) {
|
|
|
483
489
|
];
|
|
484
490
|
}
|
|
485
491
|
|
|
492
|
+
// src/strategies/portfolio-template.ts
|
|
493
|
+
import { encodeAbiParameters as encodeAbiParameters6, encodeFunctionData as encodeFunctionData6 } from "viem";
|
|
494
|
+
function buildInitData6(asset, swapAdapter, chainlinkVerifier, allocations, totalAmount, maxSlippageBps) {
|
|
495
|
+
const tokens = allocations.map((a) => a.token);
|
|
496
|
+
const weightsBps = allocations.map((a) => BigInt(a.weightBps));
|
|
497
|
+
const swapExtraData = allocations.map((a) => a.swapExtraData);
|
|
498
|
+
return encodeAbiParameters6(
|
|
499
|
+
[
|
|
500
|
+
{ type: "address" },
|
|
501
|
+
{ type: "address" },
|
|
502
|
+
{ type: "address" },
|
|
503
|
+
{ type: "address[]" },
|
|
504
|
+
{ type: "uint256[]" },
|
|
505
|
+
{ type: "uint256" },
|
|
506
|
+
{ type: "uint256" },
|
|
507
|
+
{ type: "bytes[]" }
|
|
508
|
+
],
|
|
509
|
+
[
|
|
510
|
+
asset,
|
|
511
|
+
swapAdapter,
|
|
512
|
+
chainlinkVerifier,
|
|
513
|
+
tokens,
|
|
514
|
+
weightsBps,
|
|
515
|
+
totalAmount,
|
|
516
|
+
BigInt(maxSlippageBps),
|
|
517
|
+
swapExtraData
|
|
518
|
+
]
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
function buildExecuteCalls6(clone, asset, totalAmount) {
|
|
522
|
+
return [
|
|
523
|
+
{
|
|
524
|
+
target: asset,
|
|
525
|
+
data: encodeFunctionData6({
|
|
526
|
+
abi: ERC20_ABI,
|
|
527
|
+
functionName: "approve",
|
|
528
|
+
args: [clone, totalAmount]
|
|
529
|
+
}),
|
|
530
|
+
value: 0n
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
target: clone,
|
|
534
|
+
data: encodeFunctionData6({
|
|
535
|
+
abi: BASE_STRATEGY_ABI,
|
|
536
|
+
functionName: "execute"
|
|
537
|
+
}),
|
|
538
|
+
value: 0n
|
|
539
|
+
}
|
|
540
|
+
];
|
|
541
|
+
}
|
|
542
|
+
function buildSettleCalls6(clone) {
|
|
543
|
+
return [
|
|
544
|
+
{
|
|
545
|
+
target: clone,
|
|
546
|
+
data: encodeFunctionData6({
|
|
547
|
+
abi: BASE_STRATEGY_ABI,
|
|
548
|
+
functionName: "settle"
|
|
549
|
+
}),
|
|
550
|
+
value: 0n
|
|
551
|
+
}
|
|
552
|
+
];
|
|
553
|
+
}
|
|
554
|
+
|
|
486
555
|
// src/commands/strategy-template.ts
|
|
556
|
+
import { concat as concat2, numberToHex } from "viem";
|
|
487
557
|
var ZERO = "0x0000000000000000000000000000000000000000";
|
|
558
|
+
function encodeV3Path(tokens, fees) {
|
|
559
|
+
if (tokens.length !== fees.length + 1) throw new Error("tokens.length must be fees.length + 1");
|
|
560
|
+
const parts = [];
|
|
561
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
562
|
+
parts.push(tokens[i].toLowerCase());
|
|
563
|
+
if (i < fees.length) {
|
|
564
|
+
parts.push(numberToHex(fees[i], { size: 3 }));
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return concat2(parts);
|
|
568
|
+
}
|
|
569
|
+
function buildSwapExtraData(network, tokenIn, tokenOut, feeTier, hop) {
|
|
570
|
+
if (network === "robinhood-testnet") {
|
|
571
|
+
return encodeAbiParameters7([{ type: "uint24" }], [feeTier]);
|
|
572
|
+
}
|
|
573
|
+
if (hop) {
|
|
574
|
+
const path = encodeV3Path([tokenIn, hop.via, tokenOut], [hop.feeIn, hop.feeOut]);
|
|
575
|
+
const encoded2 = encodeAbiParameters7([{ type: "bytes" }], [path]);
|
|
576
|
+
return `0x01${encoded2.slice(2)}`;
|
|
577
|
+
}
|
|
578
|
+
const encoded = encodeAbiParameters7([{ type: "uint24" }], [feeTier]);
|
|
579
|
+
return `0x00${encoded.slice(2)}`;
|
|
580
|
+
}
|
|
581
|
+
async function detectSwapRoute(asset, token, preferredFeeTier) {
|
|
582
|
+
const network = getNetwork();
|
|
583
|
+
if (network === "robinhood-testnet") {
|
|
584
|
+
return {
|
|
585
|
+
extraData: buildSwapExtraData(network, asset, token, preferredFeeTier),
|
|
586
|
+
routeDesc: `direct (fee ${preferredFeeTier})`
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const chainId = CHAIN_REGISTRY[network].chain.id;
|
|
590
|
+
const cached = getCachedSwapRoute(chainId, asset, token);
|
|
591
|
+
if (cached) {
|
|
592
|
+
const extraData = cached.hop ? buildSwapExtraData(network, asset, token, 0, {
|
|
593
|
+
via: cached.hop.via,
|
|
594
|
+
feeIn: cached.hop.feeIn,
|
|
595
|
+
feeOut: cached.hop.feeOut
|
|
596
|
+
}) : buildSwapExtraData(network, asset, token, cached.feeTier);
|
|
597
|
+
const desc = cached.hop ? `cached multi-hop: ${cached.hop.feeIn}\u2192WETH\u2192${cached.hop.feeOut}` : `cached direct (fee ${cached.feeTier})`;
|
|
598
|
+
return { extraData, routeDesc: desc };
|
|
599
|
+
}
|
|
600
|
+
const publicClient = getPublicClient();
|
|
601
|
+
const quoterAddr = UNISWAP().QUOTER_V2;
|
|
602
|
+
const quoterAbi = [{
|
|
603
|
+
type: "function",
|
|
604
|
+
name: "quoteExactInputSingle",
|
|
605
|
+
inputs: [{ type: "tuple", components: [
|
|
606
|
+
{ type: "address", name: "tokenIn" },
|
|
607
|
+
{ type: "address", name: "tokenOut" },
|
|
608
|
+
{ type: "uint256", name: "amountIn" },
|
|
609
|
+
{ type: "uint24", name: "fee" },
|
|
610
|
+
{ type: "uint160", name: "sqrtPriceLimitX96" }
|
|
611
|
+
] }],
|
|
612
|
+
outputs: [
|
|
613
|
+
{ type: "uint256" },
|
|
614
|
+
{ type: "uint160" },
|
|
615
|
+
{ type: "uint32" },
|
|
616
|
+
{ type: "uint256" }
|
|
617
|
+
],
|
|
618
|
+
stateMutability: "nonpayable"
|
|
619
|
+
}];
|
|
620
|
+
const testAmount = asset.toLowerCase() === TOKENS().USDC.toLowerCase() ? 1000000n : 1000000000000000n;
|
|
621
|
+
const feeTiers = [preferredFeeTier, 1e4, 3e3, 500].filter((v, i, a) => a.indexOf(v) === i);
|
|
622
|
+
for (const fee of feeTiers) {
|
|
623
|
+
try {
|
|
624
|
+
await publicClient.simulateContract({
|
|
625
|
+
address: quoterAddr,
|
|
626
|
+
abi: quoterAbi,
|
|
627
|
+
functionName: "quoteExactInputSingle",
|
|
628
|
+
args: [{ tokenIn: asset, tokenOut: token, amountIn: testAmount, fee, sqrtPriceLimitX96: 0n }]
|
|
629
|
+
});
|
|
630
|
+
cacheSwapRoute(chainId, asset, token, { mode: "direct", feeTier: fee, detectedAt: Math.floor(Date.now() / 1e3) });
|
|
631
|
+
return {
|
|
632
|
+
extraData: buildSwapExtraData(network, asset, token, fee),
|
|
633
|
+
routeDesc: `direct (fee ${fee})`
|
|
634
|
+
};
|
|
635
|
+
} catch {
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
const weth = TOKENS().WETH;
|
|
639
|
+
if (asset.toLowerCase() === weth.toLowerCase()) return null;
|
|
640
|
+
let bestAssetToWethFee = null;
|
|
641
|
+
for (const fee of [500, 3e3, 1e4]) {
|
|
642
|
+
try {
|
|
643
|
+
await publicClient.simulateContract({
|
|
644
|
+
address: quoterAddr,
|
|
645
|
+
abi: quoterAbi,
|
|
646
|
+
functionName: "quoteExactInputSingle",
|
|
647
|
+
args: [{ tokenIn: asset, tokenOut: weth, amountIn: testAmount, fee, sqrtPriceLimitX96: 0n }]
|
|
648
|
+
});
|
|
649
|
+
bestAssetToWethFee = fee;
|
|
650
|
+
break;
|
|
651
|
+
} catch {
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (!bestAssetToWethFee) return null;
|
|
655
|
+
const wethTestAmount = 1000000000000000n;
|
|
656
|
+
for (const fee of feeTiers) {
|
|
657
|
+
try {
|
|
658
|
+
await publicClient.simulateContract({
|
|
659
|
+
address: quoterAddr,
|
|
660
|
+
abi: quoterAbi,
|
|
661
|
+
functionName: "quoteExactInputSingle",
|
|
662
|
+
args: [{ tokenIn: weth, tokenOut: token, amountIn: wethTestAmount, fee, sqrtPriceLimitX96: 0n }]
|
|
663
|
+
});
|
|
664
|
+
const hop = { via: weth, feeIn: bestAssetToWethFee, feeOut: fee };
|
|
665
|
+
cacheSwapRoute(chainId, asset, token, { mode: "multi-hop", feeTier: 0, hop, detectedAt: Math.floor(Date.now() / 1e3) });
|
|
666
|
+
return {
|
|
667
|
+
extraData: buildSwapExtraData(network, asset, token, 0, hop),
|
|
668
|
+
routeDesc: `multi-hop: ${bestAssetToWethFee}\u2192WETH\u2192${fee}`
|
|
669
|
+
};
|
|
670
|
+
} catch {
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
488
675
|
var TEMPLATES = [
|
|
489
676
|
{
|
|
490
677
|
name: "Moonwell Supply",
|
|
@@ -515,6 +702,12 @@ var TEMPLATES = [
|
|
|
515
702
|
key: "mamo-yield",
|
|
516
703
|
description: "Deposit into Mamo for optimized yield across Moonwell + Morpho vaults",
|
|
517
704
|
addressKey: "MAMO_YIELD"
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
name: "Portfolio",
|
|
708
|
+
key: "portfolio",
|
|
709
|
+
description: "Weighted portfolio of tokens (stock tokens, crypto) with rebalancing",
|
|
710
|
+
addressKey: "PORTFOLIO"
|
|
518
711
|
}
|
|
519
712
|
];
|
|
520
713
|
function resolveTemplate(key) {
|
|
@@ -709,6 +902,67 @@ async function buildInitDataForTemplate(templateKey, opts, vault) {
|
|
|
709
902
|
assetAmount: amount
|
|
710
903
|
};
|
|
711
904
|
}
|
|
905
|
+
if (templateKey === "portfolio") {
|
|
906
|
+
if (!opts.amount) {
|
|
907
|
+
console.error(chalk.red("--amount is required for portfolio template"));
|
|
908
|
+
process.exit(1);
|
|
909
|
+
}
|
|
910
|
+
if (!opts.tokens || !opts.weights) {
|
|
911
|
+
console.error(chalk.red("--tokens and --weights are required for portfolio template"));
|
|
912
|
+
console.error(chalk.dim(" --tokens: comma-separated token addresses or symbols"));
|
|
913
|
+
console.error(chalk.dim(" --weights: comma-separated bps (must sum to 10000)"));
|
|
914
|
+
process.exit(1);
|
|
915
|
+
}
|
|
916
|
+
const tokens = TOKENS();
|
|
917
|
+
const defaultAsset = getNetwork() === "robinhood-testnet" ? "WETH" : "USDC";
|
|
918
|
+
const assetSymbol = opts.asset || defaultAsset;
|
|
919
|
+
const asset = resolveToken(assetSymbol);
|
|
920
|
+
const decimals = assetSymbol.toUpperCase() === "USDC" ? 6 : 18;
|
|
921
|
+
const totalAmount = parseUnits(opts.amount, decimals);
|
|
922
|
+
const maxSlippageBps = Number(opts.maxSlippage || "500");
|
|
923
|
+
const feeTier = opts.feeTier || "3000";
|
|
924
|
+
const tokenAddrs = opts.tokens.split(",").map((t) => {
|
|
925
|
+
const trimmed = t.trim();
|
|
926
|
+
if (isAddress(trimmed)) return trimmed;
|
|
927
|
+
const allTokens = tokens;
|
|
928
|
+
const resolved = allTokens[trimmed.toUpperCase()];
|
|
929
|
+
if (resolved && resolved !== ZERO) return resolved;
|
|
930
|
+
console.error(chalk.red(`Unknown token: ${trimmed}`));
|
|
931
|
+
process.exit(1);
|
|
932
|
+
});
|
|
933
|
+
const weightsBps = opts.weights.split(",").map((w) => Number(w.trim()));
|
|
934
|
+
if (tokenAddrs.length !== weightsBps.length) {
|
|
935
|
+
console.error(chalk.red("--tokens and --weights must have same length"));
|
|
936
|
+
process.exit(1);
|
|
937
|
+
}
|
|
938
|
+
if (weightsBps.reduce((a, b) => a + b, 0) !== 1e4) {
|
|
939
|
+
console.error(chalk.red(`Weights must sum to 10000`));
|
|
940
|
+
process.exit(1);
|
|
941
|
+
}
|
|
942
|
+
const swapAdapter = opts.swapAdapter || resolveSwapAdapter();
|
|
943
|
+
const chainlinkVerifier = CHAINLINK().VERIFIER_PROXY;
|
|
944
|
+
console.log(chalk.dim(" Detecting swap routes..."));
|
|
945
|
+
const allocations = [];
|
|
946
|
+
for (let i = 0; i < tokenAddrs.length; i++) {
|
|
947
|
+
const token = tokenAddrs[i];
|
|
948
|
+
const route = await detectSwapRoute(asset, token, Number(feeTier));
|
|
949
|
+
if (!route) {
|
|
950
|
+
console.error(chalk.red(`No swap route found for ${token}. No Uniswap V3 pool (direct or via WETH).`));
|
|
951
|
+
process.exit(1);
|
|
952
|
+
}
|
|
953
|
+
console.log(chalk.dim(` ${token.slice(0, 10)}... \u2192 ${route.routeDesc}`));
|
|
954
|
+
allocations.push({
|
|
955
|
+
token,
|
|
956
|
+
weightBps: weightsBps[i],
|
|
957
|
+
swapExtraData: route.extraData
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
return {
|
|
961
|
+
initData: buildInitData6(asset, swapAdapter, chainlinkVerifier, allocations, totalAmount, maxSlippageBps),
|
|
962
|
+
asset,
|
|
963
|
+
assetAmount: totalAmount
|
|
964
|
+
};
|
|
965
|
+
}
|
|
712
966
|
throw new Error(`No init builder for template: ${templateKey}`);
|
|
713
967
|
}
|
|
714
968
|
function buildCallsForTemplate(templateKey, clone, asset, assetAmount, extraApprovals) {
|
|
@@ -744,8 +998,28 @@ function buildCallsForTemplate(templateKey, clone, asset, assetAmount, extraAppr
|
|
|
744
998
|
settleCalls: buildSettleCalls5(clone)
|
|
745
999
|
};
|
|
746
1000
|
}
|
|
1001
|
+
if (templateKey === "portfolio") {
|
|
1002
|
+
return {
|
|
1003
|
+
executeCalls: buildExecuteCalls6(clone, asset, assetAmount),
|
|
1004
|
+
settleCalls: buildSettleCalls6(clone)
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
747
1007
|
throw new Error(`No call builder for template: ${templateKey}`);
|
|
748
1008
|
}
|
|
1009
|
+
function resolveSwapAdapter() {
|
|
1010
|
+
const network = getNetwork();
|
|
1011
|
+
if (network === "robinhood-testnet") {
|
|
1012
|
+
if (SYNTHRA().ROUTER === ZERO) {
|
|
1013
|
+
console.error(chalk.red("Synthra DEX not available"));
|
|
1014
|
+
process.exit(1);
|
|
1015
|
+
}
|
|
1016
|
+
return "0xdae81cDCfcB14c56fCeB788A147Fcd6CbEdfEeca";
|
|
1017
|
+
}
|
|
1018
|
+
const adapterAddr = UNISWAP().SWAP_ADAPTER;
|
|
1019
|
+
if (adapterAddr !== ZERO) return adapterAddr;
|
|
1020
|
+
console.error(chalk.red("UniswapSwapAdapter not deployed yet. Use --swap-adapter to specify manually."));
|
|
1021
|
+
process.exit(1);
|
|
1022
|
+
}
|
|
749
1023
|
function resolveToken(symbolOrAddress) {
|
|
750
1024
|
if (isAddress(symbolOrAddress)) return symbolOrAddress;
|
|
751
1025
|
const upper = symbolOrAddress.toUpperCase();
|
|
@@ -792,23 +1066,39 @@ function serializeCalls(calls) {
|
|
|
792
1066
|
function registerStrategyTemplateCommands(strategy2) {
|
|
793
1067
|
strategy2.command("list").description("List available strategy templates").action(() => {
|
|
794
1068
|
const templates = STRATEGY_TEMPLATES();
|
|
1069
|
+
const network = getNetwork();
|
|
795
1070
|
console.log();
|
|
796
|
-
console.log(chalk.bold("Strategy Templates"));
|
|
1071
|
+
console.log(chalk.bold("Strategy Templates"), chalk.dim(`(${network})`));
|
|
797
1072
|
console.log(chalk.dim("\u2500".repeat(60)));
|
|
1073
|
+
let availableCount = 0;
|
|
798
1074
|
for (const t of TEMPLATES) {
|
|
799
1075
|
const addr = templates[t.addressKey];
|
|
800
|
-
|
|
1076
|
+
if (addr !== ZERO) {
|
|
1077
|
+
availableCount++;
|
|
1078
|
+
console.log();
|
|
1079
|
+
console.log(` ${chalk.bold(t.name)} (${chalk.cyan(t.key)})`);
|
|
1080
|
+
console.log(` ${t.description}`);
|
|
1081
|
+
console.log(` Template: ${chalk.green(addr)}`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
if (availableCount === 0) {
|
|
801
1085
|
console.log();
|
|
802
|
-
console.log(
|
|
803
|
-
|
|
804
|
-
|
|
1086
|
+
console.log(chalk.yellow(" No strategy templates deployed on this network."));
|
|
1087
|
+
}
|
|
1088
|
+
const unavailable = TEMPLATES.filter((t) => templates[t.addressKey] === ZERO);
|
|
1089
|
+
if (unavailable.length > 0) {
|
|
1090
|
+
console.log();
|
|
1091
|
+
console.log(chalk.dim(` Not available on ${network}:`));
|
|
1092
|
+
for (const t of unavailable) {
|
|
1093
|
+
console.log(chalk.dim(` ${t.name} (${t.key})`));
|
|
1094
|
+
}
|
|
805
1095
|
}
|
|
806
1096
|
console.log();
|
|
807
1097
|
console.log(chalk.dim("Clone a template: sherwood strategy clone <template> --vault <addr> ..."));
|
|
808
1098
|
console.log(chalk.dim("Full proposal: sherwood strategy propose <template> --vault <addr> ..."));
|
|
809
1099
|
console.log();
|
|
810
1100
|
});
|
|
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) => {
|
|
1101
|
+
strategy2.command("clone").description("Clone a strategy template and initialize it").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell, mamo-yield, portfolio").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)").option("--tokens <list>", "Comma-separated token addresses or symbols (Portfolio)").option("--weights <list>", "Comma-separated weights in bps, must sum to 10000 (Portfolio)").option("--max-slippage <bps>", "Max slippage bps (Portfolio, default: 500)").option("--fee-tier <n>", "Pool fee tier (Portfolio, default: 3000)").option("--swap-adapter <address>", "Swap adapter address (Portfolio)").action(async (templateKey, opts) => {
|
|
812
1102
|
const vault = opts.vault;
|
|
813
1103
|
if (!isAddress(vault)) {
|
|
814
1104
|
console.error(chalk.red("Invalid vault address"));
|
|
@@ -856,7 +1146,7 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
856
1146
|
console.log(chalk.dim("Use this address in your proposal batch calls."));
|
|
857
1147
|
console.log();
|
|
858
1148
|
});
|
|
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) => {
|
|
1149
|
+
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, portfolio").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)").option("--tokens <list>", "Comma-separated token addresses or symbols (Portfolio)").option("--weights <list>", "Comma-separated weights in bps, must sum to 10000 (Portfolio)").option("--max-slippage <bps>", "Max slippage bps (Portfolio, default: 500)").option("--fee-tier <n>", "Pool fee tier (Portfolio, default: 3000)").option("--swap-adapter <address>", "Swap adapter address (Portfolio)").action(async (templateKey, opts) => {
|
|
860
1150
|
const clone = opts.clone;
|
|
861
1151
|
const vault = opts.vault;
|
|
862
1152
|
if (!isAddress(clone)) {
|
|
@@ -912,7 +1202,7 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
912
1202
|
console.log(` Proposer: ${chalk.green(getAccount().address)}`);
|
|
913
1203
|
console.log();
|
|
914
1204
|
});
|
|
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) => {
|
|
1205
|
+
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, portfolio").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)").option("--tokens <list>", "Comma-separated token addresses or symbols (Portfolio)").option("--weights <list>", "Comma-separated weights in bps, must sum to 10000 (Portfolio)").option("--max-slippage <bps>", "Max slippage bps (Portfolio, default: 500)").option("--fee-tier <n>", "Pool fee tier (Portfolio, default: 3000)").option("--swap-adapter <address>", "Swap adapter address (Portfolio)").action(async (templateKey, opts) => {
|
|
916
1206
|
const vault = opts.vault;
|
|
917
1207
|
if (!isAddress(vault)) {
|
|
918
1208
|
console.error(chalk.red("Invalid vault address"));
|
|
@@ -1003,9 +1293,9 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
1003
1293
|
console.error(chalk.red("Missing --name, --performance-fee, or --duration. Use --write-calls to skip proposal submission."));
|
|
1004
1294
|
process.exit(1);
|
|
1005
1295
|
}
|
|
1006
|
-
const { propose: propose2 } = await import("./governor-
|
|
1296
|
+
const { propose: propose2 } = await import("./governor-YPDAOT3S.js");
|
|
1007
1297
|
const { pinJSON } = await import("./ipfs-22YLNQ2C.js");
|
|
1008
|
-
const { parseDuration: parseDuration2 } = await import("./governor-
|
|
1298
|
+
const { parseDuration: parseDuration2 } = await import("./governor-YPDAOT3S.js");
|
|
1009
1299
|
const performanceFeeBps = BigInt(opts.performanceFee);
|
|
1010
1300
|
if (performanceFeeBps < 0n || performanceFeeBps > 10000n) {
|
|
1011
1301
|
console.error(chalk.red("--performance-fee must be 0-10000 (basis points)"));
|
|
@@ -1063,6 +1353,380 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
1063
1353
|
}
|
|
1064
1354
|
console.log();
|
|
1065
1355
|
});
|
|
1356
|
+
strategy2.command("status").description("Show portfolio strategy status \u2014 allocations, balances, drift, PnL (Portfolio only)").argument("<clone>", "Strategy clone address").option("--json", "Output machine-readable JSON for agent consumption").action(async (cloneArg, opts) => {
|
|
1357
|
+
if (!isAddress(cloneArg)) {
|
|
1358
|
+
console.error(chalk.red("Invalid clone address"));
|
|
1359
|
+
process.exit(1);
|
|
1360
|
+
}
|
|
1361
|
+
const clone = cloneArg;
|
|
1362
|
+
const publicClient = getPublicClient();
|
|
1363
|
+
const network = getNetwork();
|
|
1364
|
+
const jsonMode = !!opts.json;
|
|
1365
|
+
const spinner = jsonMode ? null : ora("Reading strategy state...").start();
|
|
1366
|
+
try {
|
|
1367
|
+
const [stateRaw, vault, proposer, assetAddr, totalAmount, maxSlippage, allocations, swapExtraData] = await Promise.all([
|
|
1368
|
+
publicClient.readContract({ address: clone, abi: PORTFOLIO_STRATEGY_ABI, functionName: "state" }),
|
|
1369
|
+
publicClient.readContract({ address: clone, abi: PORTFOLIO_STRATEGY_ABI, functionName: "vault" }),
|
|
1370
|
+
publicClient.readContract({ address: clone, abi: PORTFOLIO_STRATEGY_ABI, functionName: "proposer" }),
|
|
1371
|
+
publicClient.readContract({ address: clone, abi: PORTFOLIO_STRATEGY_ABI, functionName: "asset" }),
|
|
1372
|
+
publicClient.readContract({ address: clone, abi: PORTFOLIO_STRATEGY_ABI, functionName: "totalAmount" }),
|
|
1373
|
+
publicClient.readContract({ address: clone, abi: PORTFOLIO_STRATEGY_ABI, functionName: "maxSlippageBps" }),
|
|
1374
|
+
publicClient.readContract({ address: clone, abi: PORTFOLIO_STRATEGY_ABI, functionName: "getAllocations" }),
|
|
1375
|
+
publicClient.readContract({ address: clone, abi: PORTFOLIO_STRATEGY_ABI, functionName: "getSwapExtraData" })
|
|
1376
|
+
]);
|
|
1377
|
+
const stateNames = ["Pending", "Executed", "Settled"];
|
|
1378
|
+
const stateStr = stateNames[Number(stateRaw)] || `Unknown(${stateRaw})`;
|
|
1379
|
+
const [assetDecimals, assetSymbol] = await Promise.all([
|
|
1380
|
+
publicClient.readContract({ address: assetAddr, abi: erc20Abi, functionName: "decimals" }),
|
|
1381
|
+
publicClient.readContract({ address: assetAddr, abi: erc20Abi, functionName: "symbol" })
|
|
1382
|
+
]);
|
|
1383
|
+
const tokenReads = allocations.map((a) => Promise.all([
|
|
1384
|
+
publicClient.readContract({ address: a.token, abi: erc20Abi, functionName: "balanceOf", args: [clone] }),
|
|
1385
|
+
publicClient.readContract({ address: a.token, abi: erc20Abi, functionName: "symbol" }),
|
|
1386
|
+
publicClient.readContract({ address: a.token, abi: erc20Abi, functionName: "decimals" })
|
|
1387
|
+
]));
|
|
1388
|
+
const tokenData = await Promise.all(tokenReads);
|
|
1389
|
+
const assetBalance = await publicClient.readContract({
|
|
1390
|
+
address: assetAddr,
|
|
1391
|
+
abi: erc20Abi,
|
|
1392
|
+
functionName: "balanceOf",
|
|
1393
|
+
args: [clone]
|
|
1394
|
+
});
|
|
1395
|
+
const feeTiers = swapExtraData.map((data) => {
|
|
1396
|
+
try {
|
|
1397
|
+
const [fee] = decodeAbiParameters([{ type: "uint24" }], data);
|
|
1398
|
+
return Number(fee) > 0 && Number(fee) <= 1e5 ? Number(fee) : 3e3;
|
|
1399
|
+
} catch {
|
|
1400
|
+
return 3e3;
|
|
1401
|
+
}
|
|
1402
|
+
});
|
|
1403
|
+
let prices = [];
|
|
1404
|
+
let priceSource = null;
|
|
1405
|
+
try {
|
|
1406
|
+
const { getTokenPricesInAsset } = await import("./price-SWNEGWO4.js");
|
|
1407
|
+
prices = await getTokenPricesInAsset({
|
|
1408
|
+
tokens: allocations.map((a, i) => ({
|
|
1409
|
+
token: a.token,
|
|
1410
|
+
tokenDecimals: tokenData[i][2],
|
|
1411
|
+
feeTier: feeTiers[i]
|
|
1412
|
+
})),
|
|
1413
|
+
asset: assetAddr,
|
|
1414
|
+
assetDecimals
|
|
1415
|
+
});
|
|
1416
|
+
priceSource = prices.find((p) => p !== null)?.source ?? null;
|
|
1417
|
+
} catch {
|
|
1418
|
+
}
|
|
1419
|
+
const pricesAvailable = prices.some((p) => p !== null);
|
|
1420
|
+
const assetBalanceNum = Number(formatUnits(assetBalance, assetDecimals));
|
|
1421
|
+
let totalPortfolioValue = assetBalanceNum;
|
|
1422
|
+
let totalInvested = 0n;
|
|
1423
|
+
const allocStatuses = allocations.map((alloc, i) => {
|
|
1424
|
+
const [balance, symbol, decimals] = tokenData[i];
|
|
1425
|
+
const balanceNum = Number(formatUnits(balance, decimals));
|
|
1426
|
+
const investedNum = Number(formatUnits(alloc.investedAmount, assetDecimals));
|
|
1427
|
+
totalInvested += alloc.investedAmount;
|
|
1428
|
+
const price = prices[i];
|
|
1429
|
+
let currentValue = null;
|
|
1430
|
+
if (price) {
|
|
1431
|
+
currentValue = balanceNum * price.price;
|
|
1432
|
+
totalPortfolioValue += currentValue;
|
|
1433
|
+
}
|
|
1434
|
+
return {
|
|
1435
|
+
token: alloc.token,
|
|
1436
|
+
symbol,
|
|
1437
|
+
decimals,
|
|
1438
|
+
targetWeightBps: Number(alloc.targetWeightBps),
|
|
1439
|
+
balance: formatUnits(balance, decimals),
|
|
1440
|
+
balanceRaw: balance.toString(),
|
|
1441
|
+
investedAmount: formatUnits(alloc.investedAmount, assetDecimals),
|
|
1442
|
+
currentPrice: price?.price ?? null,
|
|
1443
|
+
currentValue,
|
|
1444
|
+
actualWeightBps: null,
|
|
1445
|
+
// computed after totals
|
|
1446
|
+
driftBps: null,
|
|
1447
|
+
pnl: currentValue !== null ? currentValue - investedNum : null,
|
|
1448
|
+
pnlPct: currentValue !== null && investedNum > 0 ? (currentValue - investedNum) / investedNum * 100 : null
|
|
1449
|
+
};
|
|
1450
|
+
});
|
|
1451
|
+
let maxDriftBps = 0;
|
|
1452
|
+
let maxDriftToken = "";
|
|
1453
|
+
if (pricesAvailable && totalPortfolioValue > 0) {
|
|
1454
|
+
for (const a of allocStatuses) {
|
|
1455
|
+
if (a.currentValue !== null) {
|
|
1456
|
+
a.actualWeightBps = Math.round(a.currentValue / totalPortfolioValue * 1e4);
|
|
1457
|
+
a.driftBps = a.actualWeightBps - a.targetWeightBps;
|
|
1458
|
+
if (Math.abs(a.driftBps) > Math.abs(maxDriftBps)) {
|
|
1459
|
+
maxDriftBps = a.driftBps;
|
|
1460
|
+
maxDriftToken = a.symbol;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
const totalInvestedNum = Number(formatUnits(totalInvested, assetDecimals));
|
|
1466
|
+
const totalPnl = pricesAvailable ? totalPortfolioValue - totalInvestedNum : null;
|
|
1467
|
+
const totalPnlPct = totalPnl !== null && totalInvestedNum > 0 ? totalPnl / totalInvestedNum * 100 : null;
|
|
1468
|
+
spinner?.succeed("Strategy state loaded");
|
|
1469
|
+
if (jsonMode) {
|
|
1470
|
+
const result = {
|
|
1471
|
+
clone,
|
|
1472
|
+
vault,
|
|
1473
|
+
proposer,
|
|
1474
|
+
state: stateStr,
|
|
1475
|
+
network,
|
|
1476
|
+
asset: { address: assetAddr, symbol: assetSymbol, decimals: assetDecimals },
|
|
1477
|
+
totalDeployed: formatUnits(totalAmount, assetDecimals),
|
|
1478
|
+
maxSlippageBps: Number(maxSlippage),
|
|
1479
|
+
assetBalance: formatUnits(assetBalance, assetDecimals),
|
|
1480
|
+
pricesAvailable,
|
|
1481
|
+
priceSource,
|
|
1482
|
+
portfolio: {
|
|
1483
|
+
totalValue: pricesAvailable ? totalPortfolioValue : null,
|
|
1484
|
+
totalPnl,
|
|
1485
|
+
totalPnlPct
|
|
1486
|
+
},
|
|
1487
|
+
allocations: allocStatuses,
|
|
1488
|
+
maxDriftBps: pricesAvailable ? maxDriftBps : null,
|
|
1489
|
+
maxDriftToken: pricesAvailable ? maxDriftToken : null,
|
|
1490
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1491
|
+
};
|
|
1492
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
const stateColor = stateStr === "Executed" ? chalk.green : stateStr === "Settled" ? chalk.blue : chalk.yellow;
|
|
1496
|
+
console.log();
|
|
1497
|
+
console.log(chalk.bold("Portfolio Strategy Status"));
|
|
1498
|
+
console.log(chalk.dim("\u2500".repeat(70)));
|
|
1499
|
+
console.log(` Clone: ${chalk.cyan(clone)}`);
|
|
1500
|
+
console.log(` Vault: ${chalk.cyan(vault)}`);
|
|
1501
|
+
console.log(` Proposer: ${chalk.cyan(proposer)}`);
|
|
1502
|
+
console.log(` State: ${stateColor(stateStr)}`);
|
|
1503
|
+
console.log(` Asset: ${assetSymbol} (${assetAddr})`);
|
|
1504
|
+
console.log(` Deployed: ${formatUnits(totalAmount, assetDecimals)} ${assetSymbol}`);
|
|
1505
|
+
console.log(` Max Slip: ${Number(maxSlippage) / 100}%`);
|
|
1506
|
+
console.log(` Asset held: ${formatUnits(assetBalance, assetDecimals)} ${assetSymbol}`);
|
|
1507
|
+
if (pricesAvailable) {
|
|
1508
|
+
const pvStr = totalPortfolioValue.toFixed(6);
|
|
1509
|
+
const pnlStr = totalPnl !== null ? (totalPnl >= 0 ? "+" : "") + totalPnl.toFixed(6) : "n/a";
|
|
1510
|
+
const pnlPctStr = totalPnlPct !== null ? (totalPnlPct >= 0 ? "+" : "") + totalPnlPct.toFixed(1) + "%" : "";
|
|
1511
|
+
const pnlColor = totalPnl !== null && totalPnl >= 0 ? chalk.green : chalk.red;
|
|
1512
|
+
console.log(` Value: ${pvStr} ${assetSymbol} | PnL: ${pnlColor(`${pnlStr} (${pnlPctStr})`)}`);
|
|
1513
|
+
console.log(` Price src: ${priceSource}`);
|
|
1514
|
+
}
|
|
1515
|
+
console.log();
|
|
1516
|
+
console.log(chalk.bold("Allocations"));
|
|
1517
|
+
console.log(chalk.dim("\u2500".repeat(70)));
|
|
1518
|
+
if (pricesAvailable) {
|
|
1519
|
+
console.log(
|
|
1520
|
+
chalk.dim(" Token".padEnd(10)) + chalk.dim("Target".padEnd(9)) + chalk.dim("Actual".padEnd(9)) + chalk.dim("Drift".padEnd(8)) + chalk.dim("Value".padEnd(16)) + chalk.dim("PnL".padEnd(16)) + chalk.dim("Balance")
|
|
1521
|
+
);
|
|
1522
|
+
for (const a of allocStatuses) {
|
|
1523
|
+
const targetStr = (a.targetWeightBps / 100).toFixed(1) + "%";
|
|
1524
|
+
const actualStr = a.actualWeightBps !== null ? (a.actualWeightBps / 100).toFixed(1) + "%" : "\u2014";
|
|
1525
|
+
const driftStr = a.driftBps !== null ? (a.driftBps >= 0 ? "+" : "") + a.driftBps : "\u2014";
|
|
1526
|
+
const driftColor = a.driftBps !== null ? Math.abs(a.driftBps) > 500 ? chalk.red : Math.abs(a.driftBps) > 200 ? chalk.yellow : chalk.dim : chalk.dim;
|
|
1527
|
+
const valueStr = a.currentValue !== null ? a.currentValue.toFixed(6) : "\u2014";
|
|
1528
|
+
const pnlPctStr = a.pnlPct !== null ? (a.pnlPct >= 0 ? "+" : "") + a.pnlPct.toFixed(1) + "%" : "\u2014";
|
|
1529
|
+
const pnlColor = a.pnlPct !== null ? a.pnlPct >= 0 ? chalk.green : chalk.red : chalk.dim;
|
|
1530
|
+
console.log(
|
|
1531
|
+
` ${chalk.bold(a.symbol.padEnd(8))}${targetStr.padEnd(9)}${actualStr.padEnd(9)}${driftColor(driftStr.toString().padEnd(8))}${valueStr.padEnd(16)}${pnlColor(pnlPctStr.padEnd(16))}${a.balance}`
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1534
|
+
} else {
|
|
1535
|
+
console.log(chalk.dim(" Prices unavailable \u2014 showing raw balances only."));
|
|
1536
|
+
console.log();
|
|
1537
|
+
console.log(
|
|
1538
|
+
chalk.dim(" Token".padEnd(12)) + chalk.dim("Weight".padEnd(10)) + chalk.dim("Invested".padEnd(18)) + chalk.dim("Balance".padEnd(18)) + chalk.dim("Address")
|
|
1539
|
+
);
|
|
1540
|
+
for (const a of allocStatuses) {
|
|
1541
|
+
const weightPct = (a.targetWeightBps / 100).toFixed(1) + "%";
|
|
1542
|
+
console.log(
|
|
1543
|
+
` ${chalk.bold(a.symbol.padEnd(10))}${weightPct.padEnd(10)}${a.investedAmount.padEnd(18)}${a.balance.padEnd(18)}${chalk.dim(a.token)}`
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
console.log(chalk.dim("\u2500".repeat(70)));
|
|
1548
|
+
console.log(` Total invested: ${formatUnits(totalInvested, assetDecimals)} ${assetSymbol}`);
|
|
1549
|
+
if (pricesAvailable && maxDriftToken) {
|
|
1550
|
+
const absMaxDrift = Math.abs(maxDriftBps);
|
|
1551
|
+
const driftColor = absMaxDrift > 500 ? chalk.red : absMaxDrift > 200 ? chalk.yellow : chalk.green;
|
|
1552
|
+
console.log(` Max drift: ${driftColor(`${maxDriftBps > 0 ? "+" : ""}${maxDriftBps} bps`)} (${maxDriftToken})`);
|
|
1553
|
+
}
|
|
1554
|
+
if (stateStr === "Executed") {
|
|
1555
|
+
console.log();
|
|
1556
|
+
console.log(chalk.dim("Rebalance: sherwood strategy rebalance " + clone));
|
|
1557
|
+
console.log(chalk.dim("With new weights: sherwood strategy rebalance " + clone + " --new-weights 2500,2500,2000,1500,1500"));
|
|
1558
|
+
}
|
|
1559
|
+
console.log();
|
|
1560
|
+
} catch (err) {
|
|
1561
|
+
spinner?.fail("Failed to read strategy state");
|
|
1562
|
+
console.error(chalk.red(formatContractError(err)));
|
|
1563
|
+
process.exit(1);
|
|
1564
|
+
}
|
|
1565
|
+
});
|
|
1566
|
+
strategy2.command("rebalance").description("Rebalance a portfolio strategy \u2014 sell all positions, re-buy at target weights (Portfolio only)").argument("<clone>", "Strategy clone address").option("--new-weights <list>", "Comma-separated new weights in bps (must sum to 10000). Updates weights before rebalancing.").option("--max-slippage <bps>", "New max slippage bps (used with --new-weights)").option("--dry-run", "Show what would happen without executing").action(async (cloneArg, opts) => {
|
|
1567
|
+
if (!isAddress(cloneArg)) {
|
|
1568
|
+
console.error(chalk.red("Invalid clone address"));
|
|
1569
|
+
process.exit(1);
|
|
1570
|
+
}
|
|
1571
|
+
const clone = cloneArg;
|
|
1572
|
+
const publicClient = getPublicClient();
|
|
1573
|
+
const account = getAccount();
|
|
1574
|
+
const chain = getChain();
|
|
1575
|
+
const verifySpinner = ora("Verifying strategy state...").start();
|
|
1576
|
+
try {
|
|
1577
|
+
const [stateRaw, proposer, assetAddr, allocations] = await Promise.all([
|
|
1578
|
+
publicClient.readContract({ address: clone, abi: PORTFOLIO_STRATEGY_ABI, functionName: "state" }),
|
|
1579
|
+
publicClient.readContract({ address: clone, abi: PORTFOLIO_STRATEGY_ABI, functionName: "proposer" }),
|
|
1580
|
+
publicClient.readContract({ address: clone, abi: PORTFOLIO_STRATEGY_ABI, functionName: "asset" }),
|
|
1581
|
+
publicClient.readContract({ address: clone, abi: PORTFOLIO_STRATEGY_ABI, functionName: "getAllocations" })
|
|
1582
|
+
]);
|
|
1583
|
+
if (Number(stateRaw) !== 1) {
|
|
1584
|
+
const stateNames = ["Pending", "Executed", "Settled"];
|
|
1585
|
+
verifySpinner.fail(`Strategy is ${stateNames[Number(stateRaw)] || "Unknown"}, must be Executed to rebalance`);
|
|
1586
|
+
process.exit(1);
|
|
1587
|
+
}
|
|
1588
|
+
if (proposer.toLowerCase() !== account.address.toLowerCase()) {
|
|
1589
|
+
verifySpinner.fail(`Only the proposer (${proposer}) can rebalance. Your wallet: ${account.address}`);
|
|
1590
|
+
process.exit(1);
|
|
1591
|
+
}
|
|
1592
|
+
const [assetDecimals, assetSymbol] = await Promise.all([
|
|
1593
|
+
publicClient.readContract({ address: assetAddr, abi: erc20Abi, functionName: "decimals" }),
|
|
1594
|
+
publicClient.readContract({ address: assetAddr, abi: erc20Abi, functionName: "symbol" })
|
|
1595
|
+
]);
|
|
1596
|
+
const tokenReads = allocations.map((a) => Promise.all([
|
|
1597
|
+
publicClient.readContract({ address: a.token, abi: erc20Abi, functionName: "balanceOf", args: [clone] }),
|
|
1598
|
+
publicClient.readContract({ address: a.token, abi: erc20Abi, functionName: "symbol" }),
|
|
1599
|
+
publicClient.readContract({ address: a.token, abi: erc20Abi, functionName: "decimals" })
|
|
1600
|
+
]));
|
|
1601
|
+
const tokenData = await Promise.all(tokenReads);
|
|
1602
|
+
verifySpinner.succeed("Strategy is Executed \u2014 ready to rebalance");
|
|
1603
|
+
console.log();
|
|
1604
|
+
console.log(chalk.bold("Current Allocations"));
|
|
1605
|
+
console.log(chalk.dim("\u2500".repeat(50)));
|
|
1606
|
+
for (let i = 0; i < allocations.length; i++) {
|
|
1607
|
+
const alloc = allocations[i];
|
|
1608
|
+
const [balance, symbol, decimals] = tokenData[i];
|
|
1609
|
+
const weightPct = (Number(alloc.targetWeightBps) / 100).toFixed(1) + "%";
|
|
1610
|
+
console.log(
|
|
1611
|
+
` ${chalk.bold(symbol.padEnd(8))} ${weightPct.padEnd(8)} bal: ${formatUnits(balance, decimals)}`
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1614
|
+
let newWeightsBps;
|
|
1615
|
+
if (opts.newWeights) {
|
|
1616
|
+
newWeightsBps = opts.newWeights.split(",").map((w) => Number(w.trim()));
|
|
1617
|
+
if (newWeightsBps.length !== allocations.length) {
|
|
1618
|
+
console.error(chalk.red(`--new-weights must have ${allocations.length} values (one per token)`));
|
|
1619
|
+
process.exit(1);
|
|
1620
|
+
}
|
|
1621
|
+
const weightSum = newWeightsBps.reduce((a, b) => a + b, 0);
|
|
1622
|
+
if (weightSum !== 1e4) {
|
|
1623
|
+
console.error(chalk.red(`Weights must sum to 10000, got ${weightSum}`));
|
|
1624
|
+
process.exit(1);
|
|
1625
|
+
}
|
|
1626
|
+
console.log();
|
|
1627
|
+
console.log(chalk.bold("New Target Weights"));
|
|
1628
|
+
console.log(chalk.dim("\u2500".repeat(50)));
|
|
1629
|
+
for (let i = 0; i < allocations.length; i++) {
|
|
1630
|
+
const [, symbol] = tokenData[i];
|
|
1631
|
+
const oldPct = (Number(allocations[i].targetWeightBps) / 100).toFixed(1);
|
|
1632
|
+
const newPct = (newWeightsBps[i] / 100).toFixed(1);
|
|
1633
|
+
const arrow = oldPct !== newPct ? chalk.yellow("\u2192") : chalk.dim("\u2192");
|
|
1634
|
+
console.log(` ${chalk.bold(symbol.padEnd(8))} ${oldPct}% ${arrow} ${newPct}%`);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
if (opts.dryRun) {
|
|
1638
|
+
console.log();
|
|
1639
|
+
console.log(chalk.yellow("Dry run \u2014 no transactions sent."));
|
|
1640
|
+
console.log(chalk.dim("The rebalance would:"));
|
|
1641
|
+
console.log(chalk.dim(" 1. Sell all current token positions back to " + assetSymbol));
|
|
1642
|
+
console.log(chalk.dim(" 2. Re-buy at " + (newWeightsBps ? "new" : "current") + " target weights"));
|
|
1643
|
+
console.log();
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
if (newWeightsBps) {
|
|
1647
|
+
const maxSlip = Number(opts.maxSlippage || "0");
|
|
1648
|
+
const swapData = await publicClient.readContract({
|
|
1649
|
+
address: clone,
|
|
1650
|
+
abi: PORTFOLIO_STRATEGY_ABI,
|
|
1651
|
+
functionName: "getSwapExtraData"
|
|
1652
|
+
});
|
|
1653
|
+
const updateSpinner = ora("Updating target weights...").start();
|
|
1654
|
+
try {
|
|
1655
|
+
const innerData = encodeAbiParameters7(
|
|
1656
|
+
[{ type: "uint256[]" }, { type: "uint256" }, { type: "bytes[]" }],
|
|
1657
|
+
[newWeightsBps.map((w) => BigInt(w)), BigInt(maxSlip), swapData]
|
|
1658
|
+
);
|
|
1659
|
+
const updateHash = await writeContractWithRetry({
|
|
1660
|
+
account,
|
|
1661
|
+
chain,
|
|
1662
|
+
address: clone,
|
|
1663
|
+
abi: PORTFOLIO_STRATEGY_ABI,
|
|
1664
|
+
functionName: "updateParams",
|
|
1665
|
+
args: [innerData]
|
|
1666
|
+
});
|
|
1667
|
+
const receipt = await waitForReceipt(updateHash);
|
|
1668
|
+
if (receipt.status === "reverted") throw new Error("updateParams reverted");
|
|
1669
|
+
updateSpinner.succeed("Target weights updated");
|
|
1670
|
+
console.log(chalk.dim(` Tx: ${getExplorerUrl(updateHash)}`));
|
|
1671
|
+
} catch (err) {
|
|
1672
|
+
updateSpinner.fail("Failed to update weights");
|
|
1673
|
+
console.error(chalk.red(formatContractError(err)));
|
|
1674
|
+
process.exit(1);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
const rebalanceSpinner = ora("Rebalancing portfolio (sell all \u2192 re-buy at targets)...").start();
|
|
1678
|
+
try {
|
|
1679
|
+
const rebalanceHash = await writeContractWithRetry({
|
|
1680
|
+
account,
|
|
1681
|
+
chain,
|
|
1682
|
+
address: clone,
|
|
1683
|
+
abi: PORTFOLIO_STRATEGY_ABI,
|
|
1684
|
+
functionName: "rebalance"
|
|
1685
|
+
});
|
|
1686
|
+
const receipt = await waitForReceipt(rebalanceHash);
|
|
1687
|
+
if (receipt.status === "reverted") throw new Error("rebalance() reverted on-chain");
|
|
1688
|
+
rebalanceSpinner.succeed("Portfolio rebalanced");
|
|
1689
|
+
console.log(chalk.dim(` Tx: ${getExplorerUrl(rebalanceHash)}`));
|
|
1690
|
+
} catch (err) {
|
|
1691
|
+
rebalanceSpinner.fail("Rebalance failed");
|
|
1692
|
+
console.error(chalk.red(formatContractError(err)));
|
|
1693
|
+
process.exit(1);
|
|
1694
|
+
}
|
|
1695
|
+
const postSpinner = ora("Reading updated allocations...").start();
|
|
1696
|
+
const postAllocations = await publicClient.readContract({
|
|
1697
|
+
address: clone,
|
|
1698
|
+
abi: PORTFOLIO_STRATEGY_ABI,
|
|
1699
|
+
functionName: "getAllocations"
|
|
1700
|
+
});
|
|
1701
|
+
const postReads = postAllocations.map((a) => Promise.all([
|
|
1702
|
+
publicClient.readContract({ address: a.token, abi: erc20Abi, functionName: "balanceOf", args: [clone] }),
|
|
1703
|
+
publicClient.readContract({ address: a.token, abi: erc20Abi, functionName: "symbol" }),
|
|
1704
|
+
publicClient.readContract({ address: a.token, abi: erc20Abi, functionName: "decimals" })
|
|
1705
|
+
]));
|
|
1706
|
+
const postData = await Promise.all(postReads);
|
|
1707
|
+
postSpinner.succeed("Updated allocations");
|
|
1708
|
+
console.log();
|
|
1709
|
+
console.log(chalk.bold("Post-Rebalance Allocations"));
|
|
1710
|
+
console.log(chalk.dim("\u2500".repeat(50)));
|
|
1711
|
+
for (let i = 0; i < postAllocations.length; i++) {
|
|
1712
|
+
const alloc = postAllocations[i];
|
|
1713
|
+
const [balance, symbol, decimals] = postData[i];
|
|
1714
|
+
const weightPct = (Number(alloc.targetWeightBps) / 100).toFixed(1) + "%";
|
|
1715
|
+
const investedStr = formatUnits(alloc.investedAmount, assetDecimals);
|
|
1716
|
+
console.log(
|
|
1717
|
+
` ${chalk.bold(symbol.padEnd(8))} ${weightPct.padEnd(8)} invested: ${investedStr.padEnd(14)} bal: ${formatUnits(balance, decimals)}`
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
console.log();
|
|
1721
|
+
console.log(chalk.green("\u2713 Rebalance complete"));
|
|
1722
|
+
console.log();
|
|
1723
|
+
} catch (err) {
|
|
1724
|
+
if (err.code === "ERR_MODULE_NOT_FOUND" || err instanceof TypeError) throw err;
|
|
1725
|
+
verifySpinner.fail("Pre-flight check failed");
|
|
1726
|
+
console.error(chalk.red(formatContractError(err)));
|
|
1727
|
+
process.exit(1);
|
|
1728
|
+
}
|
|
1729
|
+
});
|
|
1066
1730
|
}
|
|
1067
1731
|
|
|
1068
1732
|
// src/lib/factory.ts
|
|
@@ -1250,7 +1914,7 @@ async function getActiveSyndicates2(creator) {
|
|
|
1250
1914
|
}
|
|
1251
1915
|
|
|
1252
1916
|
// src/commands/venice.ts
|
|
1253
|
-
import { formatUnits as
|
|
1917
|
+
import { formatUnits as formatUnits3, isAddress as isAddress2 } from "viem";
|
|
1254
1918
|
import chalk2 from "chalk";
|
|
1255
1919
|
import ora2 from "ora";
|
|
1256
1920
|
import { readFileSync } from "fs";
|
|
@@ -1273,7 +1937,7 @@ function registerVeniceCommands(program2) {
|
|
|
1273
1937
|
console.log(chalk2.yellow(" Use the VeniceInferenceStrategy via a proposal to distribute sVVV to agents."));
|
|
1274
1938
|
process.exit(1);
|
|
1275
1939
|
}
|
|
1276
|
-
checkSpinner.succeed(`sVVV balance: ${
|
|
1940
|
+
checkSpinner.succeed(`sVVV balance: ${formatUnits3(sVvvBalance, 18)}`);
|
|
1277
1941
|
} catch (err) {
|
|
1278
1942
|
checkSpinner.fail("Failed to check sVVV balance");
|
|
1279
1943
|
console.error(chalk2.red(formatContractError(err)));
|
|
@@ -1286,7 +1950,7 @@ function registerVeniceCommands(program2) {
|
|
|
1286
1950
|
console.log(chalk2.dim(` Key: ${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`));
|
|
1287
1951
|
console.log(chalk2.dim(" Saved to ~/.sherwood/config.json"));
|
|
1288
1952
|
try {
|
|
1289
|
-
const { createVeniceProvisionAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-
|
|
1953
|
+
const { createVeniceProvisionAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-YZIHYQAI.js");
|
|
1290
1954
|
const { uid } = await createVeniceProvisionAttestation(account.address);
|
|
1291
1955
|
if (uid !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
1292
1956
|
console.log(chalk2.dim(` Attested: ${getEasScanUrl2(uid)}`));
|
|
@@ -1358,17 +2022,17 @@ function registerVeniceCommands(program2) {
|
|
|
1358
2022
|
console.log(chalk2.bold("Venice Inference Status"));
|
|
1359
2023
|
console.log(chalk2.dim("\u2500".repeat(50)));
|
|
1360
2024
|
console.log(chalk2.bold("\n Vault"));
|
|
1361
|
-
console.log(` Profit available: ${
|
|
1362
|
-
console.log(` VVV (unstaked): ${
|
|
2025
|
+
console.log(` Profit available: ${formatUnits3(profit, assetDecimals)} ${assetSymbol}`);
|
|
2026
|
+
console.log(` VVV (unstaked): ${formatUnits3(vaultVvvBalance, 18)}`);
|
|
1363
2027
|
console.log(chalk2.bold("\n Agent sVVV Balances"));
|
|
1364
2028
|
for (const { agent, balance } of agentBalances) {
|
|
1365
2029
|
const isMe = agent.toLowerCase() === account.address.toLowerCase();
|
|
1366
2030
|
const label = isMe ? chalk2.green(`${agent} (you)`) : agent;
|
|
1367
|
-
console.log(` ${label}: ${
|
|
2031
|
+
console.log(` ${label}: ${formatUnits3(balance, 18)} sVVV`);
|
|
1368
2032
|
}
|
|
1369
2033
|
console.log(chalk2.bold("\n Your Wallet"));
|
|
1370
|
-
console.log(` sVVV: ${
|
|
1371
|
-
console.log(` Pending rewards: ${
|
|
2034
|
+
console.log(` sVVV: ${formatUnits3(mySvvv, 18)}`);
|
|
2035
|
+
console.log(` Pending rewards: ${formatUnits3(myPending, 18)} VVV`);
|
|
1372
2036
|
console.log(chalk2.bold("\n Venice API"));
|
|
1373
2037
|
console.log(` Key: ${apiKey ? `${apiKey.slice(0, 8)}...${apiKey.slice(-4)}` : chalk2.dim("not provisioned")}`);
|
|
1374
2038
|
console.log(` Status: ${apiKeyValid ? chalk2.green("valid") : chalk2.red("invalid/missing")}`);
|
|
@@ -1437,10 +2101,10 @@ ${opts.prompt}`;
|
|
|
1437
2101
|
console.log(chalk2.dim(`Model: ${result.model} | Tokens: ${result.usage.promptTokens} in, ${result.usage.completionTokens} out, ${result.usage.totalTokens} total`));
|
|
1438
2102
|
}
|
|
1439
2103
|
try {
|
|
1440
|
-
const { createVeniceInferenceAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-
|
|
2104
|
+
const { createVeniceInferenceAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-YZIHYQAI.js");
|
|
1441
2105
|
const { keccak256, toHex, isAddress: isAddr } = await import("viem");
|
|
1442
|
-
const { getChainContracts: getChainContracts2 } = await import("./config-
|
|
1443
|
-
const { getChain: getActiveChain } = await import("./network-
|
|
2106
|
+
const { getChainContracts: getChainContracts2 } = await import("./config-YXZKPSZB.js");
|
|
2107
|
+
const { getChain: getActiveChain } = await import("./network-QEMP3DSP.js");
|
|
1444
2108
|
const vaultRecipient = opts.vault && isAddr(opts.vault) ? opts.vault : getChainContracts2(getActiveChain().id).vault;
|
|
1445
2109
|
const promptHash = keccak256(toHex(userContent)).slice(0, 18);
|
|
1446
2110
|
const { uid } = await createVeniceInferenceAttestation(
|
|
@@ -1464,12 +2128,12 @@ ${opts.prompt}`;
|
|
|
1464
2128
|
}
|
|
1465
2129
|
|
|
1466
2130
|
// src/commands/allowance.ts
|
|
1467
|
-
import { parseUnits as parseUnits4, formatUnits as
|
|
2131
|
+
import { parseUnits as parseUnits4, formatUnits as formatUnits4, isAddress as isAddress3 } from "viem";
|
|
1468
2132
|
import chalk3 from "chalk";
|
|
1469
2133
|
import ora3 from "ora";
|
|
1470
2134
|
|
|
1471
2135
|
// src/strategies/allowance-disburse.ts
|
|
1472
|
-
import { encodeFunctionData as
|
|
2136
|
+
import { encodeFunctionData as encodeFunctionData7, parseUnits as parseUnits3 } from "viem";
|
|
1473
2137
|
function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDecimals, minUsdc, swapPath) {
|
|
1474
2138
|
const assetAmount = parseUnits3(config.amount, assetDecimals);
|
|
1475
2139
|
const isUsdc = assetAddress.toLowerCase() === TOKENS().USDC.toLowerCase();
|
|
@@ -1478,7 +2142,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
|
|
|
1478
2142
|
if (!isUsdc) {
|
|
1479
2143
|
calls.push({
|
|
1480
2144
|
target: assetAddress,
|
|
1481
|
-
data:
|
|
2145
|
+
data: encodeFunctionData7({
|
|
1482
2146
|
abi: ERC20_ABI,
|
|
1483
2147
|
functionName: "approve",
|
|
1484
2148
|
args: [UNISWAP().SWAP_ROUTER, assetAmount]
|
|
@@ -1488,7 +2152,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
|
|
|
1488
2152
|
if (isWeth) {
|
|
1489
2153
|
calls.push({
|
|
1490
2154
|
target: UNISWAP().SWAP_ROUTER,
|
|
1491
|
-
data:
|
|
2155
|
+
data: encodeFunctionData7({
|
|
1492
2156
|
abi: SWAP_ROUTER_EXACT_INPUT_SINGLE_ABI,
|
|
1493
2157
|
functionName: "exactInputSingle",
|
|
1494
2158
|
args: [
|
|
@@ -1508,7 +2172,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
|
|
|
1508
2172
|
} else {
|
|
1509
2173
|
calls.push({
|
|
1510
2174
|
target: UNISWAP().SWAP_ROUTER,
|
|
1511
|
-
data:
|
|
2175
|
+
data: encodeFunctionData7({
|
|
1512
2176
|
abi: SWAP_ROUTER_ABI,
|
|
1513
2177
|
functionName: "exactInput",
|
|
1514
2178
|
args: [
|
|
@@ -1528,7 +2192,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
|
|
|
1528
2192
|
for (const agent of agents) {
|
|
1529
2193
|
calls.push({
|
|
1530
2194
|
target: TOKENS().USDC,
|
|
1531
|
-
data:
|
|
2195
|
+
data: encodeFunctionData7({
|
|
1532
2196
|
abi: ERC20_ABI,
|
|
1533
2197
|
functionName: "transfer",
|
|
1534
2198
|
args: [agent, perAgent]
|
|
@@ -1593,9 +2257,9 @@ function registerAllowanceCommands(program2) {
|
|
|
1593
2257
|
console.log(chalk3.dim("\u2500".repeat(40)));
|
|
1594
2258
|
console.log(` Asset: ${assetSymbol} (${assetDecimals} decimals)`);
|
|
1595
2259
|
console.log(` Amount: ${opts.amount} ${assetSymbol}`);
|
|
1596
|
-
console.log(` Vault balance: ${
|
|
1597
|
-
console.log(` Deposited: ${
|
|
1598
|
-
console.log(` Profit: ${
|
|
2260
|
+
console.log(` Vault balance: ${formatUnits4(assetBalance, assetDecimals)} ${assetSymbol}`);
|
|
2261
|
+
console.log(` Deposited: ${formatUnits4(totalDeposited, assetDecimals)} ${assetSymbol}`);
|
|
2262
|
+
console.log(` Profit: ${formatUnits4(profit, assetDecimals)} ${assetSymbol}`);
|
|
1599
2263
|
console.log(` Agents: ${agents.length} (USDC will be split equally)`);
|
|
1600
2264
|
if (!isUsdc) {
|
|
1601
2265
|
console.log(` Routing: ${isWeth ? `WETH \u2192 USDC (fee ${fee})` : `${assetSymbol} \u2192 WETH \u2192 USDC (fee ${fee})`}`);
|
|
@@ -1604,7 +2268,7 @@ function registerAllowanceCommands(program2) {
|
|
|
1604
2268
|
console.log(` Vault: ${vaultAddress}`);
|
|
1605
2269
|
console.log();
|
|
1606
2270
|
if (requestedAmount > profit) {
|
|
1607
|
-
console.warn(chalk3.yellow(` Warning: amount (${opts.amount}) exceeds available profit (${
|
|
2271
|
+
console.warn(chalk3.yellow(` Warning: amount (${opts.amount}) exceeds available profit (${formatUnits4(profit, assetDecimals)})`));
|
|
1608
2272
|
console.warn(chalk3.yellow(" This will use deposited capital, not just profits."));
|
|
1609
2273
|
console.log();
|
|
1610
2274
|
}
|
|
@@ -1638,7 +2302,7 @@ function registerAllowanceCommands(program2) {
|
|
|
1638
2302
|
}
|
|
1639
2303
|
minUsdc = applySlippage(amountOut, slippageBps);
|
|
1640
2304
|
quoteSpinner.succeed(
|
|
1641
|
-
`Quote: ${
|
|
2305
|
+
`Quote: ${formatUnits4(amountOut, 6)} USDC (min: ${formatUnits4(minUsdc, 6)}, per agent: ${formatUnits4(minUsdc / BigInt(agents.length), 6)})`
|
|
1642
2306
|
);
|
|
1643
2307
|
} catch (err) {
|
|
1644
2308
|
quoteSpinner.fail("Failed to fetch quote");
|
|
@@ -1648,7 +2312,7 @@ function registerAllowanceCommands(program2) {
|
|
|
1648
2312
|
}
|
|
1649
2313
|
const perAgent = minUsdc / BigInt(agents.length);
|
|
1650
2314
|
if (isUsdc) {
|
|
1651
|
-
console.log(chalk3.dim(` Per agent: ${
|
|
2315
|
+
console.log(chalk3.dim(` Per agent: ${formatUnits4(perAgent, 6)} USDC`));
|
|
1652
2316
|
console.log();
|
|
1653
2317
|
}
|
|
1654
2318
|
const config = {
|
|
@@ -1715,14 +2379,14 @@ function registerAllowanceCommands(program2) {
|
|
|
1715
2379
|
console.log(chalk3.dim("\u2500".repeat(50)));
|
|
1716
2380
|
console.log(chalk3.bold("\n Vault"));
|
|
1717
2381
|
console.log(` Asset: ${assetSymbol}`);
|
|
1718
|
-
console.log(` Balance: ${
|
|
1719
|
-
console.log(` Deposited: ${
|
|
1720
|
-
console.log(` Profit: ${
|
|
2382
|
+
console.log(` Balance: ${formatUnits4(assetBalance, assetDecimals)} ${assetSymbol}`);
|
|
2383
|
+
console.log(` Deposited: ${formatUnits4(totalDeposited, assetDecimals)} ${assetSymbol}`);
|
|
2384
|
+
console.log(` Profit: ${formatUnits4(profit, assetDecimals)} ${assetSymbol}`);
|
|
1721
2385
|
console.log(chalk3.bold("\n Agent USDC Balances"));
|
|
1722
2386
|
for (const { agent, balance } of agentBalances) {
|
|
1723
2387
|
const isMe = agent.toLowerCase() === account.address.toLowerCase();
|
|
1724
2388
|
const label = isMe ? chalk3.green(`${agent} (you)`) : agent;
|
|
1725
|
-
console.log(` ${label}: ${
|
|
2389
|
+
console.log(` ${label}: ${formatUnits4(balance, 6)} USDC`);
|
|
1726
2390
|
}
|
|
1727
2391
|
console.log();
|
|
1728
2392
|
} catch (err) {
|
|
@@ -1927,7 +2591,7 @@ function parseBigIntArg(value, name) {
|
|
|
1927
2591
|
}
|
|
1928
2592
|
|
|
1929
2593
|
// src/lib/simulate.ts
|
|
1930
|
-
import { decodeFunctionData, encodeFunctionData as
|
|
2594
|
+
import { decodeFunctionData, encodeFunctionData as encodeFunctionData8 } from "viem";
|
|
1931
2595
|
import chalk5 from "chalk";
|
|
1932
2596
|
var G = chalk5.green;
|
|
1933
2597
|
var W = chalk5.white;
|
|
@@ -2446,7 +3110,7 @@ async function simulateViaApi(vault, calls) {
|
|
|
2446
3110
|
async function simulateViaEthCall(vault, calls) {
|
|
2447
3111
|
const governor = getGovernorAddress();
|
|
2448
3112
|
const client = getPublicClient();
|
|
2449
|
-
const input2 =
|
|
3113
|
+
const input2 = encodeFunctionData8({
|
|
2450
3114
|
abi: SYNDICATE_VAULT_ABI,
|
|
2451
3115
|
functionName: "executeGovernorBatch",
|
|
2452
3116
|
args: [calls.map((c) => ({ target: c.target, data: c.data, value: c.value }))]
|
|
@@ -2569,8 +3233,8 @@ function printSimulationResult(result, callType) {
|
|
|
2569
3233
|
}
|
|
2570
3234
|
async function sendSimulationAlert(subdomain, proposalId, vault, execResult, settleResult) {
|
|
2571
3235
|
try {
|
|
2572
|
-
const xmtp = await import("./xmtp-
|
|
2573
|
-
const { getAccount: getAccount2 } = await import("./client-
|
|
3236
|
+
const xmtp = await import("./xmtp-YNOGIZ4G.js");
|
|
3237
|
+
const { getAccount: getAccount2 } = await import("./client-CWQFYZ5Z.js");
|
|
2574
3238
|
const group = await xmtp.getGroup("", subdomain);
|
|
2575
3239
|
const allRisks = [...execResult.risks, ...settleResult?.risks ?? []];
|
|
2576
3240
|
const criticals = allRisks.filter((r) => r.level === "critical");
|
|
@@ -3262,7 +3926,7 @@ try {
|
|
|
3262
3926
|
var require2 = createRequire(import.meta.url);
|
|
3263
3927
|
var { version: CLI_VERSION } = require2("../package.json");
|
|
3264
3928
|
async function loadXmtp() {
|
|
3265
|
-
return import("./xmtp-
|
|
3929
|
+
return import("./xmtp-YNOGIZ4G.js");
|
|
3266
3930
|
}
|
|
3267
3931
|
async function loadCron() {
|
|
3268
3932
|
return import("./cron-EOULYO3R.js");
|
|
@@ -3508,8 +4172,11 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
3508
4172
|
const xmtp = await loadXmtp();
|
|
3509
4173
|
const xmtpClient = await xmtp.getXmtpClient();
|
|
3510
4174
|
const groupId = await xmtp.createSyndicateGroup(xmtpClient, subdomain, opts.publicChat);
|
|
3511
|
-
await setTextRecord(subdomain, "xmtpGroupId", groupId, result.vault);
|
|
3512
4175
|
cacheGroupId(subdomain, groupId);
|
|
4176
|
+
try {
|
|
4177
|
+
await setTextRecord(subdomain, "xmtpGroupId", groupId, result.vault);
|
|
4178
|
+
} catch {
|
|
4179
|
+
}
|
|
3513
4180
|
} catch {
|
|
3514
4181
|
console.warn(chalk8.yellow("\n \u26A0 Could not create XMTP chat group"));
|
|
3515
4182
|
console.warn(chalk8.dim(` Recover later with: sherwood chat ${subdomain} init`));
|
|
@@ -4222,7 +4889,7 @@ vaultCmd.command("balance").description("Show LP share balance and asset value")
|
|
|
4222
4889
|
var strategy = program.command("strategy").description("Strategy templates \u2014 list, clone, propose");
|
|
4223
4890
|
registerStrategyTemplateCommands(strategy);
|
|
4224
4891
|
program.command("providers").description("List available DeFi providers").action(async () => {
|
|
4225
|
-
const { MessariProvider, NansenProvider } = await import("./research-
|
|
4892
|
+
const { MessariProvider, NansenProvider } = await import("./research-ZX7T2ZIJ.js");
|
|
4226
4893
|
const providers = [new MoonwellProvider(), new UniswapProvider(), new MessariProvider(), new NansenProvider()];
|
|
4227
4894
|
for (const p of providers) {
|
|
4228
4895
|
const info = p.info();
|
|
@@ -4233,7 +4900,7 @@ ${info.name} (${info.type})`);
|
|
|
4233
4900
|
}
|
|
4234
4901
|
});
|
|
4235
4902
|
try {
|
|
4236
|
-
const { registerChatCommands } = await import("./chat-
|
|
4903
|
+
const { registerChatCommands } = await import("./chat-52PWNH7Y.js");
|
|
4237
4904
|
registerChatCommands(program);
|
|
4238
4905
|
} catch {
|
|
4239
4906
|
program.command("chat <name> [action] [actionArgs...]").description("Syndicate chat (XMTP) \u2014 requires @xmtp/cli").action(() => {
|
|
@@ -4243,16 +4910,16 @@ try {
|
|
|
4243
4910
|
process.exit(1);
|
|
4244
4911
|
});
|
|
4245
4912
|
}
|
|
4246
|
-
var { registerSessionCommands } = await import("./session-
|
|
4913
|
+
var { registerSessionCommands } = await import("./session-NMA4RLKC.js");
|
|
4247
4914
|
registerSessionCommands(program);
|
|
4248
4915
|
registerVeniceCommands(program);
|
|
4249
4916
|
registerAllowanceCommands(program);
|
|
4250
4917
|
registerIdentityCommands(program);
|
|
4251
4918
|
registerProposalCommands(program);
|
|
4252
4919
|
registerGovernorCommands(program);
|
|
4253
|
-
var { registerResearchCommands } = await import("./research-
|
|
4920
|
+
var { registerResearchCommands } = await import("./research-PH77VSE4.js");
|
|
4254
4921
|
registerResearchCommands(program);
|
|
4255
|
-
var { registerTradeCommands } = await import("./trade-
|
|
4922
|
+
var { registerTradeCommands } = await import("./trade-N2A74UPX.js");
|
|
4256
4923
|
registerTradeCommands(program);
|
|
4257
4924
|
var configCmd = program.command("config");
|
|
4258
4925
|
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) => {
|