@blockrun/mcp 0.3.0 → 0.4.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/README.md +20 -4
- package/dist/index.js +846 -482
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -4,26 +4,32 @@
|
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import { LLMClient } from "@blockrun/llm";
|
|
7
|
+
import { LLMClient, ImageClient } from "@blockrun/llm";
|
|
8
8
|
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
9
9
|
import * as fs from "fs";
|
|
10
10
|
import * as path from "path";
|
|
11
11
|
import * as os from "os";
|
|
12
|
+
import QRCode from "qrcode";
|
|
13
|
+
import { Jimp } from "jimp";
|
|
14
|
+
import open from "open";
|
|
12
15
|
var WALLET_DIR = path.join(os.homedir(), ".blockrun");
|
|
13
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";
|
|
14
20
|
var MODEL_TIERS = {
|
|
15
|
-
fast: ["google/gemini-2.5-flash", "openai/gpt-
|
|
16
|
-
balanced: ["openai/gpt-
|
|
17
|
-
powerful: ["openai/gpt-5.
|
|
18
|
-
cheap: ["google/gemini-2.5-flash", "deepseek/deepseek-chat", "openai/gpt-
|
|
19
|
-
reasoning: ["openai/o3", "openai/o1", "deepseek/deepseek-reasoner"]
|
|
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"]
|
|
20
26
|
};
|
|
21
27
|
var walletWasCreated = false;
|
|
22
28
|
var walletAddress = null;
|
|
23
29
|
var client = null;
|
|
30
|
+
var imageClient = null;
|
|
24
31
|
var cachedModels = null;
|
|
25
32
|
var sessionBudget = { limit: null, spent: 0, calls: 0 };
|
|
26
|
-
var USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
27
33
|
var BASE_RPC_URLS = [
|
|
28
34
|
"https://mainnet.base.org",
|
|
29
35
|
"https://base.llamarpc.com",
|
|
@@ -69,6 +75,13 @@ function getClient() {
|
|
|
69
75
|
}
|
|
70
76
|
return client;
|
|
71
77
|
}
|
|
78
|
+
function getImageClient() {
|
|
79
|
+
if (!imageClient) {
|
|
80
|
+
const privateKey = getOrCreateWalletKey();
|
|
81
|
+
imageClient = new ImageClient({ privateKey });
|
|
82
|
+
}
|
|
83
|
+
return imageClient;
|
|
84
|
+
}
|
|
72
85
|
function getWalletInfo() {
|
|
73
86
|
const llm = getClient();
|
|
74
87
|
const address = llm.getWalletAddress();
|
|
@@ -78,65 +91,9 @@ function getWalletInfo() {
|
|
|
78
91
|
chainId: 8453,
|
|
79
92
|
currency: "USDC",
|
|
80
93
|
isNew: walletWasCreated,
|
|
81
|
-
basescanUrl: `https://basescan.org/address/${address}
|
|
82
|
-
fundingOptions: {
|
|
83
|
-
coinbase: "Send USDC, select 'Base' network",
|
|
84
|
-
bridge: "https://bridge.base.org",
|
|
85
|
-
buy: "https://www.coinbase.com/onramp"
|
|
86
|
-
}
|
|
94
|
+
basescanUrl: `https://basescan.org/address/${address}`
|
|
87
95
|
};
|
|
88
96
|
}
|
|
89
|
-
function getWalletSetupInstructions() {
|
|
90
|
-
if (!walletAddress) {
|
|
91
|
-
getClient();
|
|
92
|
-
}
|
|
93
|
-
return `
|
|
94
|
-
================================================================================
|
|
95
|
-
BLOCKRUN WALLET SETUP
|
|
96
|
-
================================================================================
|
|
97
|
-
|
|
98
|
-
Your wallet address: ${walletAddress}
|
|
99
|
-
|
|
100
|
-
To use BlockRun AI models, you need USDC on Base network.
|
|
101
|
-
|
|
102
|
-
HOW TO FUND YOUR WALLET:
|
|
103
|
-
------------------------
|
|
104
|
-
|
|
105
|
-
Option 1: Transfer from Coinbase
|
|
106
|
-
1. Open Coinbase app or website
|
|
107
|
-
2. Go to Send/Receive
|
|
108
|
-
3. Select USDC
|
|
109
|
-
4. Choose "Base" network (important!)
|
|
110
|
-
5. Paste address: ${walletAddress}
|
|
111
|
-
6. Send any amount ($5 is enough to start)
|
|
112
|
-
|
|
113
|
-
Option 2: Bridge from other chains
|
|
114
|
-
1. Go to https://bridge.base.org
|
|
115
|
-
2. Connect your existing wallet
|
|
116
|
-
3. Bridge USDC to Base
|
|
117
|
-
4. Send to: ${walletAddress}
|
|
118
|
-
|
|
119
|
-
Option 3: Buy directly
|
|
120
|
-
1. Go to https://www.coinbase.com/onramp
|
|
121
|
-
2. Buy USDC on Base network
|
|
122
|
-
3. Send to: ${walletAddress}
|
|
123
|
-
|
|
124
|
-
VERIFY YOUR BALANCE:
|
|
125
|
-
https://basescan.org/address/${walletAddress}
|
|
126
|
-
|
|
127
|
-
PRICING (pay only for what you use):
|
|
128
|
-
- GPT-4o: ~$0.005 per request
|
|
129
|
-
- Claude Sonnet: ~$0.003 per request
|
|
130
|
-
- Gemini Flash: ~$0.0001 per request
|
|
131
|
-
- Full pricing: https://blockrun.ai/pricing
|
|
132
|
-
|
|
133
|
-
SECURITY NOTE:
|
|
134
|
-
Your private key is stored at: ~/.blockrun/.session
|
|
135
|
-
This key NEVER leaves your machine - only used for signing payments locally.
|
|
136
|
-
|
|
137
|
-
================================================================================
|
|
138
|
-
`;
|
|
139
|
-
}
|
|
140
97
|
async function getUsdcBalance(address) {
|
|
141
98
|
const data = {
|
|
142
99
|
jsonrpc: "2.0",
|
|
@@ -164,6 +121,42 @@ async function getUsdcBalance(address) {
|
|
|
164
121
|
}
|
|
165
122
|
return null;
|
|
166
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
|
+
}
|
|
167
160
|
function recordSpending(cost) {
|
|
168
161
|
sessionBudget.spent += cost;
|
|
169
162
|
sessionBudget.calls += 1;
|
|
@@ -175,90 +168,226 @@ function checkBudget() {
|
|
|
175
168
|
const remaining = sessionBudget.limit - sessionBudget.spent;
|
|
176
169
|
return { allowed: remaining > 0, remaining };
|
|
177
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
|
+
}
|
|
178
191
|
var server = new McpServer({
|
|
179
192
|
name: "blockrun-mcp",
|
|
180
|
-
version: "0.
|
|
193
|
+
version: "0.4.0"
|
|
181
194
|
});
|
|
182
195
|
server.registerTool(
|
|
183
|
-
"
|
|
196
|
+
"blockrun_wallet",
|
|
184
197
|
{
|
|
185
|
-
description: `
|
|
186
|
-
Pay-per-request with x402 micropayments - no API keys needed.
|
|
198
|
+
description: `Manage your BlockRun wallet - check status, get funding instructions, open QR code, or manage session budget.
|
|
187
199
|
|
|
188
|
-
|
|
189
|
-
-
|
|
190
|
-
-
|
|
191
|
-
-
|
|
192
|
-
-
|
|
193
|
-
- deepseek/deepseek-chat: Very affordable
|
|
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
|
|
194
205
|
|
|
195
|
-
|
|
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 })`,
|
|
196
211
|
inputSchema: {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
max_tokens: z.number().optional().default(1024).describe("Maximum tokens in response"),
|
|
201
|
-
temperature: z.number().optional().default(1).describe("Creativity level 0-2")
|
|
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')")
|
|
202
215
|
}
|
|
203
216
|
},
|
|
204
|
-
async ({
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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";
|
|
215
236
|
return {
|
|
216
|
-
content: [{ type: "text", text:
|
|
217
|
-
|
|
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
|
+
}
|
|
218
244
|
};
|
|
219
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
|
+
};
|
|
220
325
|
}
|
|
221
326
|
);
|
|
222
327
|
server.registerTool(
|
|
223
|
-
"
|
|
328
|
+
"blockrun_chat",
|
|
224
329
|
{
|
|
225
|
-
description: `
|
|
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)
|
|
226
341
|
|
|
227
|
-
|
|
228
|
-
- fast:
|
|
229
|
-
- balanced:
|
|
230
|
-
- powerful:
|
|
231
|
-
- cheap:
|
|
232
|
-
- reasoning:
|
|
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)
|
|
233
348
|
|
|
234
|
-
|
|
349
|
+
Use blockrun_models to see all available models with pricing.`,
|
|
235
350
|
inputSchema: {
|
|
236
|
-
mode: z.enum(["fast", "balanced", "powerful", "cheap", "reasoning"]).describe("Routing mode"),
|
|
237
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)"),
|
|
238
354
|
system: z.string().optional().describe("Optional system prompt"),
|
|
239
|
-
max_tokens: z.number().optional().default(1024).describe("
|
|
240
|
-
|
|
241
|
-
outputSchema: {
|
|
242
|
-
model_used: z.string().describe("The model that was used"),
|
|
243
|
-
response: z.string().describe("The AI response")
|
|
355
|
+
max_tokens: z.number().optional().default(1024).describe("Max tokens in response"),
|
|
356
|
+
temperature: z.number().optional().default(1).describe("Creativity 0-2")
|
|
244
357
|
}
|
|
245
358
|
},
|
|
246
|
-
async ({
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
for (const model of models) {
|
|
359
|
+
async ({ message, model, mode, system, max_tokens, temperature }) => {
|
|
360
|
+
const llm = getClient();
|
|
361
|
+
if (model) {
|
|
250
362
|
try {
|
|
251
|
-
const llm = getClient();
|
|
252
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, {
|
|
253
383
|
system,
|
|
254
384
|
maxTokens: max_tokens
|
|
255
385
|
});
|
|
256
|
-
const result = { model_used: model, response };
|
|
257
386
|
return {
|
|
258
|
-
content: [{ type: "text", text: `[
|
|
387
|
+
content: [{ type: "text", text: `[${m}]
|
|
259
388
|
|
|
260
389
|
${response}` }],
|
|
261
|
-
structuredContent:
|
|
390
|
+
structuredContent: { model_used: m, response }
|
|
262
391
|
};
|
|
263
392
|
} catch (error) {
|
|
264
393
|
lastError = error;
|
|
@@ -275,19 +404,10 @@ ${response}` }],
|
|
|
275
404
|
server.registerTool(
|
|
276
405
|
"blockrun_models",
|
|
277
406
|
{
|
|
278
|
-
description: "List
|
|
407
|
+
description: "List available AI models with pricing. Use to discover models and compare costs.",
|
|
279
408
|
inputSchema: {
|
|
280
409
|
category: z.enum(["all", "chat", "reasoning", "image", "embedding"]).optional().default("all").describe("Filter by category"),
|
|
281
|
-
provider: z.string().optional().describe("Filter by provider (e.g., 'openai', 'anthropic'
|
|
282
|
-
},
|
|
283
|
-
outputSchema: {
|
|
284
|
-
count: z.number().describe("Number of models returned"),
|
|
285
|
-
models: z.array(z.object({
|
|
286
|
-
id: z.string(),
|
|
287
|
-
name: z.string().optional(),
|
|
288
|
-
inputPrice: z.number().optional(),
|
|
289
|
-
outputPrice: z.number().optional()
|
|
290
|
-
})).describe("List of available models")
|
|
410
|
+
provider: z.string().optional().describe("Filter by provider (e.g., 'openai', 'anthropic')")
|
|
291
411
|
}
|
|
292
412
|
},
|
|
293
413
|
async ({ category, provider }) => {
|
|
@@ -305,234 +425,424 @@ server.registerTool(
|
|
|
305
425
|
}
|
|
306
426
|
if (category && category !== "all") {
|
|
307
427
|
if (category === "image") {
|
|
308
|
-
models = models.filter(
|
|
309
|
-
(m) => m.id.includes("dall-e") || m.id.includes("flux") || m.id.includes("banana")
|
|
310
|
-
);
|
|
311
|
-
} else if (category === "reasoning") {
|
|
312
|
-
models = models.filter(
|
|
313
|
-
(m) => m.id.includes("/o1") || m.id.includes("/o3") || m.id.includes("reasoner")
|
|
314
|
-
);
|
|
428
|
+
models = models.filter((m) => m.id.includes("dall-e") || m.id.includes("flux") || m.id.includes("banana"));
|
|
315
429
|
} else if (category === "embedding") {
|
|
316
430
|
models = models.filter((m) => m.id.includes("embed"));
|
|
431
|
+
} else {
|
|
432
|
+
models = models.filter((m) => m.categories?.includes(category));
|
|
317
433
|
}
|
|
318
434
|
}
|
|
319
435
|
const lines = models.map((m) => {
|
|
320
436
|
const input = m.inputPrice ? `$${m.inputPrice}/M in` : "";
|
|
321
437
|
const output = m.outputPrice ? `$${m.outputPrice}/M out` : "";
|
|
322
438
|
const pricing = [input, output].filter(Boolean).join(", ");
|
|
323
|
-
|
|
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}`;
|
|
324
442
|
});
|
|
325
|
-
const structuredModels = models.map((m) => ({
|
|
326
|
-
id: m.id,
|
|
327
|
-
name: m.name,
|
|
328
|
-
inputPrice: m.inputPrice,
|
|
329
|
-
outputPrice: m.outputPrice
|
|
330
|
-
}));
|
|
331
443
|
return {
|
|
332
|
-
content: [{ type: "text", text: `
|
|
333
|
-
|
|
444
|
+
content: [{ type: "text", text: `Models (${models.length}):
|
|
334
445
|
${lines.join("\n")}` }],
|
|
335
|
-
structuredContent: { count: models.length, models
|
|
446
|
+
structuredContent: { count: models.length, models }
|
|
336
447
|
};
|
|
337
448
|
}
|
|
338
449
|
);
|
|
339
450
|
server.registerTool(
|
|
340
451
|
"blockrun_image",
|
|
341
452
|
{
|
|
342
|
-
description: `Generate images
|
|
343
|
-
|
|
344
|
-
Models:
|
|
345
|
-
- openai/dall-e-3: High quality, creative ($0.04-0.08/image)
|
|
346
|
-
- together/flux-schnell: Fast generation ($0.02/image)
|
|
347
|
-
- google/nano-banana: Experimental Google model`,
|
|
453
|
+
description: `Generate images. Models: openai/dall-e-3 ($0.04-0.08), together/flux-schnell ($0.02), google/nano-banana`,
|
|
348
454
|
inputSchema: {
|
|
349
|
-
prompt: z.string().describe("
|
|
350
|
-
model: z.enum(["openai/dall-e-3", "together/flux-schnell", "google/nano-banana"]).optional().default("openai/dall-e-3")
|
|
351
|
-
size: z.enum(["1024x1024", "1792x1024", "1024x1792"]).optional().default("1024x1024")
|
|
352
|
-
quality: z.enum(["standard", "hd"]).optional().default("standard")
|
|
353
|
-
},
|
|
354
|
-
outputSchema: {
|
|
355
|
-
url: z.string().describe("URL of the generated image"),
|
|
356
|
-
prompt: z.string().describe("The prompt used"),
|
|
357
|
-
model: z.string().describe("The model used")
|
|
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")
|
|
358
459
|
}
|
|
359
460
|
},
|
|
360
461
|
async ({ prompt, model, size, quality }) => {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
+
}
|
|
375
476
|
return {
|
|
376
|
-
content: [{ type: "text", text: `Image
|
|
377
|
-
|
|
378
|
-
|
|
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}`) }],
|
|
379
493
|
isError: true
|
|
380
494
|
};
|
|
381
495
|
}
|
|
382
|
-
|
|
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) {
|
|
383
510
|
return {
|
|
384
|
-
content: [{ type: "text", text:
|
|
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.` }],
|
|
385
512
|
isError: true
|
|
386
513
|
};
|
|
387
514
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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);
|
|
391
530
|
return {
|
|
392
|
-
content: [{ type: "text", text: formatError(
|
|
531
|
+
content: [{ type: "text", text: formatError(errorMessage) }],
|
|
393
532
|
isError: true
|
|
394
533
|
};
|
|
395
534
|
}
|
|
396
|
-
return {
|
|
397
|
-
content: [{ type: "text", text: `Image generated successfully!
|
|
398
|
-
|
|
399
|
-
URL: ${imageUrl}
|
|
400
|
-
|
|
401
|
-
Prompt: ${prompt}
|
|
402
|
-
Model: ${model}` }],
|
|
403
|
-
structuredContent: { url: imageUrl, prompt, model }
|
|
404
|
-
};
|
|
405
535
|
}
|
|
406
536
|
);
|
|
407
537
|
server.registerTool(
|
|
408
|
-
"
|
|
538
|
+
"blockrun_dex",
|
|
409
539
|
{
|
|
410
|
-
description:
|
|
411
|
-
inputSchema: {},
|
|
412
|
-
outputSchema: {
|
|
413
|
-
address: z.string().describe("Wallet address"),
|
|
414
|
-
network: z.string().describe("Network name"),
|
|
415
|
-
chainId: z.number().describe("Chain ID"),
|
|
416
|
-
currency: z.string().describe("Currency"),
|
|
417
|
-
isNew: z.boolean().describe("Whether this is a newly created wallet"),
|
|
418
|
-
basescanUrl: z.string().describe("Link to view on Basescan")
|
|
419
|
-
}
|
|
420
|
-
},
|
|
421
|
-
async () => {
|
|
422
|
-
const info = getWalletInfo();
|
|
423
|
-
const isNewWallet = info.isNew;
|
|
424
|
-
let text = `BlockRun Wallet Information
|
|
425
|
-
============================
|
|
540
|
+
description: `Get real-time DEX data from DexScreener. FREE - no payment required.
|
|
426
541
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
542
|
+
Use for:
|
|
543
|
+
- Token prices and liquidity across chains
|
|
544
|
+
- Trading volume and price changes
|
|
545
|
+
- Finding token pairs and contracts
|
|
430
546
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
-----------------
|
|
441
|
-
Send USDC to the address above on Base network.
|
|
442
|
-
|
|
443
|
-
Quick options:
|
|
444
|
-
1. From Coinbase: ${info.fundingOptions.coinbase}
|
|
445
|
-
2. Bridge: ${info.fundingOptions.bridge}
|
|
446
|
-
3. Buy: ${info.fundingOptions.buy}
|
|
447
|
-
|
|
448
|
-
Full instructions: Run blockrun_setup tool
|
|
449
|
-
`;
|
|
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.)")
|
|
450
556
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
+
};
|
|
460
571
|
}
|
|
461
|
-
|
|
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
|
+
}
|
|
462
611
|
}
|
|
463
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
|
+
}
|
|
464
663
|
server.registerTool(
|
|
465
|
-
"
|
|
664
|
+
"blockrun_whale",
|
|
466
665
|
{
|
|
467
|
-
description: `
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
-
|
|
471
|
-
-
|
|
472
|
-
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
+
}
|
|
475
680
|
},
|
|
476
|
-
async () => {
|
|
477
|
-
|
|
478
|
-
|
|
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
|
+
};
|
|
479
736
|
}
|
|
480
737
|
);
|
|
481
738
|
server.registerTool(
|
|
482
|
-
"
|
|
739
|
+
"blockrun_analyze",
|
|
483
740
|
{
|
|
484
|
-
description: `
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
-
|
|
488
|
-
-
|
|
489
|
-
-
|
|
490
|
-
-
|
|
491
|
-
|
|
492
|
-
Example
|
|
493
|
-
- "what is @elonmusk posting about today"
|
|
494
|
-
- "trending AI news"
|
|
495
|
-
- "reactions to [event]"`,
|
|
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?" })`,
|
|
496
750
|
inputSchema: {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
query: z.string(),
|
|
502
|
-
model: z.string(),
|
|
503
|
-
response: z.string()
|
|
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")
|
|
504
755
|
}
|
|
505
756
|
},
|
|
506
|
-
async ({
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
+
`;
|
|
515
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.`;
|
|
516
830
|
try {
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
When searching, focus on:
|
|
521
|
-
- Recent and relevant posts
|
|
522
|
-
- Key accounts and verified sources
|
|
523
|
-
- Engagement metrics when relevant
|
|
524
|
-
Format your response clearly with sources when available.
|
|
525
|
-
Max results requested: ${max_results}`;
|
|
526
|
-
const response = await llm.chat(model, query, {
|
|
527
|
-
system,
|
|
528
|
-
search: true
|
|
831
|
+
const analysis = await llm.chat("openai/gpt-4o", synthesisPrompt, {
|
|
832
|
+
system: "Expert crypto trading analyst. Provide data-driven analysis.",
|
|
833
|
+
maxTokens: 1500
|
|
529
834
|
});
|
|
530
|
-
recordSpending(2e-3);
|
|
531
835
|
return {
|
|
532
|
-
content: [{
|
|
836
|
+
content: [{
|
|
837
|
+
type: "text",
|
|
838
|
+
text: `[BlockRun Trading Analysis: ${token}]
|
|
533
839
|
|
|
534
|
-
${
|
|
535
|
-
|
|
840
|
+
${analysis}
|
|
841
|
+
|
|
842
|
+
---
|
|
843
|
+
Data sources: DexScreener${include_twitter ? ", Twitter/X (Grok)" : ""}${include_whale ? ", Whale Tracker" : ""}`
|
|
844
|
+
}],
|
|
845
|
+
structuredContent: { token, question: analysisPrompt, analysis }
|
|
536
846
|
};
|
|
537
847
|
} catch (error) {
|
|
538
848
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -543,141 +853,272 @@ ${response}` }],
|
|
|
543
853
|
}
|
|
544
854
|
}
|
|
545
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
|
+
}
|
|
546
887
|
server.registerTool(
|
|
547
|
-
"
|
|
888
|
+
"blockrun_signal",
|
|
548
889
|
{
|
|
549
|
-
description: `
|
|
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
|
|
550
895
|
|
|
551
|
-
Returns:
|
|
552
|
-
- Current USDC balance
|
|
553
|
-
- Wallet address
|
|
554
|
-
- Link to view on Basescan
|
|
896
|
+
Returns: BUY / SELL / HOLD signal with confidence level.
|
|
555
897
|
|
|
556
|
-
|
|
557
|
-
inputSchema: {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
balance: z.number().nullable(),
|
|
561
|
-
network: z.string(),
|
|
562
|
-
basescanUrl: z.string()
|
|
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")
|
|
563
902
|
}
|
|
564
903
|
},
|
|
565
|
-
async () => {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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}]
|
|
572
956
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
Network: Base (Chain ID: 8453)
|
|
957
|
+
Signal: ${signalType} (${confidence}% confidence)
|
|
958
|
+
Price: $${currentPrice.toFixed(2)}
|
|
576
959
|
|
|
577
|
-
|
|
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"}
|
|
578
965
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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
|
+
}
|
|
589
995
|
}
|
|
590
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
|
+
};
|
|
591
1007
|
server.registerTool(
|
|
592
|
-
"
|
|
1008
|
+
"blockrun_swap",
|
|
593
1009
|
{
|
|
594
|
-
description: `
|
|
1010
|
+
description: `Execute token swaps on Base network using 0x aggregator.
|
|
595
1011
|
|
|
596
|
-
|
|
597
|
-
- check: View current spending and budget status
|
|
598
|
-
- set: Set a spending limit (e.g., $1.00)
|
|
599
|
-
- clear: Remove spending limit (unlimited)
|
|
1012
|
+
\u26A0\uFE0F REAL MONEY - requires user confirmation before execution.
|
|
600
1013
|
|
|
601
|
-
|
|
1014
|
+
Example: blockrun_swap({ from: "USDC", to: "ETH", amount: 10 })`,
|
|
602
1015
|
inputSchema: {
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
spent: z.number(),
|
|
609
|
-
calls: z.number(),
|
|
610
|
-
remaining: z.number().nullable()
|
|
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)")
|
|
611
1021
|
}
|
|
612
1022
|
},
|
|
613
|
-
async ({
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
${
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
+
};
|
|
652
1085
|
}
|
|
653
|
-
|
|
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
|
+
}
|
|
654
1104
|
}
|
|
655
1105
|
);
|
|
656
1106
|
server.registerResource(
|
|
657
1107
|
"wallet",
|
|
658
1108
|
"blockrun://wallet",
|
|
659
|
-
{
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
uri: "blockrun://wallet",
|
|
668
|
-
mimeType: "application/json",
|
|
669
|
-
text: JSON.stringify(info, null, 2)
|
|
670
|
-
}]
|
|
671
|
-
};
|
|
672
|
-
}
|
|
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
|
+
})
|
|
673
1117
|
);
|
|
674
1118
|
server.registerResource(
|
|
675
1119
|
"models",
|
|
676
1120
|
"blockrun://models",
|
|
677
|
-
{
|
|
678
|
-
description: "List of all available AI models with pricing",
|
|
679
|
-
mimeType: "application/json"
|
|
680
|
-
},
|
|
1121
|
+
{ description: "Available AI models with pricing", mimeType: "application/json" },
|
|
681
1122
|
async () => {
|
|
682
1123
|
const llm = getClient();
|
|
683
1124
|
if (!cachedModels) {
|
|
@@ -695,87 +1136,10 @@ server.registerResource(
|
|
|
695
1136
|
};
|
|
696
1137
|
}
|
|
697
1138
|
);
|
|
698
|
-
server.registerPrompt(
|
|
699
|
-
"quick_chat",
|
|
700
|
-
{
|
|
701
|
-
description: "Start a quick chat with a recommended model",
|
|
702
|
-
argsSchema: {
|
|
703
|
-
message: z.string().describe("Your message"),
|
|
704
|
-
style: z.enum(["concise", "detailed", "creative"]).optional().default("concise").describe("Response style")
|
|
705
|
-
}
|
|
706
|
-
},
|
|
707
|
-
async ({ message, style }) => {
|
|
708
|
-
const systemPrompts = {
|
|
709
|
-
concise: "Be concise and direct. Give short, focused answers.",
|
|
710
|
-
detailed: "Provide thorough, comprehensive answers with examples.",
|
|
711
|
-
creative: "Be creative and imaginative in your responses."
|
|
712
|
-
};
|
|
713
|
-
return {
|
|
714
|
-
messages: [
|
|
715
|
-
{
|
|
716
|
-
role: "user",
|
|
717
|
-
content: {
|
|
718
|
-
type: "text",
|
|
719
|
-
text: `[System: ${systemPrompts[style || "concise"]}]
|
|
720
|
-
|
|
721
|
-
${message}`
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
]
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
);
|
|
728
|
-
server.registerPrompt(
|
|
729
|
-
"code_review",
|
|
730
|
-
{
|
|
731
|
-
description: "Get a code review from a powerful model",
|
|
732
|
-
argsSchema: {
|
|
733
|
-
code: z.string().describe("The code to review"),
|
|
734
|
-
language: z.string().optional().describe("Programming language"),
|
|
735
|
-
focus: z.enum(["bugs", "performance", "style", "all"]).optional().default("all").describe("What to focus on")
|
|
736
|
-
}
|
|
737
|
-
},
|
|
738
|
-
async ({ code, language, focus }) => {
|
|
739
|
-
const focusInstructions = {
|
|
740
|
-
bugs: "Focus on potential bugs, errors, and edge cases.",
|
|
741
|
-
performance: "Focus on performance issues and optimization opportunities.",
|
|
742
|
-
style: "Focus on code style, readability, and best practices.",
|
|
743
|
-
all: "Review for bugs, performance, and style."
|
|
744
|
-
};
|
|
745
|
-
return {
|
|
746
|
-
messages: [
|
|
747
|
-
{
|
|
748
|
-
role: "user",
|
|
749
|
-
content: {
|
|
750
|
-
type: "text",
|
|
751
|
-
text: `Please review this ${language || ""} code. ${focusInstructions[focus || "all"]}
|
|
752
|
-
|
|
753
|
-
\`\`\`${language || ""}
|
|
754
|
-
${code}
|
|
755
|
-
\`\`\``
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
]
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
);
|
|
762
|
-
function formatError(message) {
|
|
763
|
-
const isPaymentError = message.toLowerCase().includes("payment") || message.toLowerCase().includes("402") || message.toLowerCase().includes("balance") || message.toLowerCase().includes("insufficient");
|
|
764
|
-
let errorText = `Error: ${message}`;
|
|
765
|
-
if (isPaymentError) {
|
|
766
|
-
errorText += `
|
|
767
|
-
|
|
768
|
-
This error usually means your wallet needs funding.
|
|
769
|
-
Run the blockrun_setup tool to get your wallet address and funding instructions.
|
|
770
|
-
|
|
771
|
-
Quick fix: Send USDC to your wallet on Base network.`;
|
|
772
|
-
}
|
|
773
|
-
return errorText;
|
|
774
|
-
}
|
|
775
1139
|
async function main() {
|
|
776
1140
|
const transport = new StdioServerTransport();
|
|
777
1141
|
await server.connect(transport);
|
|
778
|
-
console.error("BlockRun MCP Server started (v0.
|
|
1142
|
+
console.error("BlockRun MCP Server started (v0.4.0)");
|
|
779
1143
|
}
|
|
780
1144
|
main().catch((error) => {
|
|
781
1145
|
console.error("Fatal error:", error);
|