@clawnch/clawncher-mcp 0.1.2 → 0.1.4

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,792 +1,984 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * Clawncher MCP Server
4
- *
5
- * Model Context Protocol server exposing Clawncher SDK capabilities as tools
6
- * for AI agents. Supports token deployment, trading, liquidity management,
7
- * fee claiming, and market data on Base.
8
- *
9
- * Environment variables:
10
- * CLAWNCHER_PRIVATE_KEY - Wallet private key (required for write operations)
11
- * CLAWNCHER_NETWORK - "mainnet" or "sepolia" (default: "mainnet")
12
- * CLAWNCHER_RPC_URL - Custom RPC URL (optional)
13
- * CLAWNCHER_API_URL - API base URL (default: "https://clawn.ch")
14
- * CLAWNCHER_API_KEY - API key for verified agent deploys (optional)
15
- */
16
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
17
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
18
- import { z } from 'zod';
19
- import { createWalletClient, createPublicClient, http, formatEther, parseUnits, } from 'viem';
20
- import { privateKeyToAccount } from 'viem/accounts';
21
- import { base, baseSepolia } from 'viem/chains';
22
- import { ClawnchDeployer, ClawnchReader, ClawnchClient, ClawncherClaimer, ClawnchPortfolio, ClawnchSwapper, ClawnchApiDeployer, getAddresses, NATIVE_TOKEN_ADDRESS, } from '@clawnch/clawncher-sdk';
23
- // ============================================================================
24
- // Configuration
25
- // ============================================================================
26
- const VERSION = '0.1.0';
2
+ #!/usr/bin/env node
3
+
4
+ // src/index.ts
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import { z } from "zod";
8
+ import {
9
+ createWalletClient,
10
+ createPublicClient,
11
+ http,
12
+ formatEther,
13
+ parseUnits
14
+ } from "viem";
15
+ import { privateKeyToAccount } from "viem/accounts";
16
+ import { base, baseSepolia } from "viem/chains";
17
+ import {
18
+ ClawnchReader,
19
+ ClawnchClient,
20
+ ClawncherClaimer,
21
+ ClawnchPortfolio,
22
+ ClawnchSwapper,
23
+ ClawnchApiDeployer,
24
+ WayfinderClient,
25
+ getAddresses,
26
+ NATIVE_TOKEN_ADDRESS,
27
+ WAYFINDER_CHAIN_NAMES
28
+ } from "@clawnch/clawncher-sdk";
29
+ var VERSION = "0.1.0";
27
30
  function getConfig() {
28
- const network = (process.env.CLAWNCHER_NETWORK || 'mainnet');
29
- if (network !== 'mainnet' && network !== 'sepolia') {
30
- throw new Error('CLAWNCHER_NETWORK must be "mainnet" or "sepolia"');
31
- }
32
- const chain = network === 'mainnet' ? base : baseSepolia;
33
- const rpcUrl = process.env.CLAWNCHER_RPC_URL ||
34
- (network === 'mainnet' ? 'https://mainnet.base.org' : 'https://sepolia.base.org');
35
- const apiUrl = process.env.CLAWNCHER_API_URL || 'https://clawn.ch';
36
- const apiKey = process.env.CLAWNCHER_API_KEY;
37
- const privateKey = process.env.CLAWNCHER_PRIVATE_KEY;
38
- return { network, chain, rpcUrl, apiUrl, apiKey, privateKey };
31
+ const network = process.env.CLAWNCHER_NETWORK || "mainnet";
32
+ if (network !== "mainnet" && network !== "sepolia") {
33
+ throw new Error('CLAWNCHER_NETWORK must be "mainnet" or "sepolia"');
34
+ }
35
+ const chain = network === "mainnet" ? base : baseSepolia;
36
+ const rpcUrl = process.env.CLAWNCHER_RPC_URL || (network === "mainnet" ? "https://mainnet.base.org" : "https://sepolia.base.org");
37
+ const apiUrl = process.env.CLAWNCHER_API_URL || "https://clawn.ch";
38
+ const apiKey = process.env.CLAWNCHER_API_KEY;
39
+ const privateKey = process.env.CLAWNCHER_PRIVATE_KEY;
40
+ return { network, chain, rpcUrl, apiUrl, apiKey, privateKey };
39
41
  }
40
- // ============================================================================
41
- // Lazy-initialized clients
42
- // ============================================================================
43
- let _publicClient = null;
44
- let _walletClient = null;
45
- let _apiClient = null;
42
+ var _publicClient = null;
43
+ var _walletClient = null;
44
+ var _apiClient = null;
45
+ var _wayfinderClient = null;
46
46
  function getPublicClient() {
47
- if (!_publicClient) {
48
- const { chain, rpcUrl } = getConfig();
49
- _publicClient = createPublicClient({ chain, transport: http(rpcUrl) });
50
- }
51
- return _publicClient;
47
+ if (!_publicClient) {
48
+ const { chain, rpcUrl } = getConfig();
49
+ _publicClient = createPublicClient({ chain, transport: http(rpcUrl) });
50
+ }
51
+ return _publicClient;
52
52
  }
53
53
  function getWalletClient() {
54
- if (!_walletClient) {
55
- const { chain, rpcUrl, privateKey } = getConfig();
56
- if (!privateKey) {
57
- throw new Error('CLAWNCHER_PRIVATE_KEY is required for write operations');
58
- }
59
- const key = privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`;
60
- const account = privateKeyToAccount(key);
61
- _walletClient = createWalletClient({
62
- account,
63
- chain,
64
- transport: http(rpcUrl),
65
- });
66
- }
67
- return _walletClient;
54
+ if (!_walletClient) {
55
+ const { chain, rpcUrl, privateKey } = getConfig();
56
+ if (!privateKey) {
57
+ throw new Error("CLAWNCHER_PRIVATE_KEY is required for write operations");
58
+ }
59
+ const key = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
60
+ const account = privateKeyToAccount(key);
61
+ _walletClient = createWalletClient({
62
+ account,
63
+ chain,
64
+ transport: http(rpcUrl)
65
+ });
66
+ }
67
+ return _walletClient;
68
68
  }
69
69
  function getApiClient() {
70
- if (!_apiClient) {
71
- const { apiUrl } = getConfig();
72
- _apiClient = new ClawnchClient({ baseUrl: apiUrl });
73
- }
74
- return _apiClient;
70
+ if (!_apiClient) {
71
+ const { apiUrl } = getConfig();
72
+ _apiClient = new ClawnchClient({ baseUrl: apiUrl });
73
+ }
74
+ return _apiClient;
75
+ }
76
+ function getWayfinderClient() {
77
+ if (!_wayfinderClient) {
78
+ const apiKey = process.env.WAYFINDER_API_KEY;
79
+ if (!apiKey) {
80
+ throw new Error("WAYFINDER_API_KEY is required for Wayfinder operations");
81
+ }
82
+ _wayfinderClient = new WayfinderClient({
83
+ apiKey,
84
+ apiUrl: process.env.WAYFINDER_API_URL,
85
+ pythonPath: process.env.WAYFINDER_PYTHON_PATH
86
+ });
87
+ }
88
+ return _wayfinderClient;
75
89
  }
76
90
  function getWalletAddress() {
77
- const wallet = getWalletClient();
78
- return wallet.account.address;
91
+ const wallet = getWalletClient();
92
+ return wallet.account.address;
79
93
  }
80
- // Helpers
81
94
  function isValidAddress(addr) {
82
- return /^0x[0-9a-fA-F]{40}$/.test(addr);
95
+ return /^0x[0-9a-fA-F]{40}$/.test(addr);
83
96
  }
84
97
  function requireAddress(addr, label) {
85
- if (!isValidAddress(addr)) {
86
- throw new Error(`Invalid ${label} address: ${addr}`);
87
- }
88
- return addr;
98
+ if (!isValidAddress(addr)) {
99
+ throw new Error(`Invalid ${label} address: ${addr}`);
100
+ }
101
+ return addr;
89
102
  }
90
- // ============================================================================
91
- // MCP Server
92
- // ============================================================================
93
- const server = new McpServer({
94
- name: 'clawncher',
95
- version: VERSION,
103
+ var server = new McpServer({
104
+ name: "clawncher",
105
+ version: VERSION
96
106
  });
97
- // ────────────────────────────────────────────────────────────────────────────
98
- // Tool: clawncher_deploy
99
- // ────────────────────────────────────────────────────────────────────────────
100
- // @ts-expect-error TS2589: deep type instantiation from MCP SDK + Zod generics
101
- server.tool('clawncher_deploy', 'Deploy a new ERC-20 token on Base with a Uniswap V4 liquidity pool. Returns the transaction hash and token address. Requires CLAWNCHER_PRIVATE_KEY.', {
102
- name: z.string().min(1).max(32).describe('Token name (e.g. "My Token")'),
103
- symbol: z.string().min(1).max(10).describe('Token ticker symbol (e.g. "MYTKN")'),
104
- image: z.string().url().optional().describe('Token image URL'),
105
- description: z.string().optional().describe('Token description'),
106
- recipient: z.string().optional().describe('Fee recipient address (defaults to deployer wallet)'),
107
- fee_preference: z.enum(['Clawnch', 'Paired', 'Both']).default('Clawnch').describe('Fee type: Clawnch (token), Paired (WETH), or Both'),
108
- vault_percent: z.number().min(1).max(90).optional().describe('Vault allocation percentage (1-90)'),
109
- vault_lockup_days: z.number().min(7).default(7).optional().describe('Vault lockup duration in days (min 7)'),
110
- vault_recipient: z.string().optional().describe('Vault recipient address (defaults to deployer)'),
111
- vanity: z.boolean().default(true).describe('Enable vanity address (0xccc... prefix)'),
112
- dry_run: z.boolean().default(false).describe('Validate only, do not deploy'),
113
- network: z.enum(['mainnet', 'sepolia']).optional().describe('Override network (defaults to CLAWNCHER_NETWORK env)'),
114
- }, async (params) => {
107
+ server.tool(
108
+ "clawncher_deploy",
109
+ "Deploy a new ERC-20 token on Base with a Uniswap V4 liquidity pool, verified badge, and listing on clawn.ch. Requires CLAWNCHER_API_KEY and CLAWNCHER_PRIVATE_KEY. Free (1 per hour). Set bypass_rate_limit=true to skip cooldown by burning 10,000 CLAWNCH.",
110
+ {
111
+ name: z.string().min(1).max(32).describe("Token name"),
112
+ symbol: z.string().min(1).max(10).describe("Token ticker symbol"),
113
+ image: z.string().url().optional().describe("Token image URL"),
114
+ description: z.string().optional().describe("Token description"),
115
+ vanity: z.boolean().default(true).describe("Enable vanity address (0xccc...)"),
116
+ bypass_rate_limit: z.boolean().default(false).describe("Burn 10,000 CLAWNCH to skip 1-per-hour rate limit")
117
+ },
118
+ async (params) => {
115
119
  try {
116
- const config = getConfig();
117
- const network = params.network || config.network;
118
- const wallet = getWalletClient();
119
- const publicClient = getPublicClient();
120
- const walletAddress = wallet.account.address;
121
- const recipient = params.recipient
122
- ? requireAddress(params.recipient, 'recipient')
123
- : walletAddress;
124
- const vaultRecipient = params.vault_recipient
125
- ? requireAddress(params.vault_recipient, 'vault recipient')
126
- : walletAddress;
127
- const deployer = new ClawnchDeployer({
128
- wallet: wallet,
129
- publicClient: publicClient,
130
- network,
131
- });
132
- const deployOptions = {
133
- name: params.name,
134
- symbol: params.symbol,
135
- tokenAdmin: walletAddress,
136
- image: params.image,
137
- metadata: params.description ? { description: params.description } : undefined,
138
- rewards: {
139
- recipients: [{
140
- recipient,
141
- admin: walletAddress,
142
- bps: 10000,
143
- feePreference: params.fee_preference,
144
- }],
145
- },
146
- vanity: params.vanity,
147
- dryRun: params.dry_run,
148
- };
149
- if (params.vault_percent) {
150
- deployOptions.vault = {
151
- percentage: params.vault_percent,
152
- lockupDuration: (params.vault_lockup_days || 7) * 86400,
153
- recipient: vaultRecipient,
154
- };
155
- }
156
- const result = await deployer.deploy(deployOptions);
157
- if (params.dry_run) {
158
- const dryResult = result;
159
- return {
160
- content: [{
161
- type: 'text',
162
- text: JSON.stringify({
163
- valid: dryResult.valid,
164
- estimatedGas: dryResult.estimatedGas?.toString(),
165
- estimatedCostEth: dryResult.estimatedCostEth,
166
- translatedConfig: dryResult.translatedConfig,
167
- }, null, 2),
168
- }],
169
- };
170
- }
171
- if (result.error) {
172
- return {
173
- content: [{ type: 'text', text: `Deploy failed: ${result.error.message}` }],
174
- isError: true,
175
- };
176
- }
177
- const receipt = await result.waitForTransaction();
120
+ const config = getConfig();
121
+ if (!config.apiKey) {
178
122
  return {
179
- content: [{
180
- type: 'text',
181
- text: JSON.stringify({
182
- success: true,
183
- txHash: result.txHash,
184
- tokenAddress: receipt.address,
185
- deployer: walletAddress,
186
- network,
187
- name: params.name,
188
- symbol: params.symbol,
189
- vanity: params.vanity,
190
- }, null, 2),
191
- }],
123
+ content: [{ type: "text", text: "CLAWNCHER_API_KEY is required for verified deploys. Register first with clawncher_agent_register." }],
124
+ isError: true
192
125
  };
193
- }
194
- catch (err) {
195
- return {
196
- content: [{ type: 'text', text: `Deploy error: ${err.message}` }],
197
- isError: true,
198
- };
199
- }
200
- });
201
- // ────────────────────────────────────────────────────────────────────────────
202
- // Tool: clawncher_deploy_verified
203
- // ────────────────────────────────────────────────────────────────────────────
204
- server.tool('clawncher_deploy_verified', 'Deploy a token via the Clawnch API as a verified agent. Gets @clawnch badge. Requires CLAWNCHER_API_KEY and CLAWNCHER_PRIVATE_KEY. Free (1 per hour). Set bypass_rate_limit=true to skip cooldown by burning 10,000 CLAWNCH.', {
205
- name: z.string().min(1).max(32).describe('Token name'),
206
- symbol: z.string().min(1).max(10).describe('Token ticker symbol'),
207
- image: z.string().url().optional().describe('Token image URL'),
208
- description: z.string().optional().describe('Token description'),
209
- vanity: z.boolean().default(true).describe('Enable vanity address (0xccc...)'),
210
- bypass_rate_limit: z.boolean().default(false).describe('Burn 10,000 CLAWNCH to skip 1-per-hour rate limit'),
211
- }, async (params) => {
126
+ }
127
+ const wallet = getWalletClient();
128
+ const publicClient = getPublicClient();
129
+ const apiDeployer = new ClawnchApiDeployer({
130
+ apiKey: config.apiKey,
131
+ wallet,
132
+ publicClient,
133
+ network: config.network,
134
+ apiBaseUrl: config.apiUrl
135
+ });
136
+ const result = await apiDeployer.deploy({
137
+ name: params.name,
138
+ symbol: params.symbol,
139
+ image: params.image,
140
+ description: params.description,
141
+ vanity: params.vanity,
142
+ bypassRateLimit: params.bypass_rate_limit
143
+ });
144
+ return {
145
+ content: [{
146
+ type: "text",
147
+ text: JSON.stringify({
148
+ success: result.success,
149
+ txHash: result.txHash,
150
+ tokenAddress: result.tokenAddress,
151
+ clawnchBurned: result.clawnchBurned,
152
+ burnTxHash: result.burnTxHash,
153
+ rateLimitBypassed: result.rateLimitBypassed,
154
+ deployedFrom: result.deployedFrom,
155
+ agentId: result.agentId
156
+ }, null, 2)
157
+ }]
158
+ };
159
+ } catch (err) {
160
+ return {
161
+ content: [{ type: "text", text: `Verified deploy error: ${err.message}` }],
162
+ isError: true
163
+ };
164
+ }
165
+ }
166
+ );
167
+ server.tool(
168
+ "clawncher_token_info",
169
+ "Get comprehensive on-chain details for a Clawncher token: name, symbol, supply, deployment info, vault status, MEV config, reward recipients, and claimable fees.",
170
+ {
171
+ address: z.string().describe("Token contract address (0x...)"),
172
+ network: z.enum(["mainnet", "sepolia"]).optional().describe("Override network")
173
+ },
174
+ async (params) => {
212
175
  try {
213
- const config = getConfig();
214
- if (!config.apiKey) {
215
- return {
216
- content: [{ type: 'text', text: 'CLAWNCHER_API_KEY is required for verified deploys. Register first with clawncher_agent_register.' }],
217
- isError: true,
218
- };
219
- }
220
- const wallet = getWalletClient();
221
- const publicClient = getPublicClient();
222
- const apiDeployer = new ClawnchApiDeployer({
223
- apiKey: config.apiKey,
224
- wallet: wallet,
225
- publicClient: publicClient,
226
- network: config.network,
227
- apiBaseUrl: config.apiUrl,
228
- });
229
- const result = await apiDeployer.deploy({
230
- name: params.name,
231
- symbol: params.symbol,
232
- image: params.image,
233
- description: params.description,
234
- vanity: params.vanity,
235
- bypassRateLimit: params.bypass_rate_limit,
236
- });
176
+ const config = getConfig();
177
+ const network = params.network || config.network;
178
+ const publicClient = getPublicClient();
179
+ const tokenAddress = requireAddress(params.address, "token");
180
+ const reader = new ClawnchReader({
181
+ publicClient,
182
+ network
183
+ });
184
+ const details = await reader.getTokenDetails(tokenAddress);
185
+ if (!details) {
237
186
  return {
238
- content: [{
239
- type: 'text',
240
- text: JSON.stringify({
241
- success: result.success,
242
- txHash: result.txHash,
243
- tokenAddress: result.tokenAddress,
244
- clawnchBurned: result.clawnchBurned,
245
- burnTxHash: result.burnTxHash,
246
- rateLimitBypassed: result.rateLimitBypassed,
247
- deployedFrom: result.deployedFrom,
248
- agentId: result.agentId,
249
- }, null, 2),
250
- }],
187
+ content: [{ type: "text", text: `Token not found or not a Clawncher token: ${tokenAddress}` }]
251
188
  };
252
- }
253
- catch (err) {
254
- return {
255
- content: [{ type: 'text', text: `Verified deploy error: ${err.message}` }],
256
- isError: true,
189
+ }
190
+ const formatted = {
191
+ address: details.address,
192
+ name: details.name,
193
+ symbol: details.symbol,
194
+ decimals: details.decimals,
195
+ totalSupply: formatEther(details.totalSupply),
196
+ tokenAdmin: details.tokenAdmin,
197
+ network
198
+ };
199
+ if (details.deployment) {
200
+ formatted.deployment = {
201
+ hook: details.deployment.hook,
202
+ locker: details.deployment.locker,
203
+ extensions: details.deployment.extensions
257
204
  };
258
- }
259
- });
260
- // ────────────────────────────────────────────────────────────────────────────
261
- // Tool: clawncher_token_info
262
- // ────────────────────────────────────────────────────────────────────────────
263
- server.tool('clawncher_token_info', 'Get comprehensive on-chain details for a Clawncher token: name, symbol, supply, deployment info, vault status, MEV config, reward recipients, and claimable fees.', {
264
- address: z.string().describe('Token contract address (0x...)'),
265
- network: z.enum(['mainnet', 'sepolia']).optional().describe('Override network'),
266
- }, async (params) => {
267
- try {
268
- const config = getConfig();
269
- const network = params.network || config.network;
270
- const publicClient = getPublicClient();
271
- const tokenAddress = requireAddress(params.address, 'token');
272
- const reader = new ClawnchReader({
273
- publicClient: publicClient,
274
- network,
275
- });
276
- const details = await reader.getTokenDetails(tokenAddress);
277
- if (!details) {
278
- return {
279
- content: [{ type: 'text', text: `Token not found or not a Clawncher token: ${tokenAddress}` }],
280
- };
281
- }
282
- const formatted = {
283
- address: details.address,
284
- name: details.name,
285
- symbol: details.symbol,
286
- decimals: details.decimals,
287
- totalSupply: formatEther(details.totalSupply),
288
- tokenAdmin: details.tokenAdmin,
289
- network,
205
+ }
206
+ if (details.rewards) {
207
+ formatted.rewards = {
208
+ rewardBps: details.rewards.rewardBps,
209
+ rewardRecipients: details.rewards.rewardRecipients,
210
+ rewardAdmins: details.rewards.rewardAdmins,
211
+ numPositions: details.rewards.numPositions.toString()
290
212
  };
291
- if (details.deployment) {
292
- formatted.deployment = {
293
- hook: details.deployment.hook,
294
- locker: details.deployment.locker,
295
- extensions: details.deployment.extensions,
296
- };
297
- }
298
- if (details.rewards) {
299
- formatted.rewards = {
300
- rewardBps: details.rewards.rewardBps,
301
- rewardRecipients: details.rewards.rewardRecipients,
302
- rewardAdmins: details.rewards.rewardAdmins,
303
- numPositions: details.rewards.numPositions.toString(),
304
- };
305
- }
306
- if (details.vault) {
307
- formatted.vault = {
308
- amountTotal: formatEther(details.vault.amountTotal),
309
- amountClaimed: formatEther(details.vault.amountClaimed),
310
- amountAvailable: formatEther(details.vault.amountAvailable),
311
- isUnlocked: details.vault.isUnlocked,
312
- isFullyVested: details.vault.isFullyVested,
313
- percentVested: details.vault.percentVested,
314
- lockupEndTime: details.vault.lockupEndTime.toString(),
315
- vestingEndTime: details.vault.vestingEndTime.toString(),
316
- admin: details.vault.admin,
317
- };
318
- }
319
- if (details.mev) {
320
- formatted.mev = {
321
- startingFee: details.mev.startingFee,
322
- endingFee: details.mev.endingFee,
323
- currentFee: details.mev.currentFee,
324
- isDecayComplete: details.mev.isDecayComplete,
325
- secondsToDecay: details.mev.secondsToDecay.toString(),
326
- };
327
- }
328
- return {
329
- content: [{ type: 'text', text: JSON.stringify(formatted, null, 2) }],
213
+ }
214
+ if (details.vault) {
215
+ formatted.vault = {
216
+ amountTotal: formatEther(details.vault.amountTotal),
217
+ amountClaimed: formatEther(details.vault.amountClaimed),
218
+ amountAvailable: formatEther(details.vault.amountAvailable),
219
+ isUnlocked: details.vault.isUnlocked,
220
+ isFullyVested: details.vault.isFullyVested,
221
+ percentVested: details.vault.percentVested,
222
+ lockupEndTime: details.vault.lockupEndTime.toString(),
223
+ vestingEndTime: details.vault.vestingEndTime.toString(),
224
+ admin: details.vault.admin
330
225
  };
331
- }
332
- catch (err) {
333
- return {
334
- content: [{ type: 'text', text: `Token info error: ${err.message}` }],
335
- isError: true,
226
+ }
227
+ if (details.mev) {
228
+ formatted.mev = {
229
+ startingFee: details.mev.startingFee,
230
+ endingFee: details.mev.endingFee,
231
+ currentFee: details.mev.currentFee,
232
+ isDecayComplete: details.mev.isDecayComplete,
233
+ secondsToDecay: details.mev.secondsToDecay.toString()
336
234
  };
337
- }
338
- });
339
- // ────────────────────────────────────────────────────────────────────────────
340
- // Tool: clawncher_swap
341
- // ────────────────────────────────────────────────────────────────────────────
342
- server.tool('clawncher_swap', 'Execute a token swap on Base via 0x aggregation. Supports any ERC-20 pair. Use "ETH" or the native token address for ETH. Requires CLAWNCHER_PRIVATE_KEY.', {
235
+ }
236
+ return {
237
+ content: [{ type: "text", text: JSON.stringify(formatted, null, 2) }]
238
+ };
239
+ } catch (err) {
240
+ return {
241
+ content: [{ type: "text", text: `Token info error: ${err.message}` }],
242
+ isError: true
243
+ };
244
+ }
245
+ }
246
+ );
247
+ server.tool(
248
+ "clawncher_swap",
249
+ 'Execute a token swap on Base via 0x aggregation. Supports any ERC-20 pair. Use "ETH" or the native token address for ETH. Requires CLAWNCHER_PRIVATE_KEY.',
250
+ {
343
251
  sell_token: z.string().describe('Token to sell (address, or "ETH")'),
344
252
  buy_token: z.string().describe('Token to buy (address, or "ETH")'),
345
253
  amount: z.string().describe('Amount to sell in human-readable units (e.g. "0.01", "100")'),
346
- slippage_bps: z.number().default(100).describe('Slippage tolerance in basis points (100 = 1%)'),
347
- price_only: z.boolean().default(false).describe('If true, return price quote without executing'),
348
- }, async (params) => {
254
+ slippage_bps: z.number().default(100).describe("Slippage tolerance in basis points (100 = 1%)"),
255
+ price_only: z.boolean().default(false).describe("If true, return price quote without executing")
256
+ },
257
+ async (params) => {
349
258
  try {
350
- const config = getConfig();
351
- const wallet = getWalletClient();
352
- const publicClient = getPublicClient();
353
- const swapper = new ClawnchSwapper({
354
- wallet: wallet,
355
- publicClient: publicClient,
356
- network: config.network,
357
- apiBaseUrl: config.apiUrl,
358
- });
359
- const sellToken = params.sell_token.toLowerCase() === 'eth'
360
- ? NATIVE_TOKEN_ADDRESS
361
- : requireAddress(params.sell_token, 'sell token');
362
- const buyToken = params.buy_token.toLowerCase() === 'eth'
363
- ? NATIVE_TOKEN_ADDRESS
364
- : requireAddress(params.buy_token, 'buy token');
365
- const sellDecimals = await swapper.getDecimals(sellToken);
366
- const sellAmount = parseUnits(params.amount, sellDecimals);
367
- if (params.price_only) {
368
- const price = await swapper.getPrice({
369
- sellToken,
370
- buyToken,
371
- sellAmount,
372
- slippageBps: params.slippage_bps,
373
- });
374
- const buyDecimals = await swapper.getDecimals(buyToken);
375
- const buySymbol = buyToken === NATIVE_TOKEN_ADDRESS ? 'ETH' : await swapper.getSymbol(buyToken);
376
- const sellSymbol = sellToken === NATIVE_TOKEN_ADDRESS ? 'ETH' : await swapper.getSymbol(sellToken);
377
- return {
378
- content: [{
379
- type: 'text',
380
- text: JSON.stringify({
381
- sellToken: sellSymbol,
382
- buyToken: buySymbol,
383
- sellAmount: params.amount,
384
- buyAmount: formatUnitsCustom(price.buyAmount, buyDecimals),
385
- minBuyAmount: formatUnitsCustom(price.minBuyAmount, buyDecimals),
386
- gasEstimate: price.gas.toString(),
387
- liquidityAvailable: price.liquidityAvailable,
388
- }, null, 2),
389
- }],
390
- };
391
- }
392
- const result = await swapper.swap({
393
- sellToken,
394
- buyToken,
395
- sellAmount,
396
- slippageBps: params.slippage_bps,
259
+ const config = getConfig();
260
+ const wallet = getWalletClient();
261
+ const publicClient = getPublicClient();
262
+ const swapper = new ClawnchSwapper({
263
+ wallet,
264
+ publicClient,
265
+ network: config.network,
266
+ apiBaseUrl: config.apiUrl
267
+ });
268
+ const sellToken = params.sell_token.toLowerCase() === "eth" ? NATIVE_TOKEN_ADDRESS : requireAddress(params.sell_token, "sell token");
269
+ const buyToken = params.buy_token.toLowerCase() === "eth" ? NATIVE_TOKEN_ADDRESS : requireAddress(params.buy_token, "buy token");
270
+ const sellDecimals = await swapper.getDecimals(sellToken);
271
+ const sellAmount = parseUnits(params.amount, sellDecimals);
272
+ if (params.price_only) {
273
+ const price = await swapper.getPrice({
274
+ sellToken,
275
+ buyToken,
276
+ sellAmount,
277
+ slippageBps: params.slippage_bps
397
278
  });
398
- const buyDecimals = await swapper.getDecimals(buyToken);
399
- const buySymbol = buyToken === NATIVE_TOKEN_ADDRESS ? 'ETH' : await swapper.getSymbol(buyToken);
400
- return {
401
- content: [{
402
- type: 'text',
403
- text: JSON.stringify({
404
- success: true,
405
- txHash: result.txHash,
406
- buyAmount: formatUnitsCustom(result.buyAmount, buyDecimals),
407
- buyToken: buySymbol,
408
- sellAmount: params.amount,
409
- gasUsed: result.gasUsed.toString(),
410
- }, null, 2),
411
- }],
412
- };
413
- }
414
- catch (err) {
279
+ const buyDecimals2 = await swapper.getDecimals(buyToken);
280
+ const buySymbol2 = buyToken === NATIVE_TOKEN_ADDRESS ? "ETH" : await swapper.getSymbol(buyToken);
281
+ const sellSymbol = sellToken === NATIVE_TOKEN_ADDRESS ? "ETH" : await swapper.getSymbol(sellToken);
415
282
  return {
416
- content: [{ type: 'text', text: `Swap error: ${err.message}` }],
417
- isError: true,
283
+ content: [{
284
+ type: "text",
285
+ text: JSON.stringify({
286
+ sellToken: sellSymbol,
287
+ buyToken: buySymbol2,
288
+ sellAmount: params.amount,
289
+ buyAmount: formatUnitsCustom(price.buyAmount, buyDecimals2),
290
+ minBuyAmount: formatUnitsCustom(price.minBuyAmount, buyDecimals2),
291
+ gasEstimate: price.gas.toString(),
292
+ liquidityAvailable: price.liquidityAvailable
293
+ }, null, 2)
294
+ }]
418
295
  };
419
- }
420
- });
421
- // ────────────────────────────────────────────────────────────────────────────
422
- // Tool: clawncher_fees_check
423
- // ────────────────────────────────────────────────────────────────────────────
424
- server.tool('clawncher_fees_check', 'Check claimable LP trading fees for a wallet address. Returns available WETH and token fees.', {
425
- wallet: z.string().optional().describe('Wallet address to check (defaults to configured wallet)'),
426
- tokens: z.string().optional().describe('Comma-separated token addresses to check (optional, uses API to discover)'),
427
- }, async (params) => {
296
+ }
297
+ const result = await swapper.swap({
298
+ sellToken,
299
+ buyToken,
300
+ sellAmount,
301
+ slippageBps: params.slippage_bps
302
+ });
303
+ const buyDecimals = await swapper.getDecimals(buyToken);
304
+ const buySymbol = buyToken === NATIVE_TOKEN_ADDRESS ? "ETH" : await swapper.getSymbol(buyToken);
305
+ return {
306
+ content: [{
307
+ type: "text",
308
+ text: JSON.stringify({
309
+ success: true,
310
+ txHash: result.txHash,
311
+ buyAmount: formatUnitsCustom(result.buyAmount, buyDecimals),
312
+ buyToken: buySymbol,
313
+ sellAmount: params.amount,
314
+ gasUsed: result.gasUsed.toString()
315
+ }, null, 2)
316
+ }]
317
+ };
318
+ } catch (err) {
319
+ return {
320
+ content: [{ type: "text", text: `Swap error: ${err.message}` }],
321
+ isError: true
322
+ };
323
+ }
324
+ }
325
+ );
326
+ server.tool(
327
+ "clawncher_fees_check",
328
+ "Check claimable LP trading fees for a wallet address. Returns available WETH and token fees.",
329
+ {
330
+ wallet: z.string().optional().describe("Wallet address to check (defaults to configured wallet)"),
331
+ tokens: z.string().optional().describe("Comma-separated token addresses to check (optional, uses API to discover)")
332
+ },
333
+ async (params) => {
428
334
  try {
429
- const walletAddr = params.wallet
430
- ? requireAddress(params.wallet, 'wallet')
431
- : getWalletAddress();
432
- const client = getApiClient();
433
- const fees = await client.getAvailableFees(walletAddr, params.tokens);
434
- return {
435
- content: [{
436
- type: 'text',
437
- text: JSON.stringify({
438
- wallet: fees.wallet,
439
- weth: fees.weth,
440
- tokens: fees.tokens,
441
- tokensChecked: fees.tokensChecked,
442
- }, null, 2),
443
- }],
444
- };
445
- }
446
- catch (err) {
447
- return {
448
- content: [{ type: 'text', text: `Fees check error: ${err.message}` }],
449
- isError: true,
450
- };
451
- }
452
- });
453
- // ────────────────────────────────────────────────────────────────────────────
454
- // Tool: clawncher_fees_claim
455
- // ────────────────────────────────────────────────────────────────────────────
456
- server.tool('clawncher_fees_claim', 'Claim accumulated LP trading fees for a specific token. Collects rewards and claims both WETH and token fees. Requires CLAWNCHER_PRIVATE_KEY.', {
457
- token: z.string().describe('Token contract address to claim fees for'),
458
- }, async (params) => {
335
+ const walletAddr = params.wallet ? requireAddress(params.wallet, "wallet") : getWalletAddress();
336
+ const client = getApiClient();
337
+ const fees = await client.getAvailableFees(walletAddr, params.tokens);
338
+ return {
339
+ content: [{
340
+ type: "text",
341
+ text: JSON.stringify({
342
+ wallet: fees.wallet,
343
+ weth: fees.weth,
344
+ tokens: fees.tokens,
345
+ tokensChecked: fees.tokensChecked
346
+ }, null, 2)
347
+ }]
348
+ };
349
+ } catch (err) {
350
+ return {
351
+ content: [{ type: "text", text: `Fees check error: ${err.message}` }],
352
+ isError: true
353
+ };
354
+ }
355
+ }
356
+ );
357
+ server.tool(
358
+ "clawncher_fees_claim",
359
+ "Claim accumulated LP trading fees for a specific token. Collects rewards and claims both WETH and token fees. Requires CLAWNCHER_PRIVATE_KEY.",
360
+ {
361
+ token: z.string().describe("Token contract address to claim fees for")
362
+ },
363
+ async (params) => {
459
364
  try {
460
- const config = getConfig();
461
- const wallet = getWalletClient();
462
- const publicClient = getPublicClient();
463
- const tokenAddress = requireAddress(params.token, 'token');
464
- const claimer = new ClawncherClaimer({
465
- wallet: wallet,
466
- publicClient: publicClient,
467
- network: config.network,
468
- });
469
- const result = await claimer.claimAll(tokenAddress, wallet.account.address);
470
- return {
471
- content: [{
472
- type: 'text',
473
- text: JSON.stringify({
474
- success: true,
475
- collectRewards: { txHash: result.collectRewards.txHash },
476
- claimFeesWeth: result.claimFeesWeth ? { txHash: result.claimFeesWeth.txHash } : null,
477
- claimFeesToken: result.claimFeesToken ? { txHash: result.claimFeesToken.txHash } : null,
478
- }, null, 2),
479
- }],
480
- };
481
- }
482
- catch (err) {
483
- return {
484
- content: [{ type: 'text', text: `Fees claim error: ${err.message}` }],
485
- isError: true,
486
- };
487
- }
488
- });
489
- // ────────────────────────────────────────────────────────────────────────────
490
- // Tool: clawncher_stats
491
- // ────────────────────────────────────────────────────────────────────────────
492
- server.tool('clawncher_stats', 'Get Clawnch market statistics: total tokens launched, volume, $CLAWNCH price and market cap.', {}, async () => {
365
+ const config = getConfig();
366
+ const wallet = getWalletClient();
367
+ const publicClient = getPublicClient();
368
+ const tokenAddress = requireAddress(params.token, "token");
369
+ const claimer = new ClawncherClaimer({
370
+ wallet,
371
+ publicClient,
372
+ network: config.network
373
+ });
374
+ const result = await claimer.claimAll(tokenAddress, wallet.account.address);
375
+ return {
376
+ content: [{
377
+ type: "text",
378
+ text: JSON.stringify({
379
+ success: true,
380
+ collectRewards: { txHash: result.collectRewards.txHash },
381
+ claimFeesWeth: result.claimFeesWeth ? { txHash: result.claimFeesWeth.txHash } : null,
382
+ claimFeesToken: result.claimFeesToken ? { txHash: result.claimFeesToken.txHash } : null
383
+ }, null, 2)
384
+ }]
385
+ };
386
+ } catch (err) {
387
+ return {
388
+ content: [{ type: "text", text: `Fees claim error: ${err.message}` }],
389
+ isError: true
390
+ };
391
+ }
392
+ }
393
+ );
394
+ server.tool(
395
+ "clawncher_stats",
396
+ "Get Clawnch market statistics: total tokens launched, volume, $CLAWNCH price and market cap.",
397
+ {},
398
+ async () => {
493
399
  try {
494
- const client = getApiClient();
495
- const stats = await client.getStats();
496
- return {
497
- content: [{
498
- type: 'text',
499
- text: JSON.stringify(stats, null, 2),
500
- }],
501
- };
502
- }
503
- catch (err) {
504
- return {
505
- content: [{ type: 'text', text: `Stats error: ${err.message}` }],
506
- isError: true,
507
- };
508
- }
509
- });
510
- // ────────────────────────────────────────────────────────────────────────────
511
- // Tool: clawncher_tokens
512
- // ────────────────────────────────────────────────────────────────────────────
513
- server.tool('clawncher_tokens', 'List tokens launched via Clawnch. Can filter by agent name, source, or address.', {
514
- limit: z.number().default(20).describe('Max tokens to return (default 20)'),
515
- offset: z.number().default(0).describe('Offset for pagination'),
516
- agent: z.string().optional().describe('Filter by agent name'),
517
- source: z.string().optional().describe('Filter by source platform'),
518
- address: z.string().optional().describe('Filter by contract address'),
519
- }, async (params) => {
400
+ const client = getApiClient();
401
+ const stats = await client.getStats();
402
+ return {
403
+ content: [{
404
+ type: "text",
405
+ text: JSON.stringify(stats, null, 2)
406
+ }]
407
+ };
408
+ } catch (err) {
409
+ return {
410
+ content: [{ type: "text", text: `Stats error: ${err.message}` }],
411
+ isError: true
412
+ };
413
+ }
414
+ }
415
+ );
416
+ server.tool(
417
+ "clawncher_tokens",
418
+ "List tokens launched via Clawnch. Can filter by agent name, source, or address.",
419
+ {
420
+ limit: z.number().default(20).describe("Max tokens to return (default 20)"),
421
+ offset: z.number().default(0).describe("Offset for pagination"),
422
+ agent: z.string().optional().describe("Filter by agent name"),
423
+ source: z.string().optional().describe("Filter by source platform"),
424
+ address: z.string().optional().describe("Filter by contract address")
425
+ },
426
+ async (params) => {
520
427
  try {
521
- const client = getApiClient();
522
- const response = await client.getLaunches({
523
- limit: params.limit,
524
- offset: params.offset,
525
- agent: params.agent,
526
- source: params.source,
527
- address: params.address,
528
- });
529
- return {
530
- content: [{
531
- type: 'text',
532
- text: JSON.stringify(response, null, 2),
533
- }],
534
- };
535
- }
536
- catch (err) {
428
+ const client = getApiClient();
429
+ const response = await client.getLaunches({
430
+ limit: params.limit,
431
+ offset: params.offset,
432
+ agent: params.agent,
433
+ source: params.source,
434
+ address: params.address
435
+ });
436
+ return {
437
+ content: [{
438
+ type: "text",
439
+ text: JSON.stringify(response, null, 2)
440
+ }]
441
+ };
442
+ } catch (err) {
443
+ return {
444
+ content: [{ type: "text", text: `Tokens list error: ${err.message}` }],
445
+ isError: true
446
+ };
447
+ }
448
+ }
449
+ );
450
+ server.tool(
451
+ "clawncher_portfolio",
452
+ "Get portfolio summary for a wallet: all Clawncher tokens with claimable fees. Uses on-chain data.",
453
+ {
454
+ wallet: z.string().optional().describe("Wallet address (defaults to configured wallet)"),
455
+ network: z.enum(["mainnet", "sepolia"]).optional().describe("Override network")
456
+ },
457
+ async (params) => {
458
+ try {
459
+ const config = getConfig();
460
+ const network = params.network || config.network;
461
+ const publicClient = getPublicClient();
462
+ const walletAddr = params.wallet ? requireAddress(params.wallet, "wallet") : getWalletAddress();
463
+ const portfolio = new ClawnchPortfolio({
464
+ publicClient,
465
+ network
466
+ });
467
+ const tokens = await portfolio.discoverTokens(walletAddr);
468
+ if (tokens.length === 0) {
537
469
  return {
538
- content: [{ type: 'text', text: `Tokens list error: ${err.message}` }],
539
- isError: true,
470
+ content: [{ type: "text", text: JSON.stringify({ wallet: walletAddr, tokens: [], message: "No Clawncher tokens found for this wallet" }, null, 2) }]
540
471
  };
541
- }
542
- });
543
- // ────────────────────────────────────────────────────────────────────────────
544
- // Tool: clawncher_portfolio
545
- // ────────────────────────────────────────────────────────────────────────────
546
- server.tool('clawncher_portfolio', 'Get portfolio summary for a wallet: all Clawncher tokens with claimable fees. Uses on-chain data.', {
547
- wallet: z.string().optional().describe('Wallet address (defaults to configured wallet)'),
548
- network: z.enum(['mainnet', 'sepolia']).optional().describe('Override network'),
549
- }, async (params) => {
550
- try {
551
- const config = getConfig();
552
- const network = params.network || config.network;
553
- const publicClient = getPublicClient();
554
- const walletAddr = params.wallet
555
- ? requireAddress(params.wallet, 'wallet')
556
- : getWalletAddress();
557
- const portfolio = new ClawnchPortfolio({
558
- publicClient: publicClient,
472
+ }
473
+ const holdings = await portfolio.getTokensForWallet(walletAddr, tokens);
474
+ const totals = await portfolio.getTotalClaimable(walletAddr, tokens);
475
+ return {
476
+ content: [{
477
+ type: "text",
478
+ text: JSON.stringify({
479
+ wallet: walletAddr,
559
480
  network,
560
- });
561
- // Discover tokens first
562
- const tokens = await portfolio.discoverTokens(walletAddr);
563
- if (tokens.length === 0) {
564
- return {
565
- content: [{ type: 'text', text: JSON.stringify({ wallet: walletAddr, tokens: [], message: 'No Clawncher tokens found for this wallet' }, null, 2) }],
566
- };
481
+ tokensFound: tokens.length,
482
+ holdings: holdings.map((h) => ({
483
+ address: h.address,
484
+ name: h.name,
485
+ symbol: h.symbol,
486
+ rewardBps: h.bps,
487
+ claimableWeth: h.formattedWeth,
488
+ claimableToken: h.formattedToken
489
+ })),
490
+ totals: {
491
+ weth: totals.formattedWeth,
492
+ tokens: totals.tokens.map((t) => ({
493
+ address: t.address,
494
+ symbol: t.symbol,
495
+ amount: t.formatted
496
+ }))
497
+ }
498
+ }, null, 2)
499
+ }]
500
+ };
501
+ } catch (err) {
502
+ return {
503
+ content: [{ type: "text", text: `Portfolio error: ${err.message}` }],
504
+ isError: true
505
+ };
506
+ }
507
+ }
508
+ );
509
+ server.tool(
510
+ "clawncher_addresses",
511
+ "Get all Clawncher and Uniswap contract addresses for a network.",
512
+ {
513
+ network: z.enum(["mainnet", "sepolia"]).optional().describe("Network (defaults to configured)")
514
+ },
515
+ async (params) => {
516
+ try {
517
+ const config = getConfig();
518
+ const network = params.network || config.network;
519
+ const addresses = getAddresses(network);
520
+ return {
521
+ content: [{
522
+ type: "text",
523
+ text: JSON.stringify({ network, ...addresses }, null, 2)
524
+ }]
525
+ };
526
+ } catch (err) {
527
+ return {
528
+ content: [{ type: "text", text: `Addresses error: ${err.message}` }],
529
+ isError: true
530
+ };
531
+ }
532
+ }
533
+ );
534
+ server.tool(
535
+ "clawncher_agent_register",
536
+ "Register as a verified Clawnch agent. Returns an API key for verified deploys. Requires CLAWNCHER_PRIVATE_KEY and signing a message.",
537
+ {
538
+ name: z.string().min(1).describe("Agent name"),
539
+ description: z.string().describe("Agent description")
540
+ },
541
+ async (params) => {
542
+ try {
543
+ const config = getConfig();
544
+ const wallet = getWalletClient();
545
+ const publicClient = getPublicClient();
546
+ const result = await ClawnchApiDeployer.register(
547
+ {
548
+ wallet,
549
+ publicClient,
550
+ apiBaseUrl: config.apiUrl
551
+ },
552
+ {
553
+ name: params.name,
554
+ wallet: wallet.account.address,
555
+ description: params.description
567
556
  }
568
- const holdings = await portfolio.getTokensForWallet(walletAddr, tokens);
569
- const totals = await portfolio.getTotalClaimable(walletAddr, tokens);
570
- return {
571
- content: [{
572
- type: 'text',
573
- text: JSON.stringify({
574
- wallet: walletAddr,
575
- network,
576
- tokensFound: tokens.length,
577
- holdings: holdings.map(h => ({
578
- address: h.address,
579
- name: h.name,
580
- symbol: h.symbol,
581
- rewardBps: h.bps,
582
- claimableWeth: h.formattedWeth,
583
- claimableToken: h.formattedToken,
584
- })),
585
- totals: {
586
- weth: totals.formattedWeth,
587
- tokens: totals.tokens.map(t => ({
588
- address: t.address,
589
- symbol: t.symbol,
590
- amount: t.formatted,
591
- })),
592
- },
593
- }, null, 2),
594
- }],
595
- };
596
- }
597
- catch (err) {
598
- return {
599
- content: [{ type: 'text', text: `Portfolio error: ${err.message}` }],
600
- isError: true,
601
- };
602
- }
603
- });
604
- // ────────────────────────────────────────────────────────────────────────────
605
- // Tool: clawncher_addresses
606
- // ────────────────────────────────────────────────────────────────────────────
607
- server.tool('clawncher_addresses', 'Get all Clawncher and Uniswap contract addresses for a network.', {
608
- network: z.enum(['mainnet', 'sepolia']).optional().describe('Network (defaults to configured)'),
609
- }, async (params) => {
557
+ );
558
+ return {
559
+ content: [{
560
+ type: "text",
561
+ text: JSON.stringify({
562
+ success: true,
563
+ apiKey: result.apiKey,
564
+ agentId: result.agentId,
565
+ wallet: result.wallet,
566
+ note: "Save the apiKey as CLAWNCHER_API_KEY for verified deploys. Free deploys are rate-limited to 1 per hour. To bypass, approve the Clawnch deployer to spend your $CLAWNCH (10,000 CLAWNCH per bypass)."
567
+ }, null, 2)
568
+ }]
569
+ };
570
+ } catch (err) {
571
+ return {
572
+ content: [{ type: "text", text: `Registration error: ${err.message}` }],
573
+ isError: true
574
+ };
575
+ }
576
+ }
577
+ );
578
+ server.tool(
579
+ "clawncher_agent_status",
580
+ "Check the status of the configured verified agent: registration, CLAWNCH balance, allowance, and launch count.",
581
+ {},
582
+ async () => {
610
583
  try {
611
- const config = getConfig();
612
- const network = params.network || config.network;
613
- const addresses = getAddresses(network);
584
+ const config = getConfig();
585
+ if (!config.apiKey) {
614
586
  return {
615
- content: [{
616
- type: 'text',
617
- text: JSON.stringify({ network, ...addresses }, null, 2),
618
- }],
587
+ content: [{ type: "text", text: "CLAWNCHER_API_KEY not set. Register first with clawncher_agent_register." }],
588
+ isError: true
619
589
  };
620
- }
621
- catch (err) {
622
- return {
623
- content: [{ type: 'text', text: `Addresses error: ${err.message}` }],
624
- isError: true,
625
- };
626
- }
627
- });
628
- // ────────────────────────────────────────────────────────────────────────────
629
- // Tool: clawncher_agent_register
630
- // ────────────────────────────────────────────────────────────────────────────
631
- server.tool('clawncher_agent_register', 'Register as a verified Clawnch agent. Returns an API key for verified deploys. Requires CLAWNCHER_PRIVATE_KEY and signing a message.', {
632
- name: z.string().min(1).describe('Agent name'),
633
- description: z.string().describe('Agent description'),
634
- }, async (params) => {
590
+ }
591
+ const wallet = getWalletClient();
592
+ const publicClient = getPublicClient();
593
+ const apiDeployer = new ClawnchApiDeployer({
594
+ apiKey: config.apiKey,
595
+ wallet,
596
+ publicClient,
597
+ network: config.network,
598
+ apiBaseUrl: config.apiUrl
599
+ });
600
+ const status = await apiDeployer.getStatus();
601
+ return {
602
+ content: [{
603
+ type: "text",
604
+ text: JSON.stringify(status, null, 2)
605
+ }]
606
+ };
607
+ } catch (err) {
608
+ return {
609
+ content: [{ type: "text", text: `Agent status error: ${err.message}` }],
610
+ isError: true
611
+ };
612
+ }
613
+ }
614
+ );
615
+ server.tool(
616
+ "clawncher_wallet_balance",
617
+ "Get ETH and ERC-20 token balances for the configured wallet or any address.",
618
+ {
619
+ wallet: z.string().optional().describe("Wallet address (defaults to configured wallet)"),
620
+ token: z.string().optional().describe("ERC-20 token address to check balance (omit for ETH only)")
621
+ },
622
+ async (params) => {
635
623
  try {
636
- const config = getConfig();
637
- const wallet = getWalletClient();
638
- const publicClient = getPublicClient();
639
- const result = await ClawnchApiDeployer.register({
640
- wallet: wallet,
641
- publicClient: publicClient,
642
- apiBaseUrl: config.apiUrl,
643
- }, {
644
- name: params.name,
645
- wallet: wallet.account.address,
646
- description: params.description,
647
- });
648
- return {
649
- content: [{
650
- type: 'text',
651
- text: JSON.stringify({
652
- success: true,
653
- apiKey: result.apiKey,
654
- agentId: result.agentId,
655
- wallet: result.wallet,
656
- note: 'Save the apiKey as CLAWNCHER_API_KEY for verified deploys. Free deploys are rate-limited to 1 per hour. To bypass, approve the Clawnch deployer to spend your $CLAWNCH (10,000 CLAWNCH per bypass).',
657
- }, null, 2),
658
- }],
659
- };
660
- }
661
- catch (err) {
662
- return {
663
- content: [{ type: 'text', text: `Registration error: ${err.message}` }],
664
- isError: true,
624
+ const publicClient = getPublicClient();
625
+ const walletAddr = params.wallet ? requireAddress(params.wallet, "wallet") : getWalletAddress();
626
+ const ethBalance = await publicClient.getBalance({ address: walletAddr });
627
+ const result = {
628
+ wallet: walletAddr,
629
+ ethBalance: formatEther(ethBalance)
630
+ };
631
+ if (params.token) {
632
+ const tokenAddr = requireAddress(params.token, "token");
633
+ const erc20Abi = [
634
+ { type: "function", name: "balanceOf", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }], stateMutability: "view" },
635
+ { type: "function", name: "decimals", inputs: [], outputs: [{ type: "uint8" }], stateMutability: "view" },
636
+ { type: "function", name: "symbol", inputs: [], outputs: [{ type: "string" }], stateMutability: "view" }
637
+ ];
638
+ const [balance, decimals, symbol] = await Promise.all([
639
+ publicClient.readContract({ address: tokenAddr, abi: erc20Abi, functionName: "balanceOf", args: [walletAddr] }),
640
+ publicClient.readContract({ address: tokenAddr, abi: erc20Abi, functionName: "decimals" }),
641
+ publicClient.readContract({ address: tokenAddr, abi: erc20Abi, functionName: "symbol" })
642
+ ]);
643
+ result.tokenBalance = {
644
+ address: tokenAddr,
645
+ symbol,
646
+ balance: formatUnitsCustom(balance, decimals),
647
+ raw: balance.toString()
665
648
  };
666
- }
667
- });
668
- // ────────────────────────────────────────────────────────────────────────────
669
- // Tool: clawncher_agent_status
670
- // ────────────────────────────────────────────────────────────────────────────
671
- server.tool('clawncher_agent_status', 'Check the status of the configured verified agent: registration, CLAWNCH balance, allowance, and launch count.', {}, async () => {
649
+ }
650
+ return {
651
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
652
+ };
653
+ } catch (err) {
654
+ return {
655
+ content: [{ type: "text", text: `Balance error: ${err.message}` }],
656
+ isError: true
657
+ };
658
+ }
659
+ }
660
+ );
661
+ server.tool(
662
+ "clawncher_skill",
663
+ "Retrieve the Clawncher SDK quick-start documentation (skill doc). Returns the full markdown guide for using Clawncher.",
664
+ {},
665
+ async () => {
672
666
  try {
673
- const config = getConfig();
674
- if (!config.apiKey) {
675
- return {
676
- content: [{ type: 'text', text: 'CLAWNCHER_API_KEY not set. Register first with clawncher_agent_register.' }],
677
- isError: true,
678
- };
667
+ const client = getApiClient();
668
+ const skill = await client.getSkill();
669
+ return {
670
+ content: [{ type: "text", text: skill }]
671
+ };
672
+ } catch (err) {
673
+ return {
674
+ content: [{ type: "text", text: `Skill fetch error: ${err.message}` }],
675
+ isError: true
676
+ };
677
+ }
678
+ }
679
+ );
680
+ server.tool(
681
+ "wayfinder_swap",
682
+ "Get cross-chain swap quotes via Wayfinder BRAP aggregator (LiFi, Squid, etc.). Supports any chain pair: Ethereum, Base, Arbitrum, Polygon, BSC, Avalanche, HyperEVM. Set execute=true to sign and broadcast via the Wayfinder CLI (requires Python + wayfinder-paths installed locally). Requires WAYFINDER_API_KEY.",
683
+ {
684
+ from_token: z.string().describe('Source token (contract address or ID like "usd-coin")'),
685
+ to_token: z.string().describe('Destination token (contract address or ID like "ethereum")'),
686
+ from_chain: z.number().describe("Source chain ID (1=Ethereum, 8453=Base, 42161=Arbitrum, 137=Polygon)"),
687
+ to_chain: z.number().describe("Destination chain ID"),
688
+ wallet_address: z.string().describe("Sender wallet address"),
689
+ amount: z.string().describe('Amount in raw token units (WEI). For 5 USDC (6 decimals) = "5000000"'),
690
+ slippage: z.number().optional().describe("Slippage tolerance (0.005 = 0.5%). Default: 0.005"),
691
+ execute: z.boolean().default(false).describe("If true, execute the swap via Wayfinder CLI (requires Python + wayfinder-paths)"),
692
+ wallet_label: z.string().optional().describe("Wayfinder wallet label (required if execute=true)")
693
+ },
694
+ async (params) => {
695
+ try {
696
+ const wf = getWayfinderClient();
697
+ if (params.execute) {
698
+ if (!params.wallet_label) {
699
+ return {
700
+ content: [{ type: "text", text: "wallet_label is required when execute=true. This must match a wallet in your wayfinder config.json." }],
701
+ isError: true
702
+ };
679
703
  }
680
- const wallet = getWalletClient();
681
- const publicClient = getPublicClient();
682
- const apiDeployer = new ClawnchApiDeployer({
683
- apiKey: config.apiKey,
684
- wallet: wallet,
685
- publicClient: publicClient,
686
- network: config.network,
687
- apiBaseUrl: config.apiUrl,
688
- });
689
- const status = await apiDeployer.getStatus();
690
- return {
704
+ const pythonStatus = await wf.checkPython();
705
+ if (!pythonStatus.wayfinderInstalled) {
706
+ return {
691
707
  content: [{
692
- type: 'text',
693
- text: JSON.stringify(status, null, 2),
694
- }],
695
- };
696
- }
697
- catch (err) {
708
+ type: "text",
709
+ text: `Wayfinder CLI not available.
710
+
711
+ Python: ${pythonStatus.available ? `${pythonStatus.version} at ${pythonStatus.pythonPath}` : "NOT FOUND"}
712
+ wayfinder-paths: NOT INSTALLED
713
+
714
+ Install with:
715
+ pip3 install wayfinder-paths`
716
+ }],
717
+ isError: true
718
+ };
719
+ }
720
+ const result = await wf.executeSwap({
721
+ kind: "swap",
722
+ walletLabel: params.wallet_label,
723
+ amount: params.amount,
724
+ fromToken: params.from_token,
725
+ toToken: params.to_token
726
+ });
698
727
  return {
699
- content: [{ type: 'text', text: `Agent status error: ${err.message}` }],
700
- isError: true,
728
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
729
+ isError: !result.ok
701
730
  };
702
- }
703
- });
704
- // ────────────────────────────────────────────────────────────────────────────
705
- // Tool: clawncher_wallet_balance
706
- // ────────────────────────────────────────────────────────────────────────────
707
- server.tool('clawncher_wallet_balance', 'Get ETH and ERC-20 token balances for the configured wallet or any address.', {
708
- wallet: z.string().optional().describe('Wallet address (defaults to configured wallet)'),
709
- token: z.string().optional().describe('ERC-20 token address to check balance (omit for ETH only)'),
710
- }, async (params) => {
731
+ }
732
+ const quote = await wf.quoteSwap({
733
+ fromToken: params.from_token,
734
+ toToken: params.to_token,
735
+ fromChain: params.from_chain,
736
+ toChain: params.to_chain,
737
+ fromWallet: params.wallet_address,
738
+ amount: params.amount,
739
+ slippage: params.slippage
740
+ });
741
+ const fromChainName = WAYFINDER_CHAIN_NAMES[params.from_chain] ?? `Chain ${params.from_chain}`;
742
+ const toChainName = WAYFINDER_CHAIN_NAMES[params.to_chain] ?? `Chain ${params.to_chain}`;
743
+ const summary = {
744
+ route: `${fromChainName} -> ${toChainName}`,
745
+ quoteCount: quote.quoteCount,
746
+ bestQuote: quote.bestQuote ? {
747
+ provider: quote.bestQuote.provider,
748
+ inputAmountUsd: `$${quote.bestQuote.inputAmountUsd.toFixed(2)}`,
749
+ outputAmountUsd: `$${quote.bestQuote.outputAmountUsd.toFixed(2)}`,
750
+ feeTotalUsd: `$${quote.bestQuote.feeEstimate.feeTotalUsd.toFixed(2)}`,
751
+ hasCalldata: !!quote.bestQuote.calldata
752
+ } : null,
753
+ allQuotes: quote.quotes.map((q) => ({
754
+ provider: q.provider,
755
+ outputAmountUsd: `$${q.outputAmountUsd.toFixed(2)}`,
756
+ feeTotalUsd: `$${q.feeEstimate.feeTotalUsd.toFixed(2)}`,
757
+ error: q.error
758
+ }))
759
+ };
760
+ return {
761
+ content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
762
+ };
763
+ } catch (err) {
764
+ return {
765
+ content: [{ type: "text", text: `Wayfinder swap error: ${err.message}` }],
766
+ isError: true
767
+ };
768
+ }
769
+ }
770
+ );
771
+ server.tool(
772
+ "wayfinder_balances",
773
+ "Get multi-chain wallet balances via Wayfinder. Shows tokens across Ethereum, Base, Arbitrum, Polygon, BSC, Avalanche with USD values. Optionally includes recent transaction activity. Requires WAYFINDER_API_KEY.",
774
+ {
775
+ wallet_address: z.string().describe("Wallet address to check balances for"),
776
+ include_activity: z.boolean().default(false).describe("Include recent transaction activity"),
777
+ exclude_spam: z.boolean().default(true).describe("Exclude spam tokens")
778
+ },
779
+ async (params) => {
711
780
  try {
712
- const publicClient = getPublicClient();
713
- const walletAddr = params.wallet
714
- ? requireAddress(params.wallet, 'wallet')
715
- : getWalletAddress();
716
- const ethBalance = await publicClient.getBalance({ address: walletAddr });
717
- const result = {
718
- wallet: walletAddr,
719
- ethBalance: formatEther(ethBalance),
781
+ const wf = getWayfinderClient();
782
+ const balances = await wf.getBalances(params.wallet_address, params.exclude_spam);
783
+ const result = {
784
+ address: balances.address,
785
+ totalValueUsd: `$${balances.totalValueUsd.toFixed(2)}`,
786
+ tokens: balances.tokens.filter((t) => t.balanceUsd > 0.01).sort((a, b) => b.balanceUsd - a.balanceUsd).map((t) => ({
787
+ symbol: t.symbol,
788
+ chain: t.chainName || WAYFINDER_CHAIN_NAMES[t.chainId] || `Chain ${t.chainId}`,
789
+ balance: t.balanceFormatted,
790
+ valueUsd: `$${t.balanceUsd.toFixed(2)}`
791
+ }))
792
+ };
793
+ if (params.include_activity) {
794
+ const activity = await wf.getWalletActivity(params.wallet_address, 10);
795
+ result.recentActivity = activity.activities.map((a) => ({
796
+ type: a.type,
797
+ summary: a.summary,
798
+ timestamp: a.timestamp,
799
+ valueUsd: a.valueUsd ? `$${a.valueUsd.toFixed(2)}` : void 0
800
+ }));
801
+ }
802
+ return {
803
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
804
+ };
805
+ } catch (err) {
806
+ return {
807
+ content: [{ type: "text", text: `Wayfinder balances error: ${err.message}` }],
808
+ isError: true
809
+ };
810
+ }
811
+ }
812
+ );
813
+ server.tool(
814
+ "wayfinder_tokens",
815
+ 'Resolve or search for tokens across chains via Wayfinder. Use action="resolve" to get full details for a specific token, or action="search" to fuzzy-search by name/symbol. Requires WAYFINDER_API_KEY.',
816
+ {
817
+ action: z.enum(["resolve", "search", "gas"]).describe('"resolve" for specific token details, "search" for fuzzy search, "gas" for chain gas token'),
818
+ query: z.string().describe('Token identifier: address, symbol, CoinGecko ID (e.g. "USDC", "0xA0b8...", "usd-coin"), or chain code for gas'),
819
+ chain: z.string().optional().describe('Chain code for search/gas (e.g. "base", "ethereum", "arbitrum")'),
820
+ chain_id: z.number().optional().describe("Chain ID filter for resolve (e.g. 8453 for Base)")
821
+ },
822
+ async (params) => {
823
+ try {
824
+ const wf = getWayfinderClient();
825
+ if (params.action === "resolve") {
826
+ const token = await wf.resolveToken(params.query, params.chain_id);
827
+ return {
828
+ content: [{
829
+ type: "text",
830
+ text: JSON.stringify({
831
+ name: token.name,
832
+ symbol: token.symbol,
833
+ address: token.address,
834
+ chainId: token.chainId,
835
+ decimals: token.decimals,
836
+ priceUsd: token.priceUsd ? `$${token.priceUsd.toFixed(4)}` : null,
837
+ priceChange24h: token.priceChange24h ? `${token.priceChange24h.toFixed(2)}%` : null,
838
+ marketCapUsd: token.marketCapUsd ? `$${token.marketCapUsd.toLocaleString()}` : null,
839
+ coingeckoId: token.coingeckoId
840
+ }, null, 2)
841
+ }]
720
842
  };
721
- if (params.token) {
722
- const tokenAddr = requireAddress(params.token, 'token');
723
- // Use direct contract reads — no wallet/private key needed
724
- const erc20Abi = [
725
- { type: 'function', name: 'balanceOf', inputs: [{ type: 'address' }], outputs: [{ type: 'uint256' }], stateMutability: 'view' },
726
- { type: 'function', name: 'decimals', inputs: [], outputs: [{ type: 'uint8' }], stateMutability: 'view' },
727
- { type: 'function', name: 'symbol', inputs: [], outputs: [{ type: 'string' }], stateMutability: 'view' },
728
- ];
729
- const [balance, decimals, symbol] = await Promise.all([
730
- publicClient.readContract({ address: tokenAddr, abi: erc20Abi, functionName: 'balanceOf', args: [walletAddr] }),
731
- publicClient.readContract({ address: tokenAddr, abi: erc20Abi, functionName: 'decimals' }),
732
- publicClient.readContract({ address: tokenAddr, abi: erc20Abi, functionName: 'symbol' }),
733
- ]);
734
- result.tokenBalance = {
735
- address: tokenAddr,
736
- symbol,
737
- balance: formatUnitsCustom(balance, decimals),
738
- raw: balance.toString(),
739
- };
843
+ }
844
+ if (params.action === "search") {
845
+ if (!params.chain) {
846
+ return {
847
+ content: [{ type: "text", text: 'chain parameter is required for search (e.g. "base", "ethereum")' }],
848
+ isError: true
849
+ };
740
850
  }
851
+ const results = await wf.searchTokens(params.query, params.chain);
741
852
  return {
742
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
853
+ content: [{
854
+ type: "text",
855
+ text: JSON.stringify({
856
+ query: params.query,
857
+ chain: params.chain,
858
+ results: results.slice(0, 20).map((r) => ({
859
+ tokenId: r.tokenId,
860
+ name: r.name,
861
+ symbol: r.symbol,
862
+ address: r.address
863
+ }))
864
+ }, null, 2)
865
+ }]
743
866
  };
744
- }
745
- catch (err) {
867
+ }
868
+ if (params.action === "gas") {
869
+ const chain = params.chain || params.query;
870
+ const gasToken = await wf.getGasToken(chain);
746
871
  return {
747
- content: [{ type: 'text', text: `Balance error: ${err.message}` }],
748
- isError: true,
872
+ content: [{
873
+ type: "text",
874
+ text: JSON.stringify({
875
+ chain,
876
+ name: gasToken.name,
877
+ symbol: gasToken.symbol,
878
+ decimals: gasToken.decimals,
879
+ priceUsd: gasToken.priceUsd ? `$${gasToken.priceUsd.toFixed(2)}` : null
880
+ }, null, 2)
881
+ }]
749
882
  };
750
- }
751
- });
752
- // ────────────────────────────────────────────────────────────────────────────
753
- // Tool: clawncher_skill
754
- // ────────────────────────────────────────────────────────────────────────────
755
- server.tool('clawncher_skill', 'Retrieve the Clawncher SDK quick-start documentation (skill doc). Returns the full markdown guide for using Clawncher.', {}, async () => {
883
+ }
884
+ return {
885
+ content: [{ type: "text", text: 'Invalid action. Use "resolve", "search", or "gas".' }],
886
+ isError: true
887
+ };
888
+ } catch (err) {
889
+ return {
890
+ content: [{ type: "text", text: `Wayfinder tokens error: ${err.message}` }],
891
+ isError: true
892
+ };
893
+ }
894
+ }
895
+ );
896
+ server.tool(
897
+ "wayfinder_strategy",
898
+ "List and run Wayfinder DeFi strategies (basis trading, stablecoin yield, leveraged wstETH, etc.). Strategies manage deposits, rebalancing, and exits automatically. Requires Python 3.12+ and wayfinder-paths installed locally. Requires WAYFINDER_API_KEY.",
899
+ {
900
+ action: z.enum(["list", "status", "deposit", "update", "exit", "withdraw", "analyze", "quote"]).describe("Action to perform"),
901
+ strategy: z.string().optional().describe('Strategy name (e.g. "basis_trading_strategy"). Required for all actions except "list"'),
902
+ main_token_amount: z.number().optional().describe("Main token deposit amount (for deposit action)"),
903
+ gas_token_amount: z.number().optional().describe("Gas token amount (for deposit action)"),
904
+ wallet_label: z.string().optional().describe("Override wallet label"),
905
+ dry_run: z.boolean().default(false).describe("Run on Gorlami fork (simulation, no real transactions)")
906
+ },
907
+ async (params) => {
756
908
  try {
757
- const client = getApiClient();
758
- const skill = await client.getSkill();
909
+ const wf = getWayfinderClient();
910
+ const pythonStatus = await wf.checkPython();
911
+ if (!pythonStatus.wayfinderInstalled) {
759
912
  return {
760
- content: [{ type: 'text', text: skill }],
913
+ content: [{
914
+ type: "text",
915
+ text: `Wayfinder strategies require Python + wayfinder-paths.
916
+
917
+ Python: ${pythonStatus.available ? `${pythonStatus.version} at ${pythonStatus.pythonPath}` : "NOT FOUND"}
918
+ wayfinder-paths: NOT INSTALLED
919
+
920
+ Install with:
921
+ pip3 install wayfinder-paths
922
+
923
+ Available strategies:
924
+ - basis_trading_strategy (Hyperliquid basis/funding)
925
+ - moonwell_wsteth_loop_strategy (leveraged wstETH on Moonwell)
926
+ - hyperlend_stable_yield_strategy (stablecoin lending on HyperLend)
927
+ - boros_hype_strategy (Boros + HYPE)`
928
+ }],
929
+ isError: true
761
930
  };
762
- }
763
- catch (err) {
931
+ }
932
+ if (params.action === "list") {
933
+ const strategies = await wf.listStrategies();
764
934
  return {
765
- content: [{ type: 'text', text: `Skill fetch error: ${err.message}` }],
766
- isError: true,
935
+ content: [{
936
+ type: "text",
937
+ text: JSON.stringify({ strategies }, null, 2)
938
+ }]
767
939
  };
768
- }
769
- });
770
- // ============================================================================
771
- // Helpers
772
- // ============================================================================
940
+ }
941
+ if (!params.strategy) {
942
+ return {
943
+ content: [{ type: "text", text: "strategy parameter is required for this action." }],
944
+ isError: true
945
+ };
946
+ }
947
+ const result = await wf.runStrategy({
948
+ strategy: params.strategy,
949
+ action: params.action,
950
+ mainTokenAmount: params.main_token_amount,
951
+ gasTokenAmount: params.gas_token_amount,
952
+ walletLabel: params.wallet_label,
953
+ dryRun: params.dry_run
954
+ });
955
+ return {
956
+ content: [{
957
+ type: "text",
958
+ text: JSON.stringify(result, null, 2)
959
+ }],
960
+ isError: !result.success
961
+ };
962
+ } catch (err) {
963
+ return {
964
+ content: [{ type: "text", text: `Wayfinder strategy error: ${err.message}` }],
965
+ isError: true
966
+ };
967
+ }
968
+ }
969
+ );
773
970
  function formatUnitsCustom(value, decimals) {
774
- const str = value.toString().padStart(decimals + 1, '0');
775
- const intPart = str.slice(0, str.length - decimals) || '0';
776
- const fracPart = str.slice(str.length - decimals);
777
- // Trim trailing zeros from fractional part
778
- const trimmed = fracPart.replace(/0+$/, '');
779
- return trimmed ? `${intPart}.${trimmed}` : intPart;
971
+ const str = value.toString().padStart(decimals + 1, "0");
972
+ const intPart = str.slice(0, str.length - decimals) || "0";
973
+ const fracPart = str.slice(str.length - decimals);
974
+ const trimmed = fracPart.replace(/0+$/, "");
975
+ return trimmed ? `${intPart}.${trimmed}` : intPart;
780
976
  }
781
- // ============================================================================
782
- // Start
783
- // ============================================================================
784
977
  async function main() {
785
- const transport = new StdioServerTransport();
786
- await server.connect(transport);
978
+ const transport = new StdioServerTransport();
979
+ await server.connect(transport);
787
980
  }
788
981
  main().catch((err) => {
789
- console.error('MCP server failed to start:', err);
790
- process.exit(1);
982
+ console.error("MCP server failed to start:", err);
983
+ process.exit(1);
791
984
  });
792
- //# sourceMappingURL=index.js.map