@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 +915 -723
- package/package.json +2 -2
- package/dist/index.d.ts +0 -17
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,792 +1,984 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
78
|
-
|
|
91
|
+
const wallet = getWalletClient();
|
|
92
|
+
return wallet.account.address;
|
|
79
93
|
}
|
|
80
|
-
// Helpers
|
|
81
94
|
function isValidAddress(addr) {
|
|
82
|
-
|
|
95
|
+
return /^0x[0-9a-fA-F]{40}$/.test(addr);
|
|
83
96
|
}
|
|
84
97
|
function requireAddress(addr, label) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
98
|
+
if (!isValidAddress(addr)) {
|
|
99
|
+
throw new Error(`Invalid ${label} address: ${addr}`);
|
|
100
|
+
}
|
|
101
|
+
return addr;
|
|
89
102
|
}
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
180
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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(
|
|
347
|
-
price_only: z.boolean().default(false).describe(
|
|
348
|
-
},
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
|
399
|
-
const
|
|
400
|
-
|
|
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
|
-
|
|
417
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
limit: z.number().default(20).describe(
|
|
515
|
-
offset: z.number().default(0).describe(
|
|
516
|
-
agent: z.string().optional().describe(
|
|
517
|
-
source: z.string().optional().describe(
|
|
518
|
-
address: z.string().optional().describe(
|
|
519
|
-
},
|
|
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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
536
|
-
|
|
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
|
-
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
-
|
|
612
|
-
|
|
613
|
-
const addresses = getAddresses(network);
|
|
584
|
+
const config = getConfig();
|
|
585
|
+
if (!config.apiKey) {
|
|
614
586
|
return {
|
|
615
|
-
|
|
616
|
-
|
|
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
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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
|
|
681
|
-
|
|
682
|
-
|
|
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
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
-
|
|
700
|
-
|
|
728
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
729
|
+
isError: !result.ok
|
|
701
730
|
};
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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
|
-
|
|
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
|
-
|
|
867
|
+
}
|
|
868
|
+
if (params.action === "gas") {
|
|
869
|
+
const chain = params.chain || params.query;
|
|
870
|
+
const gasToken = await wf.getGasToken(chain);
|
|
746
871
|
return {
|
|
747
|
-
|
|
748
|
-
|
|
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
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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
|
-
|
|
758
|
-
|
|
909
|
+
const wf = getWayfinderClient();
|
|
910
|
+
const pythonStatus = await wf.checkPython();
|
|
911
|
+
if (!pythonStatus.wayfinderInstalled) {
|
|
759
912
|
return {
|
|
760
|
-
|
|
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
|
-
|
|
931
|
+
}
|
|
932
|
+
if (params.action === "list") {
|
|
933
|
+
const strategies = await wf.listStrategies();
|
|
764
934
|
return {
|
|
765
|
-
|
|
766
|
-
|
|
935
|
+
content: [{
|
|
936
|
+
type: "text",
|
|
937
|
+
text: JSON.stringify({ strategies }, null, 2)
|
|
938
|
+
}]
|
|
767
939
|
};
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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
|
-
|
|
786
|
-
|
|
978
|
+
const transport = new StdioServerTransport();
|
|
979
|
+
await server.connect(transport);
|
|
787
980
|
}
|
|
788
981
|
main().catch((err) => {
|
|
789
|
-
|
|
790
|
-
|
|
982
|
+
console.error("MCP server failed to start:", err);
|
|
983
|
+
process.exit(1);
|
|
791
984
|
});
|
|
792
|
-
//# sourceMappingURL=index.js.map
|