@moly-mcp/lido 1.0.1 → 1.0.2
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/bin.js +21 -6
- package/dist/chunk-RE3UIDLV.js +545 -0
- package/dist/server/index.js +18 -526
- package/dist/session-55BKKXQV.js +494 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -185,8 +185,13 @@ async function runWizard() {
|
|
|
185
185
|
if (snippet) {
|
|
186
186
|
note(snippet, "Add to your AI client config");
|
|
187
187
|
}
|
|
188
|
-
|
|
189
|
-
|
|
188
|
+
const terminalMode = ai !== null && clientChoice === "none";
|
|
189
|
+
if (terminalMode) {
|
|
190
|
+
outro(`Launching Moly Terminal... ${mode} \xB7 ${network} \xB7 ready`);
|
|
191
|
+
} else {
|
|
192
|
+
outro(`Starting Moly MCP Server... ${mode} \xB7 ${network} \xB7 ready`);
|
|
193
|
+
}
|
|
194
|
+
return { cfg, terminalMode };
|
|
190
195
|
}
|
|
191
196
|
|
|
192
197
|
// src/bin.ts
|
|
@@ -199,8 +204,13 @@ async function main() {
|
|
|
199
204
|
switch (command) {
|
|
200
205
|
// ── moly setup ────────────────────────────────────────────────────
|
|
201
206
|
case "setup": {
|
|
202
|
-
await runWizard();
|
|
203
|
-
|
|
207
|
+
const { cfg, terminalMode } = await runWizard();
|
|
208
|
+
if (terminalMode) {
|
|
209
|
+
const { startChatSession } = await import("./session-55BKKXQV.js");
|
|
210
|
+
await startChatSession(cfg);
|
|
211
|
+
} else {
|
|
212
|
+
await startServer();
|
|
213
|
+
}
|
|
204
214
|
break;
|
|
205
215
|
}
|
|
206
216
|
// ── moly config ───────────────────────────────────────────────────
|
|
@@ -239,8 +249,13 @@ async function main() {
|
|
|
239
249
|
// ── moly (no args) — wizard if first run, else start server ───────
|
|
240
250
|
default: {
|
|
241
251
|
if (!configExists()) {
|
|
242
|
-
const cfg = await runWizard();
|
|
243
|
-
|
|
252
|
+
const { cfg, terminalMode } = await runWizard();
|
|
253
|
+
if (terminalMode) {
|
|
254
|
+
const { startChatSession } = await import("./session-55BKKXQV.js");
|
|
255
|
+
await startChatSession(cfg);
|
|
256
|
+
} else {
|
|
257
|
+
await startServer();
|
|
258
|
+
}
|
|
244
259
|
} else {
|
|
245
260
|
const cfg = loadConfig();
|
|
246
261
|
process.stderr.write(
|
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
loadConfig,
|
|
4
|
+
redactedConfig,
|
|
5
|
+
saveConfig
|
|
6
|
+
} from "./chunk-PIFEXJ56.js";
|
|
7
|
+
|
|
8
|
+
// src/tools/balance.ts
|
|
9
|
+
import { formatEther } from "viem";
|
|
10
|
+
|
|
11
|
+
// src/server/runtime.ts
|
|
12
|
+
import { createPublicClient, createWalletClient, http, defineChain } from "viem";
|
|
13
|
+
import { mainnet } from "viem/chains";
|
|
14
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
15
|
+
import { LidoSDK } from "@lidofinance/lido-ethereum-sdk";
|
|
16
|
+
|
|
17
|
+
// src/config/types.ts
|
|
18
|
+
var CHAIN_CONFIG = {
|
|
19
|
+
hoodi: {
|
|
20
|
+
chainId: 560048,
|
|
21
|
+
stETH: "0x3508A952176b3c15387C97BE809eaffB1982176a",
|
|
22
|
+
wstETH: "0x7E99eE3C66636DE415D2d7C880938F2f40f94De4",
|
|
23
|
+
voting: "0x49B3512c44891bef83F8967d075121Bd1b07a01B",
|
|
24
|
+
defaultRpc: "https://hoodi.drpc.org",
|
|
25
|
+
name: "Hoodi Testnet"
|
|
26
|
+
},
|
|
27
|
+
mainnet: {
|
|
28
|
+
chainId: 1,
|
|
29
|
+
stETH: "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84",
|
|
30
|
+
wstETH: "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0",
|
|
31
|
+
voting: "0x2e59A20f205bB85a89C53f1936454680651E618e",
|
|
32
|
+
defaultRpc: "https://eth.llamarpc.com",
|
|
33
|
+
name: "Ethereum Mainnet"
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// src/server/runtime.ts
|
|
38
|
+
var hoodi = defineChain({
|
|
39
|
+
id: 560048,
|
|
40
|
+
name: "Hoodi Testnet",
|
|
41
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
42
|
+
rpcUrls: { default: { http: ["https://hoodi.drpc.org"] } }
|
|
43
|
+
});
|
|
44
|
+
var _runtime = null;
|
|
45
|
+
function buildRuntime() {
|
|
46
|
+
const config = loadConfig();
|
|
47
|
+
const chainCfg = CHAIN_CONFIG[config.network];
|
|
48
|
+
const rpcUrl = config.rpc ?? chainCfg.defaultRpc;
|
|
49
|
+
const viemChain = config.network === "mainnet" ? mainnet : hoodi;
|
|
50
|
+
const publicClient = createPublicClient({
|
|
51
|
+
chain: viemChain,
|
|
52
|
+
transport: http(rpcUrl)
|
|
53
|
+
});
|
|
54
|
+
const sdk = new LidoSDK({
|
|
55
|
+
chainId: chainCfg.chainId,
|
|
56
|
+
rpcProvider: publicClient
|
|
57
|
+
});
|
|
58
|
+
let _wallet = null;
|
|
59
|
+
function getWallet() {
|
|
60
|
+
if (_wallet) return _wallet;
|
|
61
|
+
const pk = config.privateKey;
|
|
62
|
+
if (!pk) throw new Error("No private key configured. Run: moly setup");
|
|
63
|
+
const account = privateKeyToAccount(pk);
|
|
64
|
+
_wallet = createWalletClient({
|
|
65
|
+
account,
|
|
66
|
+
chain: viemChain,
|
|
67
|
+
transport: http(rpcUrl)
|
|
68
|
+
});
|
|
69
|
+
return _wallet;
|
|
70
|
+
}
|
|
71
|
+
function getAddress() {
|
|
72
|
+
const pk = config.privateKey;
|
|
73
|
+
if (!pk) throw new Error("No private key configured. Run: moly setup");
|
|
74
|
+
return privateKeyToAccount(pk).address;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
config,
|
|
78
|
+
chainAddresses: chainCfg,
|
|
79
|
+
publicClient,
|
|
80
|
+
sdk,
|
|
81
|
+
getWallet,
|
|
82
|
+
getAddress,
|
|
83
|
+
reload() {
|
|
84
|
+
_runtime = null;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function getRuntime() {
|
|
89
|
+
if (!_runtime) _runtime = buildRuntime();
|
|
90
|
+
return _runtime;
|
|
91
|
+
}
|
|
92
|
+
function applySettingsUpdate(patch) {
|
|
93
|
+
const current = loadConfig();
|
|
94
|
+
if (patch.network !== void 0) current.network = patch.network;
|
|
95
|
+
if (patch.mode !== void 0) current.mode = patch.mode;
|
|
96
|
+
if (patch.rpc !== void 0) current.rpc = patch.rpc;
|
|
97
|
+
if (patch.model !== void 0 && current.ai) current.ai.model = patch.model;
|
|
98
|
+
saveConfig(current);
|
|
99
|
+
_runtime = null;
|
|
100
|
+
return current;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/tools/balance.ts
|
|
104
|
+
async function getBalance(address) {
|
|
105
|
+
const rt = getRuntime();
|
|
106
|
+
const addr = address ?? rt.getAddress();
|
|
107
|
+
const [eth, steth, wsteth] = await Promise.all([
|
|
108
|
+
rt.sdk.core.balanceETH(addr),
|
|
109
|
+
rt.sdk.steth.balance(addr),
|
|
110
|
+
rt.sdk.wsteth.balance(addr)
|
|
111
|
+
]);
|
|
112
|
+
return {
|
|
113
|
+
address: addr,
|
|
114
|
+
mode: rt.config.mode,
|
|
115
|
+
network: rt.chainAddresses.name,
|
|
116
|
+
balances: {
|
|
117
|
+
eth: formatEther(eth),
|
|
118
|
+
stETH: formatEther(steth),
|
|
119
|
+
wstETH: formatEther(wsteth)
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async function getRewards(address, days = 7) {
|
|
124
|
+
const rt = getRuntime();
|
|
125
|
+
const addr = address ?? rt.getAddress();
|
|
126
|
+
const rewards = await rt.sdk.rewards.getRewardsFromChain({
|
|
127
|
+
address: addr,
|
|
128
|
+
stepBlock: 1e3,
|
|
129
|
+
back: { days: BigInt(days) }
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
address: addr,
|
|
133
|
+
period: `${days} days`,
|
|
134
|
+
totalRewards: formatEther(rewards.totalRewards),
|
|
135
|
+
baseBalance: formatEther(rewards.baseBalance),
|
|
136
|
+
rewards: rewards.rewards.slice(0, 10).map((e) => ({
|
|
137
|
+
type: e.type,
|
|
138
|
+
change: formatEther(e.change),
|
|
139
|
+
balance: formatEther(e.balance)
|
|
140
|
+
}))
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/tools/stake.ts
|
|
145
|
+
import { parseEther, formatEther as formatEther2 } from "viem";
|
|
146
|
+
var SUBMIT_ABI = [
|
|
147
|
+
{
|
|
148
|
+
name: "submit",
|
|
149
|
+
type: "function",
|
|
150
|
+
inputs: [{ name: "_referral", type: "address" }],
|
|
151
|
+
outputs: [{ type: "uint256" }],
|
|
152
|
+
stateMutability: "payable"
|
|
153
|
+
}
|
|
154
|
+
];
|
|
155
|
+
var REFERRAL = "0x0000000000000000000000000000000000000000";
|
|
156
|
+
async function stakeEth(amountEth, dryRun) {
|
|
157
|
+
const rt = getRuntime();
|
|
158
|
+
const value = parseEther(amountEth);
|
|
159
|
+
const account = rt.getAddress();
|
|
160
|
+
const lidoAddress = rt.chainAddresses.stETH;
|
|
161
|
+
const shouldDryRun = rt.config.mode === "simulation" ? dryRun !== false : !!dryRun;
|
|
162
|
+
if (shouldDryRun) {
|
|
163
|
+
let estimatedGas = "unavailable";
|
|
164
|
+
try {
|
|
165
|
+
const gas = await rt.publicClient.estimateContractGas({
|
|
166
|
+
address: lidoAddress,
|
|
167
|
+
abi: SUBMIT_ABI,
|
|
168
|
+
functionName: "submit",
|
|
169
|
+
args: [REFERRAL],
|
|
170
|
+
value,
|
|
171
|
+
account: "0x0000000000000000000000000000000000000001"
|
|
172
|
+
});
|
|
173
|
+
estimatedGas = gas.toString();
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
simulated: true,
|
|
178
|
+
mode: rt.config.mode,
|
|
179
|
+
network: rt.chainAddresses.name,
|
|
180
|
+
action: "stake",
|
|
181
|
+
amountEth,
|
|
182
|
+
estimatedGas,
|
|
183
|
+
expectedStETH: amountEth,
|
|
184
|
+
note: "stETH rebases daily \u2014 your balance grows automatically after staking."
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const tx = await rt.sdk.stake.stakeEth({
|
|
188
|
+
value,
|
|
189
|
+
account: { address: account },
|
|
190
|
+
callback: () => {
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
return {
|
|
194
|
+
simulated: false,
|
|
195
|
+
mode: rt.config.mode,
|
|
196
|
+
network: rt.chainAddresses.name,
|
|
197
|
+
action: "stake",
|
|
198
|
+
amountEth,
|
|
199
|
+
txHash: tx.hash,
|
|
200
|
+
stethReceived: formatEther2(tx.result?.stethReceived ?? 0n),
|
|
201
|
+
sharesReceived: formatEther2(tx.result?.sharesReceived ?? 0n)
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/tools/unstake.ts
|
|
206
|
+
import { parseEther as parseEther2, formatEther as formatEther3 } from "viem";
|
|
207
|
+
async function requestWithdrawal(amountSteth, dryRun) {
|
|
208
|
+
const rt = getRuntime();
|
|
209
|
+
const amount = parseEther2(amountSteth);
|
|
210
|
+
const shouldDryRun = rt.config.mode === "simulation" ? dryRun !== false : !!dryRun;
|
|
211
|
+
if (shouldDryRun) {
|
|
212
|
+
return {
|
|
213
|
+
simulated: true,
|
|
214
|
+
mode: rt.config.mode,
|
|
215
|
+
network: rt.chainAddresses.name,
|
|
216
|
+
action: "request_withdrawal",
|
|
217
|
+
amountSteth,
|
|
218
|
+
minWithdrawal: "0.1 stETH",
|
|
219
|
+
maxWithdrawal: "1000 stETH per request",
|
|
220
|
+
note: "Withdrawal requests enter a queue. Finalization can take hours to days depending on validator exits. You will receive an ERC-721 NFT representing your position."
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const account = rt.getAddress();
|
|
224
|
+
const tx = await rt.sdk.withdraw.request.requestWithdrawal({
|
|
225
|
+
amount,
|
|
226
|
+
token: "stETH",
|
|
227
|
+
account,
|
|
228
|
+
callback: () => {
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
return {
|
|
232
|
+
simulated: false,
|
|
233
|
+
mode: rt.config.mode,
|
|
234
|
+
network: rt.chainAddresses.name,
|
|
235
|
+
action: "request_withdrawal",
|
|
236
|
+
amountSteth,
|
|
237
|
+
txHash: tx.hash,
|
|
238
|
+
requestIds: tx.result?.requests?.map((r) => r.requestId.toString()),
|
|
239
|
+
note: "Check status with get_withdrawal_status. Claim when finalized."
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
async function claimWithdrawals(requestIds, dryRun) {
|
|
243
|
+
const rt = getRuntime();
|
|
244
|
+
const ids = requestIds.map(BigInt);
|
|
245
|
+
const shouldDryRun = rt.config.mode === "simulation" ? dryRun !== false : !!dryRun;
|
|
246
|
+
if (shouldDryRun) {
|
|
247
|
+
return {
|
|
248
|
+
simulated: true,
|
|
249
|
+
mode: rt.config.mode,
|
|
250
|
+
network: rt.chainAddresses.name,
|
|
251
|
+
action: "claim_withdrawals",
|
|
252
|
+
requestIds,
|
|
253
|
+
note: "Claims can only be made after requests are finalized. Use get_withdrawal_status first."
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const account = rt.getAddress();
|
|
257
|
+
const tx = await rt.sdk.withdraw.claim.claimRequests({
|
|
258
|
+
requestsIds: ids,
|
|
259
|
+
account,
|
|
260
|
+
callback: () => {
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
return {
|
|
264
|
+
simulated: false,
|
|
265
|
+
mode: rt.config.mode,
|
|
266
|
+
network: rt.chainAddresses.name,
|
|
267
|
+
action: "claim_withdrawals",
|
|
268
|
+
requestIds,
|
|
269
|
+
txHash: tx.hash
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
async function getWithdrawalRequests(address) {
|
|
273
|
+
const rt = getRuntime();
|
|
274
|
+
const addr = address ?? rt.getAddress();
|
|
275
|
+
const requests = await rt.sdk.withdraw.views.getWithdrawalRequestsIds({ account: addr });
|
|
276
|
+
return {
|
|
277
|
+
address: addr,
|
|
278
|
+
mode: rt.config.mode,
|
|
279
|
+
network: rt.chainAddresses.name,
|
|
280
|
+
requests: requests.map((id) => id.toString()),
|
|
281
|
+
count: requests.length
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
async function getWithdrawalStatus(requestIds) {
|
|
285
|
+
const rt = getRuntime();
|
|
286
|
+
const ids = requestIds.map(BigInt);
|
|
287
|
+
const statuses = await rt.sdk.withdraw.views.getWithdrawalStatus({ requestsIds: ids });
|
|
288
|
+
return {
|
|
289
|
+
mode: rt.config.mode,
|
|
290
|
+
network: rt.chainAddresses.name,
|
|
291
|
+
statuses: statuses.map((s, i) => ({
|
|
292
|
+
requestId: requestIds[i],
|
|
293
|
+
amountOfStETH: formatEther3(s.amountOfStETH),
|
|
294
|
+
amountOfShares: formatEther3(s.amountOfShares),
|
|
295
|
+
owner: s.owner,
|
|
296
|
+
isFinalized: s.isFinalized,
|
|
297
|
+
isClaimed: s.isClaimed
|
|
298
|
+
}))
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/tools/wrap.ts
|
|
303
|
+
import { parseEther as parseEther3, formatEther as formatEther4 } from "viem";
|
|
304
|
+
async function wrapSteth(amountSteth, dryRun) {
|
|
305
|
+
const rt = getRuntime();
|
|
306
|
+
const amount = parseEther3(amountSteth);
|
|
307
|
+
const shouldDryRun = rt.config.mode === "simulation" ? dryRun !== false : !!dryRun;
|
|
308
|
+
const expectedWstETH = await rt.sdk.wrap.convertStethToWsteth(amount);
|
|
309
|
+
if (shouldDryRun) {
|
|
310
|
+
return {
|
|
311
|
+
simulated: true,
|
|
312
|
+
mode: rt.config.mode,
|
|
313
|
+
network: rt.chainAddresses.name,
|
|
314
|
+
action: "wrap_steth",
|
|
315
|
+
amountSteth,
|
|
316
|
+
expectedWstETH: formatEther4(expectedWstETH),
|
|
317
|
+
note: "wstETH is non-rebasing \u2014 balance stays fixed while value grows. Better for DeFi."
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const account = rt.getAddress();
|
|
321
|
+
const tx = await rt.sdk.wrap.wrapSteth({
|
|
322
|
+
value: amount,
|
|
323
|
+
account,
|
|
324
|
+
callback: () => {
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
return {
|
|
328
|
+
simulated: false,
|
|
329
|
+
mode: rt.config.mode,
|
|
330
|
+
network: rt.chainAddresses.name,
|
|
331
|
+
action: "wrap_steth",
|
|
332
|
+
amountSteth,
|
|
333
|
+
txHash: tx.hash,
|
|
334
|
+
wstethReceived: formatEther4(tx.result?.wstethReceived ?? 0n)
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
async function unwrapWsteth(amountWsteth, dryRun) {
|
|
338
|
+
const rt = getRuntime();
|
|
339
|
+
const amount = parseEther3(amountWsteth);
|
|
340
|
+
const shouldDryRun = rt.config.mode === "simulation" ? dryRun !== false : !!dryRun;
|
|
341
|
+
const expectedStETH = await rt.sdk.wrap.convertWstethToSteth(amount);
|
|
342
|
+
if (shouldDryRun) {
|
|
343
|
+
return {
|
|
344
|
+
simulated: true,
|
|
345
|
+
mode: rt.config.mode,
|
|
346
|
+
network: rt.chainAddresses.name,
|
|
347
|
+
action: "unwrap_wsteth",
|
|
348
|
+
amountWsteth,
|
|
349
|
+
expectedStETH: formatEther4(expectedStETH),
|
|
350
|
+
note: "Unwrapping gives rebasing stETH back. Balance updates daily with rewards."
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const account = rt.getAddress();
|
|
354
|
+
const tx = await rt.sdk.wrap.unwrap({
|
|
355
|
+
value: amount,
|
|
356
|
+
account,
|
|
357
|
+
callback: () => {
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
return {
|
|
361
|
+
simulated: false,
|
|
362
|
+
mode: rt.config.mode,
|
|
363
|
+
network: rt.chainAddresses.name,
|
|
364
|
+
action: "unwrap_wsteth",
|
|
365
|
+
amountWsteth,
|
|
366
|
+
txHash: tx.hash,
|
|
367
|
+
stethReceived: formatEther4(tx.result?.stethReceived ?? 0n)
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
async function getConversionRate() {
|
|
371
|
+
const rt = getRuntime();
|
|
372
|
+
const oneEther = parseEther3("1");
|
|
373
|
+
const [wstethPerSteth, stethPerWsteth] = await Promise.all([
|
|
374
|
+
rt.sdk.wrap.convertStethToWsteth(oneEther),
|
|
375
|
+
rt.sdk.wrap.convertWstethToSteth(oneEther)
|
|
376
|
+
]);
|
|
377
|
+
return {
|
|
378
|
+
mode: rt.config.mode,
|
|
379
|
+
network: rt.chainAddresses.name,
|
|
380
|
+
"1_stETH_in_wstETH": formatEther4(wstethPerSteth),
|
|
381
|
+
"1_wstETH_in_stETH": formatEther4(stethPerWsteth),
|
|
382
|
+
note: "wstETH/stETH ratio increases over time as staking rewards accumulate."
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/tools/governance.ts
|
|
387
|
+
import { createPublicClient as createPublicClient2, http as http2, parseAbi, formatEther as formatEther5 } from "viem";
|
|
388
|
+
import { mainnet as mainnet2 } from "viem/chains";
|
|
389
|
+
import { defineChain as defineChain2 } from "viem";
|
|
390
|
+
var hoodi2 = defineChain2({
|
|
391
|
+
id: 560048,
|
|
392
|
+
name: "Hoodi Testnet",
|
|
393
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
394
|
+
rpcUrls: { default: { http: ["https://hoodi.drpc.org"] } }
|
|
395
|
+
});
|
|
396
|
+
var VOTING_ABI = parseAbi([
|
|
397
|
+
"function vote(uint256 _voteId, bool _supports, bool _executesIfDecided) external",
|
|
398
|
+
"function getVote(uint256 _voteId) external view returns (bool open, bool executed, uint64 startDate, uint64 snapshotBlock, uint64 supportRequired, uint64 minAcceptQuorum, uint256 yea, uint256 nay, uint256 votingPower, bytes script)",
|
|
399
|
+
"function votesLength() external view returns (uint256)"
|
|
400
|
+
]);
|
|
401
|
+
async function getProposals(count = 5) {
|
|
402
|
+
const rt = getRuntime();
|
|
403
|
+
const votingAddress = rt.chainAddresses.voting;
|
|
404
|
+
const rpcUrl = rt.config.rpc ?? rt.chainAddresses.defaultRpc;
|
|
405
|
+
const viemChain = rt.config.network === "mainnet" ? mainnet2 : hoodi2;
|
|
406
|
+
const client = createPublicClient2({ chain: viemChain, transport: http2(rpcUrl) });
|
|
407
|
+
const totalVotes = await client.readContract({
|
|
408
|
+
address: votingAddress,
|
|
409
|
+
abi: VOTING_ABI,
|
|
410
|
+
functionName: "votesLength"
|
|
411
|
+
});
|
|
412
|
+
const latest = Number(totalVotes);
|
|
413
|
+
const from = Math.max(0, latest - count);
|
|
414
|
+
const ids = Array.from({ length: latest - from }, (_, i) => from + i);
|
|
415
|
+
const votes = await Promise.all(
|
|
416
|
+
ids.map(async (id) => {
|
|
417
|
+
const v = await client.readContract({
|
|
418
|
+
address: votingAddress,
|
|
419
|
+
abi: VOTING_ABI,
|
|
420
|
+
functionName: "getVote",
|
|
421
|
+
args: [BigInt(id)]
|
|
422
|
+
});
|
|
423
|
+
return {
|
|
424
|
+
id,
|
|
425
|
+
open: v[0],
|
|
426
|
+
executed: v[1],
|
|
427
|
+
startDate: new Date(Number(v[2]) * 1e3).toISOString(),
|
|
428
|
+
yea: formatEther5(v[6]),
|
|
429
|
+
nay: formatEther5(v[7]),
|
|
430
|
+
votingPower: formatEther5(v[8])
|
|
431
|
+
};
|
|
432
|
+
})
|
|
433
|
+
);
|
|
434
|
+
return {
|
|
435
|
+
mode: rt.config.mode,
|
|
436
|
+
network: rt.chainAddresses.name,
|
|
437
|
+
votingContract: votingAddress,
|
|
438
|
+
totalProposals: latest,
|
|
439
|
+
proposals: votes.reverse()
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
async function getProposal(proposalId) {
|
|
443
|
+
const rt = getRuntime();
|
|
444
|
+
const votingAddress = rt.chainAddresses.voting;
|
|
445
|
+
const rpcUrl = rt.config.rpc ?? rt.chainAddresses.defaultRpc;
|
|
446
|
+
const viemChain = rt.config.network === "mainnet" ? mainnet2 : hoodi2;
|
|
447
|
+
const client = createPublicClient2({ chain: viemChain, transport: http2(rpcUrl) });
|
|
448
|
+
const v = await client.readContract({
|
|
449
|
+
address: votingAddress,
|
|
450
|
+
abi: VOTING_ABI,
|
|
451
|
+
functionName: "getVote",
|
|
452
|
+
args: [BigInt(proposalId)]
|
|
453
|
+
});
|
|
454
|
+
return {
|
|
455
|
+
mode: rt.config.mode,
|
|
456
|
+
network: rt.chainAddresses.name,
|
|
457
|
+
id: proposalId,
|
|
458
|
+
open: v[0],
|
|
459
|
+
executed: v[1],
|
|
460
|
+
startDate: new Date(Number(v[2]) * 1e3).toISOString(),
|
|
461
|
+
snapshotBlock: v[3].toString(),
|
|
462
|
+
supportRequired: `${(Number(v[4]) / 1e16).toFixed(1)}%`,
|
|
463
|
+
minAcceptQuorum: `${(Number(v[5]) / 1e16).toFixed(1)}%`,
|
|
464
|
+
yea: formatEther5(v[6]),
|
|
465
|
+
nay: formatEther5(v[7]),
|
|
466
|
+
votingPower: formatEther5(v[8])
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
async function castVote(proposalId, support, dryRun) {
|
|
470
|
+
const rt = getRuntime();
|
|
471
|
+
const votingAddress = rt.chainAddresses.voting;
|
|
472
|
+
const shouldDryRun = rt.config.mode === "simulation" ? dryRun !== false : !!dryRun;
|
|
473
|
+
if (shouldDryRun) {
|
|
474
|
+
const proposal = await getProposal(proposalId);
|
|
475
|
+
return {
|
|
476
|
+
simulated: true,
|
|
477
|
+
mode: rt.config.mode,
|
|
478
|
+
network: rt.chainAddresses.name,
|
|
479
|
+
action: "cast_vote",
|
|
480
|
+
proposalId,
|
|
481
|
+
vote: support ? "YEA" : "NAY",
|
|
482
|
+
proposal,
|
|
483
|
+
note: "You need LDO tokens to vote. Voting power is based on LDO balance at snapshot block."
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
const wallet = rt.getWallet();
|
|
487
|
+
const account = rt.getAddress();
|
|
488
|
+
const viemChain = rt.config.network === "mainnet" ? mainnet2 : hoodi2;
|
|
489
|
+
const hash = await wallet.writeContract({
|
|
490
|
+
chain: viemChain,
|
|
491
|
+
address: votingAddress,
|
|
492
|
+
abi: VOTING_ABI,
|
|
493
|
+
functionName: "vote",
|
|
494
|
+
args: [BigInt(proposalId), support, false],
|
|
495
|
+
account
|
|
496
|
+
});
|
|
497
|
+
return {
|
|
498
|
+
simulated: false,
|
|
499
|
+
mode: rt.config.mode,
|
|
500
|
+
network: rt.chainAddresses.name,
|
|
501
|
+
action: "cast_vote",
|
|
502
|
+
proposalId,
|
|
503
|
+
vote: support ? "YEA" : "NAY",
|
|
504
|
+
txHash: hash
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/tools/settings.ts
|
|
509
|
+
function getSettings() {
|
|
510
|
+
const cfg = loadConfig();
|
|
511
|
+
return {
|
|
512
|
+
...redactedConfig(cfg),
|
|
513
|
+
note: "Use update_settings to change mode, network, or rpc. Private key and API keys can only be changed via: moly setup"
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
function updateSettings(patch) {
|
|
517
|
+
if (Object.keys(patch).length === 0) {
|
|
518
|
+
return { error: "No settings provided to update." };
|
|
519
|
+
}
|
|
520
|
+
const updated = applySettingsUpdate(patch);
|
|
521
|
+
return {
|
|
522
|
+
updated: true,
|
|
523
|
+
changes: patch,
|
|
524
|
+
current: redactedConfig(updated),
|
|
525
|
+
note: "Settings saved and applied. SDK reinitialized with new config."
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
export {
|
|
530
|
+
getBalance,
|
|
531
|
+
getRewards,
|
|
532
|
+
stakeEth,
|
|
533
|
+
requestWithdrawal,
|
|
534
|
+
claimWithdrawals,
|
|
535
|
+
getWithdrawalRequests,
|
|
536
|
+
getWithdrawalStatus,
|
|
537
|
+
wrapSteth,
|
|
538
|
+
unwrapWsteth,
|
|
539
|
+
getConversionRate,
|
|
540
|
+
getProposals,
|
|
541
|
+
getProposal,
|
|
542
|
+
castVote,
|
|
543
|
+
getSettings,
|
|
544
|
+
updateSettings
|
|
545
|
+
};
|