helius-mcp 0.2.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -129
- package/dist/index.js +3 -11
- package/dist/tools/accounts.js +260 -150
- package/dist/tools/assets.js +214 -9
- package/dist/tools/balance.js +3 -5
- package/dist/tools/blocks.d.ts +2 -0
- package/dist/tools/blocks.js +100 -0
- package/dist/tools/config.js +81 -20
- package/dist/tools/das-extras.d.ts +2 -0
- package/dist/tools/das-extras.js +93 -0
- package/dist/tools/enhanced-websockets.d.ts +0 -10
- package/dist/tools/enhanced-websockets.js +52 -237
- package/dist/tools/{rpc.d.ts → fees.d.ts} +1 -1
- package/dist/tools/fees.js +74 -0
- package/dist/tools/guides.d.ts +2 -0
- package/dist/tools/guides.js +680 -0
- package/dist/tools/index.js +25 -9
- package/dist/tools/laserstream.d.ts +0 -14
- package/dist/tools/laserstream.js +77 -311
- package/dist/tools/network.d.ts +2 -0
- package/dist/tools/network.js +79 -0
- package/dist/tools/{das.d.ts → plans.d.ts} +1 -1
- package/dist/tools/plans.js +272 -0
- package/dist/tools/tokens.d.ts +2 -0
- package/dist/tools/tokens.js +60 -0
- package/dist/tools/transactions.js +574 -221
- package/dist/tools/wallet.d.ts +2 -0
- package/dist/tools/wallet.js +271 -0
- package/dist/tools/webhooks.js +19 -12
- package/dist/types/transaction-types.d.ts +0 -20
- package/dist/types/transaction-types.js +2 -22
- package/dist/utils/formatters.d.ts +2 -0
- package/dist/utils/formatters.js +10 -2
- package/dist/utils/helius.d.ts +3 -23
- package/dist/utils/helius.js +26 -48
- package/package.json +15 -5
- package/dist/tools/assets-extended.d.ts +0 -2
- package/dist/tools/assets-extended.js +0 -54
- package/dist/tools/das.js +0 -431
- package/dist/tools/enhanced-transactions.d.ts +0 -2
- package/dist/tools/enhanced-transactions.js +0 -51
- package/dist/tools/priority-fees.d.ts +0 -2
- package/dist/tools/priority-fees.js +0 -58
- package/dist/tools/rpc.js +0 -131
- package/dist/tools/transactions-enhanced.d.ts +0 -2
- package/dist/tools/transactions-enhanced.js +0 -165
package/README.md
CHANGED
|
@@ -1,150 +1,42 @@
|
|
|
1
1
|
# Helius MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
MCP server for Helius - Solana blockchain data access for Claude.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Setup
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- ✅ Enhanced Transactions - Human-readable parsing
|
|
11
|
-
- ✅ Priority Fees - Real-time fee estimation
|
|
12
|
-
- ✅ Webhooks - Event monitoring and notifications
|
|
13
|
-
- ✅ Enhanced WebSockets - Real-time streaming (1.5-2× faster)
|
|
14
|
-
- ✅ Laserstream gRPC - Lowest latency streaming with 24h replay
|
|
15
|
-
|
|
16
|
-
**Network Support:**
|
|
17
|
-
- Mainnet Beta
|
|
18
|
-
- Devnet
|
|
19
|
-
|
|
20
|
-
## Installation
|
|
7
|
+
```bash
|
|
8
|
+
claude mcp add helius npx helius-mcp@latest
|
|
9
|
+
```
|
|
21
10
|
|
|
11
|
+
Set your API key:
|
|
22
12
|
```bash
|
|
23
|
-
|
|
13
|
+
export HELIUS_API_KEY=your-api-key
|
|
24
14
|
```
|
|
25
15
|
|
|
26
|
-
##
|
|
16
|
+
## Tools (29)
|
|
27
17
|
|
|
28
|
-
|
|
18
|
+
**DAS API (12):** getAsset, getAssetBatch, getAssetsByOwner, getAssetsByGroup, getAssetsByCreator, getAssetsByAuthority, searchAssets, getAssetProof, getAssetProofBatch, getSignaturesForAsset, getNftEditions, getTokenAccounts
|
|
29
19
|
|
|
30
|
-
|
|
20
|
+
**RPC (4):** getBalance, getAccountInfo, getMultipleAccounts, getSignaturesForAddress
|
|
31
21
|
|
|
32
|
-
|
|
22
|
+
**Transactions (1):** parseTransactions
|
|
33
23
|
|
|
34
|
-
|
|
24
|
+
**Priority Fees (1):** getPriorityFeeEstimate
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
"mcpServers": {
|
|
39
|
-
"helius": {
|
|
40
|
-
"command": "helius-mcp",
|
|
41
|
-
"env": {
|
|
42
|
-
"HELIUS_API_KEY": "your-api-key-here",
|
|
43
|
-
"HELIUS_NETWORK": "mainnet-beta"
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
```
|
|
26
|
+
**Webhooks (5):** getAllWebhooks, getWebhookByID, createWebhook, updateWebhook, deleteWebhook
|
|
49
27
|
|
|
50
|
-
|
|
28
|
+
**Enhanced WebSockets (3):** transactionSubscribe, accountSubscribe, getEnhancedWebSocketInfo
|
|
51
29
|
|
|
52
|
-
|
|
30
|
+
**Laserstream gRPC (2):** laserstreamSubscribe, getLaserstreamInfo
|
|
53
31
|
|
|
54
|
-
|
|
32
|
+
**Config (1):** setHeliusApiKey
|
|
55
33
|
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
What's the SOL balance for vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg?
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
**Get NFT information:**
|
|
62
|
-
```
|
|
63
|
-
Show me details for NFT FKZJkqv2YbXmcGWbp2RJfhVmpE6PnSaq6dMj1DKXgj1m
|
|
64
|
-
```
|
|
34
|
+
## Networks
|
|
65
35
|
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
Set up a webhook to track transactions for wallet ABC...XYZ
|
|
69
|
-
```
|
|
36
|
+
Mainnet Beta and Devnet. Set via `HELIUS_NETWORK` env var.
|
|
70
37
|
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
Show me how to subscribe to Raydium swap transactions
|
|
74
|
-
```
|
|
38
|
+
## Docs
|
|
75
39
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
### Config (1 tool)
|
|
79
|
-
- `setHeliusApiKey` - Configure API key and network
|
|
80
|
-
|
|
81
|
-
### DAS API (12 tools)
|
|
82
|
-
- `getAsset` - NFT/token details
|
|
83
|
-
- `getAssetBatch` - Batch fetch assets
|
|
84
|
-
- `getAssetsByOwner` - Wallet's assets
|
|
85
|
-
- `getAssetsByGroup` - Collection NFTs
|
|
86
|
-
- `getAssetsByCreator` - Creator's assets
|
|
87
|
-
- `getAssetsByAuthority` - Authority's assets
|
|
88
|
-
- `searchAssets` - Advanced search
|
|
89
|
-
- `getAssetProof` - cNFT Merkle proof
|
|
90
|
-
- `getAssetProofBatch` - Batch proofs
|
|
91
|
-
- `getSignaturesForAsset` - cNFT transaction history
|
|
92
|
-
- `getNftEditions` - Edition NFTs
|
|
93
|
-
- `getTokenAccounts` - Token account queries
|
|
94
|
-
|
|
95
|
-
### RPC Methods (4 tools)
|
|
96
|
-
- `getBalance` - SOL balance
|
|
97
|
-
- `getAccountInfo` - Account details
|
|
98
|
-
- `getMultipleAccounts` - Batch account lookups
|
|
99
|
-
- `getSignaturesForAddress` - Transaction signatures
|
|
100
|
-
|
|
101
|
-
### Enhanced Transactions (1 tool)
|
|
102
|
-
- `parseTransactions` - Human-readable parsing
|
|
103
|
-
|
|
104
|
-
### Priority Fees (1 tool)
|
|
105
|
-
- `getPriorityFeeEstimate` - Optimal fees
|
|
106
|
-
|
|
107
|
-
### Webhooks (5 tools)
|
|
108
|
-
- `getAllWebhooks` - List webhooks
|
|
109
|
-
- `getWebhookByID` - Webhook details
|
|
110
|
-
- `createWebhook` - Create webhook
|
|
111
|
-
- `updateWebhook` - Update webhook
|
|
112
|
-
- `deleteWebhook` - Delete webhook
|
|
113
|
-
|
|
114
|
-
### Enhanced WebSockets (3 tools)
|
|
115
|
-
- `transactionSubscribe` - Real-time transaction streaming
|
|
116
|
-
- `accountSubscribe` - Real-time account monitoring
|
|
117
|
-
- `getEnhancedWebSocketInfo` - WebSocket service info
|
|
118
|
-
|
|
119
|
-
### Laserstream gRPC (2 tools)
|
|
120
|
-
- `laserstreamSubscribe` - High-performance gRPC streaming
|
|
121
|
-
- `getLaserstreamInfo` - Laserstream service info
|
|
122
|
-
|
|
123
|
-
## Plan Requirements
|
|
124
|
-
|
|
125
|
-
### Enhanced WebSockets
|
|
126
|
-
- Free/Developer: ❌ Not available
|
|
127
|
-
- Business: ✅ 250 connections
|
|
128
|
-
- Professional: ✅ 250 connections
|
|
129
|
-
|
|
130
|
-
### Laserstream gRPC
|
|
131
|
-
- Free: ❌ Not available
|
|
132
|
-
- Developer: ✅ Devnet only
|
|
133
|
-
- Business: ✅ Devnet only
|
|
134
|
-
- Professional: ✅ Mainnet + Devnet
|
|
135
|
-
|
|
136
|
-
## Documentation
|
|
137
|
-
|
|
138
|
-
- [Helius API Documentation](https://docs.helius.dev)
|
|
40
|
+
- [Helius API](https://docs.helius.dev)
|
|
139
41
|
- [Enhanced WebSockets](https://docs.helius.dev/enhanced-websockets)
|
|
140
|
-
- [Laserstream
|
|
141
|
-
- [MCP Protocol](https://modelcontextprotocol.io)
|
|
142
|
-
|
|
143
|
-
## Support
|
|
144
|
-
|
|
145
|
-
- GitHub Issues: [helius-labs/helius-mcp-server](https://github.com/helius-labs/helius-mcp-server/issues)
|
|
146
|
-
- Discord: [Helius Discord](https://discord.gg/aYYStRz3YX)
|
|
147
|
-
|
|
148
|
-
## License
|
|
149
|
-
|
|
150
|
-
MIT
|
|
42
|
+
- [Laserstream](https://docs.helius.dev/laserstream)
|
package/dist/index.js
CHANGED
|
@@ -4,26 +4,18 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
4
4
|
import { registerTools } from './tools/index.js';
|
|
5
5
|
import { setApiKey } from './utils/helius.js';
|
|
6
6
|
const server = new McpServer({
|
|
7
|
-
name: 'helius-
|
|
8
|
-
version: '0.
|
|
7
|
+
name: 'helius-mcp',
|
|
8
|
+
version: '0.3.0'
|
|
9
9
|
});
|
|
10
|
-
// Register all tools
|
|
11
10
|
registerTools(server);
|
|
12
|
-
// Start the server
|
|
13
11
|
async function main() {
|
|
14
|
-
// Auto-configure API key from environment if available
|
|
15
12
|
if (process.env.HELIUS_API_KEY) {
|
|
16
13
|
setApiKey(process.env.HELIUS_API_KEY);
|
|
17
|
-
console.error('✅ Helius API key loaded from environment');
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
console.error('ℹ️ No API key found. Use setHeliusApiKey tool or set HELIUS_API_KEY environment variable');
|
|
21
14
|
}
|
|
22
15
|
const transport = new StdioServerTransport();
|
|
23
16
|
await server.connect(transport);
|
|
24
|
-
console.error('🚀 Helius MCP Server v0.2.4 running (29 tools)');
|
|
25
17
|
}
|
|
26
18
|
main().catch((error) => {
|
|
27
|
-
console.error('Failed to start
|
|
19
|
+
console.error('Failed to start:', error);
|
|
28
20
|
process.exit(1);
|
|
29
21
|
});
|
package/dist/tools/accounts.js
CHANGED
|
@@ -1,197 +1,307 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { getHeliusClient, hasApiKey } from '../utils/helius.js';
|
|
2
|
+
import { getHeliusClient, hasApiKey, getRpcUrl } from '../utils/helius.js';
|
|
3
3
|
import { formatAddress, formatSol } from '../utils/formatters.js';
|
|
4
4
|
import { noApiKeyResponse } from './shared.js';
|
|
5
|
+
function formatParsedAccountData(account) {
|
|
6
|
+
const lines = [];
|
|
7
|
+
const parsedData = account.data;
|
|
8
|
+
if (parsedData?.program) {
|
|
9
|
+
lines.push(` Program: ${parsedData.program}`);
|
|
10
|
+
}
|
|
11
|
+
if (parsedData?.parsed?.type) {
|
|
12
|
+
lines.push(` Account Type: ${parsedData.parsed.type}`);
|
|
13
|
+
}
|
|
14
|
+
if (parsedData?.parsed?.info) {
|
|
15
|
+
for (const [key, value] of Object.entries(parsedData.parsed.info)) {
|
|
16
|
+
const display = typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
17
|
+
lines.push(` ${key}: ${display}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return lines;
|
|
21
|
+
}
|
|
5
22
|
export function registerAccountTools(server) {
|
|
6
|
-
// Get
|
|
7
|
-
server.tool('
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
23
|
+
// Get Account Info (single or batch) — uses direct JSON-RPC
|
|
24
|
+
server.tool('getAccountInfo', 'Get detailed Solana account information for one or more accounts. For a single account: returns owner program, lamport balance, data size, executable status, and rent epoch. For batch: pass up to 100 addresses in "addresses" for fast bulk lookups. Use jsonParsed encoding (default) on token mint addresses to see Token-2022 extensions, authorities, and supply data. Use this to inspect any on-chain account.', {
|
|
25
|
+
address: z.string().optional().describe('Single account address (base58 encoded). Use this OR addresses, not both.'),
|
|
26
|
+
addresses: z.array(z.string()).optional().describe('Array of account addresses for batch lookup (up to 100). Use this OR address, not both.'),
|
|
27
|
+
encoding: z.enum(['base58', 'base64', 'jsonParsed']).optional().default('jsonParsed').describe('Data encoding format')
|
|
28
|
+
}, async ({ address, addresses, encoding }) => {
|
|
11
29
|
if (!hasApiKey())
|
|
12
30
|
return noApiKeyResponse();
|
|
13
|
-
|
|
31
|
+
const url = getRpcUrl();
|
|
32
|
+
// Validate: must provide exactly one of address or addresses
|
|
33
|
+
if (!address && (!addresses || addresses.length === 0)) {
|
|
14
34
|
return {
|
|
15
35
|
content: [{
|
|
16
36
|
type: 'text',
|
|
17
|
-
text:
|
|
18
|
-
}]
|
|
19
|
-
isError: true
|
|
37
|
+
text: `**Error:** Provide either \`address\` (single account) or \`addresses\` (batch of up to 100).`
|
|
38
|
+
}]
|
|
20
39
|
};
|
|
21
40
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
41
|
+
if (address && addresses && addresses.length > 0) {
|
|
42
|
+
return {
|
|
43
|
+
content: [{
|
|
44
|
+
type: 'text',
|
|
45
|
+
text: `**Error:** Provide either \`address\` or \`addresses\`, not both.`
|
|
46
|
+
}]
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// --- Batch mode ---
|
|
50
|
+
if (addresses && addresses.length > 0) {
|
|
51
|
+
if (addresses.length > 100) {
|
|
26
52
|
return {
|
|
27
53
|
content: [{
|
|
28
54
|
type: 'text',
|
|
29
|
-
text:
|
|
30
|
-
}]
|
|
31
|
-
isError: true
|
|
55
|
+
text: `**Error:** Maximum 100 accounts per request. You provided ${addresses.length}.`
|
|
56
|
+
}]
|
|
32
57
|
};
|
|
33
58
|
}
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
59
|
+
const response = await fetch(url, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
body: JSON.stringify({
|
|
63
|
+
jsonrpc: '2.0',
|
|
64
|
+
id: 'get-multiple-accounts',
|
|
65
|
+
method: 'getMultipleAccounts',
|
|
66
|
+
params: [addresses, { encoding }]
|
|
67
|
+
})
|
|
68
|
+
});
|
|
69
|
+
const data = await response.json();
|
|
70
|
+
if (data.error) {
|
|
71
|
+
return {
|
|
72
|
+
content: [{
|
|
73
|
+
type: 'text',
|
|
74
|
+
text: `**Error**\n\n${data.error.message}`
|
|
75
|
+
}]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const accounts = data.result?.value || [];
|
|
79
|
+
const lines = [`**Multiple Accounts** (${addresses.length} requested)`, ''];
|
|
39
80
|
addresses.forEach((addr, i) => {
|
|
40
|
-
const
|
|
41
|
-
if (!
|
|
42
|
-
lines.push(
|
|
43
|
-
return;
|
|
81
|
+
const account = accounts[i];
|
|
82
|
+
if (!account) {
|
|
83
|
+
lines.push(`**${formatAddress(addr)}:** Not found`);
|
|
44
84
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
lines.push(`
|
|
85
|
+
else {
|
|
86
|
+
lines.push(`**${formatAddress(addr)}**`);
|
|
87
|
+
lines.push(` Balance: ${formatSol(account.lamports)}`);
|
|
88
|
+
lines.push(` Owner: ${account.owner}`);
|
|
89
|
+
lines.push(` Executable: ${account.executable ? 'Yes' : 'No'}`);
|
|
90
|
+
if (account.space !== undefined) {
|
|
91
|
+
lines.push(` Data Size: ${account.space} bytes`);
|
|
92
|
+
}
|
|
93
|
+
// Show parsed data (Token-2022 extensions, mint info, etc.)
|
|
94
|
+
const parsedLines = formatParsedAccountData(account);
|
|
95
|
+
lines.push(...parsedLines);
|
|
50
96
|
}
|
|
51
|
-
lines.push(
|
|
97
|
+
lines.push('');
|
|
52
98
|
});
|
|
53
99
|
return {
|
|
54
|
-
content: [{
|
|
100
|
+
content: [{
|
|
101
|
+
type: 'text',
|
|
102
|
+
text: lines.join('\n')
|
|
103
|
+
}]
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// --- Single account mode ---
|
|
107
|
+
const response = await fetch(url, {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: { 'Content-Type': 'application/json' },
|
|
110
|
+
body: JSON.stringify({
|
|
111
|
+
jsonrpc: '2.0',
|
|
112
|
+
id: 'get-account-info',
|
|
113
|
+
method: 'getAccountInfo',
|
|
114
|
+
params: [address, { encoding }]
|
|
115
|
+
})
|
|
116
|
+
});
|
|
117
|
+
const data = await response.json();
|
|
118
|
+
if (data.error) {
|
|
119
|
+
return {
|
|
120
|
+
content: [{
|
|
121
|
+
type: 'text',
|
|
122
|
+
text: `**Error**\n\n${data.error.message}`
|
|
123
|
+
}]
|
|
55
124
|
};
|
|
56
125
|
}
|
|
57
|
-
|
|
126
|
+
const account = data.result?.value;
|
|
127
|
+
if (!account) {
|
|
58
128
|
return {
|
|
59
|
-
content: [{
|
|
60
|
-
|
|
129
|
+
content: [{
|
|
130
|
+
type: 'text',
|
|
131
|
+
text: `**Account ${formatAddress(address)}**\n\nAccount not found or has no data.`
|
|
132
|
+
}]
|
|
61
133
|
};
|
|
62
134
|
}
|
|
135
|
+
const lines = [
|
|
136
|
+
`**Account ${formatAddress(address)}**`,
|
|
137
|
+
'',
|
|
138
|
+
`**Balance:** ${formatSol(account.lamports)} (${account.lamports.toLocaleString()} lamports)`,
|
|
139
|
+
`**Owner:** ${account.owner}`,
|
|
140
|
+
`**Executable:** ${account.executable ? 'Yes' : 'No'}`,
|
|
141
|
+
];
|
|
142
|
+
if (account.space !== undefined) {
|
|
143
|
+
lines.push(`**Data Size:** ${account.space} bytes`);
|
|
144
|
+
}
|
|
145
|
+
// Show parsed data details when using jsonParsed
|
|
146
|
+
const parsedLines = formatParsedAccountData(account);
|
|
147
|
+
if (parsedLines.length > 0) {
|
|
148
|
+
lines.push('', '**Parsed Data:**');
|
|
149
|
+
lines.push(...parsedLines);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
content: [{
|
|
153
|
+
type: 'text',
|
|
154
|
+
text: lines.join('\n')
|
|
155
|
+
}]
|
|
156
|
+
};
|
|
63
157
|
});
|
|
64
|
-
// Get
|
|
65
|
-
server.tool('
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}).optional(),
|
|
72
|
-
dataSize: z.number().optional()
|
|
73
|
-
})).optional().describe('Optional filters to narrow results'),
|
|
74
|
-
limit: z.number().optional().default(10).describe('Max accounts to return')
|
|
75
|
-
}, async ({ programId, filters, limit }) => {
|
|
158
|
+
// Get Token Accounts
|
|
159
|
+
server.tool('getTokenAccounts', 'Query token accounts with advanced filters. Can filter by mint address, owner address, or both. Returns token account addresses and balances.', {
|
|
160
|
+
owner: z.string().optional().describe('Filter by owner wallet address'),
|
|
161
|
+
mint: z.string().optional().describe('Filter by token mint address'),
|
|
162
|
+
page: z.number().optional().default(1).describe('Page number (starts at 1)'),
|
|
163
|
+
limit: z.number().optional().default(20).describe('Results per page (max 1000)')
|
|
164
|
+
}, async ({ owner, mint, page, limit }) => {
|
|
76
165
|
if (!hasApiKey())
|
|
77
166
|
return noApiKeyResponse();
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const connection = helius.connection;
|
|
81
|
-
if (!connection || !connection.getProgramAccounts) {
|
|
82
|
-
return {
|
|
83
|
-
content: [{
|
|
84
|
-
type: 'text',
|
|
85
|
-
text: '**Error**\n\nProgram account lookup not available'
|
|
86
|
-
}],
|
|
87
|
-
isError: true
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
const config = {};
|
|
91
|
-
if (filters && filters.length > 0) {
|
|
92
|
-
config.filters = filters;
|
|
93
|
-
}
|
|
94
|
-
const accounts = await connection.getProgramAccounts(programId, config);
|
|
95
|
-
if (!accounts || accounts.length === 0) {
|
|
96
|
-
return {
|
|
97
|
-
content: [{
|
|
98
|
-
type: 'text',
|
|
99
|
-
text: `**No Accounts Found**\n\nProgram: ${formatAddress(programId)}`
|
|
100
|
-
}]
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
const lines = [
|
|
104
|
-
`**Program Accounts**`,
|
|
105
|
-
``,
|
|
106
|
-
`Program: ${formatAddress(programId)}`,
|
|
107
|
-
`Total: ${accounts.length} accounts`,
|
|
108
|
-
``
|
|
109
|
-
];
|
|
110
|
-
const displayAccounts = accounts.slice(0, limit);
|
|
111
|
-
displayAccounts.forEach((account, i) => {
|
|
112
|
-
const pubkey = account.pubkey.toBase58();
|
|
113
|
-
const lamports = account.account.lamports;
|
|
114
|
-
const dataLength = account.account.data.length;
|
|
115
|
-
lines.push(`${i + 1}. ${formatAddress(pubkey)}`);
|
|
116
|
-
lines.push(` Balance: ${formatSol(lamports)}`);
|
|
117
|
-
lines.push(` Data: ${dataLength} bytes`);
|
|
118
|
-
});
|
|
119
|
-
if (accounts.length > limit) {
|
|
120
|
-
lines.push(``);
|
|
121
|
-
lines.push(`💡 Showing first ${limit} of ${accounts.length} accounts`);
|
|
122
|
-
}
|
|
167
|
+
const helius = getHeliusClient();
|
|
168
|
+
if (!owner && !mint) {
|
|
123
169
|
return {
|
|
124
|
-
content: [{
|
|
170
|
+
content: [{
|
|
171
|
+
type: 'text',
|
|
172
|
+
text: `**Error:** You must provide at least one of: owner or mint address.`
|
|
173
|
+
}]
|
|
125
174
|
};
|
|
126
175
|
}
|
|
127
|
-
|
|
176
|
+
const params = { page, limit };
|
|
177
|
+
if (owner)
|
|
178
|
+
params.owner = owner;
|
|
179
|
+
if (mint)
|
|
180
|
+
params.mint = mint;
|
|
181
|
+
const response = await helius.getTokenAccounts(params);
|
|
182
|
+
const items = (response.token_accounts || []);
|
|
183
|
+
if (items.length === 0) {
|
|
184
|
+
const filterDesc = owner && mint
|
|
185
|
+
? `owner=${formatAddress(owner)}, mint=${formatAddress(mint)}`
|
|
186
|
+
: owner
|
|
187
|
+
? `owner=${formatAddress(owner)}`
|
|
188
|
+
: `mint=${formatAddress(mint)}`;
|
|
128
189
|
return {
|
|
129
|
-
content: [{
|
|
130
|
-
|
|
190
|
+
content: [{
|
|
191
|
+
type: 'text',
|
|
192
|
+
text: `**Token Accounts** (${filterDesc})\n\nNo token accounts found.`
|
|
193
|
+
}]
|
|
131
194
|
};
|
|
132
195
|
}
|
|
196
|
+
const lines = [`**Token Accounts** (${response.total || items.length} total, page ${page})`, ''];
|
|
197
|
+
items.forEach((account) => {
|
|
198
|
+
const flags = [];
|
|
199
|
+
if (account.frozen)
|
|
200
|
+
flags.push('Frozen');
|
|
201
|
+
if (account.delegated_amount && account.delegated_amount > 0)
|
|
202
|
+
flags.push('Delegated');
|
|
203
|
+
const flagStr = flags.length > 0 ? ` [${flags.join(', ')}]` : '';
|
|
204
|
+
lines.push(`- **Account:** ${formatAddress(account.address)}${flagStr}`);
|
|
205
|
+
lines.push(` Mint: ${formatAddress(account.mint)}`);
|
|
206
|
+
lines.push(` Owner: ${formatAddress(account.owner)}`);
|
|
207
|
+
lines.push(` Amount: ${account.amount.toLocaleString()}`);
|
|
208
|
+
if (account.delegated_amount && account.delegated_amount > 0) {
|
|
209
|
+
lines.push(` Delegated: ${account.delegated_amount.toLocaleString()}`);
|
|
210
|
+
}
|
|
211
|
+
lines.push('');
|
|
212
|
+
});
|
|
213
|
+
return {
|
|
214
|
+
content: [{
|
|
215
|
+
type: 'text',
|
|
216
|
+
text: lines.join('\n')
|
|
217
|
+
}]
|
|
218
|
+
};
|
|
133
219
|
});
|
|
134
|
-
// Get
|
|
135
|
-
server.tool('
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
220
|
+
// Get Program Accounts (V2 with pagination)
|
|
221
|
+
server.tool('getProgramAccounts', 'Get all accounts owned by a specific program. Returns account addresses, balances, and data sizes. Use dataSize to filter by account data length (e.g. 165 for token accounts). Useful for finding all accounts created by a program like a DEX, lending protocol, or custom program.', {
|
|
222
|
+
programId: z.string().describe('Program ID (base58 encoded) — the owner program of the accounts to find'),
|
|
223
|
+
limit: z.number().optional().default(20).describe('Maximum accounts to return (default 20, max 100)'),
|
|
224
|
+
encoding: z.enum(['base58', 'base64', 'jsonParsed']).optional().default('base64').describe('Data encoding format'),
|
|
225
|
+
dataSize: z.number().optional().describe('Filter by exact account data size in bytes (e.g. 165 for SPL token accounts)'),
|
|
226
|
+
paginationKey: z.string().optional().describe('Pagination cursor from a previous response to fetch the next page')
|
|
227
|
+
}, async ({ programId, limit, encoding, dataSize, paginationKey }) => {
|
|
139
228
|
if (!hasApiKey())
|
|
140
229
|
return noApiKeyResponse();
|
|
230
|
+
const url = getRpcUrl();
|
|
231
|
+
const cappedLimit = Math.min(limit, 10_000);
|
|
232
|
+
const filters = [];
|
|
233
|
+
if (dataSize !== undefined) {
|
|
234
|
+
filters.push({ dataSize });
|
|
235
|
+
}
|
|
236
|
+
const rpcParams = {
|
|
237
|
+
encoding,
|
|
238
|
+
dataSlice: { offset: 0, length: 0 },
|
|
239
|
+
limit: cappedLimit
|
|
240
|
+
};
|
|
241
|
+
if (filters.length > 0)
|
|
242
|
+
rpcParams.filters = filters;
|
|
243
|
+
if (paginationKey)
|
|
244
|
+
rpcParams.paginationKey = paginationKey;
|
|
245
|
+
const requestBody = {
|
|
246
|
+
jsonrpc: '2.0',
|
|
247
|
+
id: 'get-program-accounts-v2',
|
|
248
|
+
method: 'getProgramAccountsV2',
|
|
249
|
+
params: [programId, rpcParams]
|
|
250
|
+
};
|
|
251
|
+
let data;
|
|
141
252
|
try {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
text: `**Account Not Found**\n\n${formatAddress(address)}\n\nThis account does not exist or has been closed.`
|
|
159
|
-
}]
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
const lines = [
|
|
163
|
-
`**Account Info**`,
|
|
164
|
-
``,
|
|
165
|
-
`Address: ${formatAddress(address)}`,
|
|
166
|
-
`Balance: ${formatSol(accountInfo.lamports)}`,
|
|
167
|
-
`Owner: ${formatAddress(accountInfo.owner.toBase58())}`,
|
|
168
|
-
`Data Length: ${accountInfo.data.length} bytes`,
|
|
169
|
-
`Rent Epoch: ${accountInfo.rentEpoch}`,
|
|
170
|
-
``
|
|
171
|
-
];
|
|
172
|
-
if (accountInfo.executable) {
|
|
173
|
-
lines.push(`⚡ **Executable Program**`);
|
|
174
|
-
lines.push(``);
|
|
175
|
-
}
|
|
176
|
-
if (encoding === 'jsonParsed' && accountInfo.data.parsed) {
|
|
177
|
-
lines.push(`**Parsed Data:**`);
|
|
178
|
-
lines.push('```json');
|
|
179
|
-
lines.push(JSON.stringify(accountInfo.data.parsed, null, 2));
|
|
180
|
-
lines.push('```');
|
|
181
|
-
}
|
|
182
|
-
else if (accountInfo.data.length > 0 && accountInfo.data.length <= 100) {
|
|
183
|
-
lines.push(`**Data (first 100 bytes):**`);
|
|
184
|
-
lines.push(`${accountInfo.data.toString('hex').slice(0, 200)}...`);
|
|
185
|
-
}
|
|
253
|
+
const response = await fetch(url, {
|
|
254
|
+
method: 'POST',
|
|
255
|
+
headers: { 'Content-Type': 'application/json' },
|
|
256
|
+
body: JSON.stringify(requestBody)
|
|
257
|
+
});
|
|
258
|
+
data = await response.json();
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
return {
|
|
262
|
+
content: [{
|
|
263
|
+
type: 'text',
|
|
264
|
+
text: `**Error fetching program accounts:** ${err instanceof Error ? err.message : String(err)}`
|
|
265
|
+
}]
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (data.error) {
|
|
186
269
|
return {
|
|
187
|
-
content: [{
|
|
270
|
+
content: [{
|
|
271
|
+
type: 'text',
|
|
272
|
+
text: `**Error**\n\n${data.error.message}`
|
|
273
|
+
}]
|
|
188
274
|
};
|
|
189
275
|
}
|
|
190
|
-
|
|
276
|
+
const accounts = data.result?.accounts || [];
|
|
277
|
+
if (accounts.length === 0) {
|
|
191
278
|
return {
|
|
192
|
-
content: [{
|
|
193
|
-
|
|
279
|
+
content: [{
|
|
280
|
+
type: 'text',
|
|
281
|
+
text: `**Program Accounts for ${formatAddress(programId)}**\n\nNo accounts found.`
|
|
282
|
+
}]
|
|
194
283
|
};
|
|
195
284
|
}
|
|
285
|
+
const totalLabel = data.result?.totalResults
|
|
286
|
+
? `${data.result.totalResults.toLocaleString()} total`
|
|
287
|
+
: `${accounts.length} returned`;
|
|
288
|
+
const lines = [`**Program Accounts for ${formatAddress(programId)}** (${totalLabel})`, ''];
|
|
289
|
+
accounts.forEach((item) => {
|
|
290
|
+
lines.push(`- **${formatAddress(item.pubkey)}**`);
|
|
291
|
+
lines.push(` Balance: ${formatSol(item.account.lamports)}`);
|
|
292
|
+
if (item.account.space !== undefined) {
|
|
293
|
+
lines.push(` Data Size: ${item.account.space} bytes`);
|
|
294
|
+
}
|
|
295
|
+
lines.push(` Executable: ${item.account.executable ? 'Yes' : 'No'}`);
|
|
296
|
+
});
|
|
297
|
+
if (data.result?.paginationKey) {
|
|
298
|
+
lines.push('', `**Next Page:** Pass \`paginationKey: "${data.result.paginationKey}"\` to fetch the next page.`);
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
content: [{
|
|
302
|
+
type: 'text',
|
|
303
|
+
text: lines.join('\n')
|
|
304
|
+
}]
|
|
305
|
+
};
|
|
196
306
|
});
|
|
197
307
|
}
|