@sherwoodagent/cli 0.88.2 → 0.89.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/{abis-WJQ6IVYV.js → abis-2VBXBA7Y.js} +16 -2
- package/dist/{agent-FCGZL4GF.js → agent-KMLNSKBS.js} +11 -11
- package/dist/{chat-AKLYUJZV.js → chat-JW5Z3JRE.js} +13 -13
- package/dist/{chunk-6SAE2VDK.js → chunk-6IC2NRED.js} +2 -2
- package/dist/{chunk-HMFPP2PE.js → chunk-77MBGRE5.js} +52 -24
- package/dist/chunk-77MBGRE5.js.map +1 -0
- package/dist/{chunk-G5MXVAFC.js → chunk-D3GTHAJS.js} +5 -5
- package/dist/chunk-G5FOHSQW.js +338 -0
- package/dist/chunk-G5FOHSQW.js.map +1 -0
- package/dist/chunk-GAQMHH44.js +18 -0
- package/dist/chunk-GAQMHH44.js.map +1 -0
- package/dist/{chunk-FWQESFFM.js → chunk-GYGSKMZE.js} +91 -8
- package/dist/chunk-GYGSKMZE.js.map +1 -0
- package/dist/{chunk-J2IZWAEW.js → chunk-JCL2DEH3.js} +2048 -2
- package/dist/chunk-JCL2DEH3.js.map +1 -0
- package/dist/{chunk-BH3VTJ4F.js → chunk-JKSDCCVL.js} +6 -6
- package/dist/{chunk-EEI6FRTE.js → chunk-JLOROS5V.js} +3 -3
- package/dist/{chunk-6D3FNU75.js → chunk-JRGYI34A.js} +5 -5
- package/dist/chunk-KZGY7NT7.js +209 -0
- package/dist/chunk-KZGY7NT7.js.map +1 -0
- package/dist/{chunk-G3FUEM5N.js → chunk-LIW6K4OJ.js} +2 -2
- package/dist/{chunk-4ITTHKRX.js → chunk-P4LFNKSX.js} +5 -5
- package/dist/{chunk-3TZIOAPH.js → chunk-QUAVAJWE.js} +13 -2
- package/dist/chunk-QUAVAJWE.js.map +1 -0
- package/dist/chunk-QVXKYT2N.js +489 -0
- package/dist/chunk-QVXKYT2N.js.map +1 -0
- package/dist/chunk-UPRFO4YU.js +458 -0
- package/dist/chunk-UPRFO4YU.js.map +1 -0
- package/dist/{chunk-QFGZQYZA.js → chunk-WDPA2BYK.js} +1024 -87
- package/dist/chunk-WDPA2BYK.js.map +1 -0
- package/dist/{chunk-CWGP4VXZ.js → chunk-YIN6R66K.js} +7 -7
- package/dist/{chunk-D4BSBW3Q.js → chunk-Z53JZORT.js} +3 -3
- package/dist/{chunk-SCQAKMMI.js → chunk-ZPOPWFSP.js} +5 -5
- package/dist/{client-PYFE6YQR.js → client-DA434B67.js} +4 -4
- package/dist/{config-ZGFS4I7K.js → config-UZ2GIXAO.js} +6 -2
- package/dist/{eas-B4HR2HXX.js → eas-ZH7BDEOK.js} +7 -7
- package/dist/{governor-QFBN6RYS.js → governor-WHT2V2EU.js} +7 -7
- package/dist/identity-TRPUQEQK.js +19 -0
- package/dist/index.js +7758 -5862
- package/dist/index.js.map +1 -1
- package/dist/launchpad-3N4ZFL6J.js +26 -0
- package/dist/lighter-perp-template-VC4TB7AM.js +92 -0
- package/dist/lighter-preflight-GH3JKLR6.js +149 -0
- package/dist/lighter-preflight-GH3JKLR6.js.map +1 -0
- package/dist/{network-7445KSCF.js → network-EZ5X2BF7.js} +3 -3
- package/dist/network-EZ5X2BF7.js.map +1 -0
- package/dist/{price-EXZDYVDD.js → price-RX5J7GTO.js} +9 -7
- package/dist/{price-EXZDYVDD.js.map → price-RX5J7GTO.js.map} +1 -1
- package/dist/{providers-6UZKDFQD.js → providers-XEEFKITN.js} +5 -5
- package/dist/{research-T7A27OMB.js → research-PYMQ5VKH.js} +9 -9
- package/dist/{session-45NQAHFI.js → session-DE47TUEW.js} +15 -15
- package/dist/{trade-ZOVM433K.js → trade-3VMAQ75D.js} +20 -18
- package/dist/{trade-ZOVM433K.js.map → trade-3VMAQ75D.js.map} +1 -1
- package/dist/{vault-E7XD6DLD.js → vault-PXGG2E3K.js} +9 -9
- package/dist/vault-PXGG2E3K.js.map +1 -0
- package/dist/{xmtp-72IRK7JL.js → xmtp-5PTIIVAI.js} +7 -7
- package/package.json +1 -1
- package/dist/chunk-3TZIOAPH.js.map +0 -1
- package/dist/chunk-FWQESFFM.js.map +0 -1
- package/dist/chunk-HMFPP2PE.js.map +0 -1
- package/dist/chunk-J2IZWAEW.js.map +0 -1
- package/dist/chunk-QFGZQYZA.js.map +0 -1
- package/dist/identity-I2BG4IGT.js +0 -18
- /package/dist/{abis-WJQ6IVYV.js.map → abis-2VBXBA7Y.js.map} +0 -0
- /package/dist/{agent-FCGZL4GF.js.map → agent-KMLNSKBS.js.map} +0 -0
- /package/dist/{chat-AKLYUJZV.js.map → chat-JW5Z3JRE.js.map} +0 -0
- /package/dist/{chunk-6SAE2VDK.js.map → chunk-6IC2NRED.js.map} +0 -0
- /package/dist/{chunk-G5MXVAFC.js.map → chunk-D3GTHAJS.js.map} +0 -0
- /package/dist/{chunk-BH3VTJ4F.js.map → chunk-JKSDCCVL.js.map} +0 -0
- /package/dist/{chunk-EEI6FRTE.js.map → chunk-JLOROS5V.js.map} +0 -0
- /package/dist/{chunk-6D3FNU75.js.map → chunk-JRGYI34A.js.map} +0 -0
- /package/dist/{chunk-G3FUEM5N.js.map → chunk-LIW6K4OJ.js.map} +0 -0
- /package/dist/{chunk-4ITTHKRX.js.map → chunk-P4LFNKSX.js.map} +0 -0
- /package/dist/{chunk-CWGP4VXZ.js.map → chunk-YIN6R66K.js.map} +0 -0
- /package/dist/{chunk-D4BSBW3Q.js.map → chunk-Z53JZORT.js.map} +0 -0
- /package/dist/{chunk-SCQAKMMI.js.map → chunk-ZPOPWFSP.js.map} +0 -0
- /package/dist/{client-PYFE6YQR.js.map → client-DA434B67.js.map} +0 -0
- /package/dist/{config-ZGFS4I7K.js.map → config-UZ2GIXAO.js.map} +0 -0
- /package/dist/{eas-B4HR2HXX.js.map → eas-ZH7BDEOK.js.map} +0 -0
- /package/dist/{governor-QFBN6RYS.js.map → governor-WHT2V2EU.js.map} +0 -0
- /package/dist/{identity-I2BG4IGT.js.map → identity-TRPUQEQK.js.map} +0 -0
- /package/dist/{network-7445KSCF.js.map → launchpad-3N4ZFL6J.js.map} +0 -0
- /package/dist/{vault-E7XD6DLD.js.map → lighter-perp-template-VC4TB7AM.js.map} +0 -0
- /package/dist/{providers-6UZKDFQD.js.map → providers-XEEFKITN.js.map} +0 -0
- /package/dist/{research-T7A27OMB.js.map → research-PYMQ5VKH.js.map} +0 -0
- /package/dist/{session-45NQAHFI.js.map → session-DE47TUEW.js.map} +0 -0
- /package/dist/{xmtp-72IRK7JL.js.map → xmtp-5PTIIVAI.js.map} +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveMetadataSummary
|
|
3
|
+
} from "./chunk-YMB4RJOR.js";
|
|
1
4
|
import {
|
|
2
5
|
PROPOSAL_STATES,
|
|
3
6
|
getProposal
|
|
4
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-D3GTHAJS.js";
|
|
5
8
|
import {
|
|
6
9
|
getPublicClient
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import {
|
|
9
|
-
resolveMetadataSummary
|
|
10
|
-
} from "./chunk-YMB4RJOR.js";
|
|
10
|
+
} from "./chunk-Z53JZORT.js";
|
|
11
11
|
|
|
12
12
|
// src/lib/events.ts
|
|
13
13
|
import { parseAbiItem } from "viem";
|
|
@@ -212,4 +212,4 @@ export {
|
|
|
212
212
|
getCurrentBlock,
|
|
213
213
|
enrichProposalEvents
|
|
214
214
|
};
|
|
215
|
-
//# sourceMappingURL=chunk-
|
|
215
|
+
//# sourceMappingURL=chunk-JKSDCCVL.js.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getAccount
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-Z53JZORT.js";
|
|
4
4
|
import {
|
|
5
5
|
getVeniceApiKey,
|
|
6
6
|
setVeniceApiKey
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-QUAVAJWE.js";
|
|
8
8
|
|
|
9
9
|
// src/lib/venice.ts
|
|
10
10
|
import chalk from "chalk";
|
|
@@ -186,4 +186,4 @@ export {
|
|
|
186
186
|
chatCompletion,
|
|
187
187
|
listModels
|
|
188
188
|
};
|
|
189
|
-
//# sourceMappingURL=chunk-
|
|
189
|
+
//# sourceMappingURL=chunk-JLOROS5V.js.map
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SHERWOOD
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GYGSKMZE.js";
|
|
4
4
|
import {
|
|
5
5
|
getAccount,
|
|
6
6
|
getPublicClient,
|
|
7
7
|
waitForReceipt,
|
|
8
8
|
writeContractWithRetry
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-Z53JZORT.js";
|
|
10
10
|
import {
|
|
11
11
|
getChain
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-LIW6K4OJ.js";
|
|
13
13
|
import {
|
|
14
14
|
SYNDICATE_FACTORY_ABI
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-JCL2DEH3.js";
|
|
16
16
|
|
|
17
17
|
// src/lib/factory.ts
|
|
18
18
|
import { decodeEventLog } from "viem";
|
|
@@ -144,4 +144,4 @@ export {
|
|
|
144
144
|
getActiveFunds,
|
|
145
145
|
updateMetadata
|
|
146
146
|
};
|
|
147
|
-
//# sourceMappingURL=chunk-
|
|
147
|
+
//# sourceMappingURL=chunk-JRGYI34A.js.map
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// src/lib/lighter-api.ts
|
|
2
|
+
var LIGHTER_API_PROFILES = {
|
|
3
|
+
rh: "https://api.rh.lighter.xyz",
|
|
4
|
+
"rh-testnet": "https://api.rh-testnet.lighter.xyz"
|
|
5
|
+
};
|
|
6
|
+
function resolveLighterApiBaseUrl(network, env = process.env) {
|
|
7
|
+
const explicit = env.LIGHTER_API_URL?.trim();
|
|
8
|
+
if (explicit) return explicit.replace(/\/+$/, "");
|
|
9
|
+
const named = env.LIGHTER_ENV?.trim().toLowerCase();
|
|
10
|
+
if (named) {
|
|
11
|
+
if (named in LIGHTER_API_PROFILES) return LIGHTER_API_PROFILES[named];
|
|
12
|
+
throw new Error(`Invalid LIGHTER_ENV "${env.LIGHTER_ENV}" \u2014 expected ${Object.keys(LIGHTER_API_PROFILES).join(" | ")}`);
|
|
13
|
+
}
|
|
14
|
+
if (network === "robinhood-mainnet") return LIGHTER_API_PROFILES.rh;
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
var FORK_UNOBSERVABLE_REASON = "Lighter's sequencer only watches Robinhood mainnet (4663). On the Tenderly fork (9994663) the L1 contract is replayed but no sequencer ever sees the clone, and fork account indices COLLIDE with real mainnet accounts \u2014 the mainnet API would report a stranger's positions and balance, so the CLI never reads it for a fork clone";
|
|
18
|
+
function resolveLighterAccountApi(network, env = process.env) {
|
|
19
|
+
const url = resolveLighterApiBaseUrl(network, env);
|
|
20
|
+
if (network === "robinhood-fork") {
|
|
21
|
+
if (url === null) return { url: null, reason: FORK_UNOBSERVABLE_REASON };
|
|
22
|
+
const real = Object.values(LIGHTER_API_PROFILES).some((p) => new URL(p).host === safeHost(url));
|
|
23
|
+
if (real) {
|
|
24
|
+
return {
|
|
25
|
+
url: null,
|
|
26
|
+
reason: `${url} is a real Lighter sequencer, set via ${env.LIGHTER_API_URL?.trim() ? "LIGHTER_API_URL" : "LIGHTER_ENV"}. It has never seen this fork's clone and fork account indices collide with its real accounts, so it would report a stranger's account \u2014 refusing to read it. Unset it; on the fork use the explicit escapes (--ticks, --assume-drained), or point LIGHTER_API_URL at a mock that serves the fork's venue state`
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return { url };
|
|
30
|
+
}
|
|
31
|
+
if (url === null) {
|
|
32
|
+
return {
|
|
33
|
+
url: null,
|
|
34
|
+
reason: `no Lighter API is configured for ${network}. Set LIGHTER_ENV=${Object.keys(LIGHTER_API_PROFILES).join("|")} or LIGHTER_API_URL to the sequencer that actually runs this clone's account`
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return { url };
|
|
38
|
+
}
|
|
39
|
+
function safeHost(url) {
|
|
40
|
+
try {
|
|
41
|
+
return new URL(url).host;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function decimalString(x) {
|
|
47
|
+
if (x === void 0 || x === null) return null;
|
|
48
|
+
const s = String(x).trim();
|
|
49
|
+
return /^-?\d+(\.\d+)?$/.test(s) ? s : null;
|
|
50
|
+
}
|
|
51
|
+
function isNonzeroDecimal(s) {
|
|
52
|
+
return s !== null && /[1-9]/.test(s);
|
|
53
|
+
}
|
|
54
|
+
var LighterApi = class {
|
|
55
|
+
constructor(baseUrl, fetchImpl = fetch) {
|
|
56
|
+
this.baseUrl = baseUrl;
|
|
57
|
+
this.fetchImpl = fetchImpl;
|
|
58
|
+
}
|
|
59
|
+
async get(path, params) {
|
|
60
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
61
|
+
for (const [k, v] of Object.entries(params ?? {})) {
|
|
62
|
+
if (v !== void 0) url.searchParams.set(k, String(v));
|
|
63
|
+
}
|
|
64
|
+
const res = await this.fetchImpl(url.toString(), { method: "GET", headers: { Accept: "application/json" } });
|
|
65
|
+
if (!res.ok) throw new Error(`Lighter API ${path} \u2192 HTTP ${res.status}`);
|
|
66
|
+
const body = await res.json();
|
|
67
|
+
if (typeof body?.code === "number" && body.code !== 200) {
|
|
68
|
+
const msg = typeof body.message === "string" ? `: ${body.message}` : "";
|
|
69
|
+
throw new Error(`Lighter API ${path} \u2192 code ${body.code}${msg}`);
|
|
70
|
+
}
|
|
71
|
+
return body;
|
|
72
|
+
}
|
|
73
|
+
/** Perp order books (market ids 0..254; spot books are filtered out). */
|
|
74
|
+
async getMarkets() {
|
|
75
|
+
const data = await this.get("/api/v1/orderBooks");
|
|
76
|
+
return (data.order_books ?? []).map((ob) => ({
|
|
77
|
+
marketId: Number(ob.market_id),
|
|
78
|
+
symbol: String(ob.symbol ?? ""),
|
|
79
|
+
status: ob.status === void 0 || ob.status === null ? null : String(ob.status),
|
|
80
|
+
marketType: ob.market_type === void 0 ? null : String(ob.market_type)
|
|
81
|
+
})).filter((m) => Number.isInteger(m.marketId) && m.marketId >= 0 && m.marketId <= 254 && m.marketType !== "spot").map(({ marketType: _t, ...m }) => m);
|
|
82
|
+
}
|
|
83
|
+
/** Account snapshot by index; null when the API does not know the account. */
|
|
84
|
+
async getAccount(accountIndex) {
|
|
85
|
+
const data = await this.get("/api/v1/account", {
|
|
86
|
+
by: "index",
|
|
87
|
+
value: accountIndex
|
|
88
|
+
});
|
|
89
|
+
const acct = data.accounts?.[0];
|
|
90
|
+
if (!acct) return null;
|
|
91
|
+
return parseLighterAccount(acct, accountIndex);
|
|
92
|
+
}
|
|
93
|
+
/** Registered L2 pubkeys for an account (public data: pubkeys only). */
|
|
94
|
+
async getApiKeys(accountIndex, apiKeyIndex) {
|
|
95
|
+
const data = await this.get("/api/v1/apikeys", {
|
|
96
|
+
account_index: accountIndex,
|
|
97
|
+
api_key_index: apiKeyIndex
|
|
98
|
+
});
|
|
99
|
+
return (data.api_keys ?? []).map((k) => ({
|
|
100
|
+
apiKeyIndex: Number(k.api_key_index),
|
|
101
|
+
publicKey: String(k.public_key ?? "")
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
function openPositions(account) {
|
|
106
|
+
return account.positions.filter((p) => p.size === "?" || isNonzeroDecimal(p.size));
|
|
107
|
+
}
|
|
108
|
+
var LIGHTER_USDG_ASSET_INDEX = 3;
|
|
109
|
+
var LIGHTER_EMPTINESS_FIELDS = [
|
|
110
|
+
"collateral",
|
|
111
|
+
"total_asset_value",
|
|
112
|
+
"total_order_count",
|
|
113
|
+
"pending_order_count",
|
|
114
|
+
"positions",
|
|
115
|
+
"assets"
|
|
116
|
+
];
|
|
117
|
+
function intOrNull(x) {
|
|
118
|
+
if (x === void 0 || x === null || x === "") return null;
|
|
119
|
+
const n = Number(x);
|
|
120
|
+
return Number.isInteger(n) ? n : null;
|
|
121
|
+
}
|
|
122
|
+
function decimalOrUnknown(x) {
|
|
123
|
+
return decimalString(x) ?? "?";
|
|
124
|
+
}
|
|
125
|
+
function parseLighterAccount(acct, fallbackIndex) {
|
|
126
|
+
const missingFields = [];
|
|
127
|
+
for (const f of LIGHTER_EMPTINESS_FIELDS) {
|
|
128
|
+
const v = acct[f];
|
|
129
|
+
const absent = f === "positions" || f === "assets" ? !Array.isArray(v) : v === void 0 || v === null;
|
|
130
|
+
if (absent) missingFields.push(f);
|
|
131
|
+
}
|
|
132
|
+
if (!missingFields.includes("collateral") && decimalString(acct.collateral) === null) missingFields.push("collateral");
|
|
133
|
+
if (!missingFields.includes("total_asset_value") && decimalString(acct.total_asset_value) === null) {
|
|
134
|
+
missingFields.push("total_asset_value");
|
|
135
|
+
}
|
|
136
|
+
for (const f of ["total_order_count", "pending_order_count"]) {
|
|
137
|
+
if (!missingFields.includes(f) && intOrNull(acct[f]) === null) missingFields.push(f);
|
|
138
|
+
}
|
|
139
|
+
const positions = Array.isArray(acct.positions) ? acct.positions : [];
|
|
140
|
+
const assets = Array.isArray(acct.assets) ? acct.assets : [];
|
|
141
|
+
const unlocks = Array.isArray(acct.pending_unlocks) ? acct.pending_unlocks : [];
|
|
142
|
+
const shares = Array.isArray(acct.shares) ? acct.shares : [];
|
|
143
|
+
return {
|
|
144
|
+
accountIndex: Number(acct.account_index ?? acct.index ?? fallbackIndex),
|
|
145
|
+
collateral: decimalString(acct.collateral),
|
|
146
|
+
availableBalance: decimalString(acct.available_balance),
|
|
147
|
+
totalAssetValue: decimalString(acct.total_asset_value),
|
|
148
|
+
totalOrderCount: intOrNull(acct.total_order_count),
|
|
149
|
+
pendingOrderCount: intOrNull(acct.pending_order_count),
|
|
150
|
+
totalIsolatedOrderCount: intOrNull(acct.total_isolated_order_count),
|
|
151
|
+
positions: positions.map((p) => ({
|
|
152
|
+
marketId: Number(p.market_id),
|
|
153
|
+
symbol: p.symbol === void 0 ? null : String(p.symbol),
|
|
154
|
+
sign: Number(p.sign ?? 1) < 0 ? -1 : 1,
|
|
155
|
+
// Unparseable size → "?", which openPositions() treats as OPEN (fail closed).
|
|
156
|
+
size: decimalString(p.position) ?? "?",
|
|
157
|
+
avgEntryPrice: decimalString(p.avg_entry_price),
|
|
158
|
+
unrealizedPnl: decimalString(p.unrealized_pnl),
|
|
159
|
+
openOrderCount: intOrNull(p.open_order_count),
|
|
160
|
+
pendingOrderCount: intOrNull(p.pending_order_count),
|
|
161
|
+
positionTiedOrderCount: intOrNull(p.position_tied_order_count),
|
|
162
|
+
allocatedMargin: p.allocated_margin === void 0 || p.allocated_margin === null ? null : decimalOrUnknown(p.allocated_margin),
|
|
163
|
+
marginMode: intOrNull(p.margin_mode)
|
|
164
|
+
})),
|
|
165
|
+
assets: assets.map((a) => ({
|
|
166
|
+
assetId: intOrNull(a.asset_id),
|
|
167
|
+
symbol: a.symbol === void 0 ? null : String(a.symbol),
|
|
168
|
+
balance: decimalOrUnknown(a.balance),
|
|
169
|
+
lockedBalance: decimalOrUnknown(a.locked_balance)
|
|
170
|
+
})),
|
|
171
|
+
pendingUnlocks: unlocks.map((u) => ({ assetIndex: intOrNull(u.asset_index), amount: decimalOrUnknown(u.amount) })).filter((u) => u.amount === "?" || isNonzeroDecimal(u.amount)),
|
|
172
|
+
poolShares: shares.map((sh) => ({ poolIndex: intOrNull(sh.public_pool_index), shares: decimalOrUnknown(sh.shares_amount) })).filter((sh) => sh.shares === "?" || isNonzeroDecimal(sh.shares)),
|
|
173
|
+
missingFields
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
var nonzero = (s) => s === "?" || isNonzeroDecimal(s);
|
|
177
|
+
function venueResidue(a) {
|
|
178
|
+
const out = [];
|
|
179
|
+
const counters = [
|
|
180
|
+
["total_order_count", a.totalOrderCount],
|
|
181
|
+
["pending_order_count", a.pendingOrderCount],
|
|
182
|
+
["total_isolated_order_count", a.totalIsolatedOrderCount]
|
|
183
|
+
];
|
|
184
|
+
for (const [name, n] of counters) if (n !== null && n !== 0) out.push(`${name} = ${n} (resting/pending orders)`);
|
|
185
|
+
for (const p of a.positions) {
|
|
186
|
+
const orders = (p.openOrderCount ?? 0) + (p.pendingOrderCount ?? 0) + (p.positionTiedOrderCount ?? 0);
|
|
187
|
+
if (orders !== 0) out.push(`market ${p.marketId}: ${orders} open/pending/tied order(s)`);
|
|
188
|
+
if (nonzero(p.allocatedMargin)) out.push(`market ${p.marketId}: isolated allocated_margin ${p.allocatedMargin}`);
|
|
189
|
+
}
|
|
190
|
+
for (const as of a.assets) {
|
|
191
|
+
const label = `asset ${as.assetId ?? "?"}${as.symbol ? ` (${as.symbol})` : ""}`;
|
|
192
|
+
if (as.assetId !== LIGHTER_USDG_ASSET_INDEX && nonzero(as.balance)) out.push(`${label}: balance ${as.balance} (not USDG \u2014 queueWithdraw cannot move it)`);
|
|
193
|
+
if (nonzero(as.lockedBalance)) out.push(`${label}: locked_balance ${as.lockedBalance}`);
|
|
194
|
+
}
|
|
195
|
+
for (const u of a.pendingUnlocks) out.push(`pending unlock of ${u.amount} (asset ${u.assetIndex ?? "?"})`);
|
|
196
|
+
for (const sh of a.poolShares) out.push(`public pool ${sh.poolIndex ?? "?"}: ${sh.shares} shares`);
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export {
|
|
201
|
+
LIGHTER_API_PROFILES,
|
|
202
|
+
resolveLighterApiBaseUrl,
|
|
203
|
+
FORK_UNOBSERVABLE_REASON,
|
|
204
|
+
resolveLighterAccountApi,
|
|
205
|
+
LighterApi,
|
|
206
|
+
openPositions,
|
|
207
|
+
venueResidue
|
|
208
|
+
};
|
|
209
|
+
//# sourceMappingURL=chunk-KZGY7NT7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/lighter-api.ts"],"sourcesContent":["/**\n * Lighter (zkLighter) public REST reads — markets, an account's positions and\n * balances, and its registered API keys. No auth, no signer, no Python: the\n * off-chain half of `sherwood lighter …` is plain HTTP.\n *\n * WHICH API ANSWERS FOR WHICH CHAIN. The strategy's positions and margin live\n * with Lighter's off-chain sequencer, which watches Robinhood MAINNET (4663)\n * only. On the Tenderly fork (9994663) the L1 contract is replayed but the\n * sequencer never sees it — and account indices assigned on the fork COLLIDE\n * with real mainnet accounts, so reading the mainnet API for a fork clone\n * would report some stranger's positions. Resolution is therefore:\n * LIGHTER_API_URL explicit base URL — always wins (tests, mirrors)\n * LIGHTER_ENV=rh|rh-testnet a named Lighter domain\n * robinhood-mainnet → rh\n * anything else → null (off-chain state UNOBSERVABLE; callers must\n * refuse anything that depends on it)\n *\n * ACCOUNT reads go through resolveLighterAccountApi(), which additionally\n * refuses to point a FORK clone at a real Lighter sequencer even when the\n * operator set LIGHTER_ENV / LIGHTER_API_URL to one — that is the path that\n * would read a stranger's account. Market-id reads (sequencer config shared by\n * every chain) may still use the mainnet API from the fork.\n */\n\nimport type { Network } from \"./network.js\";\n\nexport const LIGHTER_API_PROFILES = {\n rh: \"https://api.rh.lighter.xyz\",\n \"rh-testnet\": \"https://api.rh-testnet.lighter.xyz\",\n} as const;\n\nexport type LighterApiEnv = keyof typeof LIGHTER_API_PROFILES;\n\nexport function resolveLighterApiBaseUrl(\n network: Network,\n env: NodeJS.ProcessEnv = process.env,\n): string | null {\n const explicit = env.LIGHTER_API_URL?.trim();\n if (explicit) return explicit.replace(/\\/+$/, \"\");\n const named = env.LIGHTER_ENV?.trim().toLowerCase();\n if (named) {\n if (named in LIGHTER_API_PROFILES) return LIGHTER_API_PROFILES[named as LighterApiEnv];\n throw new Error(`Invalid LIGHTER_ENV \"${env.LIGHTER_ENV}\" — expected ${Object.keys(LIGHTER_API_PROFILES).join(\" | \")}`);\n }\n if (network === \"robinhood-mainnet\") return LIGHTER_API_PROFILES.rh;\n return null;\n}\n\n/** Why the venue is unobservable from the fork by default. */\nexport const FORK_UNOBSERVABLE_REASON =\n \"Lighter's sequencer only watches Robinhood mainnet (4663). On the Tenderly fork (9994663) the L1 contract is \" +\n \"replayed but no sequencer ever sees the clone, and fork account indices COLLIDE with real mainnet accounts — the \" +\n \"mainnet API would report a stranger's positions and balance, so the CLI never reads it for a fork clone\";\n\nexport type LighterAccountApi = { url: string; reason?: undefined } | { url: null; reason: string };\n\n/**\n * The API allowed to answer for an ACCOUNT of a clone on `network`, or null\n * with the reason. Never a real Lighter host for the fork (see header).\n */\nexport function resolveLighterAccountApi(network: Network, env: NodeJS.ProcessEnv = process.env): LighterAccountApi {\n const url = resolveLighterApiBaseUrl(network, env);\n if (network === \"robinhood-fork\") {\n if (url === null) return { url: null, reason: FORK_UNOBSERVABLE_REASON };\n const real = (Object.values(LIGHTER_API_PROFILES) as string[]).some((p) => new URL(p).host === safeHost(url));\n if (real) {\n return {\n url: null,\n reason:\n `${url} is a real Lighter sequencer, set via ${env.LIGHTER_API_URL?.trim() ? \"LIGHTER_API_URL\" : \"LIGHTER_ENV\"}. ` +\n `It has never seen this fork's clone and fork account indices collide with its real accounts, so it would ` +\n `report a stranger's account — refusing to read it. Unset it; on the fork use the explicit escapes ` +\n `(--ticks, --assume-drained), or point LIGHTER_API_URL at a mock that serves the fork's venue state`,\n };\n }\n return { url };\n }\n if (url === null) {\n return {\n url: null,\n reason:\n `no Lighter API is configured for ${network}. Set LIGHTER_ENV=${Object.keys(LIGHTER_API_PROFILES).join(\"|\")} ` +\n \"or LIGHTER_API_URL to the sequencer that actually runs this clone's account\",\n };\n }\n return { url };\n}\n\nfunction safeHost(url: string): string | null {\n try {\n return new URL(url).host;\n } catch {\n return null;\n }\n}\n\n// ── Parsed shapes (only what the CLI consumes) ──\n\nexport interface LighterMarket {\n marketId: number;\n symbol: string;\n status: string | null;\n}\n\nexport interface LighterPosition {\n marketId: number;\n symbol: string | null;\n /** 1 = long, -1 = short (the API reports magnitude + sign separately). */\n sign: number;\n /** Position size as the API's exact decimal string. */\n size: string;\n avgEntryPrice: string | null;\n unrealizedPnl: string | null;\n /** Resting / pending / TP-SL orders on this market (null = field absent). */\n openOrderCount: number | null;\n pendingOrderCount: number | null;\n positionTiedOrderCount: number | null;\n /** Isolated margin parked on this market — NOT in available_balance, even with size 0. */\n allocatedMargin: string | null;\n marginMode: number | null;\n}\n\n/** One `assets[]` entry (spot balances; USDG is asset 3, spot markets use ids 2049+). */\nexport interface LighterAccountAsset {\n assetId: number | null;\n symbol: string | null;\n /** Exact decimal strings; \"?\" when present but unparseable (treated as nonzero). */\n balance: string;\n lockedBalance: string;\n}\n\nexport interface LighterAccount {\n accountIndex: number;\n /** Exact decimal strings (never floats) — the drain math floors them to ticks. */\n collateral: string | null;\n availableBalance: string | null;\n /** Everything the account holds, valued in USDG (collateral + spot + uPnL). */\n totalAssetValue: string | null;\n /** Account-wide order counters (null = field absent). */\n totalOrderCount: number | null;\n pendingOrderCount: number | null;\n totalIsolatedOrderCount: number | null;\n positions: LighterPosition[];\n assets: LighterAccountAsset[];\n /** Pending unlocks (staking / pool exits) with a nonzero amount. */\n pendingUnlocks: Array<{ assetIndex: number | null; amount: string }>;\n /** Public-pool (LLP) share holdings with a nonzero amount. */\n poolShares: Array<{ poolIndex: number | null; shares: string }>;\n /**\n * Fields the emptiness proof needs that the response did NOT carry. Any entry\n * here means \"cannot prove the account is empty\" — callers fail closed.\n */\n missingFields: string[];\n}\n\nexport interface LighterApiKey {\n apiKeyIndex: number;\n publicKey: string;\n}\n\ntype FetchLike = (url: string, init?: { method?: string; headers?: Record<string, string> }) => Promise<{\n ok: boolean;\n status: number;\n json(): Promise<unknown>;\n}>;\n\n/** Keep a numeric field as its exact decimal string; null for anything else. */\nfunction decimalString(x: unknown): string | null {\n if (x === undefined || x === null) return null;\n const s = String(x).trim();\n return /^-?\\d+(\\.\\d+)?$/.test(s) ? s : null;\n}\n\n/** True when a decimal string is nonzero (\"0\", \"0.000\", \"-0.0\" are zero). */\nexport function isNonzeroDecimal(s: string | null): boolean {\n return s !== null && /[1-9]/.test(s);\n}\n\nexport class LighterApi {\n constructor(\n readonly baseUrl: string,\n private readonly fetchImpl: FetchLike = fetch as unknown as FetchLike,\n ) {}\n\n private async get<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n for (const [k, v] of Object.entries(params ?? {})) {\n if (v !== undefined) url.searchParams.set(k, String(v));\n }\n const res = await this.fetchImpl(url.toString(), { method: \"GET\", headers: { Accept: \"application/json\" } });\n if (!res.ok) throw new Error(`Lighter API ${path} → HTTP ${res.status}`);\n const body = (await res.json()) as T & { code?: unknown; message?: unknown };\n // Lighter answers application errors with HTTP 200 and a body-level code;\n // without this an error body parses as an empty list.\n if (typeof body?.code === \"number\" && body.code !== 200) {\n const msg = typeof body.message === \"string\" ? `: ${body.message}` : \"\";\n throw new Error(`Lighter API ${path} → code ${body.code}${msg}`);\n }\n return body;\n }\n\n /** Perp order books (market ids 0..254; spot books are filtered out). */\n async getMarkets(): Promise<LighterMarket[]> {\n const data = await this.get<{ order_books?: Array<Record<string, unknown>> }>(\"/api/v1/orderBooks\");\n return (data.order_books ?? [])\n .map((ob) => ({\n marketId: Number(ob.market_id),\n symbol: String(ob.symbol ?? \"\"),\n status: ob.status === undefined || ob.status === null ? null : String(ob.status),\n marketType: ob.market_type === undefined ? null : String(ob.market_type),\n }))\n .filter((m) => Number.isInteger(m.marketId) && m.marketId >= 0 && m.marketId <= 254 && m.marketType !== \"spot\")\n .map(({ marketType: _t, ...m }) => m);\n }\n\n /** Account snapshot by index; null when the API does not know the account. */\n async getAccount(accountIndex: number): Promise<LighterAccount | null> {\n const data = await this.get<{ accounts?: Array<Record<string, unknown>> }>(\"/api/v1/account\", {\n by: \"index\",\n value: accountIndex,\n });\n const acct = data.accounts?.[0];\n if (!acct) return null;\n return parseLighterAccount(acct, accountIndex);\n }\n\n /** Registered L2 pubkeys for an account (public data: pubkeys only). */\n async getApiKeys(accountIndex: number, apiKeyIndex?: number): Promise<LighterApiKey[]> {\n const data = await this.get<{ api_keys?: Array<Record<string, unknown>> }>(\"/api/v1/apikeys\", {\n account_index: accountIndex,\n api_key_index: apiKeyIndex,\n });\n return (data.api_keys ?? []).map((k) => ({\n apiKeyIndex: Number(k.api_key_index),\n publicKey: String(k.public_key ?? \"\"),\n }));\n }\n}\n\n/** Positions whose size is not zero — i.e. margin still locked at the venue. */\nexport function openPositions(account: LighterAccount): LighterPosition[] {\n return account.positions.filter((p) => p.size === \"?\" || isNonzeroDecimal(p.size));\n}\n\n/** Lighter's asset index for USDG — the only asset `queueWithdraw` moves. */\nexport const LIGHTER_USDG_ASSET_INDEX = 3;\n\n/**\n * The fields an emptiness proof reads. The live `/api/v1/account` returns all\n * of them (lighter-sdk `DetailedAccount`); a response without one of them\n * cannot prove the account holds nothing, so it counts as \"not proven\".\n */\nexport const LIGHTER_EMPTINESS_FIELDS = [\n \"collateral\",\n \"total_asset_value\",\n \"total_order_count\",\n \"pending_order_count\",\n \"positions\",\n \"assets\",\n] as const;\n\nfunction intOrNull(x: unknown): number | null {\n if (x === undefined || x === null || x === \"\") return null;\n const n = Number(x);\n return Number.isInteger(n) ? n : null;\n}\n\n/**\n * An absent or unparseable decimal becomes \"?\" — nonzero for every check, so an\n * entry that cannot prove it is empty blocks (fail closed).\n */\nfunction decimalOrUnknown(x: unknown): string {\n return decimalString(x) ?? \"?\";\n}\n\n/** Parse one `/api/v1/account` entry. Exported for tests. */\nexport function parseLighterAccount(acct: Record<string, unknown>, fallbackIndex: number): LighterAccount {\n const missingFields: string[] = [];\n for (const f of LIGHTER_EMPTINESS_FIELDS) {\n const v = acct[f];\n const absent = f === \"positions\" || f === \"assets\" ? !Array.isArray(v) : v === undefined || v === null;\n if (absent) missingFields.push(f);\n }\n // A present-but-garbled number is as unprovable as an absent one.\n if (!missingFields.includes(\"collateral\") && decimalString(acct.collateral) === null) missingFields.push(\"collateral\");\n if (!missingFields.includes(\"total_asset_value\") && decimalString(acct.total_asset_value) === null) {\n missingFields.push(\"total_asset_value\");\n }\n for (const f of [\"total_order_count\", \"pending_order_count\"] as const) {\n if (!missingFields.includes(f) && intOrNull(acct[f]) === null) missingFields.push(f);\n }\n\n const positions = Array.isArray(acct.positions) ? (acct.positions as Array<Record<string, unknown>>) : [];\n const assets = Array.isArray(acct.assets) ? (acct.assets as Array<Record<string, unknown>>) : [];\n const unlocks = Array.isArray(acct.pending_unlocks) ? (acct.pending_unlocks as Array<Record<string, unknown>>) : [];\n const shares = Array.isArray(acct.shares) ? (acct.shares as Array<Record<string, unknown>>) : [];\n return {\n accountIndex: Number(acct.account_index ?? acct.index ?? fallbackIndex),\n collateral: decimalString(acct.collateral),\n availableBalance: decimalString(acct.available_balance),\n totalAssetValue: decimalString(acct.total_asset_value),\n totalOrderCount: intOrNull(acct.total_order_count),\n pendingOrderCount: intOrNull(acct.pending_order_count),\n totalIsolatedOrderCount: intOrNull(acct.total_isolated_order_count),\n positions: positions.map((p) => ({\n marketId: Number(p.market_id),\n symbol: p.symbol === undefined ? null : String(p.symbol),\n sign: Number(p.sign ?? 1) < 0 ? -1 : 1,\n // Unparseable size → \"?\", which openPositions() treats as OPEN (fail closed).\n size: decimalString(p.position) ?? \"?\",\n avgEntryPrice: decimalString(p.avg_entry_price),\n unrealizedPnl: decimalString(p.unrealized_pnl),\n openOrderCount: intOrNull(p.open_order_count),\n pendingOrderCount: intOrNull(p.pending_order_count),\n positionTiedOrderCount: intOrNull(p.position_tied_order_count),\n allocatedMargin: p.allocated_margin === undefined || p.allocated_margin === null ? null : decimalOrUnknown(p.allocated_margin),\n marginMode: intOrNull(p.margin_mode),\n })),\n assets: assets.map((a) => ({\n assetId: intOrNull(a.asset_id),\n symbol: a.symbol === undefined ? null : String(a.symbol),\n balance: decimalOrUnknown(a.balance),\n lockedBalance: decimalOrUnknown(a.locked_balance),\n })),\n pendingUnlocks: unlocks\n .map((u) => ({ assetIndex: intOrNull(u.asset_index), amount: decimalOrUnknown(u.amount) }))\n .filter((u) => u.amount === \"?\" || isNonzeroDecimal(u.amount)),\n poolShares: shares\n .map((sh) => ({ poolIndex: intOrNull(sh.public_pool_index), shares: decimalOrUnknown(sh.shares_amount) }))\n .filter((sh) => sh.shares === \"?\" || isNonzeroDecimal(sh.shares)),\n missingFields,\n };\n}\n\nconst nonzero = (s: string | null) => s === \"?\" || isNonzeroDecimal(s);\n\n/**\n * Everything on the account — OTHER than an open position (see openPositions)\n * — that `queueWithdraw` cannot move and that would therefore be stranded for\n * good once the clone settles (after settle `guardrailAction` is gone, and\n * `queueWithdraw` only ever moves USDG asset 3 out of the perps route):\n * - any resting / pending order (account-wide or per market) — its margin is\n * reserved out of available_balance, and it can still fill;\n * - isolated margin allocated to a market, even with a zero position;\n * - any non-USDG asset balance, or any LOCKED balance (spot orders);\n * - pending unlocks and public-pool shares.\n * Pure; one human-readable line per finding.\n */\nexport function venueResidue(a: LighterAccount): string[] {\n const out: string[] = [];\n const counters: [string, number | null][] = [\n [\"total_order_count\", a.totalOrderCount],\n [\"pending_order_count\", a.pendingOrderCount],\n [\"total_isolated_order_count\", a.totalIsolatedOrderCount],\n ];\n for (const [name, n] of counters) if (n !== null && n !== 0) out.push(`${name} = ${n} (resting/pending orders)`);\n for (const p of a.positions) {\n const orders = (p.openOrderCount ?? 0) + (p.pendingOrderCount ?? 0) + (p.positionTiedOrderCount ?? 0);\n if (orders !== 0) out.push(`market ${p.marketId}: ${orders} open/pending/tied order(s)`);\n if (nonzero(p.allocatedMargin)) out.push(`market ${p.marketId}: isolated allocated_margin ${p.allocatedMargin}`);\n }\n for (const as of a.assets) {\n const label = `asset ${as.assetId ?? \"?\"}${as.symbol ? ` (${as.symbol})` : \"\"}`;\n if (as.assetId !== LIGHTER_USDG_ASSET_INDEX && nonzero(as.balance)) out.push(`${label}: balance ${as.balance} (not USDG — queueWithdraw cannot move it)`);\n if (nonzero(as.lockedBalance)) out.push(`${label}: locked_balance ${as.lockedBalance}`);\n }\n for (const u of a.pendingUnlocks) out.push(`pending unlock of ${u.amount} (asset ${u.assetIndex ?? \"?\"})`);\n for (const sh of a.poolShares) out.push(`public pool ${sh.poolIndex ?? \"?\"}: ${sh.shares} shares`);\n return out;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getConfigRpcUrl
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-QUAVAJWE.js";
|
|
4
4
|
|
|
5
5
|
// src/lib/network.ts
|
|
6
6
|
import { defineChain, parseEther } from "viem";
|
|
@@ -186,4 +186,4 @@ export {
|
|
|
186
186
|
getExplorerUrl,
|
|
187
187
|
isTestnet
|
|
188
188
|
};
|
|
189
|
-
//# sourceMappingURL=chunk-
|
|
189
|
+
//# sourceMappingURL=chunk-LIW6K4OJ.js.map
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ENS,
|
|
3
3
|
SHERWOOD
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-GYGSKMZE.js";
|
|
5
5
|
import {
|
|
6
6
|
getPublicClient
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-Z53JZORT.js";
|
|
8
8
|
import {
|
|
9
9
|
VALID_NETWORKS,
|
|
10
10
|
withNetwork
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-LIW6K4OJ.js";
|
|
12
12
|
import {
|
|
13
13
|
L2_REGISTRY_ABI,
|
|
14
14
|
SYNDICATE_FACTORY_ABI
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-JCL2DEH3.js";
|
|
16
16
|
|
|
17
17
|
// src/lib/ens.ts
|
|
18
18
|
import { namehash } from "viem/ens";
|
|
@@ -111,4 +111,4 @@ export {
|
|
|
111
111
|
setTextRecord,
|
|
112
112
|
getTextRecord
|
|
113
113
|
};
|
|
114
|
-
//# sourceMappingURL=chunk-
|
|
114
|
+
//# sourceMappingURL=chunk-P4LFNKSX.js.map
|
|
@@ -249,6 +249,15 @@ function clearSwapRoutes(chainId) {
|
|
|
249
249
|
delete config.swapRoutes?.[String(chainId)];
|
|
250
250
|
saveConfig(config);
|
|
251
251
|
}
|
|
252
|
+
function getLighterKeyConfig() {
|
|
253
|
+
return loadConfig().lighter;
|
|
254
|
+
}
|
|
255
|
+
function setLighterKeyConfig(key) {
|
|
256
|
+
const config = loadConfig();
|
|
257
|
+
config.lighter = key;
|
|
258
|
+
saveConfig(config);
|
|
259
|
+
fs.chmodSync(CONFIG_PATH, 384);
|
|
260
|
+
}
|
|
252
261
|
|
|
253
262
|
export {
|
|
254
263
|
loadConfig,
|
|
@@ -282,6 +291,8 @@ export {
|
|
|
282
291
|
getPrimaryFund,
|
|
283
292
|
getCachedSwapRoute,
|
|
284
293
|
cacheSwapRoute,
|
|
285
|
-
clearSwapRoutes
|
|
294
|
+
clearSwapRoutes,
|
|
295
|
+
getLighterKeyConfig,
|
|
296
|
+
setLighterKeyConfig
|
|
286
297
|
};
|
|
287
|
-
//# sourceMappingURL=chunk-
|
|
298
|
+
//# sourceMappingURL=chunk-QUAVAJWE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/config.ts","../src/lib/settings.ts"],"sourcesContent":["/**\n * Local config management — ~/.sherwood/config.json\n *\n * Stores group ID cache, per-chain contract addresses, and wallet config.\n */\n\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { resolveSetting } from \"./settings.js\";\n\nconst CONFIG_DIR = path.join(os.homedir(), \".sherwood\");\nconst CONFIG_PATH = path.join(CONFIG_DIR, \"config.json\");\nconst DASHBOARD_BRIDGE_PATH = path.join(CONFIG_DIR, \"dashboard.json\");\n\n/** Per-chain user-specific addresses (stored by chainId). */\nexport interface ChainContracts {\n vault?: string; // user's default vault address\n}\n\n/** A fund the agent belongs to (creator or member). */\nexport interface FundMembership {\n subdomain: string;\n vault: string;\n role: \"creator\" | \"agent\";\n}\n\nexport interface SherwoodConfig {\n dbEncryptionKey?: string; // legacy — no longer used, XMTP CLI manages its own DB\n privateKey?: string; // wallet private key (0x-prefixed)\n xmtpInboxId?: string;\n groupCache: Record<string, string>; // subdomain → XMTP group ID\n veniceApiKey?: string; // Venice AI inference API key\n anthropicApiKey?: string; // Anthropic Claude API key (for LLM judge)\n agentId?: number; // ERC-8004 identity token ID\n identityProvider?: \"agent0\" | \"virtuals\"; // who issued the identity (absent ⇒ agent0)\n identityChainId?: number; // chain the ERC-8004 NFT lives on (absent ⇒ active chain)\n virtuals?: VirtualsIdentity; // economyOS link details (identityProvider === \"virtuals\")\n contracts?: Record<string, ChainContracts>; // chainId → user addresses\n funds?: Record<string, FundMembership[]>; // chainId → fund memberships\n primaryFund?: Record<string, string>; // chainId → subdomain of the active fund\n rpc?: Record<string, string>; // network name → custom RPC URL\n notifyTo?: string; // destination for cron summaries (Telegram chat ID, phone, etc.)\n uniswapApiKey?: string; // Uniswap Trading API key (from developers.uniswap.org)\n positions?: unknown[]; // open trade positions (typed in positions.ts)\n closedPositions?: unknown[]; // historical closed positions\n _xmtpMigrated?: boolean; // one-time flag: revoked stale installations from ~/.xmtp/ era\n /** Cached swap routes per chain: chainId → { tokenPair → SwapRoute } */\n swapRoutes?: Record<string, Record<string, SwapRoute>>;\n // ── lighter ──\n /** Lighter L2 agent key from `sherwood lighter keygen` (see LighterKeyConfig). */\n lighter?: LighterKeyConfig;\n}\n\n/** Linked Virtuals economyOS identity (see lib/virtuals.ts). */\nexport interface VirtualsIdentity {\n agentId?: string; // Virtuals platform agent ID (opaque, e.g. \"abc-123\")\n wallet: string; // economyOS agent wallet (identity holder)\n binding?: {\n sig: string; // EIP-191 signature by the economyOS wallet\n message: string; // the exact signed binding message\n };\n}\n\n/** Persisted swap route detected by the CLI during portfolio strategy proposal. */\nexport interface SwapRoute {\n mode: \"direct\" | \"multi-hop\";\n feeTier: number; // direct pool fee tier\n hop?: { via: string; feeIn: number; feeOut: number }; // multi-hop details\n detectedAt: number; // unix timestamp\n}\n\nexport function loadConfig(): SherwoodConfig {\n if (fs.existsSync(CONFIG_PATH)) {\n const config = JSON.parse(fs.readFileSync(CONFIG_PATH, \"utf-8\"));\n if (!config.groupCache) config.groupCache = {};\n return config;\n }\n\n return { groupCache: {} };\n}\n\nexport function saveConfig(config: SherwoodConfig): void {\n fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });\n fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });\n writeDashboardBridge(config);\n}\n\n/**\n * Public-safe derivation of config.json for Hermes dashboard plugins to read.\n * Excludes privateKey + every other sensitive field. Hermes' sherwood-companion\n * plugin reads ~/.sherwood/dashboard.json to auto-detect the operator's\n * primary fund without prompting.\n *\n * Wrapped in try/catch — bridge-write failure must not break a config save.\n */\nfunction writeDashboardBridge(config: SherwoodConfig): void {\n try {\n let agentWallet: string | null = null;\n if (config.privateKey) {\n const key = config.privateKey.startsWith(\"0x\")\n ? (config.privateKey as `0x${string}`)\n : (`0x${config.privateKey}` as `0x${string}`);\n agentWallet = privateKeyToAccount(key).address;\n }\n const bridge = {\n version: 1,\n agentWallet,\n funds: config.funds ?? {},\n primaryFund: config.primaryFund ?? {},\n };\n fs.writeFileSync(\n DASHBOARD_BRIDGE_PATH,\n JSON.stringify(bridge, null, 2),\n { mode: 0o600 },\n );\n } catch {\n // Non-fatal: plugin will fall back to manual subdomain entry.\n }\n}\n\nexport function cacheGroupId(subdomain: string, groupId: string): void {\n const config = loadConfig();\n config.groupCache[subdomain] = groupId;\n saveConfig(config);\n}\n\nexport function getCachedGroupId(subdomain: string): string | undefined {\n const config = loadConfig();\n return config.groupCache[subdomain];\n}\n\nexport function invalidateCachedGroupId(subdomain: string): void {\n const config = loadConfig();\n if (config.groupCache[subdomain]) {\n delete config.groupCache[subdomain];\n saveConfig(config);\n }\n}\n\nexport function setVeniceApiKey(apiKey: string): void {\n const config = loadConfig();\n config.veniceApiKey = apiKey;\n saveConfig(config);\n}\n\nexport function getVeniceApiKey(): string | undefined {\n return resolveSetting(\"veniceApiKey\", loadConfig(), process.env);\n}\n\n// ── Anthropic API Key ──\n\nexport function setAnthropicApiKey(apiKey: string): void {\n const config = loadConfig();\n config.anthropicApiKey = apiKey;\n saveConfig(config);\n}\n\nexport function getAnthropicApiKey(): string | undefined {\n return resolveSetting(\"anthropicApiKey\", loadConfig(), process.env);\n}\n\n/**\n * Bind an agent0-minted (or loaded) identity. Clears any previously linked\n * Virtuals state — otherwise `fund join` would emit a vid tag mixing the new\n * agent0 token with the stale economyOS wallet/binding (renders ✘ to\n * creators) and `identity status` would misreport the provider.\n */\nexport function setAgentId(agentId: number): void {\n const config = loadConfig();\n config.agentId = agentId;\n delete config.identityProvider;\n delete config.identityChainId;\n delete config.virtuals;\n saveConfig(config);\n}\n\n/**\n * Persist a linked Virtuals economyOS identity. Sets the ERC-8004 token ID\n * as the active agentId and stamps provider + issuing chain alongside it.\n */\nexport function setVirtualsIdentity(\n tokenId: number,\n issuingChainId: number,\n identity: VirtualsIdentity,\n): void {\n const config = loadConfig();\n config.agentId = tokenId;\n config.identityProvider = \"virtuals\";\n config.identityChainId = issuingChainId;\n config.virtuals = identity;\n saveConfig(config);\n}\n\nexport function getVirtualsIdentity(): VirtualsIdentity | undefined {\n return loadConfig().virtuals;\n}\n\n/** Identity provider for the saved agentId. Absent field ⇒ legacy agent0 mint. */\nexport function getIdentityProvider(): \"agent0\" | \"virtuals\" {\n return loadConfig().identityProvider ?? \"agent0\";\n}\n\nexport function getIdentityChainId(): number | undefined {\n return loadConfig().identityChainId;\n}\n\nexport function getAgentId(): number | undefined {\n return loadConfig().agentId;\n}\n\nexport function setPrivateKey(key: string): void {\n const config = loadConfig();\n config.privateKey = key.startsWith(\"0x\") ? key : `0x${key}`;\n saveConfig(config);\n}\n\n/**\n * Resolve the signing key (config → PRIVATE_KEY env, 0x-normalized) or\n * throw with the canonical setup hint. Every path that signs or derives\n * identity from the key (viem clients, Agent0 SDK, XMTP) must use this,\n * so they can never resolve different keys.\n */\nexport function requirePrivateKey(): `0x${string}` {\n const key = resolveSetting(\"privateKey\", loadConfig(), process.env);\n if (!key) {\n throw new Error(\n \"Private key not found. Run 'sherwood config set --private-key <key>' or set PRIVATE_KEY env var.\",\n );\n }\n return key as `0x${string}`;\n}\n\n// ── Uniswap Trading API Key ──\n\nexport function setUniswapApiKey(apiKey: string): void {\n const config = loadConfig();\n config.uniswapApiKey = apiKey;\n saveConfig(config);\n}\n\nexport function getUniswapApiKey(): string | undefined {\n return resolveSetting(\"uniswapApiKey\", loadConfig(), process.env);\n}\n\n// ── Per-network RPC URLs ──\n\nexport function getConfigRpcUrl(network: string): string | undefined {\n return loadConfig().rpc?.[network];\n}\n\nexport function setConfigRpcUrl(network: string, url: string): void {\n const config = loadConfig();\n if (!config.rpc) config.rpc = {};\n config.rpc[network] = url;\n saveConfig(config);\n}\n\n// ── Per-chain contract addresses ──\n\nexport function getChainContracts(chainId: number): ChainContracts {\n const config = loadConfig();\n return config.contracts?.[String(chainId)] ?? {};\n}\n\nexport function getNotifyTo(): string | undefined {\n return loadConfig().notifyTo;\n}\n\nexport function setNotifyTo(id: string): void {\n const config = loadConfig();\n config.notifyTo = id;\n saveConfig(config);\n}\n\nexport function setChainContract(\n chainId: number,\n key: keyof ChainContracts,\n value: string,\n): void {\n const config = loadConfig();\n if (!config.contracts) config.contracts = {};\n const cid = String(chainId);\n if (!config.contracts[cid]) config.contracts[cid] = {};\n config.contracts[cid][key] = value;\n saveConfig(config);\n}\n\n// ── Fund Membership ──\n\n/**\n * Add (or update) a fund membership. Deduplicates by subdomain.\n * Automatically sets it as primary if it's the first fund on this chain.\n */\nexport function addFund(\n chainId: number,\n membership: FundMembership,\n): void {\n const config = loadConfig();\n const cid = String(chainId);\n if (!config.funds) config.funds = {};\n if (!config.funds[cid]) config.funds[cid] = [];\n\n // Upsert — replace existing entry with same subdomain\n const idx = config.funds[cid].findIndex(\n (s) => s.subdomain === membership.subdomain,\n );\n if (idx >= 0) {\n config.funds[cid][idx] = membership;\n } else {\n config.funds[cid].push(membership);\n }\n\n // Auto-set as primary if it's the first fund (or no primary set)\n if (!config.primaryFund) config.primaryFund = {};\n if (!config.primaryFund[cid]) {\n config.primaryFund[cid] = membership.subdomain;\n }\n\n saveConfig(config);\n}\n\n/** Get all fund memberships for a chain. */\nexport function getFunds(chainId: number): FundMembership[] {\n const config = loadConfig();\n return config.funds?.[String(chainId)] ?? [];\n}\n\n/** Set which fund is the active/primary one for CLI commands. */\nexport function setPrimaryFund(\n chainId: number,\n subdomain: string,\n): void {\n const config = loadConfig();\n if (!config.primaryFund) config.primaryFund = {};\n config.primaryFund[String(chainId)] = subdomain;\n saveConfig(config);\n}\n\n/**\n * Get the primary fund for this chain.\n * Falls back to ChainContracts.vault for backwards compat with older configs.\n */\nexport function getPrimaryFund(\n chainId: number,\n): FundMembership | undefined {\n const config = loadConfig();\n const cid = String(chainId);\n\n // Check new funds store first\n const primarySubdomain = config.primaryFund?.[cid];\n if (primarySubdomain && config.funds?.[cid]) {\n const found = config.funds[cid].find(\n (s) => s.subdomain === primarySubdomain,\n );\n if (found) return found;\n }\n\n // Backwards compat: if funds list has entries but no primary, use the first one\n if (config.funds?.[cid]?.length) {\n return config.funds[cid][0];\n }\n\n // Legacy fallback: config only has a vault address, no subdomain\n const vault = config.contracts?.[cid]?.vault;\n if (vault) {\n return { subdomain: \"\", vault, role: \"creator\" };\n }\n\n return undefined;\n}\n\n// ── Swap Route Cache ──\n\nfunction swapRouteKey(asset: string, token: string): string {\n return `${asset.toLowerCase()}→${token.toLowerCase()}`;\n}\n\n/** Get a cached swap route for a token pair on a chain. */\nexport function getCachedSwapRoute(chainId: number, asset: string, token: string): SwapRoute | undefined {\n const config = loadConfig();\n return config.swapRoutes?.[String(chainId)]?.[swapRouteKey(asset, token)];\n}\n\n/** Save a detected swap route to the config cache. */\nexport function cacheSwapRoute(chainId: number, asset: string, token: string, route: SwapRoute): void {\n const config = loadConfig();\n const cid = String(chainId);\n if (!config.swapRoutes) config.swapRoutes = {};\n if (!config.swapRoutes[cid]) config.swapRoutes[cid] = {};\n config.swapRoutes[cid][swapRouteKey(asset, token)] = route;\n saveConfig(config);\n}\n\n/** Clear all cached swap routes for a chain (e.g. after pool migration). */\nexport function clearSwapRoutes(chainId: number): void {\n const config = loadConfig();\n delete config.swapRoutes?.[String(chainId)];\n saveConfig(config);\n}\n\n// ── lighter ──\n//\n// The Lighter L2 agent key `sherwood lighter keygen` generates. The pubkey goes\n// into a lighter-perp proposal's init data (`--pubkey` defaults to it); the\n// private key is the off-chain agent's TRADE-ONLY secret — it can place and\n// cancel orders on the strategy's account but can never withdraw (withdrawals\n// are venue-authed to the strategy contract). Kept in the 0600 config file.\n\nexport interface LighterKeyConfig {\n pubKey: string; // 40-byte Goldilocks pubkey, 0x-hex\n privateKey: string; // L2 API private key, 0x-hex\n apiKeyIndex: number; // 2..254\n createdAt: string; // ISO timestamp\n}\n\nexport function getLighterKeyConfig(): LighterKeyConfig | undefined {\n return loadConfig().lighter;\n}\n\n/**\n * Saves the L2 PRIVATE key, so it also forces config.json to 0600: saveConfig's\n * `mode` only applies when the file is created, and an existing config may be\n * 0644 (older CLIs, a hand-edit, a restore).\n */\nexport function setLighterKeyConfig(key: LighterKeyConfig): void {\n const config = loadConfig();\n config.lighter = key;\n saveConfig(config);\n fs.chmodSync(CONFIG_PATH, 0o600);\n}\n// ── /lighter ──\n","/**\n * Single owner of config → env resolution for user settings. Every\n * config-or-env setting resolves through the table below — accessors in\n * config.ts and requirePrivateKey() delegate here.\n */\n\nimport { type SherwoodConfig } from \"./config.js\";\n\ntype Env = Record<string, string | undefined>;\n\nconst hex0x = (v: string): string => (v.startsWith(\"0x\") ? v : `0x${v}`);\n\ninterface SettingSpec {\n fromConfig: (c: Partial<SherwoodConfig>) => string | undefined;\n envVar?: string;\n normalize?: (v: string) => string;\n}\n\nconst SETTINGS = {\n privateKey: {\n fromConfig: (c) => c.privateKey,\n envVar: \"PRIVATE_KEY\",\n normalize: hex0x,\n },\n veniceApiKey: {\n fromConfig: (c) => c.veniceApiKey,\n envVar: \"VENICE_API_KEY\",\n },\n anthropicApiKey: {\n fromConfig: (c) => c.anthropicApiKey,\n envVar: \"ANTHROPIC_API_KEY\",\n },\n uniswapApiKey: {\n fromConfig: (c) => c.uniswapApiKey,\n envVar: \"UNISWAP_API_KEY\",\n },\n} satisfies Record<string, SettingSpec>;\n\nexport type SettingKey = keyof typeof SETTINGS;\n\nexport function resolveSetting(\n key: SettingKey,\n config: Partial<SherwoodConfig>,\n env: Env,\n): string | undefined {\n const spec: SettingSpec = SETTINGS[key];\n // Empty string counts as absent on both sources: an empty config key\n // falls through to the env, and a blank `PRIVATE_KEY=` dotenv line\n // resolves to undefined (so callers show the setup hint) instead of\n // normalizing to a bare \"0x\".\n const raw =\n spec.fromConfig(config) || (spec.envVar ? env[spec.envVar] : undefined);\n if (!raw) return undefined;\n return spec.normalize ? spec.normalize(raw) : raw;\n}\n"],"mappings":"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","names":[]}
|