@sherwoodagent/cli 0.42.0 → 0.51.0
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/{addresses-DCQMJJFI.js → addresses-DOWLGXGE.js} +4 -4
- package/dist/{agent-5YEJ6W6W.js → agent-ZPAZTJG3.js} +2923 -2023
- package/dist/agent-ZPAZTJG3.js.map +1 -0
- package/dist/autoresearch-IA7HGSNW.js +267 -0
- package/dist/autoresearch-IA7HGSNW.js.map +1 -0
- package/dist/{chat-UWJ6JBFE.js → chat-MRTPUZ3X.js} +8 -8
- package/dist/{chunk-FOOZC4AM.js → chunk-2LRC7I33.js} +2 -2
- package/dist/{chunk-4EHI4SKM.js → chunk-2W4KGGPU.js} +11 -1
- package/dist/chunk-2W4KGGPU.js.map +1 -0
- package/dist/chunk-7HJXZ4NM.js +189 -0
- package/dist/chunk-7HJXZ4NM.js.map +1 -0
- package/dist/{chunk-JNDEZFXW.js → chunk-E6LH2FJ6.js} +4 -4
- package/dist/{chunk-XAUMJZU2.js → chunk-FWC57PT7.js} +2 -2
- package/dist/{chunk-4OAZ7TP5.js → chunk-IS2CLLUT.js} +4 -4
- package/dist/{chunk-T2IA6CCW.js → chunk-KJ35EF7N.js} +4 -4
- package/dist/{chunk-GMVDHSCF.js → chunk-OHQOTSFG.js} +39 -3
- package/dist/chunk-OHQOTSFG.js.map +1 -0
- package/dist/chunk-OMW47WOO.js +700 -0
- package/dist/chunk-OMW47WOO.js.map +1 -0
- package/dist/{chunk-5Q3VT6NQ.js → chunk-R7X33D2S.js} +5 -5
- package/dist/{chunk-6U3A6ZJ2.js → chunk-WMZEOR5W.js} +6 -127
- package/dist/chunk-WMZEOR5W.js.map +1 -0
- package/dist/{chunk-SWKHSKDH.js → chunk-XDBRKW7I.js} +3 -3
- package/dist/chunk-YTYXBX7B.js +495 -0
- package/dist/chunk-YTYXBX7B.js.map +1 -0
- package/dist/{client-DRVUILER.js → client-UHHCB6EZ.js} +4 -4
- package/dist/{config-YRERDZSL.js → config-REASKDIK.js} +6 -2
- package/dist/{eas-KBU2BAOD.js → eas-PEEWGSAO.js} +6 -6
- package/dist/{governor-KH7RNH2E.js → governor-MG5VVSU4.js} +6 -6
- package/dist/index.js +1424 -270
- package/dist/index.js.map +1 -1
- package/dist/{network-3HB46JMQ.js → network-3ZU7UBDU.js} +3 -3
- package/dist/{price-TTQWI672.js → price-WCCXWPDM.js} +5 -5
- package/dist/{research-J3HYU3N6.js → research-QBKISW2M.js} +9 -9
- package/dist/research-YJWSQS2A.js +14 -0
- package/dist/serve-6MYBOAX6.js +222 -0
- package/dist/serve-6MYBOAX6.js.map +1 -0
- package/dist/{session-NUPHC6ZN.js → session-FPCPCVQX.js} +10 -10
- package/dist/summary-formatter-JIGWN4QS.js +180 -0
- package/dist/summary-formatter-JIGWN4QS.js.map +1 -0
- package/dist/{trade-VGECC4QZ.js → trade-NO6CNWCA.js} +19 -17
- package/dist/{trade-VGECC4QZ.js.map → trade-NO6CNWCA.js.map} +1 -1
- package/dist/{xmtp-JO7W6RFK.js → xmtp-ACB2W573.js} +6 -6
- package/package.json +5 -2
- package/dist/agent-5YEJ6W6W.js.map +0 -1
- package/dist/chunk-4EHI4SKM.js.map +0 -1
- package/dist/chunk-6U3A6ZJ2.js.map +0 -1
- package/dist/chunk-GMVDHSCF.js.map +0 -1
- package/dist/research-NR4VC5LN.js +0 -14
- /package/dist/{addresses-DCQMJJFI.js.map → addresses-DOWLGXGE.js.map} +0 -0
- /package/dist/{chat-UWJ6JBFE.js.map → chat-MRTPUZ3X.js.map} +0 -0
- /package/dist/{chunk-FOOZC4AM.js.map → chunk-2LRC7I33.js.map} +0 -0
- /package/dist/{chunk-JNDEZFXW.js.map → chunk-E6LH2FJ6.js.map} +0 -0
- /package/dist/{chunk-XAUMJZU2.js.map → chunk-FWC57PT7.js.map} +0 -0
- /package/dist/{chunk-4OAZ7TP5.js.map → chunk-IS2CLLUT.js.map} +0 -0
- /package/dist/{chunk-T2IA6CCW.js.map → chunk-KJ35EF7N.js.map} +0 -0
- /package/dist/{chunk-5Q3VT6NQ.js.map → chunk-R7X33D2S.js.map} +0 -0
- /package/dist/{chunk-SWKHSKDH.js.map → chunk-XDBRKW7I.js.map} +0 -0
- /package/dist/{client-DRVUILER.js.map → client-UHHCB6EZ.js.map} +0 -0
- /package/dist/{config-YRERDZSL.js.map → config-REASKDIK.js.map} +0 -0
- /package/dist/{eas-KBU2BAOD.js.map → eas-PEEWGSAO.js.map} +0 -0
- /package/dist/{governor-KH7RNH2E.js.map → governor-MG5VVSU4.js.map} +0 -0
- /package/dist/{network-3HB46JMQ.js.map → network-3ZU7UBDU.js.map} +0 -0
- /package/dist/{price-TTQWI672.js.map → price-WCCXWPDM.js.map} +0 -0
- /package/dist/{research-J3HYU3N6.js.map → research-QBKISW2M.js.map} +0 -0
- /package/dist/{research-NR4VC5LN.js.map → research-YJWSQS2A.js.map} +0 -0
- /package/dist/{session-NUPHC6ZN.js.map → session-FPCPCVQX.js.map} +0 -0
- /package/dist/{xmtp-JO7W6RFK.js.map → xmtp-ACB2W573.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
HyperliquidProvider,
|
|
4
|
+
getLatestSignals,
|
|
5
|
+
hlCancelAllOrders,
|
|
6
|
+
hlGetMeta,
|
|
7
|
+
hlPlaceLimitOrder,
|
|
8
|
+
resolveHLCoin
|
|
9
|
+
} from "./chunk-OMW47WOO.js";
|
|
2
10
|
import {
|
|
3
11
|
UniswapProvider,
|
|
4
12
|
applySlippage,
|
|
5
|
-
chatCompletion,
|
|
6
|
-
checkApiKeyValid,
|
|
7
13
|
encodeSwapPath,
|
|
8
14
|
getMultiHopQuote,
|
|
9
|
-
getQuote
|
|
15
|
+
getQuote
|
|
16
|
+
} from "./chunk-WMZEOR5W.js";
|
|
17
|
+
import {
|
|
18
|
+
chatCompletion,
|
|
19
|
+
checkApiKeyValid,
|
|
10
20
|
listModels,
|
|
11
21
|
provisionApiKey
|
|
12
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-7HJXZ4NM.js";
|
|
13
23
|
import {
|
|
14
24
|
createApproval,
|
|
15
25
|
createJoinRequest,
|
|
@@ -20,14 +30,14 @@ import {
|
|
|
20
30
|
queryJoinRequests,
|
|
21
31
|
revokeJoinRequest,
|
|
22
32
|
stripReferrerPrefix
|
|
23
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-E6LH2FJ6.js";
|
|
24
34
|
import {
|
|
25
35
|
createSyndicate,
|
|
26
36
|
getActiveSyndicates,
|
|
27
37
|
getSyndicate,
|
|
28
38
|
subdomainExists,
|
|
29
39
|
updateMetadata
|
|
30
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-IS2CLLUT.js";
|
|
31
41
|
import {
|
|
32
42
|
approveDepositor,
|
|
33
43
|
deposit,
|
|
@@ -51,7 +61,7 @@ import {
|
|
|
51
61
|
searchSyndicateAcrossChains,
|
|
52
62
|
setTextRecord,
|
|
53
63
|
setVaultAddress
|
|
54
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-R7X33D2S.js";
|
|
55
65
|
import {
|
|
56
66
|
PROPOSAL_STATE,
|
|
57
67
|
PROPOSAL_STATES,
|
|
@@ -83,7 +93,7 @@ import {
|
|
|
83
93
|
setVotingPeriod,
|
|
84
94
|
settleProposal,
|
|
85
95
|
vote
|
|
86
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-KJ35EF7N.js";
|
|
87
97
|
import {
|
|
88
98
|
AERODROME,
|
|
89
99
|
AGENT_REGISTRY,
|
|
@@ -95,7 +105,7 @@ import {
|
|
|
95
105
|
TOKENS,
|
|
96
106
|
UNISWAP,
|
|
97
107
|
VENICE
|
|
98
|
-
} from "./chunk-
|
|
108
|
+
} from "./chunk-FWC57PT7.js";
|
|
99
109
|
import {
|
|
100
110
|
formatContractError,
|
|
101
111
|
getAccount,
|
|
@@ -104,7 +114,7 @@ import {
|
|
|
104
114
|
sendTxWithRetry,
|
|
105
115
|
waitForReceipt,
|
|
106
116
|
writeContractWithRetry
|
|
107
|
-
} from "./chunk-
|
|
117
|
+
} from "./chunk-XDBRKW7I.js";
|
|
108
118
|
import {
|
|
109
119
|
CHAIN_REGISTRY,
|
|
110
120
|
VALID_NETWORKS,
|
|
@@ -115,7 +125,7 @@ import {
|
|
|
115
125
|
isTestnet,
|
|
116
126
|
setNetwork,
|
|
117
127
|
withNetwork
|
|
118
|
-
} from "./chunk-
|
|
128
|
+
} from "./chunk-2LRC7I33.js";
|
|
119
129
|
import {
|
|
120
130
|
addSyndicate,
|
|
121
131
|
cacheGroupId,
|
|
@@ -131,6 +141,7 @@ import {
|
|
|
131
141
|
getVeniceApiKey,
|
|
132
142
|
loadConfig,
|
|
133
143
|
setAgentId,
|
|
144
|
+
setAnthropicApiKey,
|
|
134
145
|
setChainContract,
|
|
135
146
|
setConfigRpcUrl,
|
|
136
147
|
setNotifyTo,
|
|
@@ -138,7 +149,7 @@ import {
|
|
|
138
149
|
setPrivateKey,
|
|
139
150
|
setUniswapApiKey,
|
|
140
151
|
setVeniceApiKey
|
|
141
|
-
} from "./chunk-
|
|
152
|
+
} from "./chunk-2W4KGGPU.js";
|
|
142
153
|
import {
|
|
143
154
|
BASE_STRATEGY_ABI,
|
|
144
155
|
ERC20_ABI,
|
|
@@ -157,8 +168,8 @@ import {
|
|
|
157
168
|
import { config as loadDotenv } from "dotenv";
|
|
158
169
|
import { createRequire } from "module";
|
|
159
170
|
import { Command, Option } from "commander";
|
|
160
|
-
import { parseUnits as
|
|
161
|
-
import
|
|
171
|
+
import { parseUnits as parseUnits7, formatUnits as formatUnits5, isAddress as isAddress6 } from "viem";
|
|
172
|
+
import chalk15 from "chalk";
|
|
162
173
|
import ora8 from "ora";
|
|
163
174
|
import { input, confirm, select } from "@inquirer/prompts";
|
|
164
175
|
|
|
@@ -1447,9 +1458,9 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
1447
1458
|
console.error(chalk.red("Missing --name, --performance-fee, or --duration. Use --write-calls to skip proposal submission."));
|
|
1448
1459
|
process.exit(1);
|
|
1449
1460
|
}
|
|
1450
|
-
const { propose: propose2 } = await import("./governor-
|
|
1461
|
+
const { propose: propose2 } = await import("./governor-MG5VVSU4.js");
|
|
1451
1462
|
const { pinJSON } = await import("./ipfs-IGXLLJCF.js");
|
|
1452
|
-
const { parseDuration: parseDuration2 } = await import("./governor-
|
|
1463
|
+
const { parseDuration: parseDuration2 } = await import("./governor-MG5VVSU4.js");
|
|
1453
1464
|
const performanceFeeBps = BigInt(opts.performanceFee);
|
|
1454
1465
|
if (performanceFeeBps < 0n || performanceFeeBps > 10000n) {
|
|
1455
1466
|
console.error(chalk.red("--performance-fee must be 0-10000 (basis points)"));
|
|
@@ -1556,7 +1567,7 @@ function registerStrategyTemplateCommands(strategy2) {
|
|
|
1556
1567
|
let prices = [];
|
|
1557
1568
|
let priceSource = null;
|
|
1558
1569
|
try {
|
|
1559
|
-
const { getTokenPricesInAsset } = await import("./price-
|
|
1570
|
+
const { getTokenPricesInAsset } = await import("./price-WCCXWPDM.js");
|
|
1560
1571
|
prices = await getTokenPricesInAsset({
|
|
1561
1572
|
tokens: allocations.map((a, i) => ({
|
|
1562
1573
|
token: a.token,
|
|
@@ -1986,7 +1997,7 @@ function registerVeniceCommands(program2) {
|
|
|
1986
1997
|
console.log(chalk2.dim(` Key: ${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`));
|
|
1987
1998
|
console.log(chalk2.dim(" Saved to ~/.sherwood/config.json"));
|
|
1988
1999
|
try {
|
|
1989
|
-
const { createVeniceProvisionAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-
|
|
2000
|
+
const { createVeniceProvisionAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-PEEWGSAO.js");
|
|
1990
2001
|
const { uid } = await createVeniceProvisionAttestation(account.address);
|
|
1991
2002
|
if (uid !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
1992
2003
|
console.log(chalk2.dim(` Attested: ${getEasScanUrl2(uid)}`));
|
|
@@ -2342,12 +2353,12 @@ ${opts.prompt}`;
|
|
|
2342
2353
|
console.log(chalk2.dim(`Model: ${result.model} | Tokens: ${result.usage.promptTokens} in, ${result.usage.completionTokens} out, ${result.usage.totalTokens} total`));
|
|
2343
2354
|
}
|
|
2344
2355
|
try {
|
|
2345
|
-
const { createVeniceInferenceAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-
|
|
2346
|
-
const { keccak256, toHex, isAddress: isAddr } = await import("viem");
|
|
2347
|
-
const { getChainContracts: getChainContracts2 } = await import("./config-
|
|
2348
|
-
const { getChain: getActiveChain } = await import("./network-
|
|
2356
|
+
const { createVeniceInferenceAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-PEEWGSAO.js");
|
|
2357
|
+
const { keccak256: keccak2562, toHex, isAddress: isAddr } = await import("viem");
|
|
2358
|
+
const { getChainContracts: getChainContracts2 } = await import("./config-REASKDIK.js");
|
|
2359
|
+
const { getChain: getActiveChain } = await import("./network-3ZU7UBDU.js");
|
|
2349
2360
|
const vaultRecipient = opts.vault && isAddr(opts.vault) ? opts.vault : getChainContracts2(getActiveChain().id).vault;
|
|
2350
|
-
const promptHash =
|
|
2361
|
+
const promptHash = keccak2562(toHex(userContent)).slice(0, 18);
|
|
2351
2362
|
const { uid } = await createVeniceInferenceAttestation(
|
|
2352
2363
|
result.model,
|
|
2353
2364
|
result.usage.promptTokens,
|
|
@@ -3518,8 +3529,8 @@ function printSimulationResult(result, callType) {
|
|
|
3518
3529
|
}
|
|
3519
3530
|
async function sendSimulationAlert(subdomain, proposalId, vault, execResult, settleResult) {
|
|
3520
3531
|
try {
|
|
3521
|
-
const xmtp = await import("./xmtp-
|
|
3522
|
-
const { getAccount: getAccount2 } = await import("./client-
|
|
3532
|
+
const xmtp = await import("./xmtp-ACB2W573.js");
|
|
3533
|
+
const { getAccount: getAccount2 } = await import("./client-UHHCB6EZ.js");
|
|
3523
3534
|
const group = await xmtp.getGroup("", subdomain);
|
|
3524
3535
|
const allRisks = [...execResult.risks, ...settleResult?.risks ?? []];
|
|
3525
3536
|
const criticals = allRisks.filter((r) => r.level === "critical");
|
|
@@ -4610,6 +4621,1143 @@ function registerGuardianCommands(program2) {
|
|
|
4610
4621
|
});
|
|
4611
4622
|
}
|
|
4612
4623
|
|
|
4624
|
+
// src/commands/grid.ts
|
|
4625
|
+
import chalk14 from "chalk";
|
|
4626
|
+
|
|
4627
|
+
// src/grid/loop.ts
|
|
4628
|
+
import chalk13 from "chalk";
|
|
4629
|
+
import { appendFile, mkdir as mkdir4 } from "fs/promises";
|
|
4630
|
+
import { join as join4 } from "path";
|
|
4631
|
+
import { homedir as homedir4 } from "os";
|
|
4632
|
+
|
|
4633
|
+
// src/grid/manager.ts
|
|
4634
|
+
import chalk9 from "chalk";
|
|
4635
|
+
|
|
4636
|
+
// src/grid/portfolio.ts
|
|
4637
|
+
import { readFile, writeFile, rename, mkdir } from "fs/promises";
|
|
4638
|
+
import { join, dirname } from "path";
|
|
4639
|
+
import { homedir } from "os";
|
|
4640
|
+
var GRID_STATE_PATH = join(homedir(), ".sherwood", "grid", "portfolio.json");
|
|
4641
|
+
function emptyStats() {
|
|
4642
|
+
return {
|
|
4643
|
+
totalRoundTrips: 0,
|
|
4644
|
+
totalPnlUsd: 0,
|
|
4645
|
+
todayPnlUsd: 0,
|
|
4646
|
+
totalFills: 0,
|
|
4647
|
+
todayFills: 0,
|
|
4648
|
+
lastDailyReset: 0,
|
|
4649
|
+
lastRebalanceAt: 0
|
|
4650
|
+
};
|
|
4651
|
+
}
|
|
4652
|
+
var GridPortfolio = class {
|
|
4653
|
+
state = null;
|
|
4654
|
+
/** Load grid state from disk. Returns null if no grid initialized yet. */
|
|
4655
|
+
async load() {
|
|
4656
|
+
try {
|
|
4657
|
+
const raw = await readFile(GRID_STATE_PATH, "utf-8");
|
|
4658
|
+
this.state = JSON.parse(raw);
|
|
4659
|
+
return this.state;
|
|
4660
|
+
} catch {
|
|
4661
|
+
return null;
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4664
|
+
/** Save grid state to disk (atomic write). */
|
|
4665
|
+
async save(state) {
|
|
4666
|
+
this.state = state;
|
|
4667
|
+
await mkdir(dirname(GRID_STATE_PATH), { recursive: true });
|
|
4668
|
+
const tmp = `${GRID_STATE_PATH}.tmp.${process.pid}`;
|
|
4669
|
+
await writeFile(tmp, JSON.stringify(state, null, 2), "utf-8");
|
|
4670
|
+
await rename(tmp, GRID_STATE_PATH);
|
|
4671
|
+
}
|
|
4672
|
+
/**
|
|
4673
|
+
* Initialize the grid by carving capital from the directional portfolio.
|
|
4674
|
+
* Called once on first startup when grid-portfolio.json doesn't exist.
|
|
4675
|
+
*
|
|
4676
|
+
* Returns the USD amount carved out (caller must deduct from directional portfolio).
|
|
4677
|
+
*/
|
|
4678
|
+
async initialize(capital, config) {
|
|
4679
|
+
const allocation = capital;
|
|
4680
|
+
const grids = config.tokens.map((token) => ({
|
|
4681
|
+
token,
|
|
4682
|
+
levels: [],
|
|
4683
|
+
openFills: [],
|
|
4684
|
+
allocation: allocation * (config.tokenSplit[token] ?? 1 / config.tokens.length),
|
|
4685
|
+
stats: emptyStats(),
|
|
4686
|
+
centerPrice: 0,
|
|
4687
|
+
atr: 0
|
|
4688
|
+
}));
|
|
4689
|
+
const state = {
|
|
4690
|
+
totalAllocation: allocation,
|
|
4691
|
+
grids,
|
|
4692
|
+
paused: false,
|
|
4693
|
+
pauseReason: "",
|
|
4694
|
+
initializedAt: Date.now()
|
|
4695
|
+
};
|
|
4696
|
+
await this.save(state);
|
|
4697
|
+
this.state = state;
|
|
4698
|
+
}
|
|
4699
|
+
/** Get current state (must load() first). */
|
|
4700
|
+
getState() {
|
|
4701
|
+
return this.state;
|
|
4702
|
+
}
|
|
4703
|
+
/** Reset daily counters if UTC day boundary crossed. */
|
|
4704
|
+
resetDailyStats(state) {
|
|
4705
|
+
const now = Date.now();
|
|
4706
|
+
const todayMidnight = /* @__PURE__ */ new Date();
|
|
4707
|
+
todayMidnight.setUTCHours(0, 0, 0, 0);
|
|
4708
|
+
const todayMs = todayMidnight.getTime();
|
|
4709
|
+
let changed = false;
|
|
4710
|
+
for (const grid of state.grids) {
|
|
4711
|
+
if (!grid.stats.lastDailyReset || grid.stats.lastDailyReset < todayMs) {
|
|
4712
|
+
grid.stats.todayPnlUsd = 0;
|
|
4713
|
+
grid.stats.todayFills = 0;
|
|
4714
|
+
grid.stats.lastDailyReset = now;
|
|
4715
|
+
changed = true;
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4718
|
+
return changed;
|
|
4719
|
+
}
|
|
4720
|
+
/** Check if grid should be paused (pool dropped below threshold). */
|
|
4721
|
+
checkPauseThreshold(state, config) {
|
|
4722
|
+
const currentValue = state.grids.reduce((sum, g) => {
|
|
4723
|
+
const openFillValue = g.openFills.filter((f) => !f.closed).reduce((s, f) => s + f.quantity * f.buyPrice, 0);
|
|
4724
|
+
return sum + g.allocation + openFillValue;
|
|
4725
|
+
}, 0);
|
|
4726
|
+
const dropPct = 1 - currentValue / state.totalAllocation;
|
|
4727
|
+
if (dropPct >= config.pauseThresholdPct) {
|
|
4728
|
+
state.paused = true;
|
|
4729
|
+
state.pauseReason = `Grid pool dropped ${(dropPct * 100).toFixed(1)}% (threshold: ${(config.pauseThresholdPct * 100).toFixed(0)}%)`;
|
|
4730
|
+
return true;
|
|
4731
|
+
}
|
|
4732
|
+
return false;
|
|
4733
|
+
}
|
|
4734
|
+
/** Aggregate stats across all token grids. */
|
|
4735
|
+
aggregateStats(state) {
|
|
4736
|
+
return {
|
|
4737
|
+
totalPnlUsd: state.grids.reduce((s, g) => s + g.stats.totalPnlUsd, 0),
|
|
4738
|
+
todayPnlUsd: state.grids.reduce((s, g) => s + g.stats.todayPnlUsd, 0),
|
|
4739
|
+
todayFills: state.grids.reduce((s, g) => s + g.stats.todayFills, 0),
|
|
4740
|
+
totalRoundTrips: state.grids.reduce((s, g) => s + g.stats.totalRoundTrips, 0)
|
|
4741
|
+
};
|
|
4742
|
+
}
|
|
4743
|
+
};
|
|
4744
|
+
|
|
4745
|
+
// src/grid/manager.ts
|
|
4746
|
+
var GridManager = class {
|
|
4747
|
+
config;
|
|
4748
|
+
portfolio;
|
|
4749
|
+
hl;
|
|
4750
|
+
constructor(config) {
|
|
4751
|
+
this.config = config;
|
|
4752
|
+
this.portfolio = new GridPortfolio();
|
|
4753
|
+
this.hl = new HyperliquidProvider();
|
|
4754
|
+
}
|
|
4755
|
+
/** Load or initialize grid state.
|
|
4756
|
+
* Also syncs new tokens added to config after initial grid setup. */
|
|
4757
|
+
async init(totalPortfolioValue) {
|
|
4758
|
+
const existing = await this.portfolio.load();
|
|
4759
|
+
if (!existing) {
|
|
4760
|
+
await this.portfolio.initialize(totalPortfolioValue, this.config);
|
|
4761
|
+
return;
|
|
4762
|
+
}
|
|
4763
|
+
let addedAllocation = 0;
|
|
4764
|
+
for (const token of this.config.tokens) {
|
|
4765
|
+
if (!existing.grids.find((g) => g.token === token)) {
|
|
4766
|
+
const split = this.config.tokenSplit[token] ?? 1 / this.config.tokens.length;
|
|
4767
|
+
const alloc = existing.totalAllocation * split;
|
|
4768
|
+
existing.grids.push({
|
|
4769
|
+
token,
|
|
4770
|
+
levels: [],
|
|
4771
|
+
openFills: [],
|
|
4772
|
+
allocation: alloc,
|
|
4773
|
+
stats: { totalRoundTrips: 0, totalPnlUsd: 0, todayPnlUsd: 0, totalFills: 0, todayFills: 0, lastDailyReset: 0, lastRebalanceAt: 0 },
|
|
4774
|
+
centerPrice: 0,
|
|
4775
|
+
atr: 0
|
|
4776
|
+
});
|
|
4777
|
+
addedAllocation += alloc;
|
|
4778
|
+
console.error(` [grid] Added new token grid: ${token} ($${alloc.toFixed(0)} allocation)`);
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
if (addedAllocation > 0) {
|
|
4782
|
+
await this.portfolio.save(existing);
|
|
4783
|
+
}
|
|
4784
|
+
}
|
|
4785
|
+
/**
|
|
4786
|
+
* Run one grid tick — called every cycle from the agent loop.
|
|
4787
|
+
*
|
|
4788
|
+
* 1. Load state + reset daily counters
|
|
4789
|
+
* 2. For each token grid: fetch price, simulate fills, check rebalance
|
|
4790
|
+
* 3. Persist state
|
|
4791
|
+
*
|
|
4792
|
+
* Returns a summary for the cycle log.
|
|
4793
|
+
*/
|
|
4794
|
+
async tick(prices, regime) {
|
|
4795
|
+
const state = await this.portfolio.load();
|
|
4796
|
+
if (!state || state.paused || !this.config.enabled) {
|
|
4797
|
+
return { fills: 0, roundTrips: 0, pnlUsd: 0, paused: state?.paused ?? false };
|
|
4798
|
+
}
|
|
4799
|
+
if (regime === "high-volatility") {
|
|
4800
|
+
console.error(chalk9.dim(` [grid] Paused: high-volatility regime \u2014 too risky for grid`));
|
|
4801
|
+
return { fills: 0, roundTrips: 0, pnlUsd: 0, paused: false };
|
|
4802
|
+
}
|
|
4803
|
+
this.portfolio.resetDailyStats(state);
|
|
4804
|
+
let totalFills = 0;
|
|
4805
|
+
let totalRoundTrips = 0;
|
|
4806
|
+
let totalPnl = 0;
|
|
4807
|
+
for (const grid of state.grids) {
|
|
4808
|
+
const price = prices[grid.token];
|
|
4809
|
+
if (!price || price <= 0) continue;
|
|
4810
|
+
if (grid.levels.length === 0 || this.needsFullRebuild(grid)) {
|
|
4811
|
+
await this.buildGrid(grid, price);
|
|
4812
|
+
}
|
|
4813
|
+
const { fills, roundTrips, pnlUsd } = this.simulateFills(grid, price);
|
|
4814
|
+
totalFills += fills;
|
|
4815
|
+
totalRoundTrips += roundTrips;
|
|
4816
|
+
totalPnl += pnlUsd;
|
|
4817
|
+
if (this.needsShift(grid, price)) {
|
|
4818
|
+
await this.shiftGrid(grid, price);
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
4821
|
+
this.portfolio.checkPauseThreshold(state, this.config);
|
|
4822
|
+
await this.portfolio.save(state);
|
|
4823
|
+
return { fills: totalFills, roundTrips: totalRoundTrips, pnlUsd: totalPnl, paused: false };
|
|
4824
|
+
}
|
|
4825
|
+
/** Build a fresh grid centered on the current price using ATR. */
|
|
4826
|
+
async buildGrid(grid, currentPrice) {
|
|
4827
|
+
const candles = await this.hl.getCandles(grid.token, "4h", 14 * 24 * 60 * 60 * 1e3);
|
|
4828
|
+
if (!candles || candles.length < this.config.atrPeriod) {
|
|
4829
|
+
console.error(chalk9.dim(` [grid] Cannot build grid for ${grid.token}: insufficient candle data`));
|
|
4830
|
+
return;
|
|
4831
|
+
}
|
|
4832
|
+
const signals = getLatestSignals(candles);
|
|
4833
|
+
const atr = signals.atr;
|
|
4834
|
+
if (!Number.isFinite(atr) || atr <= 0) {
|
|
4835
|
+
console.error(chalk9.dim(` [grid] Cannot build grid for ${grid.token}: invalid ATR (${atr})`));
|
|
4836
|
+
return;
|
|
4837
|
+
}
|
|
4838
|
+
const range = atr * this.config.atrMultiplier;
|
|
4839
|
+
const spacing = range / this.config.levelsPerSide;
|
|
4840
|
+
const effectiveCapital = grid.allocation * this.config.leverage;
|
|
4841
|
+
const quantity = effectiveCapital / (this.config.levelsPerSide * currentPrice);
|
|
4842
|
+
const levels = [];
|
|
4843
|
+
for (let i = 1; i <= this.config.levelsPerSide; i++) {
|
|
4844
|
+
levels.push({
|
|
4845
|
+
price: currentPrice - spacing * i,
|
|
4846
|
+
side: "buy",
|
|
4847
|
+
quantity,
|
|
4848
|
+
filled: false,
|
|
4849
|
+
filledAt: 0
|
|
4850
|
+
});
|
|
4851
|
+
}
|
|
4852
|
+
for (let i = 1; i <= this.config.levelsPerSide; i++) {
|
|
4853
|
+
levels.push({
|
|
4854
|
+
price: currentPrice + spacing * i,
|
|
4855
|
+
side: "sell",
|
|
4856
|
+
quantity,
|
|
4857
|
+
filled: false,
|
|
4858
|
+
filledAt: 0
|
|
4859
|
+
});
|
|
4860
|
+
}
|
|
4861
|
+
grid.levels = levels;
|
|
4862
|
+
grid.centerPrice = currentPrice;
|
|
4863
|
+
grid.atr = atr;
|
|
4864
|
+
grid.stats.lastRebalanceAt = Date.now();
|
|
4865
|
+
console.error(chalk9.dim(
|
|
4866
|
+
` [grid] Built ${grid.token} grid: center=$${currentPrice.toFixed(2)} ATR=$${atr.toFixed(2)} range=$${(currentPrice - range).toFixed(2)}-$${(currentPrice + range).toFixed(2)} spacing=$${spacing.toFixed(2)} qty=${quantity.toFixed(6)}`
|
|
4867
|
+
));
|
|
4868
|
+
}
|
|
4869
|
+
/** Simulate order fills against the current price. */
|
|
4870
|
+
simulateFills(grid, currentPrice) {
|
|
4871
|
+
let fills = 0;
|
|
4872
|
+
let roundTrips = 0;
|
|
4873
|
+
let pnlUsd = 0;
|
|
4874
|
+
const now = Date.now();
|
|
4875
|
+
const spacing = grid.atr > 0 ? grid.atr * this.config.atrMultiplier / this.config.levelsPerSide : 0;
|
|
4876
|
+
if (spacing <= 0) return { fills: 0, roundTrips: 0, pnlUsd: 0 };
|
|
4877
|
+
for (const level of grid.levels) {
|
|
4878
|
+
if (level.filled) continue;
|
|
4879
|
+
if (level.side === "buy" && currentPrice <= level.price) {
|
|
4880
|
+
level.filled = true;
|
|
4881
|
+
level.filledAt = now;
|
|
4882
|
+
fills++;
|
|
4883
|
+
grid.stats.totalFills++;
|
|
4884
|
+
grid.stats.todayFills++;
|
|
4885
|
+
grid.openFills.push({
|
|
4886
|
+
token: grid.token,
|
|
4887
|
+
buyPrice: level.price,
|
|
4888
|
+
targetSellPrice: level.price + spacing,
|
|
4889
|
+
quantity: level.quantity,
|
|
4890
|
+
filledAt: now,
|
|
4891
|
+
closed: false,
|
|
4892
|
+
pnlUsd: 0,
|
|
4893
|
+
closedAt: 0
|
|
4894
|
+
});
|
|
4895
|
+
console.error(chalk9.dim(
|
|
4896
|
+
` [grid] BUY fill ${grid.token} @ $${level.price.toFixed(2)} qty=${level.quantity.toFixed(6)}`
|
|
4897
|
+
));
|
|
4898
|
+
}
|
|
4899
|
+
if (level.side === "sell" && currentPrice >= level.price) {
|
|
4900
|
+
level.filled = true;
|
|
4901
|
+
level.filledAt = now;
|
|
4902
|
+
fills++;
|
|
4903
|
+
grid.stats.totalFills++;
|
|
4904
|
+
grid.stats.todayFills++;
|
|
4905
|
+
console.error(chalk9.dim(
|
|
4906
|
+
` [grid] SELL level ${grid.token} @ $${level.price.toFixed(2)} qty=${level.quantity.toFixed(6)}`
|
|
4907
|
+
));
|
|
4908
|
+
}
|
|
4909
|
+
}
|
|
4910
|
+
for (const openFill of grid.openFills) {
|
|
4911
|
+
if (openFill.closed) continue;
|
|
4912
|
+
if (currentPrice >= openFill.targetSellPrice) {
|
|
4913
|
+
const profit = (currentPrice - openFill.buyPrice) * openFill.quantity * this.config.leverage;
|
|
4914
|
+
if (profit >= this.config.minProfitPerFillUsd) {
|
|
4915
|
+
openFill.closed = true;
|
|
4916
|
+
openFill.pnlUsd = profit;
|
|
4917
|
+
openFill.closedAt = now;
|
|
4918
|
+
pnlUsd += profit;
|
|
4919
|
+
roundTrips++;
|
|
4920
|
+
grid.stats.totalRoundTrips++;
|
|
4921
|
+
grid.stats.totalPnlUsd += profit;
|
|
4922
|
+
grid.stats.todayPnlUsd += profit;
|
|
4923
|
+
grid.allocation += profit;
|
|
4924
|
+
console.error(chalk9.green(
|
|
4925
|
+
` [grid] ROUND-TRIP ${grid.token}: buy $${openFill.buyPrice.toFixed(2)} \u2192 sell $${currentPrice.toFixed(2)} = +$${profit.toFixed(2)}`
|
|
4926
|
+
));
|
|
4927
|
+
}
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
const pruneThreshold = now - 24 * 60 * 60 * 1e3;
|
|
4931
|
+
grid.openFills = grid.openFills.filter((f) => !f.closed || f.closedAt > pruneThreshold);
|
|
4932
|
+
return { fills, roundTrips, pnlUsd };
|
|
4933
|
+
}
|
|
4934
|
+
/** Check if the grid needs a full rebuild. */
|
|
4935
|
+
needsFullRebuild(grid) {
|
|
4936
|
+
if (grid.levels.length === 0) return true;
|
|
4937
|
+
const elapsed = Date.now() - grid.stats.lastRebalanceAt;
|
|
4938
|
+
return elapsed >= this.config.fullRebuildIntervalMs;
|
|
4939
|
+
}
|
|
4940
|
+
/** Check if price has drifted enough to warrant shifting the grid. */
|
|
4941
|
+
needsShift(grid, currentPrice) {
|
|
4942
|
+
if (grid.centerPrice <= 0 || grid.atr <= 0) return false;
|
|
4943
|
+
const range = grid.atr * this.config.atrMultiplier;
|
|
4944
|
+
const distFromCenter = Math.abs(currentPrice - grid.centerPrice);
|
|
4945
|
+
return distFromCenter / range >= this.config.rebalanceDriftPct;
|
|
4946
|
+
}
|
|
4947
|
+
/** Shift the grid to re-center on the current price. */
|
|
4948
|
+
async shiftGrid(grid, currentPrice) {
|
|
4949
|
+
console.error(chalk9.dim(
|
|
4950
|
+
` [grid] Shifting ${grid.token} grid: center $${grid.centerPrice.toFixed(2)} \u2192 $${currentPrice.toFixed(2)}`
|
|
4951
|
+
));
|
|
4952
|
+
await this.buildGrid(grid, currentPrice);
|
|
4953
|
+
}
|
|
4954
|
+
/** Get open fill exposure per token — used by the hedge manager. */
|
|
4955
|
+
getOpenFillExposure() {
|
|
4956
|
+
const state = this.portfolio.getState();
|
|
4957
|
+
if (!state) return [];
|
|
4958
|
+
return state.grids.map((g) => {
|
|
4959
|
+
const opens = g.openFills.filter((f) => !f.closed);
|
|
4960
|
+
const totalQty = opens.reduce((s, f) => s + f.quantity, 0);
|
|
4961
|
+
const totalNotional = opens.reduce((s, f) => s + f.quantity * f.buyPrice, 0);
|
|
4962
|
+
return {
|
|
4963
|
+
token: g.token,
|
|
4964
|
+
totalQuantity: totalQty,
|
|
4965
|
+
totalNotional,
|
|
4966
|
+
avgEntryPrice: totalQty > 0 ? totalNotional / totalQty : 0,
|
|
4967
|
+
fillCount: opens.length
|
|
4968
|
+
};
|
|
4969
|
+
}).filter((e) => e.fillCount > 0);
|
|
4970
|
+
}
|
|
4971
|
+
/**
|
|
4972
|
+
* Compute the orders that should be placed for the current grid state,
|
|
4973
|
+
* without simulating fills. Used by the live executor.
|
|
4974
|
+
*
|
|
4975
|
+
* Returns:
|
|
4976
|
+
* - ordersToPlace: all current grid levels that haven't been filled
|
|
4977
|
+
* - assetsToCancel: tokens whose grid was rebalanced (need cancel-and-place)
|
|
4978
|
+
* - needsRebalance: whether any grid was rebuilt this tick
|
|
4979
|
+
*/
|
|
4980
|
+
computeOrders(prices) {
|
|
4981
|
+
const state = this.portfolio.getState();
|
|
4982
|
+
if (!state || state.paused || !this.config.enabled) {
|
|
4983
|
+
return { ordersToPlace: [], assetsToCancel: [], needsRebalance: false };
|
|
4984
|
+
}
|
|
4985
|
+
const ordersToPlace = [];
|
|
4986
|
+
const assetsToCancel = [];
|
|
4987
|
+
let needsRebalance = false;
|
|
4988
|
+
for (const grid of state.grids) {
|
|
4989
|
+
const price = prices[grid.token];
|
|
4990
|
+
if (!price || price <= 0) continue;
|
|
4991
|
+
const wasEmpty = grid.levels.length === 0;
|
|
4992
|
+
const fullRebuild = wasEmpty || this.needsFullRebuild(grid);
|
|
4993
|
+
const shift = !fullRebuild && grid.centerPrice > 0 && this.needsShift(grid, price);
|
|
4994
|
+
if (fullRebuild || shift) {
|
|
4995
|
+
needsRebalance = true;
|
|
4996
|
+
if (!wasEmpty) assetsToCancel.push(grid.token);
|
|
4997
|
+
}
|
|
4998
|
+
for (const level of grid.levels) {
|
|
4999
|
+
if (level.filled) continue;
|
|
5000
|
+
ordersToPlace.push({
|
|
5001
|
+
token: grid.token,
|
|
5002
|
+
isBuy: level.side === "buy",
|
|
5003
|
+
price: level.price,
|
|
5004
|
+
quantity: level.quantity
|
|
5005
|
+
});
|
|
5006
|
+
}
|
|
5007
|
+
}
|
|
5008
|
+
return { ordersToPlace, assetsToCancel, needsRebalance };
|
|
5009
|
+
}
|
|
5010
|
+
/** Get aggregate stats for display. */
|
|
5011
|
+
getStats() {
|
|
5012
|
+
const state = this.portfolio.getState();
|
|
5013
|
+
if (!state) return null;
|
|
5014
|
+
const agg = this.portfolio.aggregateStats(state);
|
|
5015
|
+
return {
|
|
5016
|
+
...agg,
|
|
5017
|
+
allocation: state.totalAllocation,
|
|
5018
|
+
paused: state.paused
|
|
5019
|
+
};
|
|
5020
|
+
}
|
|
5021
|
+
};
|
|
5022
|
+
|
|
5023
|
+
// src/grid/config.ts
|
|
5024
|
+
var DEFAULT_GRID_CONFIG = {
|
|
5025
|
+
enabled: true,
|
|
5026
|
+
tokens: ["bitcoin", "ethereum", "solana"],
|
|
5027
|
+
allocationPct: 0.5,
|
|
5028
|
+
leverage: 5,
|
|
5029
|
+
// was 4 — +25% profit per RT
|
|
5030
|
+
levelsPerSide: 15,
|
|
5031
|
+
atrMultiplier: 2,
|
|
5032
|
+
atrPeriod: 14,
|
|
5033
|
+
rebalanceDriftPct: 0.4,
|
|
5034
|
+
// was 0.55 — rebalance faster, keep levels near price
|
|
5035
|
+
fullRebuildIntervalMs: 12 * 60 * 60 * 1e3,
|
|
5036
|
+
// 12h
|
|
5037
|
+
tokenSplit: { bitcoin: 0.45, ethereum: 0.3, solana: 0.25 },
|
|
5038
|
+
minProfitPerFillUsd: 0.5,
|
|
5039
|
+
pauseThresholdPct: 0.2
|
|
5040
|
+
};
|
|
5041
|
+
|
|
5042
|
+
// src/grid/hedge.ts
|
|
5043
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
5044
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
5045
|
+
import { homedir as homedir2 } from "os";
|
|
5046
|
+
import chalk10 from "chalk";
|
|
5047
|
+
var HEDGE_RATIO = 0.3;
|
|
5048
|
+
var MIN_FILLS_TO_HEDGE = 3;
|
|
5049
|
+
var ADJUSTMENT_THRESHOLD = 0.1;
|
|
5050
|
+
var HEDGE_STATE_PATH = join2(homedir2(), ".sherwood", "grid", "hedge.json");
|
|
5051
|
+
var GridHedgeManager = class {
|
|
5052
|
+
state = null;
|
|
5053
|
+
async load() {
|
|
5054
|
+
if (this.state) return this.state;
|
|
5055
|
+
try {
|
|
5056
|
+
const raw = await readFile2(HEDGE_STATE_PATH, "utf-8");
|
|
5057
|
+
this.state = JSON.parse(raw);
|
|
5058
|
+
} catch {
|
|
5059
|
+
this.state = {
|
|
5060
|
+
positions: [],
|
|
5061
|
+
totalRealizedPnl: 0,
|
|
5062
|
+
todayRealizedPnl: 0,
|
|
5063
|
+
lastDailyReset: Date.now()
|
|
5064
|
+
};
|
|
5065
|
+
}
|
|
5066
|
+
const todayMidnight = /* @__PURE__ */ new Date();
|
|
5067
|
+
todayMidnight.setUTCHours(0, 0, 0, 0);
|
|
5068
|
+
if (this.state.lastDailyReset < todayMidnight.getTime()) {
|
|
5069
|
+
this.state.todayRealizedPnl = 0;
|
|
5070
|
+
this.state.lastDailyReset = Date.now();
|
|
5071
|
+
}
|
|
5072
|
+
return this.state;
|
|
5073
|
+
}
|
|
5074
|
+
async save() {
|
|
5075
|
+
if (!this.state) return;
|
|
5076
|
+
await mkdir2(dirname2(HEDGE_STATE_PATH), { recursive: true });
|
|
5077
|
+
const tmp = `${HEDGE_STATE_PATH}.tmp.${process.pid}`;
|
|
5078
|
+
await writeFile2(tmp, JSON.stringify(this.state, null, 2), "utf-8");
|
|
5079
|
+
const { rename: rename3 } = await import("fs/promises");
|
|
5080
|
+
await rename3(tmp, HEDGE_STATE_PATH);
|
|
5081
|
+
}
|
|
5082
|
+
/**
|
|
5083
|
+
* Run one hedge tick. Call after grid tick with current prices and open fill data.
|
|
5084
|
+
*
|
|
5085
|
+
* @param openFills - Per-token open fill exposure from the grid portfolio
|
|
5086
|
+
* @param prices - Current mark prices per token
|
|
5087
|
+
*/
|
|
5088
|
+
async tick(openFills, prices) {
|
|
5089
|
+
const state = await this.load();
|
|
5090
|
+
let adjustments = 0;
|
|
5091
|
+
let unrealizedPnl = 0;
|
|
5092
|
+
for (const fill of openFills) {
|
|
5093
|
+
const price = prices[fill.token];
|
|
5094
|
+
if (!price || price <= 0) continue;
|
|
5095
|
+
const desiredShortQty = fill.fillCount >= MIN_FILLS_TO_HEDGE ? fill.totalQuantity * HEDGE_RATIO : 0;
|
|
5096
|
+
const existing = state.positions.find((p) => p.token === fill.token);
|
|
5097
|
+
if (desiredShortQty <= 0) {
|
|
5098
|
+
if (existing && existing.quantity > 0) {
|
|
5099
|
+
const closePnl = (existing.entryPrice - price) * existing.quantity;
|
|
5100
|
+
state.totalRealizedPnl += closePnl;
|
|
5101
|
+
state.todayRealizedPnl += closePnl;
|
|
5102
|
+
existing.realizedPnl += closePnl;
|
|
5103
|
+
console.error(chalk10.yellow(
|
|
5104
|
+
` [hedge] CLOSE ${fill.token} short ${existing.quantity.toFixed(6)} @ $${price.toFixed(2)} PnL: $${closePnl.toFixed(2)}`
|
|
5105
|
+
));
|
|
5106
|
+
existing.quantity = 0;
|
|
5107
|
+
existing.entryPrice = 0;
|
|
5108
|
+
adjustments++;
|
|
5109
|
+
}
|
|
5110
|
+
continue;
|
|
5111
|
+
}
|
|
5112
|
+
if (!existing) {
|
|
5113
|
+
state.positions.push({
|
|
5114
|
+
token: fill.token,
|
|
5115
|
+
entryPrice: price,
|
|
5116
|
+
quantity: desiredShortQty,
|
|
5117
|
+
lastAdjustedAt: Date.now(),
|
|
5118
|
+
realizedPnl: 0
|
|
5119
|
+
});
|
|
5120
|
+
console.error(chalk10.magenta(
|
|
5121
|
+
` [hedge] OPEN ${fill.token} short ${desiredShortQty.toFixed(6)} @ $${price.toFixed(2)} (${(HEDGE_RATIO * 100).toFixed(0)}% of ${fill.fillCount} open fills)`
|
|
5122
|
+
));
|
|
5123
|
+
adjustments++;
|
|
5124
|
+
} else if (existing.quantity > 0) {
|
|
5125
|
+
const diff = Math.abs(desiredShortQty - existing.quantity) / existing.quantity;
|
|
5126
|
+
if (diff > ADJUSTMENT_THRESHOLD) {
|
|
5127
|
+
if (desiredShortQty > existing.quantity) {
|
|
5128
|
+
const addQty = desiredShortQty - existing.quantity;
|
|
5129
|
+
const newAvgEntry = (existing.entryPrice * existing.quantity + price * addQty) / desiredShortQty;
|
|
5130
|
+
existing.entryPrice = newAvgEntry;
|
|
5131
|
+
existing.quantity = desiredShortQty;
|
|
5132
|
+
existing.lastAdjustedAt = Date.now();
|
|
5133
|
+
console.error(chalk10.magenta(
|
|
5134
|
+
` [hedge] INCREASE ${fill.token} short to ${desiredShortQty.toFixed(6)} @ avg $${newAvgEntry.toFixed(2)}`
|
|
5135
|
+
));
|
|
5136
|
+
adjustments++;
|
|
5137
|
+
} else {
|
|
5138
|
+
const closeQty = existing.quantity - desiredShortQty;
|
|
5139
|
+
const closePnl = (existing.entryPrice - price) * closeQty;
|
|
5140
|
+
state.totalRealizedPnl += closePnl;
|
|
5141
|
+
state.todayRealizedPnl += closePnl;
|
|
5142
|
+
existing.realizedPnl += closePnl;
|
|
5143
|
+
existing.quantity = desiredShortQty;
|
|
5144
|
+
existing.lastAdjustedAt = Date.now();
|
|
5145
|
+
console.error(chalk10.magenta(
|
|
5146
|
+
` [hedge] DECREASE ${fill.token} short to ${desiredShortQty.toFixed(6)}, closed ${closeQty.toFixed(6)} PnL: $${closePnl.toFixed(2)}`
|
|
5147
|
+
));
|
|
5148
|
+
adjustments++;
|
|
5149
|
+
}
|
|
5150
|
+
}
|
|
5151
|
+
const uPnl = (existing.entryPrice - price) * existing.quantity;
|
|
5152
|
+
unrealizedPnl += uPnl;
|
|
5153
|
+
} else {
|
|
5154
|
+
existing.entryPrice = price;
|
|
5155
|
+
existing.quantity = desiredShortQty;
|
|
5156
|
+
existing.lastAdjustedAt = Date.now();
|
|
5157
|
+
console.error(chalk10.magenta(
|
|
5158
|
+
` [hedge] REOPEN ${fill.token} short ${desiredShortQty.toFixed(6)} @ $${price.toFixed(2)}`
|
|
5159
|
+
));
|
|
5160
|
+
adjustments++;
|
|
5161
|
+
}
|
|
5162
|
+
}
|
|
5163
|
+
for (const pos of state.positions) {
|
|
5164
|
+
if (pos.quantity <= 0) continue;
|
|
5165
|
+
if (!openFills.find((f) => f.token === pos.token)) {
|
|
5166
|
+
const price = prices[pos.token];
|
|
5167
|
+
if (price) {
|
|
5168
|
+
const closePnl = (pos.entryPrice - price) * pos.quantity;
|
|
5169
|
+
state.totalRealizedPnl += closePnl;
|
|
5170
|
+
state.todayRealizedPnl += closePnl;
|
|
5171
|
+
pos.realizedPnl += closePnl;
|
|
5172
|
+
console.error(chalk10.yellow(
|
|
5173
|
+
` [hedge] CLOSE orphaned ${pos.token} short, PnL: $${closePnl.toFixed(2)}`
|
|
5174
|
+
));
|
|
5175
|
+
pos.quantity = 0;
|
|
5176
|
+
adjustments++;
|
|
5177
|
+
}
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
await this.save();
|
|
5181
|
+
return {
|
|
5182
|
+
adjustments,
|
|
5183
|
+
unrealizedPnl,
|
|
5184
|
+
totalRealizedPnl: state.totalRealizedPnl
|
|
5185
|
+
};
|
|
5186
|
+
}
|
|
5187
|
+
/** Get current hedge status for display. */
|
|
5188
|
+
getStatus() {
|
|
5189
|
+
if (!this.state) return null;
|
|
5190
|
+
return {
|
|
5191
|
+
positions: this.state.positions.filter((p) => p.quantity > 0),
|
|
5192
|
+
totalRealizedPnl: this.state.totalRealizedPnl,
|
|
5193
|
+
todayRealizedPnl: this.state.todayRealizedPnl
|
|
5194
|
+
};
|
|
5195
|
+
}
|
|
5196
|
+
};
|
|
5197
|
+
|
|
5198
|
+
// src/grid/executor.ts
|
|
5199
|
+
import chalk11 from "chalk";
|
|
5200
|
+
var GridExecutor = class {
|
|
5201
|
+
cfg;
|
|
5202
|
+
constructor(cfg) {
|
|
5203
|
+
this.cfg = cfg;
|
|
5204
|
+
}
|
|
5205
|
+
/**
|
|
5206
|
+
* Execute the order plan against Hyperliquid.
|
|
5207
|
+
* Cancels stale orders for rebalanced tokens, then places new orders.
|
|
5208
|
+
*/
|
|
5209
|
+
async execute(plan) {
|
|
5210
|
+
const errors = [];
|
|
5211
|
+
let cancelled = 0;
|
|
5212
|
+
let placed = 0;
|
|
5213
|
+
for (const token of plan.assetsToCancel) {
|
|
5214
|
+
const coin = resolveHLCoin(token);
|
|
5215
|
+
if (!coin) {
|
|
5216
|
+
errors.push(`No HL ticker for ${token}`);
|
|
5217
|
+
continue;
|
|
5218
|
+
}
|
|
5219
|
+
try {
|
|
5220
|
+
await hlCancelAllOrders(coin);
|
|
5221
|
+
cancelled++;
|
|
5222
|
+
console.error(chalk11.dim(` [grid-exec] Cancelled all orders for ${coin}`));
|
|
5223
|
+
} catch (e) {
|
|
5224
|
+
errors.push(`Cancel ${coin} failed: ${e.message}`);
|
|
5225
|
+
}
|
|
5226
|
+
}
|
|
5227
|
+
for (const order of plan.ordersToPlace) {
|
|
5228
|
+
const coin = resolveHLCoin(order.token);
|
|
5229
|
+
if (!coin) {
|
|
5230
|
+
errors.push(`No HL ticker for ${order.token}`);
|
|
5231
|
+
continue;
|
|
5232
|
+
}
|
|
5233
|
+
try {
|
|
5234
|
+
const res = await hlPlaceLimitOrder(coin, order.isBuy, order.quantity, order.price);
|
|
5235
|
+
if (res.success) {
|
|
5236
|
+
placed++;
|
|
5237
|
+
} else {
|
|
5238
|
+
errors.push(`Place ${coin} ${order.isBuy ? "buy" : "sell"} @${order.price}: ${res.error}`);
|
|
5239
|
+
}
|
|
5240
|
+
} catch (e) {
|
|
5241
|
+
errors.push(`Place ${coin} threw: ${e.message}`);
|
|
5242
|
+
}
|
|
5243
|
+
}
|
|
5244
|
+
return { placed, cancelled, errors };
|
|
5245
|
+
}
|
|
5246
|
+
};
|
|
5247
|
+
|
|
5248
|
+
// src/grid/onchain-executor.ts
|
|
5249
|
+
import chalk12 from "chalk";
|
|
5250
|
+
import { mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "fs/promises";
|
|
5251
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
5252
|
+
import { homedir as homedir3 } from "os";
|
|
5253
|
+
import { encodeAbiParameters as encodeAbiParameters10, parseUnits as parseUnits6 } from "viem";
|
|
5254
|
+
|
|
5255
|
+
// src/grid/cloid.ts
|
|
5256
|
+
import { keccak256, encodeAbiParameters as encodeAbiParameters9 } from "viem";
|
|
5257
|
+
function gridCloid(strategy2, assetIndex, isBuy, levelIndex, nonce) {
|
|
5258
|
+
const hash = keccak256(
|
|
5259
|
+
encodeAbiParameters9(
|
|
5260
|
+
[
|
|
5261
|
+
{ type: "address" },
|
|
5262
|
+
{ type: "uint32" },
|
|
5263
|
+
{ type: "bool" },
|
|
5264
|
+
{ type: "uint32" },
|
|
5265
|
+
{ type: "uint32" }
|
|
5266
|
+
],
|
|
5267
|
+
[strategy2, assetIndex, isBuy, levelIndex, nonce]
|
|
5268
|
+
)
|
|
5269
|
+
);
|
|
5270
|
+
const top16 = hash.slice(0, 2 + 32);
|
|
5271
|
+
return BigInt(top16);
|
|
5272
|
+
}
|
|
5273
|
+
|
|
5274
|
+
// src/grid/strategy-abi.ts
|
|
5275
|
+
var HYPERLIQUID_GRID_STRATEGY_ABI = [
|
|
5276
|
+
{
|
|
5277
|
+
type: "function",
|
|
5278
|
+
name: "updateParams",
|
|
5279
|
+
inputs: [{ name: "data", type: "bytes" }],
|
|
5280
|
+
outputs: [],
|
|
5281
|
+
stateMutability: "nonpayable"
|
|
5282
|
+
}
|
|
5283
|
+
];
|
|
5284
|
+
|
|
5285
|
+
// src/grid/onchain-executor.ts
|
|
5286
|
+
var ONCHAIN_STATE_PATH = join3(homedir3(), ".sherwood", "grid", "onchain-state.json");
|
|
5287
|
+
var UINT64_MAX = 18446744073709551615n;
|
|
5288
|
+
var ACTION_PLACE_GRID = 1;
|
|
5289
|
+
var ACTION_CANCEL_ALL = 2;
|
|
5290
|
+
var ACTION_CANCEL_AND_PLACE = 3;
|
|
5291
|
+
var GRID_ORDER_COMPONENTS = [
|
|
5292
|
+
{ name: "assetIndex", type: "uint32" },
|
|
5293
|
+
{ name: "isBuy", type: "bool" },
|
|
5294
|
+
{ name: "limitPx", type: "uint64" },
|
|
5295
|
+
{ name: "sz", type: "uint64" },
|
|
5296
|
+
{ name: "cloid", type: "uint128" }
|
|
5297
|
+
];
|
|
5298
|
+
var OnchainGridExecutor = class {
|
|
5299
|
+
cfg;
|
|
5300
|
+
nonces = /* @__PURE__ */ new Map();
|
|
5301
|
+
placedCloids = /* @__PURE__ */ new Map();
|
|
5302
|
+
meta = null;
|
|
5303
|
+
busy = false;
|
|
5304
|
+
constructor(cfg) {
|
|
5305
|
+
this.cfg = cfg;
|
|
5306
|
+
}
|
|
5307
|
+
/** Load persisted state. Call once before the first execute(). */
|
|
5308
|
+
async load() {
|
|
5309
|
+
try {
|
|
5310
|
+
const raw = await readFile3(ONCHAIN_STATE_PATH, "utf-8");
|
|
5311
|
+
const state = JSON.parse(raw);
|
|
5312
|
+
for (const [tok, n] of Object.entries(state.nonces)) this.nonces.set(tok, n);
|
|
5313
|
+
for (const [tok, cloids] of Object.entries(state.placedCloids)) {
|
|
5314
|
+
this.placedCloids.set(tok, cloids.map((s) => BigInt(s)));
|
|
5315
|
+
}
|
|
5316
|
+
console.error(chalk12.dim(` [grid-onchain] Loaded state: ${this.nonces.size} tokens`));
|
|
5317
|
+
} catch (e) {
|
|
5318
|
+
const code = e.code;
|
|
5319
|
+
if (code === "ENOENT") {
|
|
5320
|
+
} else {
|
|
5321
|
+
console.error(
|
|
5322
|
+
chalk12.yellow(
|
|
5323
|
+
` [grid-onchain] Failed to load state (${e.message}) \u2014 starting fresh. Existing on-chain orders may be orphaned.`
|
|
5324
|
+
)
|
|
5325
|
+
);
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
if (!this.meta) this.meta = await hlGetMeta();
|
|
5329
|
+
}
|
|
5330
|
+
/**
|
|
5331
|
+
* Atomic save: write to .tmp, then POSIX-rename. A crash mid-write leaves
|
|
5332
|
+
* either the old file intact or the new file fully written — never a
|
|
5333
|
+
* partial JSON that would crash load() on restart.
|
|
5334
|
+
*/
|
|
5335
|
+
async save() {
|
|
5336
|
+
const state = {
|
|
5337
|
+
nonces: Object.fromEntries(this.nonces),
|
|
5338
|
+
placedCloids: Object.fromEntries(
|
|
5339
|
+
[...this.placedCloids].map(([tok, cloids]) => [tok, cloids.map((c) => c.toString())])
|
|
5340
|
+
)
|
|
5341
|
+
};
|
|
5342
|
+
await mkdir3(dirname3(ONCHAIN_STATE_PATH), { recursive: true });
|
|
5343
|
+
const tmp = ONCHAIN_STATE_PATH + ".tmp";
|
|
5344
|
+
await writeFile3(tmp, JSON.stringify(state, null, 2));
|
|
5345
|
+
await rename2(tmp, ONCHAIN_STATE_PATH);
|
|
5346
|
+
}
|
|
5347
|
+
/**
|
|
5348
|
+
* Execute the order plan: cancel stale orders for rebalanced tokens, then
|
|
5349
|
+
* place new orders. Each (token) is one tx. Persists state after each tx
|
|
5350
|
+
* so a crash mid-execution still leaves a recoverable on-disk record.
|
|
5351
|
+
*
|
|
5352
|
+
* Concurrency-guarded: a second concurrent call returns immediately rather
|
|
5353
|
+
* than racing tx submission against another in-flight execute().
|
|
5354
|
+
*/
|
|
5355
|
+
async execute(plan) {
|
|
5356
|
+
if (this.busy) {
|
|
5357
|
+
return { placed: 0, cancelled: 0, txs: [], errors: ["execute() already in flight \u2014 skipping"] };
|
|
5358
|
+
}
|
|
5359
|
+
this.busy = true;
|
|
5360
|
+
try {
|
|
5361
|
+
if (!this.meta) await this.load();
|
|
5362
|
+
const errors = [];
|
|
5363
|
+
const txs = [];
|
|
5364
|
+
let placed = 0;
|
|
5365
|
+
let cancelled = 0;
|
|
5366
|
+
const ordersByToken = /* @__PURE__ */ new Map();
|
|
5367
|
+
for (const o of plan.ordersToPlace) {
|
|
5368
|
+
if (!ordersByToken.has(o.token)) ordersByToken.set(o.token, []);
|
|
5369
|
+
ordersByToken.get(o.token).push(o);
|
|
5370
|
+
}
|
|
5371
|
+
const cancelSet = new Set(plan.assetsToCancel);
|
|
5372
|
+
const allTokens = /* @__PURE__ */ new Set([...ordersByToken.keys(), ...cancelSet]);
|
|
5373
|
+
for (const token of allTokens) {
|
|
5374
|
+
const assetIndex = this.cfg.assetIndices[token];
|
|
5375
|
+
if (assetIndex === void 0) {
|
|
5376
|
+
errors.push(`No asset index for ${token}`);
|
|
5377
|
+
continue;
|
|
5378
|
+
}
|
|
5379
|
+
const coin = resolveHLCoin(token);
|
|
5380
|
+
if (!coin) {
|
|
5381
|
+
errors.push(`No HL ticker for ${token}`);
|
|
5382
|
+
continue;
|
|
5383
|
+
}
|
|
5384
|
+
const meta = this.meta.get(coin);
|
|
5385
|
+
if (!meta) {
|
|
5386
|
+
errors.push(`No HL meta for ${coin} (run \`sherwood grid status\` to refresh)`);
|
|
5387
|
+
continue;
|
|
5388
|
+
}
|
|
5389
|
+
const wantCancel = cancelSet.has(token);
|
|
5390
|
+
const orders = ordersByToken.get(token) ?? [];
|
|
5391
|
+
const wantPlace = orders.length > 0;
|
|
5392
|
+
try {
|
|
5393
|
+
if (wantCancel && wantPlace) {
|
|
5394
|
+
const tx = await this.cancelAndPlace(token, assetIndex, orders, meta);
|
|
5395
|
+
txs.push(tx);
|
|
5396
|
+
cancelled += 1;
|
|
5397
|
+
placed += orders.length;
|
|
5398
|
+
} else if (wantCancel) {
|
|
5399
|
+
const tx = await this.cancelAll(token, assetIndex);
|
|
5400
|
+
txs.push(tx);
|
|
5401
|
+
cancelled += 1;
|
|
5402
|
+
} else if (wantPlace) {
|
|
5403
|
+
const tx = await this.placeGrid(token, assetIndex, orders, meta);
|
|
5404
|
+
txs.push(tx);
|
|
5405
|
+
placed += orders.length;
|
|
5406
|
+
}
|
|
5407
|
+
await this.save();
|
|
5408
|
+
} catch (e) {
|
|
5409
|
+
errors.push(`${token}: ${e.message}`);
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
return { placed, cancelled, txs, errors };
|
|
5413
|
+
} finally {
|
|
5414
|
+
this.busy = false;
|
|
5415
|
+
}
|
|
5416
|
+
}
|
|
5417
|
+
currentNonce(token) {
|
|
5418
|
+
return this.nonces.get(token) ?? 0;
|
|
5419
|
+
}
|
|
5420
|
+
bumpNonce(token) {
|
|
5421
|
+
const next = this.currentNonce(token) + 1;
|
|
5422
|
+
this.nonces.set(token, next);
|
|
5423
|
+
return next;
|
|
5424
|
+
}
|
|
5425
|
+
/**
|
|
5426
|
+
* Pure encoding — does NOT mutate placedCloids. Caller commits CLOIDs
|
|
5427
|
+
* only after the on-chain tx confirms successfully.
|
|
5428
|
+
*
|
|
5429
|
+
* Uses viem's `parseUnits` for decimal scaling instead of float math so
|
|
5430
|
+
* high-szDecimals tokens (BTC=5, ETH=4) keep full precision on small sizes.
|
|
5431
|
+
*/
|
|
5432
|
+
encodeOrders(assetIndex, orders, meta, nonce) {
|
|
5433
|
+
const cloids = [];
|
|
5434
|
+
const encoded = orders.map((o, i) => {
|
|
5435
|
+
const cloid = gridCloid(this.cfg.strategyAddress, assetIndex, o.isBuy, i, nonce);
|
|
5436
|
+
cloids.push(cloid);
|
|
5437
|
+
const limitPx = parseUnits6(o.price.toFixed(meta.pxDecimals), meta.pxDecimals);
|
|
5438
|
+
const sz = parseUnits6(o.quantity.toFixed(meta.szDecimals), meta.szDecimals);
|
|
5439
|
+
if (limitPx > UINT64_MAX || sz > UINT64_MAX) {
|
|
5440
|
+
throw new Error(
|
|
5441
|
+
`Order exceeds uint64: token=${o.token} px=${limitPx} sz=${sz}`
|
|
5442
|
+
);
|
|
5443
|
+
}
|
|
5444
|
+
return { assetIndex, isBuy: o.isBuy, limitPx, sz, cloid };
|
|
5445
|
+
});
|
|
5446
|
+
return { encoded, cloids };
|
|
5447
|
+
}
|
|
5448
|
+
async placeGrid(token, assetIndex, orders, meta) {
|
|
5449
|
+
const nonce = this.bumpNonce(token);
|
|
5450
|
+
const { encoded, cloids } = this.encodeOrders(assetIndex, orders, meta, nonce);
|
|
5451
|
+
const data = encodeAbiParameters10(
|
|
5452
|
+
[{ type: "uint8" }, { type: "tuple[]", components: [...GRID_ORDER_COMPONENTS] }],
|
|
5453
|
+
[ACTION_PLACE_GRID, encoded]
|
|
5454
|
+
);
|
|
5455
|
+
const tx = await this.send(data);
|
|
5456
|
+
this.placedCloids.set(token, cloids);
|
|
5457
|
+
return tx;
|
|
5458
|
+
}
|
|
5459
|
+
async cancelAll(token, assetIndex) {
|
|
5460
|
+
const cloids = this.placedCloids.get(token) ?? [];
|
|
5461
|
+
if (cloids.length === 0) {
|
|
5462
|
+
this.bumpNonce(token);
|
|
5463
|
+
return "0x";
|
|
5464
|
+
}
|
|
5465
|
+
const data = encodeAbiParameters10(
|
|
5466
|
+
[{ type: "uint8" }, { type: "uint32" }, { type: "uint128[]" }],
|
|
5467
|
+
[ACTION_CANCEL_ALL, assetIndex, cloids]
|
|
5468
|
+
);
|
|
5469
|
+
const tx = await this.send(data);
|
|
5470
|
+
this.placedCloids.set(token, []);
|
|
5471
|
+
this.bumpNonce(token);
|
|
5472
|
+
return tx;
|
|
5473
|
+
}
|
|
5474
|
+
async cancelAndPlace(token, assetIndex, orders, meta) {
|
|
5475
|
+
const oldCloids = this.placedCloids.get(token) ?? [];
|
|
5476
|
+
const newNonce = this.bumpNonce(token);
|
|
5477
|
+
const { encoded, cloids: newCloids } = this.encodeOrders(assetIndex, orders, meta, newNonce);
|
|
5478
|
+
const data = encodeAbiParameters10(
|
|
5479
|
+
[
|
|
5480
|
+
{ type: "uint8" },
|
|
5481
|
+
{ type: "uint32" },
|
|
5482
|
+
{ type: "uint128[]" },
|
|
5483
|
+
{ type: "tuple[]", components: [...GRID_ORDER_COMPONENTS] }
|
|
5484
|
+
],
|
|
5485
|
+
[ACTION_CANCEL_AND_PLACE, assetIndex, oldCloids, encoded]
|
|
5486
|
+
);
|
|
5487
|
+
const tx = await this.send(data);
|
|
5488
|
+
this.placedCloids.set(token, newCloids);
|
|
5489
|
+
return tx;
|
|
5490
|
+
}
|
|
5491
|
+
/**
|
|
5492
|
+
* Submit and wait for receipt. Throwing here surfaces to the per-token
|
|
5493
|
+
* try/catch in execute(), recording the error and skipping the save() so
|
|
5494
|
+
* placedCloids never reflects orders that didn't land.
|
|
5495
|
+
*/
|
|
5496
|
+
async send(data) {
|
|
5497
|
+
const tx = await writeContractWithRetry({
|
|
5498
|
+
address: this.cfg.strategyAddress,
|
|
5499
|
+
abi: HYPERLIQUID_GRID_STRATEGY_ABI,
|
|
5500
|
+
functionName: "updateParams",
|
|
5501
|
+
args: [data]
|
|
5502
|
+
});
|
|
5503
|
+
const pub = getPublicClient();
|
|
5504
|
+
const receipt = await pub.waitForTransactionReceipt({ hash: tx, timeout: 3e4 });
|
|
5505
|
+
if (receipt.status !== "success") {
|
|
5506
|
+
throw new Error(`Tx ${tx} reverted on-chain`);
|
|
5507
|
+
}
|
|
5508
|
+
console.error(chalk12.dim(` [grid-onchain] tx ${tx.slice(0, 10)}... confirmed`));
|
|
5509
|
+
return tx;
|
|
5510
|
+
}
|
|
5511
|
+
};
|
|
5512
|
+
|
|
5513
|
+
// src/grid/loop.ts
|
|
5514
|
+
var GRID_CYCLES_PATH = join4(homedir4(), ".sherwood", "grid", "cycles.jsonl");
|
|
5515
|
+
var GridLoop = class {
|
|
5516
|
+
cfg;
|
|
5517
|
+
gridConfig;
|
|
5518
|
+
manager;
|
|
5519
|
+
hedge;
|
|
5520
|
+
hl;
|
|
5521
|
+
executor = null;
|
|
5522
|
+
running = false;
|
|
5523
|
+
cycleCount = 0;
|
|
5524
|
+
timer = null;
|
|
5525
|
+
constructor(cfg) {
|
|
5526
|
+
this.cfg = cfg;
|
|
5527
|
+
this.gridConfig = { ...DEFAULT_GRID_CONFIG, ...cfg.config };
|
|
5528
|
+
this.manager = new GridManager(this.gridConfig);
|
|
5529
|
+
this.hedge = new GridHedgeManager();
|
|
5530
|
+
this.hl = new HyperliquidProvider();
|
|
5531
|
+
if (cfg.live) {
|
|
5532
|
+
if (!cfg.assetIndices) {
|
|
5533
|
+
throw new Error("assetIndices required when live=true");
|
|
5534
|
+
}
|
|
5535
|
+
if (cfg.strategyAddress) {
|
|
5536
|
+
this.executor = new OnchainGridExecutor({
|
|
5537
|
+
strategyAddress: cfg.strategyAddress,
|
|
5538
|
+
assetIndices: cfg.assetIndices
|
|
5539
|
+
});
|
|
5540
|
+
} else {
|
|
5541
|
+
this.executor = new GridExecutor({ assetIndices: cfg.assetIndices });
|
|
5542
|
+
}
|
|
5543
|
+
}
|
|
5544
|
+
}
|
|
5545
|
+
/** Start the grid loop. Resolves when shut down via SIGINT/SIGTERM. */
|
|
5546
|
+
async start() {
|
|
5547
|
+
this.running = true;
|
|
5548
|
+
const shutdown = () => {
|
|
5549
|
+
console.error(chalk13.yellow("\n [grid-loop] Shutting down\u2026"));
|
|
5550
|
+
this.running = false;
|
|
5551
|
+
if (this.timer) {
|
|
5552
|
+
clearTimeout(this.timer);
|
|
5553
|
+
this.timer = null;
|
|
5554
|
+
}
|
|
5555
|
+
};
|
|
5556
|
+
process.on("SIGINT", shutdown);
|
|
5557
|
+
process.on("SIGTERM", shutdown);
|
|
5558
|
+
await this.manager.init(this.cfg.capital);
|
|
5559
|
+
if (this.executor instanceof OnchainGridExecutor) {
|
|
5560
|
+
await this.executor.load();
|
|
5561
|
+
}
|
|
5562
|
+
console.error(chalk13.cyan(
|
|
5563
|
+
`
|
|
5564
|
+
[grid-loop] Started \u2014 capital=$${this.cfg.capital.toFixed(0)} cycle=${(this.cfg.cycle / 1e3).toFixed(0)}s tokens=[${this.gridConfig.tokens.join(", ")}] leverage=${this.gridConfig.leverage}x levels=${this.gridConfig.levelsPerSide}/side
|
|
5565
|
+
`
|
|
5566
|
+
));
|
|
5567
|
+
while (this.running) {
|
|
5568
|
+
try {
|
|
5569
|
+
await this.tick();
|
|
5570
|
+
} catch (err) {
|
|
5571
|
+
console.error(chalk13.red(` [grid-loop] Tick error: ${err.message}`));
|
|
5572
|
+
}
|
|
5573
|
+
if (this.running) {
|
|
5574
|
+
await new Promise((resolve2) => {
|
|
5575
|
+
this.timer = setTimeout(resolve2, this.cfg.cycle);
|
|
5576
|
+
});
|
|
5577
|
+
}
|
|
5578
|
+
}
|
|
5579
|
+
process.off("SIGINT", shutdown);
|
|
5580
|
+
process.off("SIGTERM", shutdown);
|
|
5581
|
+
console.error(chalk13.yellow(" [grid-loop] Stopped."));
|
|
5582
|
+
}
|
|
5583
|
+
/** Execute one grid cycle. */
|
|
5584
|
+
async tick() {
|
|
5585
|
+
this.cycleCount++;
|
|
5586
|
+
const start = Date.now();
|
|
5587
|
+
const prices = {};
|
|
5588
|
+
for (const token of this.gridConfig.tokens) {
|
|
5589
|
+
const data = await this.hl.getHyperliquidData(token);
|
|
5590
|
+
if (data?.markPrice && data.markPrice > 0) {
|
|
5591
|
+
prices[token] = data.markPrice;
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
if (Object.keys(prices).length === 0) {
|
|
5595
|
+
console.error(chalk13.dim(` [grid-loop] #${this.cycleCount} \u2014 no prices, skipping`));
|
|
5596
|
+
return;
|
|
5597
|
+
}
|
|
5598
|
+
const result = await this.manager.tick(prices);
|
|
5599
|
+
const elapsed = Date.now() - start;
|
|
5600
|
+
if (this.executor) {
|
|
5601
|
+
const plan = this.manager.computeOrders(prices);
|
|
5602
|
+
if (plan.ordersToPlace.length > 0 || plan.assetsToCancel.length > 0) {
|
|
5603
|
+
const res = await this.executor.execute(plan);
|
|
5604
|
+
if (res.errors.length > 0) {
|
|
5605
|
+
console.error(chalk13.yellow(` [grid-loop] Executor errors: ${res.errors.join("; ")}`));
|
|
5606
|
+
}
|
|
5607
|
+
if (res.placed > 0 || res.cancelled > 0) {
|
|
5608
|
+
console.error(chalk13.cyan(` [grid-loop] Live: placed=${res.placed} cancelled=${res.cancelled}`));
|
|
5609
|
+
}
|
|
5610
|
+
}
|
|
5611
|
+
}
|
|
5612
|
+
if (result.roundTrips > 0) {
|
|
5613
|
+
console.error(chalk13.green(
|
|
5614
|
+
` [grid-loop] #${this.cycleCount} \u2014 ${result.roundTrips} RT(s), +$${result.pnlUsd.toFixed(2)} PnL, ${result.fills} fill(s) [${elapsed}ms]`
|
|
5615
|
+
));
|
|
5616
|
+
}
|
|
5617
|
+
if (result.fills > 0 && result.roundTrips === 0) {
|
|
5618
|
+
console.error(chalk13.dim(
|
|
5619
|
+
` [grid-loop] #${this.cycleCount} \u2014 ${result.fills} fill(s), 0 RTs [${elapsed}ms]`
|
|
5620
|
+
));
|
|
5621
|
+
}
|
|
5622
|
+
const openExposure = this.manager.getOpenFillExposure();
|
|
5623
|
+
const hedgeResult = await this.hedge.tick(openExposure, prices);
|
|
5624
|
+
if (hedgeResult.adjustments > 0) {
|
|
5625
|
+
console.error(chalk13.magenta(
|
|
5626
|
+
` [hedge] ${hedgeResult.adjustments} adjustment(s), unrealized: $${hedgeResult.unrealizedPnl.toFixed(2)}, total realized: $${hedgeResult.totalRealizedPnl.toFixed(2)}`
|
|
5627
|
+
));
|
|
5628
|
+
}
|
|
5629
|
+
const stats = this.manager.getStats();
|
|
5630
|
+
const cycleEntry = {
|
|
5631
|
+
cycleNumber: this.cycleCount,
|
|
5632
|
+
timestamp: Date.now(),
|
|
5633
|
+
gridFills: result.fills,
|
|
5634
|
+
gridRoundTrips: result.roundTrips,
|
|
5635
|
+
gridPnlUsd: result.pnlUsd,
|
|
5636
|
+
totalPnlUsd: stats?.totalPnlUsd ?? 0,
|
|
5637
|
+
todayPnlUsd: stats?.todayPnlUsd ?? 0,
|
|
5638
|
+
totalRoundTrips: stats?.totalRoundTrips ?? 0,
|
|
5639
|
+
allocation: stats?.allocation ?? 0,
|
|
5640
|
+
paused: stats?.paused ?? false,
|
|
5641
|
+
hedgeUnrealizedPnl: hedgeResult.unrealizedPnl,
|
|
5642
|
+
hedgeTotalRealizedPnl: hedgeResult.totalRealizedPnl
|
|
5643
|
+
};
|
|
5644
|
+
try {
|
|
5645
|
+
await mkdir4(join4(homedir4(), ".sherwood", "grid"), { recursive: true });
|
|
5646
|
+
await appendFile(GRID_CYCLES_PATH, JSON.stringify(cycleEntry) + "\n");
|
|
5647
|
+
} catch {
|
|
5648
|
+
}
|
|
5649
|
+
if (this.cycleCount % 60 === 0) {
|
|
5650
|
+
const stats2 = this.manager.getStats();
|
|
5651
|
+
if (stats2) {
|
|
5652
|
+
const hedgeStatus = this.hedge.getStatus();
|
|
5653
|
+
const hedgeInfo = hedgeStatus && hedgeStatus.positions.length > 0 ? ` hedge=$${hedgeResult.unrealizedPnl.toFixed(2)}unr/$${hedgeStatus.totalRealizedPnl.toFixed(2)}real` : "";
|
|
5654
|
+
console.error(chalk13.cyan(
|
|
5655
|
+
` [grid-loop] Status #${this.cycleCount} \u2014 totalPnL=$${stats2.totalPnlUsd.toFixed(2)} todayPnL=$${stats2.todayPnlUsd.toFixed(2)} RTs=${stats2.totalRoundTrips} alloc=$${stats2.allocation.toFixed(0)}${hedgeInfo}${stats2.paused ? " (PAUSED)" : ""}`
|
|
5656
|
+
));
|
|
5657
|
+
}
|
|
5658
|
+
}
|
|
5659
|
+
}
|
|
5660
|
+
};
|
|
5661
|
+
|
|
5662
|
+
// src/commands/grid.ts
|
|
5663
|
+
var DIM5 = chalk14.gray;
|
|
5664
|
+
var G5 = chalk14.green;
|
|
5665
|
+
var BOLD5 = chalk14.white.bold;
|
|
5666
|
+
var W5 = chalk14.white;
|
|
5667
|
+
var SEP5 = () => console.log(DIM5("\u2500".repeat(60)));
|
|
5668
|
+
function registerGridCommand(program2) {
|
|
5669
|
+
const grid = program2.command("grid").description("Grid trading strategy \u2014 ATR-based grid on BTC/ETH/SOL");
|
|
5670
|
+
grid.command("start").description("Start the grid trading loop").option("--capital <usd>", "Starting capital in USD", "5000").option("--cycle <seconds>", "Cycle interval in seconds", "60").option("--tokens <list>", "Comma-separated token list", "bitcoin,ethereum,solana").option("--leverage <n>", "Leverage multiplier", "5").option("--levels <n>", "Grid levels per side", "15").option("--live", "enable live execution (places real orders on Hyperliquid)").option("--asset-indices <pairs>", "comma-separated token=index pairs (e.g. bitcoin=3,ethereum=4,solana=5)").option("--strategy <address>", "on-chain strategy contract address (enables on-chain executor)").action(async (opts) => {
|
|
5671
|
+
const capital = parseFloat(opts.capital);
|
|
5672
|
+
const cycleMs = parseInt(opts.cycle, 10) * 1e3;
|
|
5673
|
+
const tokens = opts.tokens.split(",").map((t) => t.trim());
|
|
5674
|
+
const leverage = parseFloat(opts.leverage);
|
|
5675
|
+
const levels = parseInt(opts.levels, 10);
|
|
5676
|
+
const live = !!opts.live;
|
|
5677
|
+
let assetIndices;
|
|
5678
|
+
if (live) {
|
|
5679
|
+
if (!opts.assetIndices) {
|
|
5680
|
+
throw new Error("--asset-indices required when --live (e.g. --asset-indices bitcoin=3,ethereum=4,solana=5)");
|
|
5681
|
+
}
|
|
5682
|
+
assetIndices = {};
|
|
5683
|
+
for (const pair of opts.assetIndices.split(",")) {
|
|
5684
|
+
const [tok, idx] = pair.split("=");
|
|
5685
|
+
if (!tok || !idx) throw new Error(`Bad asset-indices pair: ${pair}`);
|
|
5686
|
+
assetIndices[tok.trim()] = Number(idx);
|
|
5687
|
+
}
|
|
5688
|
+
}
|
|
5689
|
+
const strategyAddress = opts.strategy;
|
|
5690
|
+
if (strategyAddress && !live) {
|
|
5691
|
+
throw new Error("--strategy requires --live");
|
|
5692
|
+
}
|
|
5693
|
+
const weight = 1 / tokens.length;
|
|
5694
|
+
const tokenSplit = {};
|
|
5695
|
+
for (const t of tokens) {
|
|
5696
|
+
tokenSplit[t] = weight;
|
|
5697
|
+
}
|
|
5698
|
+
console.log();
|
|
5699
|
+
console.log(G5.bold(" Grid Strategy"));
|
|
5700
|
+
SEP5();
|
|
5701
|
+
console.log(W5(` Capital: $${capital.toLocaleString()}`));
|
|
5702
|
+
console.log(W5(` Cycle: ${opts.cycle}s`));
|
|
5703
|
+
console.log(W5(` Tokens: ${tokens.join(", ")}`));
|
|
5704
|
+
console.log(W5(` Leverage: ${leverage}x`));
|
|
5705
|
+
console.log(W5(` Levels: ${levels}/side`));
|
|
5706
|
+
SEP5();
|
|
5707
|
+
const loop = new GridLoop({
|
|
5708
|
+
capital,
|
|
5709
|
+
cycle: cycleMs,
|
|
5710
|
+
live,
|
|
5711
|
+
assetIndices,
|
|
5712
|
+
strategyAddress,
|
|
5713
|
+
config: {
|
|
5714
|
+
...DEFAULT_GRID_CONFIG,
|
|
5715
|
+
tokens,
|
|
5716
|
+
leverage,
|
|
5717
|
+
levelsPerSide: levels,
|
|
5718
|
+
tokenSplit
|
|
5719
|
+
}
|
|
5720
|
+
});
|
|
5721
|
+
await loop.start();
|
|
5722
|
+
});
|
|
5723
|
+
grid.command("status").description("Show current grid portfolio status").action(async () => {
|
|
5724
|
+
const portfolio = new GridPortfolio();
|
|
5725
|
+
const state = await portfolio.load();
|
|
5726
|
+
if (!state) {
|
|
5727
|
+
console.log(DIM5("\n No grid portfolio found. Run `sherwood grid start` first.\n"));
|
|
5728
|
+
return;
|
|
5729
|
+
}
|
|
5730
|
+
portfolio.resetDailyStats(state);
|
|
5731
|
+
const agg = portfolio.aggregateStats(state);
|
|
5732
|
+
console.log();
|
|
5733
|
+
console.log(G5.bold(" Grid Portfolio"));
|
|
5734
|
+
SEP5();
|
|
5735
|
+
console.log(W5(` Allocation: $${state.totalAllocation.toLocaleString()}`));
|
|
5736
|
+
console.log(W5(` Status: ${state.paused ? chalk14.red("PAUSED \u2014 " + state.pauseReason) : G5("Active")}`));
|
|
5737
|
+
console.log(W5(` Initialized: ${new Date(state.initializedAt).toLocaleString()}`));
|
|
5738
|
+
SEP5();
|
|
5739
|
+
console.log();
|
|
5740
|
+
console.log(BOLD5(" Token Alloc RTs PnL Today PnL Fills"));
|
|
5741
|
+
console.log(DIM5(" " + "\u2500".repeat(72)));
|
|
5742
|
+
for (const g of state.grids) {
|
|
5743
|
+
const name = g.token.padEnd(13);
|
|
5744
|
+
const alloc = `$${g.allocation.toFixed(0)}`.padStart(8);
|
|
5745
|
+
const rts = String(g.stats.totalRoundTrips).padStart(6);
|
|
5746
|
+
const pnl = `$${g.stats.totalPnlUsd.toFixed(2)}`.padStart(10);
|
|
5747
|
+
const todayPnl = `$${g.stats.todayPnlUsd.toFixed(2)}`.padStart(10);
|
|
5748
|
+
const fills = String(g.stats.totalFills).padStart(6);
|
|
5749
|
+
const pnlColor = g.stats.totalPnlUsd >= 0 ? G5 : chalk14.red;
|
|
5750
|
+
const todayColor = g.stats.todayPnlUsd >= 0 ? G5 : chalk14.red;
|
|
5751
|
+
console.log(` ${W5(name)} ${alloc} ${rts} ${pnlColor(pnl)} ${todayColor(todayPnl)} ${fills}`);
|
|
5752
|
+
}
|
|
5753
|
+
console.log(DIM5(" " + "\u2500".repeat(72)));
|
|
5754
|
+
const totalPnlColor = agg.totalPnlUsd >= 0 ? G5 : chalk14.red;
|
|
5755
|
+
const todayTotalColor = agg.todayPnlUsd >= 0 ? G5 : chalk14.red;
|
|
5756
|
+
console.log(` ${BOLD5("TOTAL".padEnd(13))} ${`$${state.totalAllocation.toFixed(0)}`.padStart(8)} ${String(agg.totalRoundTrips).padStart(6)} ${totalPnlColor(`$${agg.totalPnlUsd.toFixed(2)}`.padStart(10))} ${todayTotalColor(`$${agg.todayPnlUsd.toFixed(2)}`.padStart(10))} ${String(agg.todayFills).padStart(6)}`);
|
|
5757
|
+
console.log();
|
|
5758
|
+
});
|
|
5759
|
+
}
|
|
5760
|
+
|
|
4613
5761
|
// src/index.ts
|
|
4614
5762
|
try {
|
|
4615
5763
|
loadDotenv();
|
|
@@ -4618,20 +5766,20 @@ try {
|
|
|
4618
5766
|
var require2 = createRequire(import.meta.url);
|
|
4619
5767
|
var { version: CLI_VERSION } = require2("../package.json");
|
|
4620
5768
|
async function loadXmtp() {
|
|
4621
|
-
return import("./xmtp-
|
|
5769
|
+
return import("./xmtp-ACB2W573.js");
|
|
4622
5770
|
}
|
|
4623
5771
|
async function loadCron() {
|
|
4624
5772
|
return import("./cron-EOULYO3R.js");
|
|
4625
5773
|
}
|
|
4626
|
-
var
|
|
4627
|
-
var
|
|
4628
|
-
var
|
|
4629
|
-
var
|
|
4630
|
-
var LABEL5 =
|
|
4631
|
-
var
|
|
5774
|
+
var G6 = chalk15.green;
|
|
5775
|
+
var W6 = chalk15.white;
|
|
5776
|
+
var DIM6 = chalk15.gray;
|
|
5777
|
+
var BOLD6 = chalk15.white.bold;
|
|
5778
|
+
var LABEL5 = chalk15.green.bold;
|
|
5779
|
+
var SEP6 = () => console.log(DIM6("\u2500".repeat(60)));
|
|
4632
5780
|
function validateAddress(value, name) {
|
|
4633
5781
|
if (!isAddress6(value)) {
|
|
4634
|
-
console.error(
|
|
5782
|
+
console.error(chalk15.red(`Invalid ${name} address: ${value}`));
|
|
4635
5783
|
process.exit(1);
|
|
4636
5784
|
}
|
|
4637
5785
|
return value;
|
|
@@ -4651,7 +5799,7 @@ program.name("sherwood").description("CLI for agent-managed investment syndicate
|
|
|
4651
5799
|
process.env.ENABLE_TESTNET = "true";
|
|
4652
5800
|
if (network !== "base") {
|
|
4653
5801
|
console.warn(
|
|
4654
|
-
|
|
5802
|
+
chalk15.yellow("[warn] --testnet ignored, --chain takes precedence")
|
|
4655
5803
|
);
|
|
4656
5804
|
} else {
|
|
4657
5805
|
network = "base-sepolia";
|
|
@@ -4659,7 +5807,7 @@ program.name("sherwood").description("CLI for agent-managed investment syndicate
|
|
|
4659
5807
|
}
|
|
4660
5808
|
setNetwork(network);
|
|
4661
5809
|
if (getNetwork() !== "base") {
|
|
4662
|
-
console.log(
|
|
5810
|
+
console.log(chalk15.yellow(`[${getNetwork()}]`));
|
|
4663
5811
|
}
|
|
4664
5812
|
});
|
|
4665
5813
|
var syndicate = program.command("syndicate");
|
|
@@ -4667,31 +5815,31 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
4667
5815
|
try {
|
|
4668
5816
|
console.log();
|
|
4669
5817
|
console.log(LABEL5(" \u25C6 Create Syndicate"));
|
|
4670
|
-
|
|
5818
|
+
SEP6();
|
|
4671
5819
|
const wallet = getAccount();
|
|
4672
|
-
console.log(
|
|
4673
|
-
console.log(
|
|
4674
|
-
|
|
5820
|
+
console.log(DIM6(` Wallet: ${wallet.address}`));
|
|
5821
|
+
console.log(DIM6(` Network: ${getChain().name}`));
|
|
5822
|
+
SEP6();
|
|
4675
5823
|
const savedAgentId = getAgentId();
|
|
4676
5824
|
const nonInteractive = opts.yes;
|
|
4677
5825
|
const name = opts.name || (nonInteractive ? (() => {
|
|
4678
5826
|
throw new Error("--name is required in non-interactive mode (-y)");
|
|
4679
5827
|
})() : await input({
|
|
4680
|
-
message:
|
|
5828
|
+
message: G6("Syndicate name"),
|
|
4681
5829
|
validate: (v) => v.length > 0 || "Name is required"
|
|
4682
5830
|
}));
|
|
4683
5831
|
const subdomain = opts.subdomain || (nonInteractive ? name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "") : await input({
|
|
4684
|
-
message:
|
|
5832
|
+
message: G6("ENS subdomain"),
|
|
4685
5833
|
default: name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, ""),
|
|
4686
5834
|
validate: (v) => v.length >= 3 || "Must be at least 3 characters"
|
|
4687
5835
|
}));
|
|
4688
5836
|
const description = opts.description || (nonInteractive ? `${name} \u2014 a Sherwood syndicate` : await input({
|
|
4689
|
-
message:
|
|
5837
|
+
message: G6("Description"),
|
|
4690
5838
|
default: `${name} \u2014 a Sherwood syndicate`
|
|
4691
5839
|
}));
|
|
4692
|
-
const agentIdStr = opts.agentId || (savedAgentId ? nonInteractive ? String(savedAgentId) : await input({ message:
|
|
5840
|
+
const agentIdStr = opts.agentId || (savedAgentId ? nonInteractive ? String(savedAgentId) : await input({ message: G6("Agent ID (ERC-8004)"), default: String(savedAgentId) }) : nonInteractive ? "0" : await input({ message: G6("Agent ID (ERC-8004)"), validate: (v) => /^\d+$/.test(v) || "Must be a number" }));
|
|
4693
5841
|
const openDeposits = opts.openDeposits !== void 0 ? opts.openDeposits : nonInteractive ? true : await confirm({
|
|
4694
|
-
message:
|
|
5842
|
+
message: G6("Open deposits? (anyone can deposit)"),
|
|
4695
5843
|
default: true
|
|
4696
5844
|
});
|
|
4697
5845
|
const ASSET_SYMBOLS = {
|
|
@@ -4707,14 +5855,14 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
4707
5855
|
asset = opts.asset;
|
|
4708
5856
|
} else {
|
|
4709
5857
|
const supported = Object.keys(ASSET_SYMBOLS).join(", ");
|
|
4710
|
-
console.error(
|
|
5858
|
+
console.error(chalk15.red(` Unknown asset "${opts.asset}". Use a symbol (${supported}) or a 0x address.`));
|
|
4711
5859
|
process.exit(1);
|
|
4712
5860
|
}
|
|
4713
5861
|
} else if (nonInteractive) {
|
|
4714
5862
|
asset = TOKENS().USDC !== "0x0000000000000000000000000000000000000000" ? ASSET_SYMBOLS.USDC : ASSET_SYMBOLS.WETH;
|
|
4715
5863
|
} else {
|
|
4716
5864
|
const assetChoice = await select({
|
|
4717
|
-
message:
|
|
5865
|
+
message: G6("Vault asset (what token do depositors provide?)"),
|
|
4718
5866
|
choices: [
|
|
4719
5867
|
{ name: "USDC", value: "USDC", description: "USD Coin (6 decimals)" },
|
|
4720
5868
|
{ name: "WETH", value: "WETH", description: "Wrapped Ether (18 decimals)" }
|
|
@@ -4730,25 +5878,25 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
4730
5878
|
const symbol = `sw${assetSymbol}`;
|
|
4731
5879
|
console.log();
|
|
4732
5880
|
console.log(LABEL5(" \u25C6 Review"));
|
|
4733
|
-
|
|
4734
|
-
console.log(
|
|
4735
|
-
console.log(
|
|
4736
|
-
console.log(
|
|
4737
|
-
console.log(
|
|
4738
|
-
console.log(
|
|
4739
|
-
console.log(
|
|
4740
|
-
console.log(
|
|
4741
|
-
|
|
5881
|
+
SEP6();
|
|
5882
|
+
console.log(W6(` Name: ${BOLD6(name)}`));
|
|
5883
|
+
console.log(W6(` ENS: ${G6(`${subdomain}.sherwoodagent.eth`)}`));
|
|
5884
|
+
console.log(W6(` Description: ${DIM6(description)}`));
|
|
5885
|
+
console.log(W6(` Agent ID: #${agentIdStr}`));
|
|
5886
|
+
console.log(W6(` Asset: ${assetSymbol} (${asset.slice(0, 10)}...)`));
|
|
5887
|
+
console.log(W6(` Share token: ${symbol}`));
|
|
5888
|
+
console.log(W6(` Open deposits: ${openDeposits ? G6("yes") : chalk15.red("no (whitelist)")}`));
|
|
5889
|
+
SEP6();
|
|
4742
5890
|
if (!nonInteractive) {
|
|
4743
|
-
const go = await confirm({ message:
|
|
5891
|
+
const go = await confirm({ message: G6("Deploy syndicate?"), default: true });
|
|
4744
5892
|
if (!go) {
|
|
4745
|
-
console.log(
|
|
5893
|
+
console.log(DIM6(" Cancelled."));
|
|
4746
5894
|
return;
|
|
4747
5895
|
}
|
|
4748
5896
|
}
|
|
4749
5897
|
let metadataURI = opts.metadataUri || "";
|
|
4750
5898
|
if (!metadataURI) {
|
|
4751
|
-
const spinner2 = ora8({ text:
|
|
5899
|
+
const spinner2 = ora8({ text: W6("Uploading metadata to IPFS..."), color: "green" }).start();
|
|
4752
5900
|
try {
|
|
4753
5901
|
const metadata = {
|
|
4754
5902
|
schema: "sherwood/syndicate/v1",
|
|
@@ -4762,9 +5910,9 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
4762
5910
|
links: {}
|
|
4763
5911
|
};
|
|
4764
5912
|
metadataURI = await uploadMetadata(metadata);
|
|
4765
|
-
spinner2.succeed(
|
|
5913
|
+
spinner2.succeed(G6(`Metadata pinned: ${DIM6(metadataURI)}`));
|
|
4766
5914
|
} catch (err) {
|
|
4767
|
-
spinner2.warn(
|
|
5915
|
+
spinner2.warn(chalk15.yellow(`IPFS upload failed \u2014 using inline metadata`));
|
|
4768
5916
|
const json = JSON.stringify({ name, description, subdomain, asset: assetSymbol, openDeposits, createdBy: "@sherwoodagent/cli" });
|
|
4769
5917
|
metadataURI = `data:application/json;base64,${Buffer.from(json).toString("base64")}`;
|
|
4770
5918
|
}
|
|
@@ -4774,43 +5922,43 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
4774
5922
|
const existingInfo = await getSyndicate(existingId);
|
|
4775
5923
|
const callerAddress = getAccount().address.toLowerCase();
|
|
4776
5924
|
if (existingInfo.creator.toLowerCase() !== callerAddress) {
|
|
4777
|
-
console.error(
|
|
5925
|
+
console.error(chalk15.red(`
|
|
4778
5926
|
\u2716 Subdomain '${subdomain}' is already taken by another syndicate.
|
|
4779
5927
|
`));
|
|
4780
5928
|
process.exit(1);
|
|
4781
5929
|
}
|
|
4782
|
-
console.log(
|
|
5930
|
+
console.log(chalk15.yellow(`
|
|
4783
5931
|
\u26A0 Syndicate '${subdomain}' already exists \u2014 resuming setup...
|
|
4784
5932
|
`));
|
|
4785
|
-
const spinner2 = ora8({ text:
|
|
5933
|
+
const spinner2 = ora8({ text: W6("Resuming setup..."), color: "green" }).start();
|
|
4786
5934
|
const chainId2 = getChain().id;
|
|
4787
5935
|
setChainContract(chainId2, "vault", existingInfo.vault);
|
|
4788
5936
|
addSyndicate(chainId2, { subdomain, vault: existingInfo.vault, role: "creator" });
|
|
4789
5937
|
setPrimarySyndicate(chainId2, subdomain);
|
|
4790
|
-
spinner2.text =
|
|
5938
|
+
spinner2.text = W6("Checking agent registration...");
|
|
4791
5939
|
try {
|
|
4792
5940
|
setVaultAddress(existingInfo.vault);
|
|
4793
5941
|
const isAlreadyAgent = await isAgent(getAccount().address);
|
|
4794
5942
|
if (!isAlreadyAgent) {
|
|
4795
|
-
spinner2.text =
|
|
5943
|
+
spinner2.text = W6("Registering creator as agent...");
|
|
4796
5944
|
await registerAgent(BigInt(agentIdStr), getAccount().address);
|
|
4797
5945
|
}
|
|
4798
5946
|
} catch {
|
|
4799
5947
|
}
|
|
4800
|
-
spinner2.text =
|
|
5948
|
+
spinner2.text = W6("Checking chat group...");
|
|
4801
5949
|
try {
|
|
4802
5950
|
const xmtp = await loadXmtp();
|
|
4803
5951
|
const xmtpClient = await xmtp.getXmtpClient();
|
|
4804
5952
|
try {
|
|
4805
5953
|
await xmtp.getGroup(xmtpClient, subdomain);
|
|
4806
5954
|
} catch {
|
|
4807
|
-
spinner2.text =
|
|
5955
|
+
spinner2.text = W6("Creating chat group...");
|
|
4808
5956
|
const groupId = await xmtp.createSyndicateGroup(xmtpClient, subdomain, opts.publicChat);
|
|
4809
5957
|
await setTextRecord(subdomain, "xmtpGroupId", groupId, existingInfo.vault);
|
|
4810
5958
|
cacheGroupId(subdomain, groupId);
|
|
4811
5959
|
}
|
|
4812
5960
|
} catch {
|
|
4813
|
-
console.warn(
|
|
5961
|
+
console.warn(chalk15.yellow("\n \u26A0 Could not set up XMTP chat group"));
|
|
4814
5962
|
}
|
|
4815
5963
|
try {
|
|
4816
5964
|
const cron = await loadCron();
|
|
@@ -4820,19 +5968,19 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
4820
5968
|
spinner2.stop();
|
|
4821
5969
|
console.log();
|
|
4822
5970
|
console.log(LABEL5(" \u25C6 Syndicate Setup Resumed"));
|
|
4823
|
-
|
|
4824
|
-
console.log(
|
|
4825
|
-
console.log(
|
|
4826
|
-
console.log(
|
|
4827
|
-
console.log(
|
|
5971
|
+
SEP6();
|
|
5972
|
+
console.log(W6(` ID: ${G6(`#${existingId}`)}`));
|
|
5973
|
+
console.log(W6(` Vault: ${G6(existingInfo.vault)}`));
|
|
5974
|
+
console.log(W6(` ENS: ${G6(`${subdomain}.sherwoodagent.eth`)}`));
|
|
5975
|
+
console.log(W6(` Chat: ${DIM6(`sherwood chat ${subdomain}`)}`));
|
|
4828
5976
|
const refParam2 = getAgentId() ? `?ref=${getAgentId()}` : "";
|
|
4829
|
-
console.log(
|
|
4830
|
-
|
|
4831
|
-
console.log(
|
|
5977
|
+
console.log(W6(` Share: ${G6(`https://sherwood.sh/syndicate/${subdomain}${refParam2}`)}`));
|
|
5978
|
+
SEP6();
|
|
5979
|
+
console.log(G6(" \u2713 Syndicate saved to ~/.sherwood/config.json (set as primary)"));
|
|
4832
5980
|
console.log();
|
|
4833
5981
|
return;
|
|
4834
5982
|
}
|
|
4835
|
-
const spinner = ora8({ text:
|
|
5983
|
+
const spinner = ora8({ text: W6("Deploying vault via factory..."), color: "green" }).start();
|
|
4836
5984
|
const result = await createSyndicate({
|
|
4837
5985
|
creatorAgentId: BigInt(agentIdStr),
|
|
4838
5986
|
metadataURI,
|
|
@@ -4847,7 +5995,7 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
4847
5995
|
addSyndicate(chainId, { subdomain, vault: result.vault, role: "creator" });
|
|
4848
5996
|
setPrimarySyndicate(chainId, subdomain);
|
|
4849
5997
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
4850
|
-
spinner.text =
|
|
5998
|
+
spinner.text = W6("Registering creator as agent...");
|
|
4851
5999
|
try {
|
|
4852
6000
|
setVaultAddress(result.vault);
|
|
4853
6001
|
const creatorAddress = getAccount().address;
|
|
@@ -4857,9 +6005,9 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
4857
6005
|
// agentAddress = creator EOA (direct execution)
|
|
4858
6006
|
);
|
|
4859
6007
|
} catch (regErr) {
|
|
4860
|
-
console.warn(
|
|
6008
|
+
console.warn(chalk15.yellow("\n \u26A0 Could not auto-register creator as agent \u2014 register manually with `syndicate add`"));
|
|
4861
6009
|
}
|
|
4862
|
-
spinner.text =
|
|
6010
|
+
spinner.text = W6("Setting up chat...");
|
|
4863
6011
|
try {
|
|
4864
6012
|
const xmtp = await loadXmtp();
|
|
4865
6013
|
const xmtpClient = await xmtp.getXmtpClient();
|
|
@@ -4870,36 +6018,36 @@ syndicate.command("create").description("Create a new syndicate via the factory
|
|
|
4870
6018
|
} catch {
|
|
4871
6019
|
}
|
|
4872
6020
|
} catch {
|
|
4873
|
-
console.warn(
|
|
4874
|
-
console.warn(
|
|
6021
|
+
console.warn(chalk15.yellow("\n \u26A0 Could not create XMTP chat group"));
|
|
6022
|
+
console.warn(chalk15.dim(` Recover later with: sherwood chat ${subdomain} init`));
|
|
4875
6023
|
}
|
|
4876
6024
|
try {
|
|
4877
6025
|
const cron = await loadCron();
|
|
4878
6026
|
const cronResult = cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
|
|
4879
6027
|
if (cronResult.isOpenClaw && cronResult.registered) {
|
|
4880
|
-
console.log(
|
|
6028
|
+
console.log(G6(" \u2713 Participation crons registered (15m check + hourly summary)"));
|
|
4881
6029
|
} else if (!cronResult.isOpenClaw) {
|
|
4882
|
-
console.log(
|
|
6030
|
+
console.log(DIM6(" Tip: Set up a scheduled process to run `sherwood session check " + subdomain + "` periodically"));
|
|
4883
6031
|
}
|
|
4884
6032
|
} catch {
|
|
4885
6033
|
}
|
|
4886
6034
|
spinner.stop();
|
|
4887
6035
|
console.log();
|
|
4888
6036
|
console.log(LABEL5(" \u25C6 Syndicate Created"));
|
|
4889
|
-
|
|
4890
|
-
console.log(
|
|
4891
|
-
console.log(
|
|
4892
|
-
console.log(
|
|
4893
|
-
console.log(
|
|
4894
|
-
console.log(
|
|
4895
|
-
console.log(
|
|
6037
|
+
SEP6();
|
|
6038
|
+
console.log(W6(` ID: ${G6(`#${result.syndicateId}`)}`));
|
|
6039
|
+
console.log(W6(` Vault: ${G6(result.vault)}`));
|
|
6040
|
+
console.log(W6(` ENS: ${G6(`${subdomain}.sherwoodagent.eth`)}`));
|
|
6041
|
+
console.log(W6(` Metadata: ${DIM6(metadataURI.length > 50 ? metadataURI.slice(0, 50) + "..." : metadataURI)}`));
|
|
6042
|
+
console.log(W6(` Explorer: ${DIM6(getExplorerUrl(result.hash))}`));
|
|
6043
|
+
console.log(W6(` Chat: ${DIM6(`sherwood chat ${subdomain}`)}`));
|
|
4896
6044
|
const refParam = getAgentId() ? `?ref=${getAgentId()}` : "";
|
|
4897
|
-
console.log(
|
|
4898
|
-
|
|
4899
|
-
console.log(
|
|
6045
|
+
console.log(W6(` Share: ${G6(`https://sherwood.sh/syndicate/${subdomain}${refParam}`)}`));
|
|
6046
|
+
SEP6();
|
|
6047
|
+
console.log(G6(" \u2713 Syndicate saved to ~/.sherwood/config.json (set as primary)"));
|
|
4900
6048
|
console.log();
|
|
4901
6049
|
} catch (err) {
|
|
4902
|
-
console.error(
|
|
6050
|
+
console.error(chalk15.red(`
|
|
4903
6051
|
\u2716 ${formatContractError(err)}`));
|
|
4904
6052
|
process.exit(1);
|
|
4905
6053
|
}
|
|
@@ -4949,38 +6097,38 @@ syndicate.command("list").description("List active syndicates (queries subgraph,
|
|
|
4949
6097
|
}
|
|
4950
6098
|
spinner.stop();
|
|
4951
6099
|
if (syndicates.length === 0) {
|
|
4952
|
-
console.log(
|
|
6100
|
+
console.log(chalk15.dim("No active syndicates found."));
|
|
4953
6101
|
return;
|
|
4954
6102
|
}
|
|
4955
6103
|
console.log();
|
|
4956
|
-
console.log(
|
|
6104
|
+
console.log(chalk15.bold(`Active Syndicates (${syndicates.length})`));
|
|
4957
6105
|
if (!process.env.SUBGRAPH_URL && !opts.allChains) {
|
|
4958
|
-
console.log(
|
|
6106
|
+
console.log(chalk15.dim(" (Set SUBGRAPH_URL for faster indexed queries)"));
|
|
4959
6107
|
}
|
|
4960
6108
|
if (opts.allChains) {
|
|
4961
|
-
console.log(
|
|
6109
|
+
console.log(chalk15.dim(" (Aggregated across chains via on-chain factory calls; SUBGRAPH_URL is single-chain only)"));
|
|
4962
6110
|
}
|
|
4963
|
-
console.log(
|
|
6111
|
+
console.log(chalk15.dim("\u2500".repeat(70)));
|
|
4964
6112
|
for (const s of syndicates) {
|
|
4965
6113
|
const ts = typeof s.createdAt === "string" ? Number(s.createdAt) : Number(s.createdAt);
|
|
4966
6114
|
const date = new Date(ts * 1e3).toLocaleDateString();
|
|
4967
6115
|
const ensName = s.subdomain ? `${s.subdomain}.sherwoodagent.eth` : "";
|
|
4968
|
-
const chainTag = s.chain ?
|
|
4969
|
-
console.log(` ${chainTag}#${s.id} ${
|
|
4970
|
-
if (ensName) console.log(` Vault: ${
|
|
6116
|
+
const chainTag = s.chain ? chalk15.dim(`[${s.chain}] `) : "";
|
|
6117
|
+
console.log(` ${chainTag}#${s.id} ${chalk15.bold(ensName || String(s.vault))}`);
|
|
6118
|
+
if (ensName) console.log(` Vault: ${chalk15.cyan(String(s.vault))}`);
|
|
4971
6119
|
console.log(` Creator: ${s.creator}`);
|
|
4972
6120
|
console.log(` Created: ${date}`);
|
|
4973
6121
|
if (s.totalDeposits) {
|
|
4974
6122
|
console.log(` Deposits: ${s.totalDeposits} USDC`);
|
|
4975
6123
|
}
|
|
4976
6124
|
if (s.metadataURI) {
|
|
4977
|
-
console.log(` Metadata: ${
|
|
6125
|
+
console.log(` Metadata: ${chalk15.dim(s.metadataURI)}`);
|
|
4978
6126
|
}
|
|
4979
6127
|
console.log();
|
|
4980
6128
|
}
|
|
4981
6129
|
} catch (err) {
|
|
4982
6130
|
spinner.fail("Failed to load syndicates");
|
|
4983
|
-
console.error(
|
|
6131
|
+
console.error(chalk15.red(formatContractError(err)));
|
|
4984
6132
|
process.exit(1);
|
|
4985
6133
|
}
|
|
4986
6134
|
});
|
|
@@ -5020,38 +6168,38 @@ syndicate.command("info").description("Display syndicate details by ID or subdom
|
|
|
5020
6168
|
spinner.stop();
|
|
5021
6169
|
if (crossChainHint) {
|
|
5022
6170
|
const others = crossChainHint.others.length > 0 ? ` (also on: ${crossChainHint.others.join(", ")})` : "";
|
|
5023
|
-
console.log(
|
|
5024
|
-
console.log(
|
|
6171
|
+
console.log(chalk15.yellow(` Found on chain: ${chalk15.bold(crossChainHint.network)}${others}`));
|
|
6172
|
+
console.log(chalk15.dim(` Re-run with --chain ${crossChainHint.network} to interact with this syndicate.`));
|
|
5025
6173
|
console.log();
|
|
5026
6174
|
}
|
|
5027
6175
|
if (!info.vault || info.vault === "0x0000000000000000000000000000000000000000") {
|
|
5028
|
-
console.log(
|
|
6176
|
+
console.log(chalk15.red(`Syndicate #${id} not found.`));
|
|
5029
6177
|
process.exit(1);
|
|
5030
6178
|
}
|
|
5031
6179
|
const date = new Date(Number(info.createdAt) * 1e3).toLocaleDateString();
|
|
5032
6180
|
console.log();
|
|
5033
|
-
console.log(
|
|
5034
|
-
console.log(
|
|
6181
|
+
console.log(chalk15.bold(`Syndicate #${info.id}`));
|
|
6182
|
+
console.log(chalk15.dim("\u2500".repeat(40)));
|
|
5035
6183
|
if (info.subdomain) {
|
|
5036
|
-
console.log(` ENS: ${
|
|
6184
|
+
console.log(` ENS: ${chalk15.bold(`${info.subdomain}.sherwoodagent.eth`)}`);
|
|
5037
6185
|
}
|
|
5038
|
-
console.log(` Vault: ${
|
|
6186
|
+
console.log(` Vault: ${chalk15.cyan(info.vault)}`);
|
|
5039
6187
|
console.log(` Creator: ${info.creator}`);
|
|
5040
6188
|
console.log(` Created: ${date}`);
|
|
5041
|
-
console.log(` Active: ${info.active ?
|
|
6189
|
+
console.log(` Active: ${info.active ? chalk15.green("yes") : chalk15.red("no")}`);
|
|
5042
6190
|
if (info.metadataURI) {
|
|
5043
|
-
console.log(` Metadata: ${
|
|
6191
|
+
console.log(` Metadata: ${chalk15.dim(info.metadataURI)}`);
|
|
5044
6192
|
}
|
|
5045
6193
|
if (info.subdomain) {
|
|
5046
6194
|
const xmtpGroupId = getCachedGroupId(info.subdomain);
|
|
5047
6195
|
if (xmtpGroupId) {
|
|
5048
|
-
console.log(` XMTP Group: ${
|
|
6196
|
+
console.log(` XMTP Group: ${chalk15.cyan(xmtpGroupId)}`);
|
|
5049
6197
|
}
|
|
5050
6198
|
}
|
|
5051
6199
|
setVaultAddress(info.vault);
|
|
5052
6200
|
const vaultInfo = crossChainHint ? await withNetwork(crossChainHint.network, () => getVaultInfo()) : await getVaultInfo();
|
|
5053
6201
|
console.log();
|
|
5054
|
-
console.log(
|
|
6202
|
+
console.log(chalk15.bold(" Vault Stats"));
|
|
5055
6203
|
console.log(` Total Assets: ${vaultInfo.totalAssets}`);
|
|
5056
6204
|
console.log(` Agent Count: ${vaultInfo.agentCount}`);
|
|
5057
6205
|
console.log(` Redemptions Locked: ${vaultInfo.redemptionsLocked}`);
|
|
@@ -5059,12 +6207,12 @@ syndicate.command("info").description("Display syndicate details by ID or subdom
|
|
|
5059
6207
|
console.log();
|
|
5060
6208
|
} catch (err) {
|
|
5061
6209
|
spinner.fail("Failed to load syndicate info");
|
|
5062
|
-
console.error(
|
|
6210
|
+
console.error(chalk15.red(formatContractError(err)));
|
|
5063
6211
|
process.exit(1);
|
|
5064
6212
|
}
|
|
5065
6213
|
});
|
|
5066
6214
|
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) => {
|
|
5067
|
-
const spinner = ora8({ text:
|
|
6215
|
+
const spinner = ora8({ text: W6("Loading syndicate..."), color: "green" }).start();
|
|
5068
6216
|
try {
|
|
5069
6217
|
const syndicateId = BigInt(opts.id);
|
|
5070
6218
|
let metadataURI = opts.uri;
|
|
@@ -5076,7 +6224,7 @@ syndicate.command("update-metadata").description("Update syndicate metadata (cre
|
|
|
5076
6224
|
}
|
|
5077
6225
|
const name = opts.name || info.subdomain;
|
|
5078
6226
|
const description = opts.description || `${name} \u2014 a Sherwood syndicate on ${info.subdomain}.sherwoodagent.eth`;
|
|
5079
|
-
spinner.text =
|
|
6227
|
+
spinner.text = W6("Uploading metadata to IPFS...");
|
|
5080
6228
|
const metadata = {
|
|
5081
6229
|
schema: "sherwood/syndicate/v1",
|
|
5082
6230
|
name,
|
|
@@ -5087,15 +6235,15 @@ syndicate.command("update-metadata").description("Update syndicate metadata (cre
|
|
|
5087
6235
|
links: {}
|
|
5088
6236
|
};
|
|
5089
6237
|
metadataURI = await uploadMetadata(metadata);
|
|
5090
|
-
spinner.text =
|
|
6238
|
+
spinner.text = W6("Updating on-chain metadata...");
|
|
5091
6239
|
}
|
|
5092
6240
|
const hash = await updateMetadata(syndicateId, metadataURI);
|
|
5093
|
-
spinner.succeed(
|
|
5094
|
-
console.log(
|
|
5095
|
-
console.log(
|
|
6241
|
+
spinner.succeed(G6(`Metadata updated`));
|
|
6242
|
+
console.log(DIM6(` IPFS: ${metadataURI}`));
|
|
6243
|
+
console.log(DIM6(` ${getExplorerUrl(hash)}`));
|
|
5096
6244
|
} catch (err) {
|
|
5097
6245
|
spinner.fail("Metadata update failed");
|
|
5098
|
-
console.error(
|
|
6246
|
+
console.error(chalk15.red(formatContractError(err)));
|
|
5099
6247
|
process.exit(1);
|
|
5100
6248
|
}
|
|
5101
6249
|
});
|
|
@@ -5106,10 +6254,10 @@ syndicate.command("approve-depositor").description("Approve an address to deposi
|
|
|
5106
6254
|
const depositor = validateAddress(opts.depositor, "depositor");
|
|
5107
6255
|
const hash = await approveDepositor(depositor);
|
|
5108
6256
|
spinner.succeed(`Depositor approved: ${hash}`);
|
|
5109
|
-
console.log(
|
|
6257
|
+
console.log(chalk15.dim(` ${getExplorerUrl(hash)}`));
|
|
5110
6258
|
} catch (err) {
|
|
5111
6259
|
spinner.fail("Approval failed");
|
|
5112
|
-
console.error(
|
|
6260
|
+
console.error(chalk15.red(formatContractError(err)));
|
|
5113
6261
|
process.exit(1);
|
|
5114
6262
|
}
|
|
5115
6263
|
});
|
|
@@ -5120,10 +6268,10 @@ syndicate.command("remove-depositor").description("Remove an address from the de
|
|
|
5120
6268
|
const depositor = validateAddress(opts.depositor, "depositor");
|
|
5121
6269
|
const hash = await removeDepositor(depositor);
|
|
5122
6270
|
spinner.succeed(`Depositor removed: ${hash}`);
|
|
5123
|
-
console.log(
|
|
6271
|
+
console.log(chalk15.dim(` ${getExplorerUrl(hash)}`));
|
|
5124
6272
|
} catch (err) {
|
|
5125
6273
|
spinner.fail("Removal failed");
|
|
5126
|
-
console.error(
|
|
6274
|
+
console.error(chalk15.red(formatContractError(err)));
|
|
5127
6275
|
process.exit(1);
|
|
5128
6276
|
}
|
|
5129
6277
|
});
|
|
@@ -5143,7 +6291,7 @@ syndicate.command("add").description("Register an agent on a syndicate vault (cr
|
|
|
5143
6291
|
if (opts.agentId) {
|
|
5144
6292
|
agentId = BigInt(opts.agentId);
|
|
5145
6293
|
} else {
|
|
5146
|
-
const { AGENT_REGISTRY: AGENT_REGISTRY2 } = await import("./addresses-
|
|
6294
|
+
const { AGENT_REGISTRY: AGENT_REGISTRY2 } = await import("./addresses-DOWLGXGE.js");
|
|
5147
6295
|
const registry2 = AGENT_REGISTRY2().IDENTITY_REGISTRY;
|
|
5148
6296
|
if (registry2 === "0x0000000000000000000000000000000000000000") {
|
|
5149
6297
|
agentId = 0n;
|
|
@@ -5164,7 +6312,7 @@ syndicate.command("add").description("Register an agent on a syndicate vault (cr
|
|
|
5164
6312
|
});
|
|
5165
6313
|
if (balance === 0n) {
|
|
5166
6314
|
spinner.fail("Agent wallet does not own an ERC-8004 identity NFT");
|
|
5167
|
-
console.error(
|
|
6315
|
+
console.error(chalk15.dim(" Mint one first: sherwood identity mint --name <name>"));
|
|
5168
6316
|
process.exit(1);
|
|
5169
6317
|
}
|
|
5170
6318
|
const tokenId = await client.readContract({
|
|
@@ -5180,13 +6328,13 @@ syndicate.command("add").description("Register an agent on a syndicate vault (cr
|
|
|
5180
6328
|
args: [agentWallet, 0n]
|
|
5181
6329
|
});
|
|
5182
6330
|
agentId = tokenId;
|
|
5183
|
-
console.log(
|
|
6331
|
+
console.log(chalk15.dim(` Resolved agent ID: #${agentId}`));
|
|
5184
6332
|
}
|
|
5185
6333
|
}
|
|
5186
6334
|
spinner.text = "Registering agent...";
|
|
5187
6335
|
const hash = await registerAgent(agentId, agentWallet);
|
|
5188
6336
|
spinner.succeed(`Agent registered: ${hash}`);
|
|
5189
|
-
console.log(
|
|
6337
|
+
console.log(chalk15.dim(` ${getExplorerUrl(hash)}`));
|
|
5190
6338
|
try {
|
|
5191
6339
|
const xmtp = await loadXmtp();
|
|
5192
6340
|
const xmtpClient = await xmtp.getXmtpClient();
|
|
@@ -5198,14 +6346,14 @@ syndicate.command("add").description("Register an agent on a syndicate vault (cr
|
|
|
5198
6346
|
syndicate: subdomain,
|
|
5199
6347
|
timestamp: Math.floor(Date.now() / 1e3)
|
|
5200
6348
|
});
|
|
5201
|
-
console.log(
|
|
6349
|
+
console.log(chalk15.dim(` Added to chat: ${subdomain}`));
|
|
5202
6350
|
} catch {
|
|
5203
|
-
console.warn(
|
|
5204
|
-
console.warn(
|
|
6351
|
+
console.warn(chalk15.yellow(" \u26A0 Could not add agent to chat group"));
|
|
6352
|
+
console.warn(chalk15.dim(` If no group exists, run: sherwood chat ${subdomain} init`));
|
|
5205
6353
|
}
|
|
5206
6354
|
} catch (err) {
|
|
5207
6355
|
spinner.fail("Registration failed");
|
|
5208
|
-
console.error(
|
|
6356
|
+
console.error(chalk15.red(formatContractError(err)));
|
|
5209
6357
|
process.exit(1);
|
|
5210
6358
|
}
|
|
5211
6359
|
});
|
|
@@ -5213,7 +6361,7 @@ syndicate.command("share").description("Print the shareable dashboard URL for yo
|
|
|
5213
6361
|
try {
|
|
5214
6362
|
const subdomain = opts.subdomain || getPrimarySyndicate(getChain().id)?.subdomain;
|
|
5215
6363
|
if (!subdomain) {
|
|
5216
|
-
console.error(
|
|
6364
|
+
console.error(chalk15.red(" No syndicate configured. Pass --subdomain <name> or run 'syndicate create/join' first."));
|
|
5217
6365
|
process.exit(1);
|
|
5218
6366
|
}
|
|
5219
6367
|
const syndicateInfo = await resolveSyndicate(subdomain);
|
|
@@ -5222,12 +6370,12 @@ syndicate.command("share").description("Print the shareable dashboard URL for yo
|
|
|
5222
6370
|
const dashboardUrl = `https://sherwood.sh/syndicate/${subdomain}${refParam}`;
|
|
5223
6371
|
console.log();
|
|
5224
6372
|
console.log(LABEL5(" \u25C6 Share Your Syndicate"));
|
|
5225
|
-
|
|
5226
|
-
console.log(
|
|
5227
|
-
console.log(
|
|
5228
|
-
console.log(
|
|
5229
|
-
console.log(
|
|
5230
|
-
|
|
6373
|
+
SEP6();
|
|
6374
|
+
console.log(W6(` Dashboard: ${G6(dashboardUrl)}`));
|
|
6375
|
+
console.log(W6(` ENS: ${DIM6(`${subdomain}.sherwoodagent.eth`)}`));
|
|
6376
|
+
console.log(W6(` Vault: ${DIM6(syndicateInfo.vault)}`));
|
|
6377
|
+
console.log(W6(` Join CLI: ${DIM6(`sherwood syndicate join --subdomain ${subdomain}`)}`));
|
|
6378
|
+
SEP6();
|
|
5231
6379
|
try {
|
|
5232
6380
|
const { exec } = await import("child_process");
|
|
5233
6381
|
const platform = (await import("os")).platform();
|
|
@@ -5235,12 +6383,12 @@ syndicate.command("share").description("Print the shareable dashboard URL for yo
|
|
|
5235
6383
|
const proc = exec(cmd);
|
|
5236
6384
|
proc.stdin?.write(dashboardUrl);
|
|
5237
6385
|
proc.stdin?.end();
|
|
5238
|
-
console.log(
|
|
6386
|
+
console.log(G6(" \u2713 Dashboard URL copied to clipboard"));
|
|
5239
6387
|
} catch {
|
|
5240
6388
|
}
|
|
5241
6389
|
console.log();
|
|
5242
6390
|
} catch (err) {
|
|
5243
|
-
console.error(
|
|
6391
|
+
console.error(chalk15.red(` \u2716 ${formatContractError(err)}`));
|
|
5244
6392
|
process.exit(1);
|
|
5245
6393
|
}
|
|
5246
6394
|
});
|
|
@@ -5265,14 +6413,14 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
|
|
|
5265
6413
|
const hits = await searchSyndicateAcrossChains(subdomain);
|
|
5266
6414
|
spinner.fail(`Syndicate "${subdomain}" not found on ${getNetwork()}`);
|
|
5267
6415
|
if (hits.length === 0) {
|
|
5268
|
-
console.error(
|
|
6416
|
+
console.error(chalk15.red(` Not found on any supported chain.`));
|
|
5269
6417
|
} else if (hits.length === 1) {
|
|
5270
|
-
console.error(
|
|
5271
|
-
console.error(
|
|
6418
|
+
console.error(chalk15.yellow(` Found on chain: ${chalk15.bold(hits[0].network)}`));
|
|
6419
|
+
console.error(chalk15.dim(` Re-run: sherwood --chain ${hits[0].network} syndicate join --subdomain ${subdomain}`));
|
|
5272
6420
|
} else {
|
|
5273
6421
|
const chains = hits.map((h) => h.network).join(", ");
|
|
5274
|
-
console.error(
|
|
5275
|
-
console.error(
|
|
6422
|
+
console.error(chalk15.yellow(` Found on chains: ${chalk15.bold(chains)}`));
|
|
6423
|
+
console.error(chalk15.dim(` Specify which chain to join with --chain <network>.`));
|
|
5276
6424
|
}
|
|
5277
6425
|
process.exit(1);
|
|
5278
6426
|
}
|
|
@@ -5285,17 +6433,17 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
|
|
|
5285
6433
|
try {
|
|
5286
6434
|
const xmtp = await loadXmtp();
|
|
5287
6435
|
await xmtp.getXmtpClient();
|
|
5288
|
-
console.log(
|
|
6436
|
+
console.log(chalk15.dim(" XMTP identity ready"));
|
|
5289
6437
|
} catch {
|
|
5290
|
-
console.warn(
|
|
6438
|
+
console.warn(chalk15.yellow(" \u26A0 Could not initialize XMTP identity"));
|
|
5291
6439
|
}
|
|
5292
6440
|
try {
|
|
5293
6441
|
const cron = await loadCron();
|
|
5294
6442
|
const cronResult = cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
|
|
5295
6443
|
if (cronResult.isOpenClaw && cronResult.registered) {
|
|
5296
|
-
console.log(
|
|
6444
|
+
console.log(chalk15.green(" \u2713 Participation crons registered"));
|
|
5297
6445
|
} else if (!cronResult.isOpenClaw) {
|
|
5298
|
-
console.log(
|
|
6446
|
+
console.log(chalk15.dim(" Tip: Set up a scheduled process to run `sherwood session check " + subdomain + "` periodically"));
|
|
5299
6447
|
}
|
|
5300
6448
|
} catch {
|
|
5301
6449
|
}
|
|
@@ -5308,14 +6456,14 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
|
|
|
5308
6456
|
);
|
|
5309
6457
|
if (existingRequest) {
|
|
5310
6458
|
spinner.succeed("You already have a pending join request for this syndicate");
|
|
5311
|
-
console.log(
|
|
5312
|
-
console.log(
|
|
6459
|
+
console.log(chalk15.dim(` Attestation: ${existingRequest.uid}`));
|
|
6460
|
+
console.log(chalk15.dim(` Submitted: ${new Date(existingRequest.time * 1e3).toLocaleString()}`));
|
|
5313
6461
|
try {
|
|
5314
6462
|
const xmtp = await loadXmtp();
|
|
5315
6463
|
await xmtp.getXmtpClient();
|
|
5316
|
-
console.log(
|
|
6464
|
+
console.log(chalk15.dim(" XMTP identity ready"));
|
|
5317
6465
|
} catch {
|
|
5318
|
-
console.warn(
|
|
6466
|
+
console.warn(chalk15.yellow(" \u26A0 Could not initialize XMTP identity"));
|
|
5319
6467
|
}
|
|
5320
6468
|
return;
|
|
5321
6469
|
}
|
|
@@ -5336,7 +6484,7 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
|
|
|
5336
6484
|
spinner.succeed("Join request created (XMTP identity ready)");
|
|
5337
6485
|
} catch {
|
|
5338
6486
|
spinner.succeed("Join request created");
|
|
5339
|
-
console.warn(
|
|
6487
|
+
console.warn(chalk15.yellow(" \u26A0 Could not initialize XMTP identity \u2014 creator may not be able to auto-add you to chat"));
|
|
5340
6488
|
}
|
|
5341
6489
|
addSyndicate(getChain().id, { subdomain, vault: syndicate2.vault, role: "agent" });
|
|
5342
6490
|
setPrimarySyndicate(getChain().id, subdomain);
|
|
@@ -5344,29 +6492,29 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
|
|
|
5344
6492
|
const cron = await loadCron();
|
|
5345
6493
|
const cronResult = cron.registerSyndicateCrons(subdomain, isTestnet(), getNotifyTo());
|
|
5346
6494
|
if (cronResult.isOpenClaw && cronResult.registered) {
|
|
5347
|
-
console.log(
|
|
6495
|
+
console.log(G6(" \u2713 Participation crons registered (will activate after approval)"));
|
|
5348
6496
|
} else if (!cronResult.isOpenClaw) {
|
|
5349
|
-
console.log(
|
|
6497
|
+
console.log(DIM6(" Tip: Set up a scheduled process to run `sherwood session check " + subdomain + "` periodically"));
|
|
5350
6498
|
}
|
|
5351
6499
|
} catch {
|
|
5352
6500
|
}
|
|
5353
6501
|
console.log();
|
|
5354
6502
|
console.log(LABEL5(" \u25C6 Join Request Submitted"));
|
|
5355
|
-
|
|
5356
|
-
console.log(
|
|
5357
|
-
console.log(
|
|
5358
|
-
console.log(
|
|
5359
|
-
console.log(
|
|
5360
|
-
console.log(
|
|
5361
|
-
console.log(
|
|
5362
|
-
|
|
5363
|
-
console.log(
|
|
5364
|
-
console.log(
|
|
5365
|
-
console.log(
|
|
6503
|
+
SEP6();
|
|
6504
|
+
console.log(W6(` Syndicate: ${G6(`${subdomain}.sherwoodagent.eth`)}`));
|
|
6505
|
+
console.log(W6(` Agent ID: #${agentId}`));
|
|
6506
|
+
console.log(W6(` Creator: ${DIM6(syndicate2.creator)}`));
|
|
6507
|
+
console.log(W6(` Attestation: ${DIM6(uid)}`));
|
|
6508
|
+
console.log(W6(` EAS Scan: ${DIM6(getEasScanUrl(uid))}`));
|
|
6509
|
+
console.log(W6(` Explorer: ${DIM6(getEasExplorerUrl(hash))}`));
|
|
6510
|
+
SEP6();
|
|
6511
|
+
console.log(G6(" \u2713 Syndicate saved to ~/.sherwood/config.json (set as primary)"));
|
|
6512
|
+
console.log(G6(" \u2713 The creator can review with:"));
|
|
6513
|
+
console.log(DIM6(` sherwood syndicate requests --subdomain ${subdomain}`));
|
|
5366
6514
|
console.log();
|
|
5367
6515
|
} catch (err) {
|
|
5368
6516
|
spinner.fail("Join request failed");
|
|
5369
|
-
console.error(
|
|
6517
|
+
console.error(chalk15.red(formatContractError(err)));
|
|
5370
6518
|
process.exit(1);
|
|
5371
6519
|
}
|
|
5372
6520
|
});
|
|
@@ -5407,34 +6555,34 @@ syndicate.command("requests").description("View pending join requests for a synd
|
|
|
5407
6555
|
);
|
|
5408
6556
|
spinner.stop();
|
|
5409
6557
|
if (requests.length === 0) {
|
|
5410
|
-
console.log(
|
|
6558
|
+
console.log(DIM6("\n No pending join requests.\n"));
|
|
5411
6559
|
return;
|
|
5412
6560
|
}
|
|
5413
6561
|
console.log();
|
|
5414
6562
|
console.log(LABEL5(` \u25C6 Pending Join Requests (${requests.length})`));
|
|
5415
|
-
|
|
6563
|
+
SEP6();
|
|
5416
6564
|
for (let i = 0; i < requests.length; i++) {
|
|
5417
6565
|
const req = requests[i];
|
|
5418
6566
|
const date = new Date(req.time * 1e3).toLocaleString();
|
|
5419
6567
|
const referrer = parseReferrer(req.decoded.message);
|
|
5420
6568
|
const cleanMessage = stripReferrerPrefix(req.decoded.message);
|
|
5421
|
-
console.log(
|
|
5422
|
-
console.log(
|
|
6569
|
+
console.log(W6(` ${i + 1}. Agent #${req.decoded.agentId} ${DIM6(`(${req.attester})`)}`));
|
|
6570
|
+
console.log(DIM6(` Message: "${cleanMessage}"`));
|
|
5423
6571
|
if (referrer !== null) {
|
|
5424
|
-
console.log(
|
|
6572
|
+
console.log(G6(` Referred by: Agent #${referrer}`));
|
|
5425
6573
|
}
|
|
5426
|
-
console.log(
|
|
5427
|
-
console.log(
|
|
6574
|
+
console.log(DIM6(` Requested: ${date}`));
|
|
6575
|
+
console.log(DIM6(` Attestation: ${req.uid}`));
|
|
5428
6576
|
console.log();
|
|
5429
6577
|
}
|
|
5430
|
-
console.log(
|
|
5431
|
-
console.log(
|
|
5432
|
-
console.log(
|
|
5433
|
-
console.log(
|
|
6578
|
+
console.log(G6(" To approve:"));
|
|
6579
|
+
console.log(DIM6(` sherwood syndicate approve --agent-id <id> --wallet <addr>`));
|
|
6580
|
+
console.log(G6(" To reject:"));
|
|
6581
|
+
console.log(DIM6(` sherwood syndicate reject --attestation <uid>`));
|
|
5434
6582
|
console.log();
|
|
5435
6583
|
} catch (err) {
|
|
5436
6584
|
spinner.fail("Failed to load requests");
|
|
5437
|
-
console.error(
|
|
6585
|
+
console.error(chalk15.red(formatContractError(err)));
|
|
5438
6586
|
process.exit(1);
|
|
5439
6587
|
}
|
|
5440
6588
|
});
|
|
@@ -5464,11 +6612,11 @@ syndicate.command("approve").description("Approve an agent join request (registe
|
|
|
5464
6612
|
agentWallet
|
|
5465
6613
|
);
|
|
5466
6614
|
agentWasRegistered = true;
|
|
5467
|
-
console.log(
|
|
6615
|
+
console.log(DIM6(` Agent registered: ${getExplorerUrl(regHash)}`));
|
|
5468
6616
|
} catch (regErr) {
|
|
5469
6617
|
const msg = regErr instanceof Error ? regErr.message : String(regErr);
|
|
5470
6618
|
if (msg.includes("0xe098d3ee") || msg.includes("AgentAlreadyRegistered")) {
|
|
5471
|
-
console.log(
|
|
6619
|
+
console.log(DIM6(" Agent already registered on vault \u2014 skipping"));
|
|
5472
6620
|
} else {
|
|
5473
6621
|
throw regErr;
|
|
5474
6622
|
}
|
|
@@ -5484,7 +6632,7 @@ syndicate.command("approve").description("Approve an agent join request (registe
|
|
|
5484
6632
|
let approvalUid;
|
|
5485
6633
|
if (alreadyApproved) {
|
|
5486
6634
|
approvalUid = alreadyApproved.uid;
|
|
5487
|
-
console.log(
|
|
6635
|
+
console.log(DIM6(` Approval attestation already exists \u2014 skipping`));
|
|
5488
6636
|
} else {
|
|
5489
6637
|
spinner.text = "Creating approval attestation...";
|
|
5490
6638
|
const result = await createApproval(
|
|
@@ -5507,23 +6655,23 @@ syndicate.command("approve").description("Approve an agent join request (registe
|
|
|
5507
6655
|
syndicate: subdomain,
|
|
5508
6656
|
timestamp: Math.floor(Date.now() / 1e3)
|
|
5509
6657
|
});
|
|
5510
|
-
console.log(
|
|
6658
|
+
console.log(DIM6(` Added to chat: ${subdomain}`));
|
|
5511
6659
|
} catch {
|
|
5512
|
-
console.warn(
|
|
5513
|
-
console.warn(
|
|
6660
|
+
console.warn(chalk15.yellow(" \u26A0 Could not add agent to chat group"));
|
|
6661
|
+
console.warn(chalk15.dim(` If no group exists, run: sherwood chat ${subdomain} init`));
|
|
5514
6662
|
}
|
|
5515
6663
|
spinner.succeed("Agent approved and registered");
|
|
5516
6664
|
console.log();
|
|
5517
6665
|
console.log(LABEL5(" \u25C6 Agent Approved"));
|
|
5518
|
-
|
|
5519
|
-
console.log(
|
|
5520
|
-
console.log(
|
|
5521
|
-
console.log(
|
|
5522
|
-
console.log(
|
|
5523
|
-
|
|
6666
|
+
SEP6();
|
|
6667
|
+
console.log(W6(` Agent ID: #${opts.agentId}`));
|
|
6668
|
+
console.log(W6(` Wallet: ${G6(opts.wallet)}`));
|
|
6669
|
+
console.log(W6(` Approval: ${DIM6(approvalUid)}`));
|
|
6670
|
+
console.log(W6(` EAS Scan: ${DIM6(getEasScanUrl(approvalUid))}`));
|
|
6671
|
+
SEP6();
|
|
5524
6672
|
} catch (err) {
|
|
5525
6673
|
spinner.fail("Approval failed");
|
|
5526
|
-
console.error(
|
|
6674
|
+
console.error(chalk15.red(formatContractError(err)));
|
|
5527
6675
|
process.exit(1);
|
|
5528
6676
|
}
|
|
5529
6677
|
});
|
|
@@ -5532,17 +6680,17 @@ syndicate.command("reject").description("Reject a join request by revoking its a
|
|
|
5532
6680
|
try {
|
|
5533
6681
|
const hash = await revokeJoinRequest(opts.attestation);
|
|
5534
6682
|
spinner.succeed("Join request rejected");
|
|
5535
|
-
console.log(
|
|
6683
|
+
console.log(DIM6(` ${getEasExplorerUrl(hash)}`));
|
|
5536
6684
|
} catch (err) {
|
|
5537
6685
|
spinner.fail("Rejection failed");
|
|
5538
|
-
console.error(
|
|
6686
|
+
console.error(chalk15.red(formatContractError(err)));
|
|
5539
6687
|
process.exit(1);
|
|
5540
6688
|
}
|
|
5541
6689
|
});
|
|
5542
6690
|
syndicate.command("leave").description("Leave a syndicate \u2014 removes participation crons and session state").option("--subdomain <name>", "Syndicate subdomain to leave").action(async (opts) => {
|
|
5543
6691
|
const subdomain = opts.subdomain || getPrimarySyndicate(getChain().id)?.subdomain;
|
|
5544
6692
|
if (!subdomain) {
|
|
5545
|
-
console.error(
|
|
6693
|
+
console.error(chalk15.red(" No syndicate configured. Pass --subdomain <name>."));
|
|
5546
6694
|
process.exit(1);
|
|
5547
6695
|
}
|
|
5548
6696
|
const spinner = ora8("Cleaning up...").start();
|
|
@@ -5558,17 +6706,17 @@ syndicate.command("leave").description("Leave a syndicate \u2014 removes partici
|
|
|
5558
6706
|
resetSession(subdomain);
|
|
5559
6707
|
spinner.succeed("Left syndicate");
|
|
5560
6708
|
if (cronsRemoved) {
|
|
5561
|
-
console.log(
|
|
6709
|
+
console.log(G6(" \u2713 Participation crons removed"));
|
|
5562
6710
|
}
|
|
5563
|
-
console.log(
|
|
6711
|
+
console.log(G6(" \u2713 Session state cleared"));
|
|
5564
6712
|
console.log();
|
|
5565
|
-
console.log(
|
|
5566
|
-
console.log(
|
|
5567
|
-
console.log(
|
|
6713
|
+
console.log(chalk15.dim(" Note: This does not remove you on-chain. To exit your position:"));
|
|
6714
|
+
console.log(chalk15.dim(" sherwood vault balance \u2014 check your LP share balance"));
|
|
6715
|
+
console.log(chalk15.dim(" Redeem shares via the vault contract or dashboard"));
|
|
5568
6716
|
console.log();
|
|
5569
6717
|
} catch (err) {
|
|
5570
6718
|
spinner.fail("Leave failed");
|
|
5571
|
-
console.error(
|
|
6719
|
+
console.error(chalk15.red(err instanceof Error ? err.message : String(err)));
|
|
5572
6720
|
process.exit(1);
|
|
5573
6721
|
}
|
|
5574
6722
|
});
|
|
@@ -5579,21 +6727,21 @@ syndicate.command("set-primary").description("Set the active syndicate for CLI c
|
|
|
5579
6727
|
if (!opts.subdomain && !opts.vault) {
|
|
5580
6728
|
const primary = getPrimarySyndicate(chainId);
|
|
5581
6729
|
if (memberships.length === 0) {
|
|
5582
|
-
console.log(
|
|
6730
|
+
console.log(DIM6("\n No syndicates saved. Create or join one first.\n"));
|
|
5583
6731
|
return;
|
|
5584
6732
|
}
|
|
5585
6733
|
console.log();
|
|
5586
6734
|
console.log(LABEL5(" \u25C6 Your Syndicates"));
|
|
5587
|
-
|
|
6735
|
+
SEP6();
|
|
5588
6736
|
for (const m of memberships) {
|
|
5589
6737
|
const isPrimary = primary?.subdomain === m.subdomain;
|
|
5590
|
-
const marker = isPrimary ?
|
|
5591
|
-
console.log(
|
|
5592
|
-
console.log(
|
|
6738
|
+
const marker = isPrimary ? G6(" \u2605") : "";
|
|
6739
|
+
console.log(W6(` ${m.subdomain}.sherwoodagent.eth${marker} ${DIM6(`(${m.role})`)}`));
|
|
6740
|
+
console.log(DIM6(` vault: ${m.vault}`));
|
|
5593
6741
|
}
|
|
5594
|
-
|
|
6742
|
+
SEP6();
|
|
5595
6743
|
if (memberships.length > 1) {
|
|
5596
|
-
console.log(
|
|
6744
|
+
console.log(DIM6(" Switch with: sherwood syndicate set-primary --subdomain <name>"));
|
|
5597
6745
|
}
|
|
5598
6746
|
console.log();
|
|
5599
6747
|
return;
|
|
@@ -5619,15 +6767,15 @@ syndicate.command("set-primary").description("Set the active syndicate for CLI c
|
|
|
5619
6767
|
}
|
|
5620
6768
|
}
|
|
5621
6769
|
if (!target) {
|
|
5622
|
-
console.error(
|
|
6770
|
+
console.error(chalk15.red(" Could not resolve syndicate. Pass --subdomain or --vault."));
|
|
5623
6771
|
process.exit(1);
|
|
5624
6772
|
}
|
|
5625
6773
|
setPrimarySyndicate(chainId, target);
|
|
5626
|
-
console.log(
|
|
6774
|
+
console.log(G6(`
|
|
5627
6775
|
\u2713 Primary syndicate set to ${target}.sherwoodagent.eth
|
|
5628
6776
|
`));
|
|
5629
6777
|
} catch (err) {
|
|
5630
|
-
console.error(
|
|
6778
|
+
console.error(chalk15.red(formatContractError(err)));
|
|
5631
6779
|
process.exit(1);
|
|
5632
6780
|
}
|
|
5633
6781
|
});
|
|
@@ -5635,13 +6783,13 @@ var vaultCmd = program.command("vault");
|
|
|
5635
6783
|
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) => {
|
|
5636
6784
|
resolveVault(opts);
|
|
5637
6785
|
const decimals = await getAssetDecimals();
|
|
5638
|
-
const amount =
|
|
6786
|
+
const amount = parseUnits7(opts.amount, decimals);
|
|
5639
6787
|
try {
|
|
5640
6788
|
if (!opts.useEth) {
|
|
5641
6789
|
await preflightDeposit(amount);
|
|
5642
6790
|
}
|
|
5643
6791
|
} catch (err) {
|
|
5644
|
-
console.error(
|
|
6792
|
+
console.error(chalk15.red(`
|
|
5645
6793
|
\u2716 ${err instanceof Error ? err.message : String(err)}
|
|
5646
6794
|
`));
|
|
5647
6795
|
process.exit(1);
|
|
@@ -5656,10 +6804,10 @@ vaultCmd.command("deposit").description("Deposit into a vault").option("--vault
|
|
|
5656
6804
|
hash = await deposit(amount);
|
|
5657
6805
|
}
|
|
5658
6806
|
spinner.succeed(`Deposited: ${hash}`);
|
|
5659
|
-
console.log(
|
|
6807
|
+
console.log(chalk15.dim(` ${getExplorerUrl(hash)}`));
|
|
5660
6808
|
} catch (err) {
|
|
5661
6809
|
spinner.fail("Deposit failed");
|
|
5662
|
-
console.error(
|
|
6810
|
+
console.error(chalk15.red(formatContractError(err)));
|
|
5663
6811
|
process.exit(1);
|
|
5664
6812
|
}
|
|
5665
6813
|
});
|
|
@@ -5670,8 +6818,8 @@ vaultCmd.command("info").description("Display vault state").option("--vault <add
|
|
|
5670
6818
|
const info = await getVaultInfo();
|
|
5671
6819
|
spinner.stop();
|
|
5672
6820
|
console.log();
|
|
5673
|
-
console.log(
|
|
5674
|
-
console.log(
|
|
6821
|
+
console.log(chalk15.bold("Vault Info"));
|
|
6822
|
+
console.log(chalk15.dim("\u2500".repeat(40)));
|
|
5675
6823
|
console.log(` Address: ${info.address}`);
|
|
5676
6824
|
console.log(` Total Assets: ${info.totalAssets}`);
|
|
5677
6825
|
console.log(` Agent Count: ${info.agentCount}`);
|
|
@@ -5680,7 +6828,7 @@ vaultCmd.command("info").description("Display vault state").option("--vault <add
|
|
|
5680
6828
|
console.log();
|
|
5681
6829
|
} catch (err) {
|
|
5682
6830
|
spinner.fail("Failed to load vault info");
|
|
5683
|
-
console.error(
|
|
6831
|
+
console.error(chalk15.red(formatContractError(err)));
|
|
5684
6832
|
process.exit(1);
|
|
5685
6833
|
}
|
|
5686
6834
|
});
|
|
@@ -5691,15 +6839,15 @@ vaultCmd.command("balance").description("Show LP share balance and asset value")
|
|
|
5691
6839
|
const balance = await getBalance(opts.address);
|
|
5692
6840
|
spinner.stop();
|
|
5693
6841
|
console.log();
|
|
5694
|
-
console.log(
|
|
5695
|
-
console.log(
|
|
6842
|
+
console.log(chalk15.bold("LP Position"));
|
|
6843
|
+
console.log(chalk15.dim("\u2500".repeat(40)));
|
|
5696
6844
|
console.log(` Shares: ${balance.shares.toString()}`);
|
|
5697
6845
|
console.log(` Asset Value: ${balance.assetsValue}`);
|
|
5698
6846
|
console.log(` % of Vault: ${balance.percentOfVault}`);
|
|
5699
6847
|
console.log();
|
|
5700
6848
|
} catch (err) {
|
|
5701
6849
|
spinner.fail("Failed to load balance");
|
|
5702
|
-
console.error(
|
|
6850
|
+
console.error(chalk15.red(formatContractError(err)));
|
|
5703
6851
|
process.exit(1);
|
|
5704
6852
|
}
|
|
5705
6853
|
});
|
|
@@ -5710,22 +6858,22 @@ vaultCmd.command("redeem").description("Redeem vault shares for the underlying a
|
|
|
5710
6858
|
let shares;
|
|
5711
6859
|
try {
|
|
5712
6860
|
if (opts.shares) {
|
|
5713
|
-
shares =
|
|
6861
|
+
shares = parseUnits7(opts.shares, shareDecimals);
|
|
5714
6862
|
} else {
|
|
5715
6863
|
shares = await getShareBalance();
|
|
5716
6864
|
if (shares === 0n) {
|
|
5717
|
-
console.error(
|
|
6865
|
+
console.error(chalk15.red("\n \u2716 No shares to redeem.\n"));
|
|
5718
6866
|
process.exit(1);
|
|
5719
6867
|
}
|
|
5720
6868
|
}
|
|
5721
6869
|
} catch (err) {
|
|
5722
|
-
console.error(
|
|
6870
|
+
console.error(chalk15.red(`
|
|
5723
6871
|
\u2716 ${err instanceof Error ? err.message : String(err)}
|
|
5724
6872
|
`));
|
|
5725
6873
|
process.exit(1);
|
|
5726
6874
|
}
|
|
5727
6875
|
if (opts.receiver && !isAddress6(opts.receiver)) {
|
|
5728
|
-
console.error(
|
|
6876
|
+
console.error(chalk15.red(`
|
|
5729
6877
|
\u2716 Invalid receiver address: ${opts.receiver}
|
|
5730
6878
|
`));
|
|
5731
6879
|
process.exit(1);
|
|
@@ -5733,7 +6881,7 @@ vaultCmd.command("redeem").description("Redeem vault shares for the underlying a
|
|
|
5733
6881
|
try {
|
|
5734
6882
|
await preflightRedeem(shares);
|
|
5735
6883
|
} catch (err) {
|
|
5736
|
-
console.error(
|
|
6884
|
+
console.error(chalk15.red(`
|
|
5737
6885
|
\u2716 ${err instanceof Error ? err.message : String(err)}
|
|
5738
6886
|
`));
|
|
5739
6887
|
process.exit(1);
|
|
@@ -5746,18 +6894,18 @@ vaultCmd.command("redeem").description("Redeem vault shares for the underlying a
|
|
|
5746
6894
|
opts.receiver
|
|
5747
6895
|
);
|
|
5748
6896
|
spinner.succeed(`Redeemed: ${hash}`);
|
|
5749
|
-
console.log(
|
|
6897
|
+
console.log(chalk15.dim(` ${getExplorerUrl(hash)}`));
|
|
5750
6898
|
console.log(` Assets received: ${formatUnits5(assetsOut, assetDecimals)}`);
|
|
5751
6899
|
} catch (err) {
|
|
5752
6900
|
spinner.fail("Redeem failed");
|
|
5753
|
-
console.error(
|
|
6901
|
+
console.error(chalk15.red(formatContractError(err)));
|
|
5754
6902
|
process.exit(1);
|
|
5755
6903
|
}
|
|
5756
6904
|
});
|
|
5757
6905
|
var strategy = program.command("strategy").description("Strategy templates \u2014 list, clone, propose");
|
|
5758
6906
|
registerStrategyTemplateCommands(strategy);
|
|
5759
6907
|
program.command("providers").description("List available DeFi providers").action(async () => {
|
|
5760
|
-
const { MessariProvider, NansenProvider } = await import("./research-
|
|
6908
|
+
const { MessariProvider, NansenProvider } = await import("./research-YJWSQS2A.js");
|
|
5761
6909
|
const providers = [new MoonwellProvider(), new UniswapProvider(), new MessariProvider(), new NansenProvider()];
|
|
5762
6910
|
for (const p of providers) {
|
|
5763
6911
|
const info = p.info();
|
|
@@ -5768,17 +6916,17 @@ ${info.name} (${info.type})`);
|
|
|
5768
6916
|
}
|
|
5769
6917
|
});
|
|
5770
6918
|
try {
|
|
5771
|
-
const { registerChatCommands } = await import("./chat-
|
|
6919
|
+
const { registerChatCommands } = await import("./chat-MRTPUZ3X.js");
|
|
5772
6920
|
registerChatCommands(program);
|
|
5773
6921
|
} catch {
|
|
5774
6922
|
program.command("chat <name> [action] [actionArgs...]").description("Syndicate chat (XMTP) \u2014 requires @xmtp/cli").action(() => {
|
|
5775
|
-
console.error(
|
|
5776
|
-
console.error(
|
|
5777
|
-
console.error(
|
|
6923
|
+
console.error(chalk15.red("XMTP CLI not available."));
|
|
6924
|
+
console.error(chalk15.dim("Install with: npm install -g @xmtp/cli"));
|
|
6925
|
+
console.error(chalk15.dim("Or reinstall: npm i -g @sherwoodagent/cli"));
|
|
5778
6926
|
process.exit(1);
|
|
5779
6927
|
});
|
|
5780
6928
|
}
|
|
5781
|
-
var { registerSessionCommands } = await import("./session-
|
|
6929
|
+
var { registerSessionCommands } = await import("./session-FPCPCVQX.js");
|
|
5782
6930
|
registerSessionCommands(program);
|
|
5783
6931
|
registerVeniceCommands(program);
|
|
5784
6932
|
registerAllowanceCommands(program);
|
|
@@ -5786,65 +6934,71 @@ registerIdentityCommands(program);
|
|
|
5786
6934
|
registerProposalCommands(program);
|
|
5787
6935
|
registerGovernorCommands(program);
|
|
5788
6936
|
registerGuardianCommands(program);
|
|
5789
|
-
var { registerResearchCommands } = await import("./research-
|
|
6937
|
+
var { registerResearchCommands } = await import("./research-QBKISW2M.js");
|
|
5790
6938
|
registerResearchCommands(program);
|
|
5791
|
-
var { registerAgentCommands } = await import("./agent-
|
|
6939
|
+
var { registerAgentCommands } = await import("./agent-ZPAZTJG3.js");
|
|
5792
6940
|
registerAgentCommands(program);
|
|
5793
|
-
var { registerTradeCommands } = await import("./trade-
|
|
6941
|
+
var { registerTradeCommands } = await import("./trade-NO6CNWCA.js");
|
|
5794
6942
|
registerTradeCommands(program);
|
|
6943
|
+
registerGridCommand(program);
|
|
5795
6944
|
var configCmd = program.command("config");
|
|
5796
|
-
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").option("--xmtp-group <subdomain:groupId>", "Cache an XMTP group ID for a syndicate (e.g. my-fund:abc123)").action((opts) => {
|
|
6945
|
+
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").option("--anthropic-api-key <key>", "Anthropic Claude API key (for LLM judge)").option("--xmtp-group <subdomain:groupId>", "Cache an XMTP group ID for a syndicate (e.g. my-fund:abc123)").action((opts) => {
|
|
5797
6946
|
let saved = false;
|
|
5798
6947
|
if (opts.privateKey) {
|
|
5799
6948
|
setPrivateKey(opts.privateKey);
|
|
5800
6949
|
const account = getAccount();
|
|
5801
|
-
console.log(
|
|
5802
|
-
console.log(
|
|
6950
|
+
console.log(chalk15.green("Private key saved to ~/.sherwood/config.json"));
|
|
6951
|
+
console.log(chalk15.dim(` Wallet: ${account.address}`));
|
|
5803
6952
|
saved = true;
|
|
5804
6953
|
}
|
|
5805
6954
|
if (opts.vault) {
|
|
5806
6955
|
const chainId = getChain().id;
|
|
5807
6956
|
setChainContract(chainId, "vault", opts.vault);
|
|
5808
|
-
console.log(
|
|
5809
|
-
console.log(
|
|
6957
|
+
console.log(chalk15.green(`Vault saved to ~/.sherwood/config.json (chain ${chainId})`));
|
|
6958
|
+
console.log(chalk15.dim(` Vault: ${opts.vault}`));
|
|
5810
6959
|
saved = true;
|
|
5811
6960
|
}
|
|
5812
6961
|
if (opts.rpc) {
|
|
5813
6962
|
const network = getNetwork();
|
|
5814
6963
|
setConfigRpcUrl(network, opts.rpc);
|
|
5815
|
-
console.log(
|
|
5816
|
-
console.log(
|
|
6964
|
+
console.log(chalk15.green(`RPC URL saved for ${network}`));
|
|
6965
|
+
console.log(chalk15.dim(` RPC: ${opts.rpc}`));
|
|
5817
6966
|
saved = true;
|
|
5818
6967
|
}
|
|
5819
6968
|
if (opts.notifyTo) {
|
|
5820
6969
|
setNotifyTo(opts.notifyTo);
|
|
5821
|
-
console.log(
|
|
5822
|
-
console.log(
|
|
6970
|
+
console.log(chalk15.green("Notify destination saved to ~/.sherwood/config.json"));
|
|
6971
|
+
console.log(chalk15.dim(` Notify to: ${opts.notifyTo}`));
|
|
5823
6972
|
saved = true;
|
|
5824
6973
|
}
|
|
5825
6974
|
if (opts.uniswapApiKey) {
|
|
5826
6975
|
setUniswapApiKey(opts.uniswapApiKey);
|
|
5827
|
-
console.log(
|
|
6976
|
+
console.log(chalk15.green("Uniswap API key saved to ~/.sherwood/config.json"));
|
|
5828
6977
|
saved = true;
|
|
5829
6978
|
}
|
|
5830
6979
|
if (opts.veniceApiKey) {
|
|
5831
6980
|
setVeniceApiKey(opts.veniceApiKey);
|
|
5832
|
-
console.log(
|
|
6981
|
+
console.log(chalk15.green("Venice API key saved to ~/.sherwood/config.json"));
|
|
6982
|
+
saved = true;
|
|
6983
|
+
}
|
|
6984
|
+
if (opts.anthropicApiKey) {
|
|
6985
|
+
setAnthropicApiKey(opts.anthropicApiKey);
|
|
6986
|
+
console.log(chalk15.green("Anthropic API key saved to ~/.sherwood/config.json"));
|
|
5833
6987
|
saved = true;
|
|
5834
6988
|
}
|
|
5835
6989
|
if (opts.xmtpGroup) {
|
|
5836
6990
|
const parts = opts.xmtpGroup.split(":");
|
|
5837
6991
|
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
5838
|
-
console.log(
|
|
6992
|
+
console.log(chalk15.red("Format: --xmtp-group <subdomain>:<groupId>"));
|
|
5839
6993
|
process.exit(1);
|
|
5840
6994
|
}
|
|
5841
6995
|
cacheGroupId(parts[0], parts[1]);
|
|
5842
|
-
console.log(
|
|
5843
|
-
console.log(
|
|
6996
|
+
console.log(chalk15.green(`XMTP group ID cached for ${parts[0]}`));
|
|
6997
|
+
console.log(chalk15.dim(` Group ID: ${parts[1]}`));
|
|
5844
6998
|
saved = true;
|
|
5845
6999
|
}
|
|
5846
7000
|
if (!saved) {
|
|
5847
|
-
console.log(
|
|
7001
|
+
console.log(chalk15.red("Provide at least one of: --private-key, --vault, --rpc, --notify-to, --uniswap-api-key, --venice-api-key, --anthropic-api-key, --xmtp-group"));
|
|
5848
7002
|
process.exit(1);
|
|
5849
7003
|
}
|
|
5850
7004
|
});
|
|
@@ -5855,25 +7009,25 @@ configCmd.command("show").description("Display current config for the active net
|
|
|
5855
7009
|
const config = loadConfig();
|
|
5856
7010
|
const customRpc = config.rpc?.[network];
|
|
5857
7011
|
console.log();
|
|
5858
|
-
console.log(
|
|
5859
|
-
console.log(
|
|
5860
|
-
console.log(` Network: ${
|
|
5861
|
-
console.log(` RPC: ${customRpc ?
|
|
5862
|
-
console.log(` Wallet: ${config.privateKey ?
|
|
5863
|
-
console.log(` Agent ID: ${config.agentId ??
|
|
5864
|
-
console.log(` Vault: ${contracts.vault ??
|
|
5865
|
-
console.log(` Uniswap: ${getUniswapApiKey() ?
|
|
5866
|
-
console.log(` Venice: ${getVeniceApiKey() ?
|
|
7012
|
+
console.log(chalk15.bold(`Sherwood Config`));
|
|
7013
|
+
console.log(chalk15.dim("\u2500".repeat(50)));
|
|
7014
|
+
console.log(` Network: ${chalk15.cyan(network)} (chain ${chainId})`);
|
|
7015
|
+
console.log(` RPC: ${customRpc ? chalk15.green(customRpc) : chalk15.dim("default")}`);
|
|
7016
|
+
console.log(` Wallet: ${config.privateKey ? chalk15.green("configured") : chalk15.dim("not set")}`);
|
|
7017
|
+
console.log(` Agent ID: ${config.agentId ?? chalk15.dim("not set")}`);
|
|
7018
|
+
console.log(` Vault: ${contracts.vault ?? chalk15.dim("not set")}`);
|
|
7019
|
+
console.log(` Uniswap: ${getUniswapApiKey() ? chalk15.green("API key configured") : chalk15.dim("not set")}`);
|
|
7020
|
+
console.log(` Venice: ${getVeniceApiKey() ? chalk15.green("API key configured") : chalk15.dim("not set")}`);
|
|
5867
7021
|
const groupEntries = Object.entries(config.groupCache || {}).filter(([, v]) => v);
|
|
5868
7022
|
if (groupEntries.length > 0) {
|
|
5869
7023
|
console.log();
|
|
5870
|
-
console.log(
|
|
7024
|
+
console.log(chalk15.bold(" XMTP Groups"));
|
|
5871
7025
|
for (const [sub, gid] of groupEntries) {
|
|
5872
|
-
console.log(` ${sub}: ${
|
|
7026
|
+
console.log(` ${sub}: ${chalk15.cyan(gid)}`);
|
|
5873
7027
|
}
|
|
5874
7028
|
}
|
|
5875
7029
|
console.log();
|
|
5876
|
-
console.log(
|
|
7030
|
+
console.log(chalk15.dim(" Config file: ~/.sherwood/config.json"));
|
|
5877
7031
|
console.log();
|
|
5878
7032
|
});
|
|
5879
7033
|
program.parse();
|