@blockrun/mcp 0.4.2 → 0.5.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/index.js CHANGED
@@ -1,1145 +1,29 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ initializeMcpServer
4
+ } from "./chunk-H2DXT5AJ.js";
2
5
 
3
6
  // src/index.ts
4
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import { z } from "zod";
7
- import { LLMClient, ImageClient } from "@blockrun/llm";
8
- import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
9
- import * as fs from "fs";
10
- import * as path from "path";
11
- import * as os from "os";
12
- import QRCode from "qrcode";
13
- import { Jimp } from "jimp";
14
- import open from "open";
15
- var WALLET_DIR = path.join(os.homedir(), ".blockrun");
16
- var WALLET_FILE = path.join(WALLET_DIR, ".session");
17
- var QR_FILE = path.join(WALLET_DIR, "qr.png");
18
- var USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
19
- var BASE_CHAIN_ID = "8453";
20
- var MODEL_TIERS = {
21
- fast: ["google/gemini-2.5-flash", "openai/gpt-5-mini", "deepseek/deepseek-chat", "google/gemini-3-flash-preview"],
22
- balanced: ["openai/gpt-5.4", "anthropic/claude-sonnet-4.6", "google/gemini-2.5-pro", "openai/gpt-5.3"],
23
- powerful: ["openai/gpt-5.4", "anthropic/claude-opus-4.6", "anthropic/claude-opus-4.5", "openai/o3"],
24
- cheap: ["nvidia/gpt-oss-120b", "google/gemini-2.5-flash", "deepseek/deepseek-chat", "openai/gpt-5.4-nano"],
25
- reasoning: ["openai/o3", "openai/o1", "openai/o3-mini", "deepseek/deepseek-reasoner"]
26
- };
27
- var walletWasCreated = false;
28
- var walletAddress = null;
29
- var client = null;
30
- var imageClient = null;
31
- var cachedModels = null;
32
- var sessionBudget = { limit: null, spent: 0, calls: 0 };
33
- var BASE_RPC_URLS = [
34
- "https://mainnet.base.org",
35
- "https://base.llamarpc.com",
36
- "https://1rpc.io/base"
37
- ];
38
- function getOrCreateWalletKey() {
39
- const envKey = process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY;
40
- if (envKey) {
41
- const account2 = privateKeyToAccount(envKey);
42
- walletAddress = account2.address;
43
- return envKey;
44
- }
45
- if (fs.existsSync(WALLET_FILE)) {
46
- try {
47
- const savedKey = fs.readFileSync(WALLET_FILE, "utf-8").trim();
48
- if (savedKey.startsWith("0x") && savedKey.length === 66) {
49
- const account2 = privateKeyToAccount(savedKey);
50
- walletAddress = account2.address;
51
- return savedKey;
52
- }
53
- } catch {
54
- }
55
- }
56
- const newKey = generatePrivateKey();
57
- const account = privateKeyToAccount(newKey);
58
- walletAddress = account.address;
59
- walletWasCreated = true;
60
- try {
61
- if (!fs.existsSync(WALLET_DIR)) {
62
- fs.mkdirSync(WALLET_DIR, { recursive: true, mode: 448 });
63
- }
64
- fs.writeFileSync(WALLET_FILE, newKey, { mode: 384 });
65
- console.error(`[BlockRun] New wallet created and saved to ${WALLET_FILE}`);
66
- } catch (err) {
67
- console.error(`[BlockRun] Warning: Could not save wallet to file: ${err}`);
68
- }
69
- return newKey;
70
- }
71
- function getClient() {
72
- if (!client) {
73
- const privateKey = getOrCreateWalletKey();
74
- client = new LLMClient({ privateKey });
75
- }
76
- return client;
77
- }
78
- function getImageClient() {
79
- if (!imageClient) {
80
- const privateKey = getOrCreateWalletKey();
81
- imageClient = new ImageClient({ privateKey });
82
- }
83
- return imageClient;
84
- }
85
- function getWalletInfo() {
86
- const llm = getClient();
87
- const address = llm.getWalletAddress();
88
- return {
89
- address,
90
- network: "Base",
91
- chainId: 8453,
92
- currency: "USDC",
93
- isNew: walletWasCreated,
94
- basescanUrl: `https://basescan.org/address/${address}`
95
- };
96
- }
97
- async function getUsdcBalance(address) {
98
- const data = {
99
- jsonrpc: "2.0",
100
- method: "eth_call",
101
- params: [{
102
- to: USDC_ADDRESS,
103
- data: `0x70a08231000000000000000000000000${address.slice(2)}`
104
- }, "latest"],
105
- id: 1
106
- };
107
- for (const rpcUrl of BASE_RPC_URLS) {
108
- try {
109
- const response = await fetch(rpcUrl, {
110
- method: "POST",
111
- headers: { "Content-Type": "application/json" },
112
- body: JSON.stringify(data)
113
- });
114
- const result = await response.json();
115
- if (result.result) {
116
- return parseInt(result.result, 16) / 1e6;
117
- }
118
- } catch {
119
- continue;
120
- }
121
- }
122
- return null;
123
- }
124
- function getEip681Uri(address, amountUsdc = 1) {
125
- const amountWei = Math.floor(amountUsdc * 1e6);
126
- return `ethereum:${USDC_ADDRESS}@${BASE_CHAIN_ID}/transfer?address=${address}&uint256=${amountWei}`;
127
- }
128
- async function generateQrPng(address) {
129
- const eip681Uri = getEip681Uri(address);
130
- const qrBuffer = await QRCode.toBuffer(eip681Uri, {
131
- type: "png",
132
- width: 400,
133
- margin: 2,
134
- errorCorrectionLevel: "H",
135
- color: { dark: "#000000", light: "#FFFFFF" }
136
- });
137
- const qrImage = await Jimp.read(qrBuffer);
138
- try {
139
- const logoUrl = "https://avatars.githubusercontent.com/u/108554348?s=200&v=4";
140
- const logo = await Jimp.read(logoUrl);
141
- const logoSize = Math.floor(qrImage.width * 0.2);
142
- logo.resize({ w: logoSize, h: logoSize });
143
- const x = Math.floor((qrImage.width - logoSize) / 2);
144
- const y = Math.floor((qrImage.height - logoSize) / 2);
145
- qrImage.composite(logo, x, y);
146
- } catch {
147
- }
148
- if (!fs.existsSync(WALLET_DIR)) {
149
- fs.mkdirSync(WALLET_DIR, { recursive: true, mode: 448 });
150
- }
151
- await qrImage.write(QR_FILE);
152
- return QR_FILE;
153
- }
154
- async function openQrInViewer(qrPath) {
155
- try {
156
- await open(qrPath);
157
- } catch {
158
- }
159
- }
160
- function recordSpending(cost) {
161
- sessionBudget.spent += cost;
162
- sessionBudget.calls += 1;
163
- }
164
- function checkBudget() {
165
- if (sessionBudget.limit === null) {
166
- return { allowed: true, remaining: null };
167
- }
168
- const remaining = sessionBudget.limit - sessionBudget.spent;
169
- return { allowed: remaining > 0, remaining };
170
- }
171
- function formatError(message) {
172
- const msgLower = message.toLowerCase();
173
- const isPaymentError = msgLower.includes("402") || msgLower.includes("balance") || msgLower.includes("insufficient") || msgLower.includes("payment") && !msgLower.includes("500");
174
- const isServerError = msgLower.includes("500") || msgLower.includes("api error after payment");
175
- let errorText = `Error: ${message}`;
176
- if (isServerError) {
177
- errorText += `
178
-
179
- This is a temporary API issue. The xAI/Grok API may be experiencing problems.
180
- Try again in a few minutes, or use a different model (e.g., openai/gpt-4o).`;
181
- } else if (isPaymentError) {
182
- errorText += `
183
-
184
- This error usually means your wallet needs funding.
185
- Run blockrun_wallet with action: "setup" to get funding instructions.
186
-
187
- Quick fix: Send USDC to your wallet on Base network.`;
188
- }
189
- return errorText;
190
- }
191
- var server = new McpServer({
192
- name: "blockrun-mcp",
193
- version: "0.4.0"
194
- });
195
- server.registerTool(
196
- "blockrun_wallet",
197
- {
198
- description: `Manage your BlockRun wallet - check status, get funding instructions, open QR code, or manage session budget.
199
-
200
- Actions:
201
- - status: Show wallet address, balance, and basescan link (default)
202
- - setup: Full funding instructions with QR code
203
- - qr: Generate and open QR code for easy funding
204
- - budget: Manage session spending limit
205
-
206
- Examples:
207
- blockrun_wallet() -> status + balance
208
- blockrun_wallet({ action: "setup" }) -> funding instructions + QR
209
- blockrun_wallet({ action: "qr" }) -> open QR code
210
- blockrun_wallet({ action: "budget", budget_action: "set", budget_amount: 1.00 })`,
211
- inputSchema: {
212
- action: z.enum(["status", "setup", "qr", "budget"]).optional().default("status").describe("What to do"),
213
- budget_action: z.enum(["set", "check", "clear"]).optional().describe("Budget action (for action='budget')"),
214
- budget_amount: z.number().optional().describe("Budget limit in USD (for budget_action='set')")
215
- }
216
- },
217
- async ({ action, budget_action, budget_amount }) => {
218
- const info = getWalletInfo();
219
- const address = info.address;
220
- if (action === "budget") {
221
- const budgetAct = budget_action || "check";
222
- if (budgetAct === "set") {
223
- if (budget_amount === void 0 || budget_amount <= 0) {
224
- return {
225
- content: [{ type: "text", text: "Error: Provide a positive budget_amount (e.g., 1.00 for $1.00)" }],
226
- isError: true
227
- };
228
- }
229
- sessionBudget.limit = budget_amount;
230
- } else if (budgetAct === "clear") {
231
- sessionBudget.limit = null;
232
- }
233
- const remaining = sessionBudget.limit !== null ? sessionBudget.limit - sessionBudget.spent : null;
234
- const limitStr = sessionBudget.limit !== null ? `$${sessionBudget.limit.toFixed(2)}` : "Unlimited";
235
- const remainingStr = remaining !== null ? `$${remaining.toFixed(4)}` : "N/A";
236
- return {
237
- content: [{ type: "text", text: `Session Budget: ${limitStr} | Spent: $${sessionBudget.spent.toFixed(4)} | Calls: ${sessionBudget.calls} | Remaining: ${remainingStr}${budgetAct === "set" ? ` | Set to $${budget_amount?.toFixed(2)}` : ""}${budgetAct === "clear" ? " | Limit removed" : ""}` }],
238
- structuredContent: {
239
- limit: sessionBudget.limit,
240
- spent: sessionBudget.spent,
241
- calls: sessionBudget.calls,
242
- remaining
243
- }
244
- };
245
- }
246
- if (action === "qr") {
247
- try {
248
- const qrPath = await generateQrPng(address);
249
- await openQrInViewer(qrPath);
250
- return {
251
- content: [{ type: "text", text: `QR code opened! Scan with MetaMask to send USDC on Base.
252
-
253
- Address: ${address}
254
- QR saved: ${qrPath}` }]
255
- };
256
- } catch (err) {
257
- return {
258
- content: [{ type: "text", text: `Failed to generate QR: ${err}` }],
259
- isError: true
260
- };
261
- }
262
- }
263
- if (action === "setup") {
264
- let qrMessage = "";
265
- try {
266
- const qrPath = await generateQrPng(address);
267
- await openQrInViewer(qrPath);
268
- qrMessage = `
269
- QR code opened for scanning! (${qrPath})`;
270
- } catch {
271
- qrMessage = "\n(QR generation failed - use address above)";
272
- }
273
- const text2 = `
274
- ================================================================================
275
- BLOCKRUN WALLET SETUP
276
- ================================================================================
277
-
278
- Your wallet address: ${address}
279
- ${qrMessage}
280
-
281
- HOW TO FUND YOUR WALLET:
282
- ------------------------
283
-
284
- Option 1: Transfer from Coinbase
285
- 1. Open Coinbase app or website
286
- 2. Go to Send/Receive -> Select USDC
287
- 3. Choose "Base" network (important!)
288
- 4. Paste: ${address}
289
- 5. Send $1-5 to start
290
-
291
- Option 2: Bridge from other chains
292
- https://bridge.base.org -> Bridge USDC to Base -> Send to address above
293
-
294
- Option 3: Buy directly
295
- https://www.coinbase.com/onramp -> Buy USDC on Base -> Send to address above
296
-
297
- VERIFY BALANCE: https://basescan.org/address/${address}
298
-
299
- PRICING (pay per use):
300
- - GPT-4o: ~$0.005/request | Claude Sonnet: ~$0.003/request
301
- - Gemini Flash: ~$0.0001/request | Full pricing: https://blockrun.ai/pricing
302
-
303
- SECURITY: Private key stored at ~/.blockrun/.session (never leaves your machine)
304
- ================================================================================`;
305
- return { content: [{ type: "text", text: text2 }] };
306
- }
307
- const balance = await getUsdcBalance(address);
308
- const balanceStr = balance !== null ? `$${balance.toFixed(6)} USDC` : "Unable to fetch";
309
- const lowBalance = balance !== null && balance < 1;
310
- const text = `Wallet: ${address}
311
- Balance: ${balanceStr}${lowBalance ? " (low - add funds)" : ""}
312
- Network: Base | View: ${info.basescanUrl}
313
- ${info.isNew ? "\nNEW WALLET - Run with action: 'setup' for funding instructions" : ""}`;
314
- return {
315
- content: [{ type: "text", text }],
316
- structuredContent: {
317
- address: info.address,
318
- balance,
319
- network: info.network,
320
- chainId: info.chainId,
321
- isNew: info.isNew,
322
- basescanUrl: info.basescanUrl
323
- }
324
- };
325
- }
326
- );
327
- server.registerTool(
328
- "blockrun_chat",
329
- {
330
- description: `Chat with AI models via BlockRun. Supports 30+ models with pay-per-request micropayments.
331
-
332
- Two ways to use:
333
- 1. Specify a model directly: model: "openai/gpt-5.4"
334
- 2. Use smart routing: mode: "fast" | "balanced" | "powerful" | "cheap" | "reasoning"
335
-
336
- Popular models:
337
- - openai/gpt-5.4, openai/gpt-5.4-mini, openai/gpt-5.4-nano
338
- - anthropic/claude-opus-4.6, anthropic/claude-sonnet-4.6
339
- - google/gemini-2.5-pro, google/gemini-2.5-flash
340
- - deepseek/deepseek-chat (very affordable)
341
-
342
- Smart routing modes:
343
- - fast: Gemini Flash, GPT-5 Mini (quickest)
344
- - balanced: GPT-5.4, Claude Sonnet 4.6 (good default)
345
- - powerful: GPT-5.4, Claude Opus 4.6 (best quality)
346
- - cheap: DeepSeek, Gemini Flash (lowest cost)
347
- - reasoning: o3, o1 (complex logic)
348
-
349
- Use blockrun_models to see all available models with pricing.`,
350
- inputSchema: {
351
- message: z.string().describe("Your message to the AI"),
352
- model: z.string().optional().describe("Specific model ID (e.g., 'openai/gpt-4o')"),
353
- mode: z.enum(["fast", "balanced", "powerful", "cheap", "reasoning"]).optional().describe("Smart routing mode (ignored if model specified)"),
354
- system: z.string().optional().describe("Optional system prompt"),
355
- max_tokens: z.number().optional().default(1024).describe("Max tokens in response"),
356
- temperature: z.number().optional().default(1).describe("Creativity 0-2")
357
- }
358
- },
359
- async ({ message, model, mode, system, max_tokens, temperature }) => {
360
- const llm = getClient();
361
- if (model) {
362
- try {
363
- const response = await llm.chat(model, message, {
364
- system,
365
- maxTokens: max_tokens,
366
- temperature
367
- });
368
- return { content: [{ type: "text", text: response }] };
369
- } catch (error) {
370
- const errorMessage2 = error instanceof Error ? error.message : String(error);
371
- return {
372
- content: [{ type: "text", text: formatError(errorMessage2) }],
373
- isError: true
374
- };
375
- }
376
- }
377
- const routingMode = mode || "balanced";
378
- const models = MODEL_TIERS[routingMode];
379
- let lastError = null;
380
- for (const m of models) {
381
- try {
382
- const response = await llm.chat(m, message, {
383
- system,
384
- maxTokens: max_tokens
385
- });
386
- return {
387
- content: [{ type: "text", text: `[${m}]
388
-
389
- ${response}` }],
390
- structuredContent: { model_used: m, response }
391
- };
392
- } catch (error) {
393
- lastError = error;
394
- continue;
395
- }
396
- }
397
- const errorMessage = lastError?.message || "All models failed";
398
- return {
399
- content: [{ type: "text", text: formatError(errorMessage) }],
400
- isError: true
401
- };
402
- }
403
- );
404
- server.registerTool(
405
- "blockrun_models",
406
- {
407
- description: "List available AI models with pricing. Use to discover models and compare costs.",
408
- inputSchema: {
409
- category: z.enum(["all", "chat", "reasoning", "image", "embedding"]).optional().default("all").describe("Filter by category"),
410
- provider: z.string().optional().describe("Filter by provider (e.g., 'openai', 'anthropic')")
411
- }
412
- },
413
- async ({ category, provider }) => {
414
- const llm = getClient();
415
- if (!cachedModels) {
416
- cachedModels = await llm.listModels();
417
- setTimeout(() => {
418
- cachedModels = null;
419
- }, 5 * 60 * 1e3);
420
- }
421
- let models = cachedModels;
422
- if (provider) {
423
- const p = provider.toLowerCase();
424
- models = models.filter((m) => m.id.toLowerCase().startsWith(p + "/"));
425
- }
426
- if (category && category !== "all") {
427
- if (category === "image") {
428
- models = models.filter((m) => m.id.includes("dall-e") || m.id.includes("flux") || m.id.includes("banana"));
429
- } else if (category === "embedding") {
430
- models = models.filter((m) => m.id.includes("embed"));
431
- } else {
432
- models = models.filter((m) => m.categories?.includes(category));
433
- }
434
- }
435
- const lines = models.map((m) => {
436
- const input = m.inputPrice ? `$${m.inputPrice}/M in` : "";
437
- const output = m.outputPrice ? `$${m.outputPrice}/M out` : "";
438
- const pricing = [input, output].filter(Boolean).join(", ");
439
- const ctx = m.contextWindow ? ` | ${Math.round(m.contextWindow / 1e3)}K ctx` : "";
440
- const cats = m.categories?.length ? ` [${m.categories.join(", ")}]` : "";
441
- return `- ${m.id}${pricing ? ` (${pricing})` : ""}${ctx}${cats}`;
9
+ var args = process.argv.slice(2);
10
+ var isHttp = args.includes("--http");
11
+ var portIdx = args.indexOf("--port");
12
+ var port = portIdx !== -1 ? parseInt(args[portIdx + 1], 10) : 3402;
13
+ async function main() {
14
+ if (isHttp) {
15
+ const { startHttpServer } = await import("./http-server.js");
16
+ await startHttpServer(port);
17
+ } else {
18
+ const server = new McpServer({
19
+ name: "blockrun-mcp",
20
+ version: "0.5.1"
442
21
  });
443
- return {
444
- content: [{ type: "text", text: `Models (${models.length}):
445
- ${lines.join("\n")}` }],
446
- structuredContent: { count: models.length, models }
447
- };
448
- }
449
- );
450
- server.registerTool(
451
- "blockrun_image",
452
- {
453
- description: `Generate images. Models: openai/dall-e-3 ($0.04-0.08), together/flux-schnell ($0.02), google/nano-banana`,
454
- inputSchema: {
455
- prompt: z.string().describe("Image description"),
456
- model: z.enum(["openai/dall-e-3", "together/flux-schnell", "google/nano-banana"]).optional().default("openai/dall-e-3"),
457
- size: z.enum(["1024x1024", "1792x1024", "1024x1792"]).optional().default("1024x1024"),
458
- quality: z.enum(["standard", "hd"]).optional().default("standard")
459
- }
460
- },
461
- async ({ prompt, model, size, quality }) => {
462
- try {
463
- const imgClient = getImageClient();
464
- const response = await imgClient.generate(prompt, {
465
- model,
466
- size,
467
- quality
468
- });
469
- const imageUrl = response.data?.[0]?.url;
470
- if (!imageUrl) {
471
- return {
472
- content: [{ type: "text", text: formatError("No image URL in response") }],
473
- isError: true
474
- };
475
- }
476
- return {
477
- content: [{ type: "text", text: `Image: ${imageUrl}
478
- Prompt: ${prompt}
479
- Model: ${model}` }],
480
- structuredContent: { url: imageUrl, prompt, model }
481
- };
482
- } catch (err) {
483
- const errMsg = err instanceof Error ? err.message : String(err);
484
- if (errMsg.includes("balance") || errMsg.includes("payment") || errMsg.includes("402")) {
485
- return {
486
- content: [{ type: "text", text: `Image generation requires payment. Run blockrun_wallet with action: "setup" for funding instructions.
487
- Error: ${errMsg}` }],
488
- isError: true
489
- };
490
- }
491
- return {
492
- content: [{ type: "text", text: formatError(`Image generation failed: ${errMsg}`) }],
493
- isError: true
494
- };
495
- }
496
- }
497
- );
498
- server.registerTool(
499
- "blockrun_twitter",
500
- {
501
- description: `Search real-time X/Twitter via Grok. Use for trending topics, @handles, breaking news.`,
502
- inputSchema: {
503
- query: z.string().describe("Search query (can include @handles, topics)"),
504
- max_results: z.number().optional().default(10).describe("Max results (1-25)")
505
- }
506
- },
507
- async ({ query, max_results }) => {
508
- const budget = checkBudget();
509
- if (!budget.allowed) {
510
- return {
511
- content: [{ type: "text", text: `Budget limit reached ($${sessionBudget.spent.toFixed(4)} of $${sessionBudget.limit?.toFixed(2)}). Use blockrun_wallet with action: "budget" to adjust.` }],
512
- isError: true
513
- };
514
- }
515
- try {
516
- const llm = getClient();
517
- const response = await llm.chat("xai/grok-3", query, {
518
- system: `Real-time X/Twitter search. Focus on recent posts, key accounts, engagement. Max results: ${max_results}`,
519
- search: true
520
- });
521
- recordSpending(2e-3);
522
- return {
523
- content: [{ type: "text", text: `[X/Twitter via Grok]
524
-
525
- ${response}` }],
526
- structuredContent: { query, model: "xai/grok-3", response }
527
- };
528
- } catch (error) {
529
- const errorMessage = error instanceof Error ? error.message : String(error);
530
- return {
531
- content: [{ type: "text", text: formatError(errorMessage) }],
532
- isError: true
533
- };
534
- }
22
+ initializeMcpServer(server);
23
+ const transport = new StdioServerTransport();
24
+ await server.connect(transport);
25
+ console.error("BlockRun MCP Server started (v0.5.1) \u2014 stdio transport");
535
26
  }
536
- );
537
- server.registerTool(
538
- "blockrun_dex",
539
- {
540
- description: `Get real-time DEX data from DexScreener. FREE - no payment required.
541
-
542
- Use for:
543
- - Token prices and liquidity across chains
544
- - Trading volume and price changes
545
- - Finding token pairs and contracts
546
-
547
- Examples:
548
- blockrun_dex({ query: "SOL" }) -> Search for SOL pairs
549
- blockrun_dex({ token: "So11...xxx" }) -> Get specific token data
550
- blockrun_dex({ symbol: "PEPE" }) -> Search by symbol`,
551
- inputSchema: {
552
- query: z.string().optional().describe("Search query (token name, symbol, or address)"),
553
- token: z.string().optional().describe("Token address for direct lookup"),
554
- symbol: z.string().optional().describe("Token symbol to search"),
555
- chain: z.string().optional().describe("Filter by chain (ethereum, solana, base, etc.)")
556
- }
557
- },
558
- async ({ query, token, symbol, chain }) => {
559
- try {
560
- let url;
561
- let searchTerm = query || symbol || "";
562
- if (token) {
563
- url = `https://api.dexscreener.com/latest/dex/tokens/${token}`;
564
- } else if (searchTerm) {
565
- url = `https://api.dexscreener.com/latest/dex/search?q=${encodeURIComponent(searchTerm)}`;
566
- } else {
567
- return {
568
- content: [{ type: "text", text: "Provide query, token address, or symbol" }],
569
- isError: true
570
- };
571
- }
572
- const response = await fetch(url);
573
- if (!response.ok) {
574
- throw new Error(`DexScreener API error: ${response.status}`);
575
- }
576
- const data = await response.json();
577
- let pairs = data.pairs || [];
578
- if (chain && pairs.length > 0) {
579
- const chainLower = chain.toLowerCase();
580
- pairs = pairs.filter((p) => p.chainId.toLowerCase().includes(chainLower));
581
- }
582
- pairs = pairs.sort((a, b) => (b.volume?.h24 || 0) - (a.volume?.h24 || 0)).slice(0, 10);
583
- if (pairs.length === 0) {
584
- return {
585
- content: [{ type: "text", text: `No pairs found for: ${searchTerm || token}` }]
586
- };
587
- }
588
- const lines = pairs.map((p) => {
589
- const price = p.priceUsd ? `$${parseFloat(p.priceUsd).toFixed(6)}` : "N/A";
590
- const change = p.priceChange?.h24 ? `${p.priceChange.h24 > 0 ? "+" : ""}${p.priceChange.h24.toFixed(2)}%` : "";
591
- const vol = p.volume?.h24 ? `$${(p.volume.h24 / 1e6).toFixed(2)}M` : "";
592
- const liq = p.liquidity?.usd ? `$${(p.liquidity.usd / 1e6).toFixed(2)}M liq` : "";
593
- const buySell = p.txns?.h24 ? `${p.txns.h24.buys}B/${p.txns.h24.sells}S` : "";
594
- return `${p.baseToken.symbol}/${p.quoteToken.symbol} (${p.chainId}/${p.dexId})
595
- Price: ${price} ${change} | Vol: ${vol} | ${liq} | Txns: ${buySell}
596
- Token: ${p.baseToken.address}`;
597
- });
598
- return {
599
- content: [{ type: "text", text: `[DexScreener - FREE]
600
-
601
- ${lines.join("\n\n")}` }],
602
- structuredContent: { pairs, count: pairs.length }
603
- };
604
- } catch (error) {
605
- const errorMessage = error instanceof Error ? error.message : String(error);
606
- return {
607
- content: [{ type: "text", text: `DexScreener error: ${errorMessage}` }],
608
- isError: true
609
- };
610
- }
611
- }
612
- );
613
- var KNOWN_LABELS = {
614
- "0x28c6c06298d514db089934071355e5743bf21d60": "Binance 14",
615
- "0x21a31ee1afc51d94c2efccaa2092ad1028285549": "Binance 15",
616
- "0xdfd5293d8e347dfe59e90efd55b2956a1343963d": "Binance 16",
617
- "0x56eddb7aa87536c09ccc2793473599fd21a8b17f": "Binance 17",
618
- "0x9696f59e4d72e237be84ffd425dcad154bf96976": "Binance 18",
619
- "0x4976a4a02f38326660d17bf34b431dc6e2eb2327": "Binance 19",
620
- "0xf977814e90da44bfa03b6295a0616a897441acec": "Binance 8",
621
- "0x5a52e96bacdabb82fd05763e25335261b270efcb": "Binance",
622
- "0x3f5ce5fbfe3e9af3971dd833d26ba9b5c936f0be": "Binance",
623
- "0xd24400ae8bfebb18ca49be86258a3c749cf46853": "Gemini 2",
624
- "0x6fc82a5fe25a5cdb58bc74600a40a69c065263f8": "Gemini 3",
625
- "0x61edcdf5bb737adffe5043706e7c5bb1f1a56eea": "Gemini 4",
626
- "0x07ee55aa48bb72dcc6e9d78256648910de513eca": "Gemini 5",
627
- "0xdc76cd25977e0a5ae17155770273ad58648900d3": "Coinbase Prime",
628
- "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43": "Coinbase 10",
629
- "0x503828976d22510aad0201ac7ec88293211d23da": "Coinbase 2",
630
- "0xddfabcdc4d8ffc6d5beaf154f18b778f892a0740": "Coinbase 3",
631
- "0x3cd751e6b0078be393132286c442345e5dc49699": "Coinbase 4",
632
- "0xb5d85cbf7cb3ee0d56b3bb207d5fc4b82f43f511": "Coinbase 5",
633
- "0xeb2629a2734e272bcc07bda959863f316f4bd4cf": "Coinbase 6",
634
- "0x02466e547bfdab679fc49e96bbfc62b9747d997c": "Coinbase 8",
635
- "0xa090e606e30bd747d4e6245a1517ebe430f0057e": "Coinbase",
636
- "0x8103683202aa8da10536036edef04cdd865c225e": "Kraken 13",
637
- "0x6cc5f688a315f3dc28a7781717a9a798a59fda7b": "OKX 1",
638
- "0x236f9f97e0e62388479bf9e5ba4889e46b0273c3": "OKX 2",
639
- "0x5041ed759dd4afc3a72b8192c143f72f4724081a": "OKX 4",
640
- "0x75e89d5979e4f6fba9f97c104c2f0afb3f1dcb88": "MEXC",
641
- "0x0d0707963952f2fba59dd06f2b425ace40b492fe": "Gate.io",
642
- "0x1c4b70a3968436b9a0a9cf5205c787eb81bb558c": "Gate.io 3",
643
- "0xd793281182a0e3e023116004778f45c29fc14f19": "Gate.io 4",
644
- "0x974caa59e49682cda0ad2bbe82983419a2ecc400": "HTX",
645
- "0x0211f3cedbef3143223d3acf0e589747933e8527": "HTX 2",
646
- "0x1062a747393198f70f71ec65a582423dba7e5ab3": "Bybit",
647
- "0xee5b5b923ffce93a870b3104b7ca09c3db80047a": "Bybit 2",
648
- "0x47ac0fb4f2d84898e4d9e7b4dab3c24507a6d503": "Binance: Foundation",
649
- "0xbe0eb53f46cd790cd13851d5eff43d12404d33e8": "Binance 7",
650
- "0x40ec5b33f54e0e8a33a975908c5ba1c14e5bbbdf": "Polygon Bridge",
651
- "0xa3a7b6f88361f48403514059f1f16c8e78d60eec": "Arbitrum Bridge",
652
- "0x99c9fc46f92e8a1c0dec1b1747d010903e884be1": "Optimism Bridge",
653
- "0x8315177ab297ba92a06054ce80a67ed4dbd7ed3a": "Arbitrum: Delayed Inbox",
654
- "0x0000000000000000000000000000000000000000": "Null/Burn Address"
655
- };
656
- function getAddressLabel(address) {
657
- const lower = address.toLowerCase();
658
- return KNOWN_LABELS[lower] || shortenAddress(address);
659
- }
660
- function shortenAddress(address) {
661
- return `${address.slice(0, 6)}...${address.slice(-4)}`;
662
- }
663
- server.registerTool(
664
- "blockrun_whale",
665
- {
666
- description: `Track large ETH transfers (whale movements). Uses BigQuery public data.
667
-
668
- Shows:
669
- - Large transfers (100+ ETH)
670
- - Exchange inflows/outflows
671
- - Labels for known addresses (Binance, Coinbase, etc.)
672
-
673
- Note: Requires GOOGLE_APPLICATION_CREDENTIALS env var for BigQuery auth.
674
- For MVP/demo: Returns simulated data if BigQuery not configured.`,
675
- inputSchema: {
676
- hours: z.number().optional().default(24).describe("Hours to look back (default: 24)"),
677
- min_eth: z.number().optional().default(100).describe("Minimum ETH amount (default: 100)"),
678
- limit: z.number().optional().default(20).describe("Max results (default: 20)")
679
- }
680
- },
681
- async ({ hours, min_eth, limit }) => {
682
- const hasGoogleCreds = process.env.GOOGLE_APPLICATION_CREDENTIALS || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
683
- if (!hasGoogleCreds) {
684
- const demoData = [
685
- { from: "0xf977814e90da44bfa03b6295a0616a897441acec", to: "0x28c6c06298d514db089934071355e5743bf21d60", value: 5e3, time: "2h ago" },
686
- { from: "0x503828976d22510aad0201ac7ec88293211d23da", to: "0x47ac0fb4f2d84898e4d9e7b4dab3c24507a6d503", value: 3200, time: "4h ago" },
687
- { from: "0x6cc5f688a315f3dc28a7781717a9a798a59fda7b", to: "0xd24400ae8bfebb18ca49be86258a3c749cf46853", value: 2100, time: "6h ago" },
688
- { from: "0x1062a747393198f70f71ec65a582423dba7e5ab3", to: "0x99c9fc46f92e8a1c0dec1b1747d010903e884be1", value: 1800, time: "8h ago" },
689
- { from: "0x75e89d5979e4f6fba9f97c104c2f0afb3f1dcb88", to: "0xa3a7b6f88361f48403514059f1f16c8e78d60eec", value: 1500, time: "12h ago" }
690
- ];
691
- const lines = demoData.map((t) => {
692
- const fromLabel = getAddressLabel(t.from);
693
- const toLabel = getAddressLabel(t.to);
694
- return `${t.value.toLocaleString()} ETH | ${fromLabel} \u2192 ${toLabel} | ${t.time}`;
695
- });
696
- return {
697
- content: [{
698
- type: "text",
699
- text: `[Whale Tracker - DEMO MODE]
700
- \u26A0\uFE0F BigQuery not configured. Showing sample data.
701
-
702
- To enable real data:
703
- 1. Create GCP project: console.cloud.google.com
704
- 2. Enable BigQuery API
705
- 3. Set GOOGLE_APPLICATION_CREDENTIALS env var
706
-
707
- Sample whale movements:
708
- ${lines.join("\n")}
709
-
710
- Total: ${demoData.reduce((s, t) => s + t.value, 0).toLocaleString()} ETH across ${demoData.length} transfers`
711
- }],
712
- structuredContent: { demo: true, transfers: demoData }
713
- };
714
- }
715
- return {
716
- content: [{
717
- type: "text",
718
- text: `[Whale Tracker]
719
-
720
- BigQuery credentials detected. Real-time query:
721
- - Looking back: ${hours}h
722
- - Min transfer: ${min_eth} ETH
723
- - Limit: ${limit} results
724
-
725
- Query would run:
726
- SELECT block_timestamp, from_address, to_address, value/1e18 as eth
727
- FROM \`bigquery-public-data.crypto_ethereum.transactions\`
728
- WHERE value > ${min_eth} * 1e18
729
- AND block_timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL ${hours} HOUR)
730
- ORDER BY value DESC
731
- LIMIT ${limit}
732
-
733
- Note: Full BigQuery integration coming soon.`
734
- }]
735
- };
736
- }
737
- );
738
- server.registerTool(
739
- "blockrun_analyze",
740
- {
741
- description: `Comprehensive trading analysis combining multiple data sources.
742
-
743
- Analyzes:
744
- - DEX data (price, volume, liquidity) via DexScreener
745
- - Twitter/X sentiment via Grok
746
- - Whale movements (if BigQuery configured)
747
- - AI synthesis of all data
748
-
749
- Example: blockrun_analyze({ token: "SOL", question: "Should I buy?" })`,
750
- inputSchema: {
751
- token: z.string().describe("Token symbol or address to analyze"),
752
- question: z.string().optional().describe("Specific question (default: general analysis)"),
753
- include_twitter: z.boolean().optional().default(true).describe("Include Twitter sentiment"),
754
- include_whale: z.boolean().optional().default(false).describe("Include whale tracking")
755
- }
756
- },
757
- async ({ token, question, include_twitter, include_whale }) => {
758
- const llm = getClient();
759
- const analysisPrompt = question || `Provide comprehensive trading analysis for ${token}`;
760
- let contextData = "";
761
- try {
762
- const dexUrl = `https://api.dexscreener.com/latest/dex/search?q=${encodeURIComponent(token)}`;
763
- const dexResponse = await fetch(dexUrl);
764
- const dexData = await dexResponse.json();
765
- if (dexData.pairs && dexData.pairs.length > 0) {
766
- const topPair = dexData.pairs[0];
767
- contextData += `
768
- ## DEX Data (DexScreener)
769
- `;
770
- contextData += `- Token: ${topPair.baseToken.name} (${topPair.baseToken.symbol})
771
- `;
772
- contextData += `- Price: $${parseFloat(topPair.priceUsd).toFixed(6)}
773
- `;
774
- contextData += `- 24h Change: ${topPair.priceChange?.h24?.toFixed(2) || "N/A"}%
775
- `;
776
- contextData += `- 24h Volume: $${((topPair.volume?.h24 || 0) / 1e6).toFixed(2)}M
777
- `;
778
- contextData += `- Liquidity: $${((topPair.liquidity?.usd || 0) / 1e6).toFixed(2)}M
779
- `;
780
- contextData += `- FDV: $${((topPair.fdv || 0) / 1e9).toFixed(2)}B
781
- `;
782
- contextData += `- Chain: ${topPair.chainId}
783
- `;
784
- }
785
- } catch (err) {
786
- contextData += `
787
- ## DEX Data: Error fetching
788
- `;
789
- }
790
- if (include_twitter) {
791
- try {
792
- const twitterResponse = await llm.chat("xai/grok-3", `What are people saying about ${token} on Twitter/X right now? Focus on: sentiment, key influencers, trending topics, price predictions.`, {
793
- system: "Real-time X/Twitter search. Provide factual summary of recent posts.",
794
- search: true
795
- });
796
- contextData += `
797
- ## Twitter/X Sentiment (via Grok)
798
- ${twitterResponse}
799
- `;
800
- } catch {
801
- contextData += `
802
- ## Twitter: Unable to fetch
803
- `;
804
- }
805
- }
806
- if (include_whale) {
807
- contextData += `
808
- ## Whale Movements
809
- `;
810
- contextData += `Note: BigQuery not configured. In production, this would show:
811
- `;
812
- contextData += `- Large transfers to/from exchanges
813
- `;
814
- contextData += `- Smart money wallet movements
815
- `;
816
- contextData += `- Exchange inflow/outflow trends
817
- `;
818
- }
819
- const synthesisPrompt = `You are a crypto trading analyst. Based on the following data, answer: "${analysisPrompt}"
820
-
821
- ${contextData}
822
-
823
- Provide:
824
- 1. Key findings (bullet points)
825
- 2. Risk assessment (Low/Medium/High)
826
- 3. Trading suggestion (if asked)
827
- 4. What to watch for
828
-
829
- Be factual and balanced. Don't give financial advice, but provide analysis based on the data.`;
830
- try {
831
- const analysis = await llm.chat("openai/gpt-4o", synthesisPrompt, {
832
- system: "Expert crypto trading analyst. Provide data-driven analysis.",
833
- maxTokens: 1500
834
- });
835
- return {
836
- content: [{
837
- type: "text",
838
- text: `[BlockRun Trading Analysis: ${token}]
839
-
840
- ${analysis}
841
-
842
- ---
843
- Data sources: DexScreener${include_twitter ? ", Twitter/X (Grok)" : ""}${include_whale ? ", Whale Tracker" : ""}`
844
- }],
845
- structuredContent: { token, question: analysisPrompt, analysis }
846
- };
847
- } catch (error) {
848
- const errorMessage = error instanceof Error ? error.message : String(error);
849
- return {
850
- content: [{ type: "text", text: formatError(errorMessage) }],
851
- isError: true
852
- };
853
- }
854
- }
855
- );
856
- function calculateEMA(prices, period) {
857
- const k = 2 / (period + 1);
858
- const ema = [prices[0]];
859
- for (let i = 1; i < prices.length; i++) {
860
- ema.push(prices[i] * k + ema[i - 1] * (1 - k));
861
- }
862
- return ema;
863
- }
864
- function calculateRSI(prices, period = 14) {
865
- if (prices.length < period + 1) return 50;
866
- let gains = 0, losses = 0;
867
- for (let i = prices.length - period; i < prices.length; i++) {
868
- const change = prices[i] - prices[i - 1];
869
- if (change > 0) gains += change;
870
- else losses -= change;
871
- }
872
- const avgGain = gains / period;
873
- const avgLoss = losses / period;
874
- if (avgLoss === 0) return 100;
875
- const rs = avgGain / avgLoss;
876
- return 100 - 100 / (1 + rs);
877
- }
878
- function calculateMACD(prices) {
879
- const ema12 = calculateEMA(prices, 12);
880
- const ema26 = calculateEMA(prices, 26);
881
- const macdLine = ema12.map((v, i) => v - ema26[i]);
882
- const signalLine = calculateEMA(macdLine.slice(-9), 9);
883
- const macd = macdLine[macdLine.length - 1];
884
- const signal = signalLine[signalLine.length - 1];
885
- return { macd, signal, histogram: macd - signal };
886
- }
887
- server.registerTool(
888
- "blockrun_signal",
889
- {
890
- description: `Generate trading signals using RSI + MACD + EMA strategy.
891
-
892
- Strategy (from freqtrade-strategies):
893
- - BUY when: RSI < 40 (oversold) + MACD > Signal + Price > EMA200
894
- - SELL when: RSI > 70 (overbought) or take profit/stop loss
895
-
896
- Returns: BUY / SELL / HOLD signal with confidence level.
897
-
898
- Example: blockrun_signal({ symbol: "BTCUSDT" })`,
899
- inputSchema: {
900
- symbol: z.string().describe("Trading pair (e.g., BTCUSDT, ETHUSDT, SOLUSDT)"),
901
- timeframe: z.enum(["5m", "15m", "1h", "4h"]).optional().default("1h").describe("Candle timeframe")
902
- }
903
- },
904
- async ({ symbol, timeframe }) => {
905
- try {
906
- const url = `https://api.binance.com/api/v3/klines?symbol=${symbol.toUpperCase()}&interval=${timeframe}&limit=250`;
907
- const response = await fetch(url);
908
- if (!response.ok) {
909
- throw new Error(`Binance API error: ${response.status}`);
910
- }
911
- const candles = await response.json();
912
- const closes = candles.map((c) => parseFloat(c[4]));
913
- const currentPrice = closes[closes.length - 1];
914
- const rsi = calculateRSI(closes);
915
- const { macd, signal, histogram } = calculateMACD(closes);
916
- const ema200 = calculateEMA(closes, 200);
917
- const currentEMA200 = ema200[ema200.length - 1];
918
- const ema50 = calculateEMA(closes, 50);
919
- const currentEMA50 = ema50[ema50.length - 1];
920
- let signalType = "HOLD";
921
- let confidence = 0;
922
- let reasons = [];
923
- const rsiOversold = rsi < 40;
924
- const macdBullish = macd > signal;
925
- const aboveEMA200 = currentPrice > currentEMA200;
926
- const aboveEMA50 = currentPrice > currentEMA50;
927
- const rsiOverbought = rsi > 70;
928
- const macdBearish = macd < signal;
929
- const belowEMA200 = currentPrice < currentEMA200;
930
- if (rsiOversold && macdBullish && aboveEMA200) {
931
- signalType = "BUY";
932
- confidence = 80;
933
- reasons.push("RSI oversold (<40)");
934
- reasons.push("MACD bullish crossover");
935
- reasons.push("Price above EMA200 (uptrend)");
936
- if (aboveEMA50) {
937
- confidence += 10;
938
- reasons.push("Price above EMA50 (strong)");
939
- }
940
- } else if (rsiOverbought || macdBearish && belowEMA200) {
941
- signalType = "SELL";
942
- confidence = rsiOverbought ? 75 : 60;
943
- if (rsiOverbought) reasons.push("RSI overbought (>70)");
944
- if (macdBearish) reasons.push("MACD bearish");
945
- if (belowEMA200) reasons.push("Price below EMA200 (downtrend)");
946
- } else {
947
- signalType = "HOLD";
948
- confidence = 50;
949
- reasons.push("No clear signal");
950
- if (rsi < 50 && macdBullish) reasons.push("Slight bullish bias");
951
- if (rsi > 50 && macdBearish) reasons.push("Slight bearish bias");
952
- }
953
- const stopLoss = signalType === "BUY" ? currentPrice * 0.9 : null;
954
- const takeProfit = signalType === "BUY" ? currentPrice * 1.2 : null;
955
- const result = `[Trading Signal: ${symbol}]
956
-
957
- Signal: ${signalType} (${confidence}% confidence)
958
- Price: $${currentPrice.toFixed(2)}
959
-
960
- Indicators:
961
- - RSI (14): ${rsi.toFixed(1)} ${rsi < 30 ? "\u{1F7E2} Oversold" : rsi > 70 ? "\u{1F534} Overbought" : "\u26AA Neutral"}
962
- - MACD: ${macd.toFixed(4)} | Signal: ${signal.toFixed(4)} | ${histogram > 0 ? "\u{1F7E2} Bullish" : "\u{1F534} Bearish"}
963
- - EMA 50: $${currentEMA50.toFixed(2)} ${currentPrice > currentEMA50 ? "\u{1F7E2} Above" : "\u{1F534} Below"}
964
- - EMA 200: $${currentEMA200.toFixed(2)} ${currentPrice > currentEMA200 ? "\u{1F7E2} Above" : "\u{1F534} Below"}
965
-
966
- Reasons:
967
- ${reasons.map((r) => `\u2022 ${r}`).join("\n")}
968
- ${signalType === "BUY" ? `
969
- Suggested:
970
- \u2022 Stop Loss: $${stopLoss?.toFixed(2)} (-10%)
971
- \u2022 Take Profit: $${takeProfit?.toFixed(2)} (+20%)` : ""}
972
-
973
- Strategy: RSI + MACD + EMA (freqtrade-strategies)
974
- Timeframe: ${timeframe}`;
975
- return {
976
- content: [{ type: "text", text: result }],
977
- structuredContent: {
978
- symbol,
979
- signal: signalType,
980
- confidence,
981
- price: currentPrice,
982
- indicators: { rsi, macd, signal, ema50: currentEMA50, ema200: currentEMA200 },
983
- stopLoss,
984
- takeProfit,
985
- reasons
986
- }
987
- };
988
- } catch (error) {
989
- const errorMessage = error instanceof Error ? error.message : String(error);
990
- return {
991
- content: [{ type: "text", text: `Signal error: ${errorMessage}` }],
992
- isError: true
993
- };
994
- }
995
- }
996
- );
997
- var ZERO_X_API = "https://api.0x.org/swap/v1";
998
- var BASE_CHAIN_ID_NUM = 8453;
999
- var BASE_TOKENS = {
1000
- "ETH": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
1001
- "WETH": "0x4200000000000000000000000000000000000006",
1002
- "USDC": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
1003
- "USDbC": "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA",
1004
- "DAI": "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
1005
- "cbETH": "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22"
1006
- };
1007
- server.registerTool(
1008
- "blockrun_swap",
1009
- {
1010
- description: `Execute token swaps on Base network using 0x aggregator.
1011
-
1012
- \u26A0\uFE0F REAL MONEY - requires user confirmation before execution.
1013
-
1014
- Example: blockrun_swap({ from: "USDC", to: "ETH", amount: 10 })`,
1015
- inputSchema: {
1016
- from: z.string().describe("Token to sell (USDC, ETH, WETH, etc.)"),
1017
- to: z.string().describe("Token to buy"),
1018
- amount: z.number().describe("Amount in 'from' token"),
1019
- slippage: z.number().optional().default(0.5).describe("Max slippage % (default 0.5)"),
1020
- execute: z.boolean().optional().default(false).describe("Set true to execute (requires confirmation)")
1021
- }
1022
- },
1023
- async ({ from, to, amount, slippage, execute }) => {
1024
- const fromUpper = from.toUpperCase();
1025
- const toUpper = to.toUpperCase();
1026
- const fromToken = BASE_TOKENS[fromUpper];
1027
- const toToken = BASE_TOKENS[toUpper];
1028
- if (!fromToken) {
1029
- return {
1030
- content: [{ type: "text", text: `Unknown token: ${from}. Supported: ${Object.keys(BASE_TOKENS).join(", ")}` }],
1031
- isError: true
1032
- };
1033
- }
1034
- if (!toToken) {
1035
- return {
1036
- content: [{ type: "text", text: `Unknown token: ${to}. Supported: ${Object.keys(BASE_TOKENS).join(", ")}` }],
1037
- isError: true
1038
- };
1039
- }
1040
- const decimals = fromUpper === "USDC" || fromUpper === "USDbC" ? 6 : 18;
1041
- const amountWei = BigInt(Math.floor(amount * 10 ** decimals));
1042
- try {
1043
- const quoteUrl = `${ZERO_X_API}/quote?` + new URLSearchParams({
1044
- sellToken: fromToken,
1045
- buyToken: toToken,
1046
- sellAmount: amountWei.toString(),
1047
- slippagePercentage: (slippage / 100).toString(),
1048
- chainId: BASE_CHAIN_ID_NUM.toString()
1049
- });
1050
- const estimatedOutput = fromUpper === "USDC" ? amount / 3300 : amount * 3300;
1051
- const quoteResult = `[Swap Quote: ${fromUpper} \u2192 ${toUpper}]
1052
-
1053
- Sell: ${amount} ${fromUpper}
1054
- Buy (est): ~${estimatedOutput.toFixed(6)} ${toUpper}
1055
- Slippage: ${slippage}%
1056
- Network: Base
1057
-
1058
- ${execute ? "\u26A0\uFE0F EXECUTION REQUESTED" : "\u{1F4A1} Set execute: true to swap"}
1059
-
1060
- Note: Full 0x integration requires API key.
1061
- For demo, this shows the quote flow.
1062
-
1063
- To execute:
1064
- 1. User confirms the swap
1065
- 2. Wallet signs transaction
1066
- 3. Swap executes on-chain
1067
- 4. Returns tx hash`;
1068
- if (execute) {
1069
- return {
1070
- content: [{
1071
- type: "text",
1072
- text: `\u26A0\uFE0F SWAP EXECUTION DISABLED FOR SAFETY
1073
-
1074
- To enable real swaps:
1075
- 1. Add 0x API key
1076
- 2. Implement transaction signing
1077
- 3. Add confirmation flow
1078
-
1079
- This is a demo. The swap would:
1080
- \u2022 Sell ${amount} ${fromUpper}
1081
- \u2022 Buy ~${estimatedOutput.toFixed(6)} ${toUpper}
1082
- \u2022 Gas: ~$0.01 on Base`
1083
- }]
1084
- };
1085
- }
1086
- return {
1087
- content: [{ type: "text", text: quoteResult }],
1088
- structuredContent: {
1089
- from: fromUpper,
1090
- to: toUpper,
1091
- sellAmount: amount,
1092
- buyAmount: estimatedOutput,
1093
- slippage,
1094
- execute: false
1095
- }
1096
- };
1097
- } catch (error) {
1098
- const errorMessage = error instanceof Error ? error.message : String(error);
1099
- return {
1100
- content: [{ type: "text", text: `Swap error: ${errorMessage}` }],
1101
- isError: true
1102
- };
1103
- }
1104
- }
1105
- );
1106
- server.registerResource(
1107
- "wallet",
1108
- "blockrun://wallet",
1109
- { description: "Wallet address and status", mimeType: "application/json" },
1110
- async () => ({
1111
- contents: [{
1112
- uri: "blockrun://wallet",
1113
- mimeType: "application/json",
1114
- text: JSON.stringify(getWalletInfo(), null, 2)
1115
- }]
1116
- })
1117
- );
1118
- server.registerResource(
1119
- "models",
1120
- "blockrun://models",
1121
- { description: "Available AI models with pricing", mimeType: "application/json" },
1122
- async () => {
1123
- const llm = getClient();
1124
- if (!cachedModels) {
1125
- cachedModels = await llm.listModels();
1126
- setTimeout(() => {
1127
- cachedModels = null;
1128
- }, 5 * 60 * 1e3);
1129
- }
1130
- return {
1131
- contents: [{
1132
- uri: "blockrun://models",
1133
- mimeType: "application/json",
1134
- text: JSON.stringify(cachedModels, null, 2)
1135
- }]
1136
- };
1137
- }
1138
- );
1139
- async function main() {
1140
- const transport = new StdioServerTransport();
1141
- await server.connect(transport);
1142
- console.error("BlockRun MCP Server started (v0.4.0)");
1143
27
  }
1144
28
  main().catch((error) => {
1145
29
  console.error("Fatal error:", error);