helius-mcp 1.2.0 → 2.0.0
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/CHANGELOG.md +27 -0
- package/README.md +42 -30
- package/dist/http.d.ts +1 -1
- package/dist/index.js +2 -56
- package/dist/results/store.d.ts +8 -0
- package/dist/results/store.js +72 -0
- package/dist/results/types.d.ts +47 -0
- package/dist/results/types.js +1 -0
- package/dist/router/action-groups.d.ts +6 -0
- package/dist/router/action-groups.js +32 -0
- package/dist/router/action-handlers.d.ts +20 -0
- package/dist/router/action-handlers.js +125 -0
- package/dist/router/actions.d.ts +12 -0
- package/dist/router/actions.js +123 -0
- package/dist/router/catalog.d.ts +6 -0
- package/dist/router/catalog.js +388 -0
- package/dist/router/context.d.ts +5 -0
- package/dist/router/context.js +10 -0
- package/dist/router/dispatch.d.ts +4 -0
- package/dist/router/dispatch.js +276 -0
- package/dist/router/instructions.d.ts +1 -0
- package/dist/router/instructions.js +25 -0
- package/dist/router/register.d.ts +2 -0
- package/dist/router/register.js +15 -0
- package/dist/router/required-params.d.ts +9 -0
- package/dist/router/required-params.js +66 -0
- package/dist/router/responses.d.ts +29 -0
- package/dist/router/responses.js +186 -0
- package/dist/router/schemas.d.ts +216 -0
- package/dist/router/schemas.js +195 -0
- package/dist/router/telemetry.d.ts +27 -0
- package/dist/router/telemetry.js +52 -0
- package/dist/router/types.d.ts +46 -0
- package/dist/router/types.js +1 -0
- package/dist/scripts/validate-catalog.d.ts +2 -2
- package/dist/scripts/validate-catalog.js +10 -10
- package/dist/tools/accounts.js +5 -5
- package/dist/tools/assets.js +5 -5
- package/dist/tools/auth.js +392 -288
- package/dist/tools/config.js +3 -3
- package/dist/tools/das-extras.js +6 -6
- package/dist/tools/docs.js +55 -41
- package/dist/tools/enhanced-websockets.js +13 -13
- package/dist/tools/fees.js +3 -3
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +2 -80
- package/dist/tools/laserstream.js +20 -23
- package/dist/tools/network.js +41 -2
- package/dist/tools/plans.d.ts +0 -5
- package/dist/tools/plans.js +167 -12
- package/dist/tools/product-catalog.d.ts +1 -0
- package/dist/tools/product-catalog.js +51 -16
- package/dist/tools/recommend.d.ts +0 -1
- package/dist/tools/recommend.js +9 -28
- package/dist/tools/shared.d.ts +1 -0
- package/dist/tools/shared.js +10 -2
- package/dist/tools/solana-knowledge.js +23 -7
- package/dist/tools/staking.d.ts +2 -0
- package/dist/tools/staking.js +268 -0
- package/dist/tools/transactions.js +167 -3
- package/dist/tools/transfers.js +38 -43
- package/dist/tools/wallet.js +27 -16
- package/dist/tools/webhooks.js +3 -3
- package/dist/tools/zk-compression.d.ts +2 -0
- package/dist/tools/zk-compression.js +781 -0
- package/dist/utils/config.d.ts +2 -2
- package/dist/utils/config.js +68 -6
- package/dist/utils/errors.d.ts +10 -1
- package/dist/utils/errors.js +46 -12
- package/dist/utils/feedback.js +1 -4
- package/dist/utils/helius.js +2 -1
- package/dist/utils/ows.d.ts +74 -0
- package/dist/utils/ows.js +155 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/system-prompts/helius/claude.system.md +56 -25
- package/system-prompts/helius/full.md +474 -130
- package/system-prompts/helius/openai.developer.md +56 -25
- package/system-prompts/helius-dflow/claude.system.md +41 -6
- package/system-prompts/helius-dflow/full.md +581 -92
- package/system-prompts/helius-dflow/openai.developer.md +41 -6
- package/system-prompts/helius-jupiter/claude.system.md +333 -0
- package/system-prompts/helius-jupiter/full.md +5109 -0
- package/system-prompts/helius-jupiter/openai.developer.md +333 -0
- package/system-prompts/helius-okx/claude.system.md +182 -0
- package/system-prompts/helius-okx/full.md +584 -0
- package/system-prompts/helius-okx/openai.developer.md +182 -0
- package/system-prompts/helius-phantom/claude.system.md +15 -2
- package/system-prompts/helius-phantom/full.md +254 -101
- package/system-prompts/helius-phantom/openai.developer.md +15 -2
- package/system-prompts/svm/claude.system.md +1 -0
- package/system-prompts/svm/full.md +1 -0
- package/system-prompts/svm/openai.developer.md +1 -0
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getHeliusClient, hasApiKey } from '../utils/helius.js';
|
|
3
|
+
import { noApiKeyResponse } from './shared.js';
|
|
4
|
+
import { mcpText, handleToolError, addressError, missingParamError } from '../utils/errors.js';
|
|
5
|
+
import { formatSol, formatAddress } from '../utils/formatters.js';
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
const bigintReplacer = (_, v) => typeof v === 'bigint' ? Number(v) : v;
|
|
8
|
+
export function registerZkCompressionTools(server) {
|
|
9
|
+
// ─── Account Queries ───
|
|
10
|
+
server.tool('getCompressedAccount', 'BEST FOR: fetching a single compressed account by address or hash. Returns account data, lamports, owner, tree info. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
11
|
+
address: z.string().optional().describe('Compressed account address (base58). Provide address or hash (at least one required).'),
|
|
12
|
+
hash: z.string().optional().describe('Compressed account hash (base58). Provide address or hash (at least one required).'),
|
|
13
|
+
}, async ({ address, hash }) => {
|
|
14
|
+
if (!hasApiKey())
|
|
15
|
+
return noApiKeyResponse();
|
|
16
|
+
if (!address && !hash) {
|
|
17
|
+
return missingParamError('getCompressedAccount', 'Provide at least one of `address` or `hash`.');
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const helius = getHeliusClient();
|
|
21
|
+
const result = await helius.zk.getCompressedAccount({
|
|
22
|
+
address: address ?? null,
|
|
23
|
+
hash,
|
|
24
|
+
});
|
|
25
|
+
const acct = result.value;
|
|
26
|
+
if (!acct) {
|
|
27
|
+
return mcpText('**Compressed Account**\n\nNo account found for the given address/hash.');
|
|
28
|
+
}
|
|
29
|
+
const lines = [
|
|
30
|
+
`**Compressed Account** (slot ${result.context.slot})`,
|
|
31
|
+
'',
|
|
32
|
+
`**Address:** ${acct.address ?? 'N/A'}`,
|
|
33
|
+
`**Owner:** ${acct.owner}`,
|
|
34
|
+
`**Lamports:** ${formatSol(acct.lamports)} (${acct.lamports.toLocaleString()} lamports)`,
|
|
35
|
+
`**Hash:** ${acct.hash}`,
|
|
36
|
+
`**Tree:** ${acct.tree}`,
|
|
37
|
+
`**Leaf Index:** ${acct.leafIndex}`,
|
|
38
|
+
`**Seq:** ${acct.seq}`,
|
|
39
|
+
];
|
|
40
|
+
if (acct.data) {
|
|
41
|
+
lines.push(`**Data:** ${JSON.stringify(acct.data, bigintReplacer)}`);
|
|
42
|
+
}
|
|
43
|
+
return mcpText(lines.join('\n'));
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
return handleToolError(err, 'Error fetching compressed account', [
|
|
47
|
+
addressError('Compressed Account', 'Invalid address or hash. Provide a valid base58-encoded value.'),
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
server.tool('getCompressedAccountsByOwner', 'BEST FOR: listing all compressed accounts owned by a wallet. Returns paginated compressed accounts with data, lamports, tree info. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
52
|
+
owner: z.string().min(1).describe('Owner wallet address (base58)'),
|
|
53
|
+
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
54
|
+
limit: z.number().optional().default(20).describe('Max results per page (default 20)'),
|
|
55
|
+
}, async ({ owner, cursor, limit }) => {
|
|
56
|
+
if (!hasApiKey())
|
|
57
|
+
return noApiKeyResponse();
|
|
58
|
+
try {
|
|
59
|
+
const helius = getHeliusClient();
|
|
60
|
+
const result = await helius.zk.getCompressedAccountsByOwner({
|
|
61
|
+
owner,
|
|
62
|
+
cursor: cursor ?? null,
|
|
63
|
+
limit: limit ?? null,
|
|
64
|
+
});
|
|
65
|
+
const val = result.value;
|
|
66
|
+
const items = val.items ?? [];
|
|
67
|
+
if (items.length === 0) {
|
|
68
|
+
return mcpText(`**Compressed Accounts for ${formatAddress(owner)}**\n\nNo compressed accounts found.`);
|
|
69
|
+
}
|
|
70
|
+
const lines = [
|
|
71
|
+
`**Compressed Accounts for ${formatAddress(owner)}** (${items.length} results, slot ${result.context.slot})`,
|
|
72
|
+
'',
|
|
73
|
+
];
|
|
74
|
+
items.forEach((acct, i) => {
|
|
75
|
+
lines.push(`${i + 1}. **Hash:** ${acct.hash}`);
|
|
76
|
+
lines.push(` Lamports: ${acct.lamports.toLocaleString()} | Tree: ${acct.tree} | Leaf: ${acct.leafIndex}`);
|
|
77
|
+
});
|
|
78
|
+
if (val.cursor) {
|
|
79
|
+
lines.push('', `**Next cursor:** \`${val.cursor}\``);
|
|
80
|
+
}
|
|
81
|
+
return mcpText(lines.join('\n'));
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
return handleToolError(err, 'Error fetching compressed accounts by owner', [
|
|
85
|
+
addressError('Compressed Accounts by Owner', 'Invalid owner address. Provide a valid base58-encoded wallet address.'),
|
|
86
|
+
]);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
server.tool('getMultipleCompressedAccounts', 'BEST FOR: batch-fetching multiple compressed accounts by addresses or hashes. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
90
|
+
addresses: z.array(z.string()).optional().describe('Array of compressed account addresses (base58). Provide addresses or hashes (at least one required).'),
|
|
91
|
+
hashes: z.array(z.string()).optional().describe('Array of compressed account hashes (base58). Provide addresses or hashes (at least one required).'),
|
|
92
|
+
}, async ({ addresses, hashes }) => {
|
|
93
|
+
if (!hasApiKey())
|
|
94
|
+
return noApiKeyResponse();
|
|
95
|
+
if (!addresses?.length && !hashes?.length) {
|
|
96
|
+
return missingParamError('getMultipleCompressedAccounts', 'Provide at least one of `addresses` or `hashes`.');
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const helius = getHeliusClient();
|
|
100
|
+
const result = await helius.zk.getMultipleCompressedAccounts({
|
|
101
|
+
addresses: addresses ?? null,
|
|
102
|
+
hashes: hashes ?? null,
|
|
103
|
+
});
|
|
104
|
+
const items = result.value.items ?? [];
|
|
105
|
+
const total = items.filter((a) => a !== null).length;
|
|
106
|
+
const lines = [
|
|
107
|
+
`**Multiple Compressed Accounts** (${total} found, slot ${result.context.slot})`,
|
|
108
|
+
'',
|
|
109
|
+
];
|
|
110
|
+
items.forEach((acct, i) => {
|
|
111
|
+
if (acct) {
|
|
112
|
+
lines.push(`${i + 1}. **Hash:** ${acct.hash}`);
|
|
113
|
+
lines.push(` Owner: ${acct.owner} | Lamports: ${acct.lamports.toLocaleString()}`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
lines.push(`${i + 1}. _(not found)_`);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return mcpText(lines.join('\n'));
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
return handleToolError(err, 'Error fetching multiple compressed accounts', [
|
|
123
|
+
addressError('Multiple Compressed Accounts', 'Invalid address or hash. Provide valid base58-encoded values.'),
|
|
124
|
+
]);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
// ─── Balance Queries ───
|
|
128
|
+
server.tool('getCompressedBalance', 'BEST FOR: checking compressed SOL balance of a single account by address or hash. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
129
|
+
address: z.string().optional().describe('Compressed account address (base58). Provide address or hash (at least one required).'),
|
|
130
|
+
hash: z.string().optional().describe('Compressed account hash (base58). Provide address or hash (at least one required).'),
|
|
131
|
+
}, async ({ address, hash }) => {
|
|
132
|
+
if (!hasApiKey())
|
|
133
|
+
return noApiKeyResponse();
|
|
134
|
+
if (!address && !hash) {
|
|
135
|
+
return missingParamError('getCompressedBalance', 'Provide at least one of `address` or `hash`.');
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const helius = getHeliusClient();
|
|
139
|
+
const result = await helius.zk.getCompressedBalance({
|
|
140
|
+
address: address ?? null,
|
|
141
|
+
hash: hash ?? null,
|
|
142
|
+
});
|
|
143
|
+
const lamports = result.value;
|
|
144
|
+
const label = address ? formatAddress(address) : hash;
|
|
145
|
+
return mcpText(`**Compressed Balance for ${label}** (slot ${result.context.slot})\n\n${formatSol(lamports)} (${lamports.toLocaleString()} lamports)`);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
return handleToolError(err, 'Error fetching compressed balance', [
|
|
149
|
+
addressError('Compressed Balance', 'Invalid address or hash. Provide a valid base58-encoded value.'),
|
|
150
|
+
]);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
server.tool('getCompressedBalanceByOwner', 'BEST FOR: checking total compressed SOL balance across all accounts owned by a wallet. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
154
|
+
owner: z.string().min(1).describe('Owner wallet address (base58)'),
|
|
155
|
+
}, async ({ owner }) => {
|
|
156
|
+
if (!hasApiKey())
|
|
157
|
+
return noApiKeyResponse();
|
|
158
|
+
try {
|
|
159
|
+
const helius = getHeliusClient();
|
|
160
|
+
const result = await helius.zk.getCompressedBalanceByOwner({ owner });
|
|
161
|
+
const lamports = result.value;
|
|
162
|
+
return mcpText(`**Compressed Balance for ${formatAddress(owner)}** (slot ${result.context.slot})\n\n${formatSol(lamports)} (${lamports.toLocaleString()} lamports)`);
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
return handleToolError(err, 'Error fetching compressed balance by owner', [
|
|
166
|
+
addressError('Compressed Balance by Owner', 'Invalid owner address. Provide a valid base58-encoded wallet address.'),
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
// ─── Token Queries ───
|
|
171
|
+
server.tool('getCompressedMintTokenHolders', 'BEST FOR: listing holders of a compressed token mint. Returns paginated list of owners and balances. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
172
|
+
mint: z.string().min(1).describe('Token mint address (base58)'),
|
|
173
|
+
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
174
|
+
limit: z.number().optional().default(20).describe('Max results per page (default 20)'),
|
|
175
|
+
}, async ({ mint, cursor, limit }) => {
|
|
176
|
+
if (!hasApiKey())
|
|
177
|
+
return noApiKeyResponse();
|
|
178
|
+
try {
|
|
179
|
+
const helius = getHeliusClient();
|
|
180
|
+
const result = await helius.zk.getCompressedMintTokenHolders({
|
|
181
|
+
mint,
|
|
182
|
+
cursor: cursor ?? null,
|
|
183
|
+
limit: limit ?? null,
|
|
184
|
+
});
|
|
185
|
+
const val = result.value;
|
|
186
|
+
const items = val.items ?? [];
|
|
187
|
+
if (items.length === 0) {
|
|
188
|
+
return mcpText(`**Compressed Token Holders for ${formatAddress(mint)}**\n\nNo holders found.`);
|
|
189
|
+
}
|
|
190
|
+
const lines = [
|
|
191
|
+
`**Compressed Token Holders for ${formatAddress(mint)}** (${items.length} results, slot ${result.context.slot})`,
|
|
192
|
+
'',
|
|
193
|
+
];
|
|
194
|
+
items.forEach((holder, i) => {
|
|
195
|
+
lines.push(`${i + 1}. **${formatAddress(holder.owner)}** — balance: ${holder.balance.toLocaleString()}`);
|
|
196
|
+
});
|
|
197
|
+
if (val.cursor) {
|
|
198
|
+
lines.push('', `**Next cursor:** \`${val.cursor}\``);
|
|
199
|
+
}
|
|
200
|
+
return mcpText(lines.join('\n'));
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
return handleToolError(err, 'Error fetching compressed token holders', [
|
|
204
|
+
addressError('Compressed Token Holders', 'Invalid mint address. Provide a valid base58-encoded token mint.'),
|
|
205
|
+
]);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
server.tool('getCompressedTokenAccountBalance', 'BEST FOR: checking token balance of a single compressed token account by address or hash. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
209
|
+
address: z.string().optional().describe('Compressed token account address (base58). Provide address or hash (at least one required).'),
|
|
210
|
+
hash: z.string().optional().describe('Compressed token account hash (base58). Provide address or hash (at least one required).'),
|
|
211
|
+
}, async ({ address, hash }) => {
|
|
212
|
+
if (!hasApiKey())
|
|
213
|
+
return noApiKeyResponse();
|
|
214
|
+
if (!address && !hash) {
|
|
215
|
+
return missingParamError('getCompressedTokenAccountBalance', 'Provide at least one of `address` or `hash`.');
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
const helius = getHeliusClient();
|
|
219
|
+
const result = await helius.zk.getCompressedTokenAccountBalance({
|
|
220
|
+
address: address ?? null,
|
|
221
|
+
hash: hash ?? null,
|
|
222
|
+
});
|
|
223
|
+
const label = address ? formatAddress(address) : hash;
|
|
224
|
+
return mcpText(`**Compressed Token Account Balance for ${label}** (slot ${result.context.slot})\n\n**Amount:** ${result.value.amount.toLocaleString()}`);
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
return handleToolError(err, 'Error fetching compressed token account balance', [
|
|
228
|
+
addressError('Compressed Token Account Balance', 'Invalid address or hash. Provide a valid base58-encoded value.'),
|
|
229
|
+
]);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
server.tool('getCompressedTokenAccountsByOwner', 'BEST FOR: listing compressed token accounts owned by a wallet, optionally filtered by mint. Returns account data with token info. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
233
|
+
owner: z.string().min(1).describe('Owner wallet address (base58)'),
|
|
234
|
+
mint: z.string().optional().describe('Optional token mint to filter by (base58)'),
|
|
235
|
+
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
236
|
+
limit: z.number().optional().default(20).describe('Max results per page (default 20)'),
|
|
237
|
+
}, async ({ owner, mint, cursor, limit }) => {
|
|
238
|
+
if (!hasApiKey())
|
|
239
|
+
return noApiKeyResponse();
|
|
240
|
+
try {
|
|
241
|
+
const helius = getHeliusClient();
|
|
242
|
+
const result = await helius.zk.getCompressedTokenAccountsByOwner({
|
|
243
|
+
owner,
|
|
244
|
+
mint: mint ?? null,
|
|
245
|
+
cursor: cursor ?? null,
|
|
246
|
+
limit: limit ?? null,
|
|
247
|
+
});
|
|
248
|
+
const val = result.value;
|
|
249
|
+
const items = val.items ?? [];
|
|
250
|
+
if (items.length === 0) {
|
|
251
|
+
return mcpText(`**Compressed Token Accounts for ${formatAddress(owner)}**\n\nNo compressed token accounts found.`);
|
|
252
|
+
}
|
|
253
|
+
const lines = [
|
|
254
|
+
`**Compressed Token Accounts for ${formatAddress(owner)}** (${items.length} results, slot ${result.context.slot})`,
|
|
255
|
+
'',
|
|
256
|
+
];
|
|
257
|
+
items.forEach((item, i) => {
|
|
258
|
+
const td = item.tokenData;
|
|
259
|
+
lines.push(`${i + 1}. **Mint:** ${formatAddress(td.mint)}`);
|
|
260
|
+
lines.push(` Amount: ${td.amount.toLocaleString()} | Delegate: ${td.delegate ?? 'none'} | State: ${td.state}`);
|
|
261
|
+
lines.push(` Hash: ${item.account.hash}`);
|
|
262
|
+
});
|
|
263
|
+
if (val.cursor) {
|
|
264
|
+
lines.push('', `**Next cursor:** \`${val.cursor}\``);
|
|
265
|
+
}
|
|
266
|
+
return mcpText(lines.join('\n'));
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
return handleToolError(err, 'Error fetching compressed token accounts by owner', [
|
|
270
|
+
addressError('Compressed Token Accounts by Owner', 'Invalid owner or mint address. Provide valid base58-encoded addresses.'),
|
|
271
|
+
]);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
server.tool('getCompressedTokenAccountsByDelegate', 'BEST FOR: listing compressed token accounts delegated to an address, optionally filtered by mint. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
275
|
+
delegate: z.string().min(1).describe('Delegate wallet address (base58)'),
|
|
276
|
+
mint: z.string().optional().describe('Optional token mint to filter by (base58)'),
|
|
277
|
+
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
278
|
+
limit: z.number().optional().default(20).describe('Max results per page (default 20)'),
|
|
279
|
+
}, async ({ delegate, mint, cursor, limit }) => {
|
|
280
|
+
if (!hasApiKey())
|
|
281
|
+
return noApiKeyResponse();
|
|
282
|
+
try {
|
|
283
|
+
const helius = getHeliusClient();
|
|
284
|
+
const result = await helius.zk.getCompressedTokenAccountsByDelegate({
|
|
285
|
+
delegate,
|
|
286
|
+
mint: mint ?? null,
|
|
287
|
+
cursor: cursor ?? null,
|
|
288
|
+
limit: limit ?? null,
|
|
289
|
+
});
|
|
290
|
+
const val = result.value;
|
|
291
|
+
const items = val.items ?? [];
|
|
292
|
+
if (items.length === 0) {
|
|
293
|
+
return mcpText(`**Compressed Token Accounts for Delegate ${formatAddress(delegate)}**\n\nNo compressed token accounts found.`);
|
|
294
|
+
}
|
|
295
|
+
const lines = [
|
|
296
|
+
`**Compressed Token Accounts for Delegate ${formatAddress(delegate)}** (${items.length} results, slot ${result.context.slot})`,
|
|
297
|
+
'',
|
|
298
|
+
];
|
|
299
|
+
items.forEach((item, i) => {
|
|
300
|
+
const td = item.tokenData;
|
|
301
|
+
lines.push(`${i + 1}. **Mint:** ${formatAddress(td.mint)}`);
|
|
302
|
+
lines.push(` Amount: ${td.amount.toLocaleString()} | Owner: ${td.owner} | State: ${td.state}`);
|
|
303
|
+
lines.push(` Hash: ${item.account.hash}`);
|
|
304
|
+
});
|
|
305
|
+
if (val.cursor) {
|
|
306
|
+
lines.push('', `**Next cursor:** \`${val.cursor}\``);
|
|
307
|
+
}
|
|
308
|
+
return mcpText(lines.join('\n'));
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
return handleToolError(err, 'Error fetching compressed token accounts by delegate', [
|
|
312
|
+
addressError('Compressed Token Accounts by Delegate', 'Invalid delegate or mint address. Provide valid base58-encoded addresses.'),
|
|
313
|
+
]);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
server.tool('getCompressedTokenBalancesByOwnerV2', 'BEST FOR: summarizing compressed token balances per mint for a wallet. Returns mint addresses and raw balances. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
317
|
+
owner: z.string().min(1).describe('Owner wallet address (base58)'),
|
|
318
|
+
mint: z.string().optional().describe('Optional token mint to filter by (base58)'),
|
|
319
|
+
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
320
|
+
limit: z.number().optional().default(20).describe('Max results per page (default 20)'),
|
|
321
|
+
}, async ({ owner, mint, cursor, limit }) => {
|
|
322
|
+
if (!hasApiKey())
|
|
323
|
+
return noApiKeyResponse();
|
|
324
|
+
try {
|
|
325
|
+
const helius = getHeliusClient();
|
|
326
|
+
const result = await helius.zk.getCompressedTokenBalancesByOwnerV2({
|
|
327
|
+
owner,
|
|
328
|
+
mint: mint ?? null,
|
|
329
|
+
cursor: cursor ?? null,
|
|
330
|
+
limit: limit ?? null,
|
|
331
|
+
});
|
|
332
|
+
const val = result.value;
|
|
333
|
+
const items = val.items ?? [];
|
|
334
|
+
if (items.length === 0) {
|
|
335
|
+
return mcpText(`**Compressed Token Balances for ${formatAddress(owner)}**\n\nNo compressed token balances found.`);
|
|
336
|
+
}
|
|
337
|
+
const lines = [
|
|
338
|
+
`**Compressed Token Balances for ${formatAddress(owner)}** (${items.length} results, slot ${result.context.slot})`,
|
|
339
|
+
'',
|
|
340
|
+
];
|
|
341
|
+
items.forEach((tb, i) => {
|
|
342
|
+
lines.push(`${i + 1}. **Mint:** ${formatAddress(tb.mint)} — balance: ${tb.balance.toLocaleString()}`);
|
|
343
|
+
});
|
|
344
|
+
if (val.cursor) {
|
|
345
|
+
lines.push('', `**Next cursor:** \`${val.cursor}\``);
|
|
346
|
+
}
|
|
347
|
+
return mcpText(lines.join('\n'));
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
return handleToolError(err, 'Error fetching compressed token balances by owner', [
|
|
351
|
+
addressError('Compressed Token Balances by Owner', 'Invalid owner or mint address. Provide valid base58-encoded addresses.'),
|
|
352
|
+
]);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
// ─── Merkle Proof Queries ───
|
|
356
|
+
server.tool('getCompressedAccountProof', 'BEST FOR: getting Merkle proof for a compressed account. Required for building ZK Compression transactions. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
357
|
+
hash: z.string().min(1).describe('Compressed account hash (base58, 32-byte leaf hash)'),
|
|
358
|
+
}, async ({ hash }) => {
|
|
359
|
+
if (!hasApiKey())
|
|
360
|
+
return noApiKeyResponse();
|
|
361
|
+
try {
|
|
362
|
+
const helius = getHeliusClient();
|
|
363
|
+
const result = await helius.zk.getCompressedAccountProof({ hash });
|
|
364
|
+
const proof = result.value;
|
|
365
|
+
const lines = [
|
|
366
|
+
`**Compressed Account Proof** (slot ${result.context.slot})`,
|
|
367
|
+
'',
|
|
368
|
+
`**Hash:** ${proof.hash}`,
|
|
369
|
+
`**Merkle Tree:** ${proof.merkleTree}`,
|
|
370
|
+
`**Root:** ${proof.root}`,
|
|
371
|
+
`**Root Seq:** ${Number(proof.rootSeq)}`,
|
|
372
|
+
`**Leaf Index:** ${proof.leafIndex}`,
|
|
373
|
+
`**Proof Length:** ${proof.proof.length} nodes`,
|
|
374
|
+
];
|
|
375
|
+
return mcpText(lines.join('\n'));
|
|
376
|
+
}
|
|
377
|
+
catch (err) {
|
|
378
|
+
return handleToolError(err, 'Error fetching compressed account proof', [
|
|
379
|
+
addressError('Compressed Account Proof', 'Invalid hash. Provide a valid base58-encoded 32-byte leaf hash.'),
|
|
380
|
+
]);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
server.tool('getMultipleCompressedAccountProofs', 'BEST FOR: batch Merkle proofs for multiple compressed accounts. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
384
|
+
hashes: z.array(z.string().min(1)).describe('Array of compressed account hashes (base58, 32-byte leaf hashes)'),
|
|
385
|
+
}, async ({ hashes }) => {
|
|
386
|
+
if (!hasApiKey())
|
|
387
|
+
return noApiKeyResponse();
|
|
388
|
+
try {
|
|
389
|
+
const helius = getHeliusClient();
|
|
390
|
+
// SDK takes a bare string[] for this method
|
|
391
|
+
const result = await helius.zk.getMultipleCompressedAccountProofs(hashes);
|
|
392
|
+
const proofs = result.value ?? [];
|
|
393
|
+
const lines = [
|
|
394
|
+
`**Multiple Compressed Account Proofs** (${proofs.length} proofs, slot ${result.context.slot})`,
|
|
395
|
+
'',
|
|
396
|
+
];
|
|
397
|
+
proofs.forEach((proof, i) => {
|
|
398
|
+
lines.push(`${i + 1}. **Hash:** ${proof.hash}`);
|
|
399
|
+
lines.push(` Tree: ${proof.merkleTree} | Root Seq: ${Number(proof.rootSeq)} | Proof: ${proof.proof.length} nodes`);
|
|
400
|
+
});
|
|
401
|
+
return mcpText(lines.join('\n'));
|
|
402
|
+
}
|
|
403
|
+
catch (err) {
|
|
404
|
+
return handleToolError(err, 'Error fetching multiple compressed account proofs', [
|
|
405
|
+
addressError('Multiple Compressed Account Proofs', 'Invalid hash(es). Provide valid base58-encoded 32-byte leaf hashes.'),
|
|
406
|
+
]);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
server.tool('getMultipleNewAddressProofs', 'BEST FOR: getting non-inclusion proofs for new addresses with specific Merkle trees. Required for creating new compressed accounts. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
410
|
+
addresses: z.array(z.object({
|
|
411
|
+
address: z.string().min(1).describe('New address to prove non-inclusion (base58)'),
|
|
412
|
+
tree: z.string().min(1).describe('Merkle tree address (base58)'),
|
|
413
|
+
})).describe('Array of address-tree pairs'),
|
|
414
|
+
}, async ({ addresses }) => {
|
|
415
|
+
if (!hasApiKey())
|
|
416
|
+
return noApiKeyResponse();
|
|
417
|
+
try {
|
|
418
|
+
const helius = getHeliusClient();
|
|
419
|
+
// SDK takes a bare AddressWithTree[] for this method
|
|
420
|
+
const result = await helius.zk.getMultipleNewAddressProofsV2(addresses);
|
|
421
|
+
const proofs = result.value ?? [];
|
|
422
|
+
const lines = [
|
|
423
|
+
`**Multiple New Address Proofs** (${proofs.length} proofs, slot ${result.context.slot})`,
|
|
424
|
+
'',
|
|
425
|
+
];
|
|
426
|
+
proofs.forEach((proof, i) => {
|
|
427
|
+
lines.push(`${i + 1}. **Address:** ${addresses[i]?.address ?? 'N/A'}`);
|
|
428
|
+
lines.push(` Tree: ${proof.merkleTree} | Root: ${proof.root} | Root Seq: ${Number(proof.rootSeq)}`);
|
|
429
|
+
});
|
|
430
|
+
return mcpText(lines.join('\n'));
|
|
431
|
+
}
|
|
432
|
+
catch (err) {
|
|
433
|
+
return handleToolError(err, 'Error fetching new address proofs', [
|
|
434
|
+
addressError('New Address Proofs', 'Invalid address or tree. Provide valid base58-encoded addresses.'),
|
|
435
|
+
]);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
// ─── Signature Queries ───
|
|
439
|
+
server.tool('getCompressionSignaturesForAccount', 'BEST FOR: getting compression transaction signatures for a specific compressed account by hash. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
440
|
+
hash: z.string().min(1).describe('Compressed account hash (base58, 32-byte leaf hash)'),
|
|
441
|
+
}, async ({ hash }) => {
|
|
442
|
+
if (!hasApiKey())
|
|
443
|
+
return noApiKeyResponse();
|
|
444
|
+
try {
|
|
445
|
+
const helius = getHeliusClient();
|
|
446
|
+
const result = await helius.zk.getCompressionSignaturesForAccount({ hash });
|
|
447
|
+
const items = result.value.items ?? [];
|
|
448
|
+
if (items.length === 0) {
|
|
449
|
+
return mcpText(`**Compression Signatures for Account ${hash}**\n\nNo signatures found.`);
|
|
450
|
+
}
|
|
451
|
+
const lines = [
|
|
452
|
+
`**Compression Signatures for Account** (${items.length} results, slot ${result.context.slot})`,
|
|
453
|
+
'',
|
|
454
|
+
];
|
|
455
|
+
items.forEach((sig, i) => {
|
|
456
|
+
lines.push(`${i + 1}. \`${sig.signature}\``);
|
|
457
|
+
if (sig.slot)
|
|
458
|
+
lines.push(` Slot: ${sig.slot}`);
|
|
459
|
+
if (sig.blockTime)
|
|
460
|
+
lines.push(` Time: ${new Date(sig.blockTime * 1000).toISOString()}`);
|
|
461
|
+
});
|
|
462
|
+
return mcpText(lines.join('\n'));
|
|
463
|
+
}
|
|
464
|
+
catch (err) {
|
|
465
|
+
return handleToolError(err, 'Error fetching compression signatures for account', [
|
|
466
|
+
addressError('Compression Signatures for Account', 'Invalid hash. Provide a valid base58-encoded 32-byte leaf hash.'),
|
|
467
|
+
]);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
server.tool('getCompressionSignaturesForAddress', 'BEST FOR: getting compression transaction signatures for a specific address. Returns paginated results. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
471
|
+
address: z.string().min(1).describe('Compressed account address (base58)'),
|
|
472
|
+
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
473
|
+
limit: z.number().optional().default(20).describe('Max results per page (default 20)'),
|
|
474
|
+
}, async ({ address, cursor, limit }) => {
|
|
475
|
+
if (!hasApiKey())
|
|
476
|
+
return noApiKeyResponse();
|
|
477
|
+
try {
|
|
478
|
+
const helius = getHeliusClient();
|
|
479
|
+
const result = await helius.zk.getCompressionSignaturesForAddress({
|
|
480
|
+
address,
|
|
481
|
+
cursor: cursor ?? null,
|
|
482
|
+
limit: limit ?? null,
|
|
483
|
+
});
|
|
484
|
+
const val = result.value;
|
|
485
|
+
const items = val.items ?? [];
|
|
486
|
+
if (items.length === 0) {
|
|
487
|
+
return mcpText(`**Compression Signatures for ${formatAddress(address)}**\n\nNo signatures found.`);
|
|
488
|
+
}
|
|
489
|
+
const lines = [
|
|
490
|
+
`**Compression Signatures for ${formatAddress(address)}** (${items.length} results, slot ${result.context.slot})`,
|
|
491
|
+
'',
|
|
492
|
+
];
|
|
493
|
+
items.forEach((sig, i) => {
|
|
494
|
+
lines.push(`${i + 1}. \`${sig.signature}\``);
|
|
495
|
+
if (sig.slot)
|
|
496
|
+
lines.push(` Slot: ${sig.slot}`);
|
|
497
|
+
if (sig.blockTime)
|
|
498
|
+
lines.push(` Time: ${new Date(sig.blockTime * 1000).toISOString()}`);
|
|
499
|
+
});
|
|
500
|
+
if (val.cursor) {
|
|
501
|
+
lines.push('', `**Next cursor:** \`${val.cursor}\``);
|
|
502
|
+
}
|
|
503
|
+
return mcpText(lines.join('\n'));
|
|
504
|
+
}
|
|
505
|
+
catch (err) {
|
|
506
|
+
return handleToolError(err, 'Error fetching compression signatures for address', [
|
|
507
|
+
addressError('Compression Signatures for Address', 'Invalid address. Provide a valid base58-encoded address.'),
|
|
508
|
+
]);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
server.tool('getCompressionSignaturesForOwner', 'BEST FOR: getting all compression transaction signatures for a wallet owner. Returns paginated results. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
512
|
+
owner: z.string().min(1).describe('Owner wallet address (base58)'),
|
|
513
|
+
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
514
|
+
limit: z.number().optional().default(20).describe('Max results per page (default 20)'),
|
|
515
|
+
}, async ({ owner, cursor, limit }) => {
|
|
516
|
+
if (!hasApiKey())
|
|
517
|
+
return noApiKeyResponse();
|
|
518
|
+
try {
|
|
519
|
+
const helius = getHeliusClient();
|
|
520
|
+
const result = await helius.zk.getCompressionSignaturesForOwner({
|
|
521
|
+
owner,
|
|
522
|
+
cursor: cursor ?? null,
|
|
523
|
+
limit: limit ?? null,
|
|
524
|
+
});
|
|
525
|
+
const val = result.value;
|
|
526
|
+
const items = val.items ?? [];
|
|
527
|
+
if (items.length === 0) {
|
|
528
|
+
return mcpText(`**Compression Signatures for Owner ${formatAddress(owner)}**\n\nNo signatures found.`);
|
|
529
|
+
}
|
|
530
|
+
const lines = [
|
|
531
|
+
`**Compression Signatures for Owner ${formatAddress(owner)}** (${items.length} results, slot ${result.context.slot})`,
|
|
532
|
+
'',
|
|
533
|
+
];
|
|
534
|
+
items.forEach((sig, i) => {
|
|
535
|
+
lines.push(`${i + 1}. \`${sig.signature}\``);
|
|
536
|
+
if (sig.slot)
|
|
537
|
+
lines.push(` Slot: ${sig.slot}`);
|
|
538
|
+
if (sig.blockTime)
|
|
539
|
+
lines.push(` Time: ${new Date(sig.blockTime * 1000).toISOString()}`);
|
|
540
|
+
});
|
|
541
|
+
if (val.cursor) {
|
|
542
|
+
lines.push('', `**Next cursor:** \`${val.cursor}\``);
|
|
543
|
+
}
|
|
544
|
+
return mcpText(lines.join('\n'));
|
|
545
|
+
}
|
|
546
|
+
catch (err) {
|
|
547
|
+
return handleToolError(err, 'Error fetching compression signatures for owner', [
|
|
548
|
+
addressError('Compression Signatures for Owner', 'Invalid owner address. Provide a valid base58-encoded wallet address.'),
|
|
549
|
+
]);
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
server.tool('getCompressionSignaturesForTokenOwner', 'BEST FOR: getting compression transaction signatures specifically for token operations by a wallet owner. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
553
|
+
owner: z.string().min(1).describe('Token owner wallet address (base58)'),
|
|
554
|
+
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
555
|
+
limit: z.number().optional().default(20).describe('Max results per page (default 20)'),
|
|
556
|
+
}, async ({ owner, cursor, limit }) => {
|
|
557
|
+
if (!hasApiKey())
|
|
558
|
+
return noApiKeyResponse();
|
|
559
|
+
try {
|
|
560
|
+
const helius = getHeliusClient();
|
|
561
|
+
const result = await helius.zk.getCompressionSignaturesForTokenOwner({
|
|
562
|
+
owner,
|
|
563
|
+
cursor: cursor ?? null,
|
|
564
|
+
limit: limit ?? null,
|
|
565
|
+
});
|
|
566
|
+
const val = result.value;
|
|
567
|
+
const items = val.items ?? [];
|
|
568
|
+
if (items.length === 0) {
|
|
569
|
+
return mcpText(`**Compression Token Signatures for ${formatAddress(owner)}**\n\nNo signatures found.`);
|
|
570
|
+
}
|
|
571
|
+
const lines = [
|
|
572
|
+
`**Compression Token Signatures for ${formatAddress(owner)}** (${items.length} results, slot ${result.context.slot})`,
|
|
573
|
+
'',
|
|
574
|
+
];
|
|
575
|
+
items.forEach((sig, i) => {
|
|
576
|
+
lines.push(`${i + 1}. \`${sig.signature}\``);
|
|
577
|
+
if (sig.slot)
|
|
578
|
+
lines.push(` Slot: ${sig.slot}`);
|
|
579
|
+
if (sig.blockTime)
|
|
580
|
+
lines.push(` Time: ${new Date(sig.blockTime * 1000).toISOString()}`);
|
|
581
|
+
});
|
|
582
|
+
if (val.cursor) {
|
|
583
|
+
lines.push('', `**Next cursor:** \`${val.cursor}\``);
|
|
584
|
+
}
|
|
585
|
+
return mcpText(lines.join('\n'));
|
|
586
|
+
}
|
|
587
|
+
catch (err) {
|
|
588
|
+
return handleToolError(err, 'Error fetching compression signatures for token owner', [
|
|
589
|
+
addressError('Compression Token Signatures', 'Invalid owner address. Provide a valid base58-encoded wallet address.'),
|
|
590
|
+
]);
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
server.tool('getLatestCompressionSignatures', 'BEST FOR: getting the most recent compression transactions across the network. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
594
|
+
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
595
|
+
limit: z.number().optional().default(20).describe('Max results per page (default 20)'),
|
|
596
|
+
}, async ({ cursor, limit }) => {
|
|
597
|
+
if (!hasApiKey())
|
|
598
|
+
return noApiKeyResponse();
|
|
599
|
+
try {
|
|
600
|
+
const helius = getHeliusClient();
|
|
601
|
+
const result = await helius.zk.getLatestCompressionSignatures({
|
|
602
|
+
cursor: cursor ?? null,
|
|
603
|
+
limit: limit ?? null,
|
|
604
|
+
});
|
|
605
|
+
const val = result.value;
|
|
606
|
+
const items = val.items ?? [];
|
|
607
|
+
if (items.length === 0) {
|
|
608
|
+
return mcpText('**Latest Compression Signatures**\n\nNo signatures found.');
|
|
609
|
+
}
|
|
610
|
+
const lines = [
|
|
611
|
+
`**Latest Compression Signatures** (${items.length} results, slot ${result.context.slot})`,
|
|
612
|
+
'',
|
|
613
|
+
];
|
|
614
|
+
items.forEach((sig, i) => {
|
|
615
|
+
lines.push(`${i + 1}. \`${sig.signature}\``);
|
|
616
|
+
if (sig.slot)
|
|
617
|
+
lines.push(` Slot: ${sig.slot}`);
|
|
618
|
+
if (sig.blockTime)
|
|
619
|
+
lines.push(` Time: ${new Date(sig.blockTime * 1000).toISOString()}`);
|
|
620
|
+
});
|
|
621
|
+
if (val.cursor) {
|
|
622
|
+
lines.push('', `**Next cursor:** \`${val.cursor}\``);
|
|
623
|
+
}
|
|
624
|
+
return mcpText(lines.join('\n'));
|
|
625
|
+
}
|
|
626
|
+
catch (err) {
|
|
627
|
+
return handleToolError(err, 'Error fetching latest compression signatures');
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
server.tool('getLatestNonVotingSignatures', 'BEST FOR: getting the most recent non-voting compression transactions across the network. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
631
|
+
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
632
|
+
limit: z.number().optional().default(20).describe('Max results per page (default 20)'),
|
|
633
|
+
}, async ({ cursor, limit }) => {
|
|
634
|
+
if (!hasApiKey())
|
|
635
|
+
return noApiKeyResponse();
|
|
636
|
+
try {
|
|
637
|
+
const helius = getHeliusClient();
|
|
638
|
+
const result = await helius.zk.getLatestNonVotingSignatures({
|
|
639
|
+
cursor: cursor ?? null,
|
|
640
|
+
limit: limit ?? null,
|
|
641
|
+
});
|
|
642
|
+
const val = result.value;
|
|
643
|
+
const items = val.items ?? [];
|
|
644
|
+
if (items.length === 0) {
|
|
645
|
+
return mcpText('**Latest Non-Voting Signatures**\n\nNo signatures found.');
|
|
646
|
+
}
|
|
647
|
+
const lines = [
|
|
648
|
+
`**Latest Non-Voting Signatures** (${items.length} results, slot ${result.context.slot})`,
|
|
649
|
+
'',
|
|
650
|
+
];
|
|
651
|
+
items.forEach((sig, i) => {
|
|
652
|
+
let line = `${i + 1}. \`${sig.signature}\``;
|
|
653
|
+
lines.push(line);
|
|
654
|
+
if (sig.slot)
|
|
655
|
+
lines.push(` Slot: ${sig.slot}`);
|
|
656
|
+
if (sig.blockTime)
|
|
657
|
+
lines.push(` Time: ${new Date(sig.blockTime * 1000).toISOString()}`);
|
|
658
|
+
if (sig.error)
|
|
659
|
+
lines.push(` **Error:** ${sig.error}`);
|
|
660
|
+
});
|
|
661
|
+
if (val.cursor) {
|
|
662
|
+
lines.push('', `**Next cursor:** \`${val.cursor}\``);
|
|
663
|
+
}
|
|
664
|
+
return mcpText(lines.join('\n'));
|
|
665
|
+
}
|
|
666
|
+
catch (err) {
|
|
667
|
+
return handleToolError(err, 'Error fetching latest non-voting signatures');
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
// ─── Transaction ───
|
|
671
|
+
server.tool('getTransactionWithCompressionInfo', 'BEST FOR: inspecting a transaction\'s compression state changes — accounts opened and closed. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
672
|
+
signature: z.string().min(1).describe('Transaction signature (base58)'),
|
|
673
|
+
}, async ({ signature }) => {
|
|
674
|
+
if (!hasApiKey())
|
|
675
|
+
return noApiKeyResponse();
|
|
676
|
+
try {
|
|
677
|
+
const helius = getHeliusClient();
|
|
678
|
+
const result = await helius.zk.getTransactionWithCompressionInfo({ signature });
|
|
679
|
+
if (!result) {
|
|
680
|
+
return mcpText(`**Transaction ${signature}**\n\nTransaction not found or has no compression info.`);
|
|
681
|
+
}
|
|
682
|
+
const info = result.compression_info;
|
|
683
|
+
const lines = [
|
|
684
|
+
`**Transaction with Compression Info**`,
|
|
685
|
+
'',
|
|
686
|
+
`**Signature:** \`${signature}\``,
|
|
687
|
+
];
|
|
688
|
+
if (info) {
|
|
689
|
+
const opened = info.openedAccounts ?? [];
|
|
690
|
+
const closed = info.closedAccounts ?? [];
|
|
691
|
+
lines.push(`**Opened Accounts:** ${opened.length}`);
|
|
692
|
+
opened.forEach((acct, i) => {
|
|
693
|
+
lines.push(` ${i + 1}. Owner: ${acct.account?.owner ?? 'N/A'} | Lamports: ${acct.account?.lamports?.toLocaleString() ?? 0}`);
|
|
694
|
+
if (acct.optionalTokenData) {
|
|
695
|
+
lines.push(` Token: mint=${acct.optionalTokenData.mint}, amount=${acct.optionalTokenData.amount}`);
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
lines.push(`**Closed Accounts:** ${closed.length}`);
|
|
699
|
+
closed.forEach((acct, i) => {
|
|
700
|
+
lines.push(` ${i + 1}. Owner: ${acct.account?.owner ?? 'N/A'} | Lamports: ${acct.account?.lamports?.toLocaleString() ?? 0}`);
|
|
701
|
+
if (acct.optionalTokenData) {
|
|
702
|
+
lines.push(` Token: mint=${acct.optionalTokenData.mint}, amount=${acct.optionalTokenData.amount}`);
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
lines.push('No compression info available for this transaction.');
|
|
708
|
+
}
|
|
709
|
+
return mcpText(lines.join('\n'));
|
|
710
|
+
}
|
|
711
|
+
catch (err) {
|
|
712
|
+
return handleToolError(err, 'Error fetching transaction with compression info', [
|
|
713
|
+
addressError('Transaction with Compression Info', 'Invalid transaction signature. Provide a valid base58-encoded signature.'),
|
|
714
|
+
]);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
// ─── Validity Proof ───
|
|
718
|
+
server.tool('getValidityProof', 'BEST FOR: generating a ZK validity proof for compressed account operations. Required for building transactions that modify compressed state. Credit cost: 10 credits (ZK Compression RPC).', {
|
|
719
|
+
hashes: z.array(z.string()).optional().describe('Compressed account hashes to prove (base58). Provide hashes and/or newAddressesWithTrees.'),
|
|
720
|
+
newAddressesWithTrees: z.array(z.object({
|
|
721
|
+
address: z.string().min(1).describe('New address (base58)'),
|
|
722
|
+
tree: z.string().min(1).describe('Merkle tree address (base58)'),
|
|
723
|
+
})).optional().describe('New address-tree pairs for non-inclusion proofs'),
|
|
724
|
+
}, async ({ hashes, newAddressesWithTrees }) => {
|
|
725
|
+
if (!hasApiKey())
|
|
726
|
+
return noApiKeyResponse();
|
|
727
|
+
if (!hashes?.length && !newAddressesWithTrees?.length) {
|
|
728
|
+
return missingParamError('getValidityProof', 'Provide at least one of `hashes` or `newAddressesWithTrees`.');
|
|
729
|
+
}
|
|
730
|
+
try {
|
|
731
|
+
const helius = getHeliusClient();
|
|
732
|
+
const result = await helius.zk.getValidityProof({
|
|
733
|
+
hashes: hashes ?? null,
|
|
734
|
+
newAddressesWithTrees: newAddressesWithTrees ?? null,
|
|
735
|
+
});
|
|
736
|
+
const val = result.value;
|
|
737
|
+
const lines = [
|
|
738
|
+
`**Validity Proof** (slot ${result.context.slot})`,
|
|
739
|
+
'',
|
|
740
|
+
`**Compressed Proof:**`,
|
|
741
|
+
` a: ${val.compressedProof.a}`,
|
|
742
|
+
` b: ${val.compressedProof.b}`,
|
|
743
|
+
` c: ${val.compressedProof.c}`,
|
|
744
|
+
`**Leaves:** ${val.leaves.length}`,
|
|
745
|
+
`**Merkle Trees:** ${val.merkleTrees.length}`,
|
|
746
|
+
`**Root Indices:** ${JSON.stringify(val.rootIndices, bigintReplacer)}`,
|
|
747
|
+
];
|
|
748
|
+
return mcpText(lines.join('\n'));
|
|
749
|
+
}
|
|
750
|
+
catch (err) {
|
|
751
|
+
return handleToolError(err, 'Error fetching validity proof', [
|
|
752
|
+
addressError('Validity Proof', 'Invalid hash or address. Provide valid base58-encoded values.'),
|
|
753
|
+
]);
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
// ─── Indexer Health ───
|
|
757
|
+
server.tool('getIndexerHealth', 'BEST FOR: checking if the ZK Compression indexer is healthy and responsive. Credit cost: 10 credits (ZK Compression RPC).', {}, async () => {
|
|
758
|
+
if (!hasApiKey())
|
|
759
|
+
return noApiKeyResponse();
|
|
760
|
+
try {
|
|
761
|
+
const helius = getHeliusClient();
|
|
762
|
+
const result = await helius.zk.getIndexerHealth();
|
|
763
|
+
return mcpText(`**ZK Compression Indexer Health**\n\nStatus: **${result}**`);
|
|
764
|
+
}
|
|
765
|
+
catch (err) {
|
|
766
|
+
return handleToolError(err, 'Error checking indexer health');
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
server.tool('getIndexerSlot', 'BEST FOR: checking the latest slot processed by the ZK Compression indexer. Useful for monitoring indexer lag. Credit cost: 10 credits (ZK Compression RPC).', {}, async () => {
|
|
770
|
+
if (!hasApiKey())
|
|
771
|
+
return noApiKeyResponse();
|
|
772
|
+
try {
|
|
773
|
+
const helius = getHeliusClient();
|
|
774
|
+
const result = await helius.zk.getIndexerSlot();
|
|
775
|
+
return mcpText(`**ZK Compression Indexer Slot**\n\nLatest indexed slot: **${result.toLocaleString()}**`);
|
|
776
|
+
}
|
|
777
|
+
catch (err) {
|
|
778
|
+
return handleToolError(err, 'Error fetching indexer slot');
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
}
|