helius-mcp 0.2.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/LICENSE +21 -0
- package/README.md +150 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +29 -0
- package/dist/tools/accounts.d.ts +2 -0
- package/dist/tools/accounts.js +197 -0
- package/dist/tools/assets-extended.d.ts +2 -0
- package/dist/tools/assets-extended.js +54 -0
- package/dist/tools/assets.d.ts +2 -0
- package/dist/tools/assets.js +156 -0
- package/dist/tools/balance.d.ts +2 -0
- package/dist/tools/balance.js +169 -0
- package/dist/tools/config.d.ts +2 -0
- package/dist/tools/config.js +33 -0
- package/dist/tools/das.d.ts +2 -0
- package/dist/tools/das.js +428 -0
- package/dist/tools/enhanced-transactions.d.ts +2 -0
- package/dist/tools/enhanced-transactions.js +51 -0
- package/dist/tools/enhanced-websockets.d.ts +12 -0
- package/dist/tools/enhanced-websockets.js +329 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +18 -0
- package/dist/tools/laserstream.d.ts +16 -0
- package/dist/tools/laserstream.js +348 -0
- package/dist/tools/priority-fees.d.ts +2 -0
- package/dist/tools/priority-fees.js +61 -0
- package/dist/tools/rpc.d.ts +2 -0
- package/dist/tools/rpc.js +130 -0
- package/dist/tools/shared.d.ts +6 -0
- package/dist/tools/shared.js +13 -0
- package/dist/tools/transactions-enhanced.d.ts +2 -0
- package/dist/tools/transactions-enhanced.js +165 -0
- package/dist/tools/transactions.d.ts +2 -0
- package/dist/tools/transactions.js +336 -0
- package/dist/tools/webhooks.d.ts +2 -0
- package/dist/tools/webhooks.js +156 -0
- package/dist/utils/formatters.d.ts +4 -0
- package/dist/utils/formatters.js +12 -0
- package/dist/utils/helius.d.ts +33 -0
- package/dist/utils/helius.js +134 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Helius Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Helius MCP Server
|
|
2
|
+
|
|
3
|
+
Official Model Context Protocol (MCP) server for Helius - Complete Solana blockchain data access for Claude.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
**29 Tools Covering:**
|
|
8
|
+
- ✅ Digital Asset Standard (DAS) API - NFTs, tokens, compressed NFTs
|
|
9
|
+
- ✅ RPC Methods - Balances, accounts, signatures
|
|
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
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g @helius-labs/mcp-server
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### 1. Get Helius API Key
|
|
29
|
+
|
|
30
|
+
Sign up at [helius.dev](https://www.helius.dev) and create an API key.
|
|
31
|
+
|
|
32
|
+
### 2. Configure Claude Desktop
|
|
33
|
+
|
|
34
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
35
|
+
|
|
36
|
+
```json
|
|
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
|
+
```
|
|
49
|
+
|
|
50
|
+
### 3. Restart Claude Desktop
|
|
51
|
+
|
|
52
|
+
The Helius tools will now be available in Claude.
|
|
53
|
+
|
|
54
|
+
## Usage Examples
|
|
55
|
+
|
|
56
|
+
**Check wallet balance:**
|
|
57
|
+
```
|
|
58
|
+
What's the SOL balance for vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg?
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Get NFT information:**
|
|
62
|
+
```
|
|
63
|
+
Show me details for NFT FKZJkqv2YbXmcGWbp2RJfhVmpE6PnSaq6dMj1DKXgj1m
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Monitor transactions:**
|
|
67
|
+
```
|
|
68
|
+
Set up a webhook to track transactions for wallet ABC...XYZ
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Real-time streaming:**
|
|
72
|
+
```
|
|
73
|
+
Show me how to subscribe to Raydium swap transactions
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Available Tools
|
|
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)
|
|
139
|
+
- [Enhanced WebSockets](https://docs.helius.dev/enhanced-websockets)
|
|
140
|
+
- [Laserstream gRPC](https://docs.helius.dev/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
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { registerTools } from './tools/index.js';
|
|
5
|
+
import { setApiKey } from './utils/helius.js';
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
name: 'helius-claude-plugin',
|
|
8
|
+
version: '0.3.0'
|
|
9
|
+
});
|
|
10
|
+
// Register all tools
|
|
11
|
+
registerTools(server);
|
|
12
|
+
// Start the server
|
|
13
|
+
async function main() {
|
|
14
|
+
// Auto-configure API key from environment if available
|
|
15
|
+
if (process.env.HELIUS_API_KEY) {
|
|
16
|
+
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
|
+
}
|
|
22
|
+
const transport = new StdioServerTransport();
|
|
23
|
+
await server.connect(transport);
|
|
24
|
+
console.error('🚀 Helius MCP Server v0.3.0 running (20 tools)');
|
|
25
|
+
}
|
|
26
|
+
main().catch((error) => {
|
|
27
|
+
console.error('Failed to start server:', error);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getHeliusClient, hasApiKey } from '../utils/helius.js';
|
|
3
|
+
import { formatAddress, formatSol } from '../utils/formatters.js';
|
|
4
|
+
import { noApiKeyResponse } from './shared.js';
|
|
5
|
+
export function registerAccountTools(server) {
|
|
6
|
+
// Get multiple account balances at once
|
|
7
|
+
server.tool('getMultipleAccounts', 'Get account info for multiple addresses in a single call (up to 100 addresses). More efficient than calling getBalance repeatedly. Returns SOL balance, owner, executable status, and rent epoch for each account.', {
|
|
8
|
+
addresses: z.array(z.string()).describe('Array of Solana addresses (max 100)'),
|
|
9
|
+
commitment: z.enum(['processed', 'confirmed', 'finalized']).optional().default('confirmed')
|
|
10
|
+
}, async ({ addresses, commitment }) => {
|
|
11
|
+
if (!hasApiKey())
|
|
12
|
+
return noApiKeyResponse();
|
|
13
|
+
if (addresses.length > 100) {
|
|
14
|
+
return {
|
|
15
|
+
content: [{
|
|
16
|
+
type: 'text',
|
|
17
|
+
text: '**Error**\n\nMaximum 100 addresses allowed per request'
|
|
18
|
+
}],
|
|
19
|
+
isError: true
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const helius = getHeliusClient();
|
|
24
|
+
const connection = helius.connection;
|
|
25
|
+
if (!connection || !connection.getMultipleAccountsInfo) {
|
|
26
|
+
return {
|
|
27
|
+
content: [{
|
|
28
|
+
type: 'text',
|
|
29
|
+
text: '**Error**\n\nMultiple account lookup not available'
|
|
30
|
+
}],
|
|
31
|
+
isError: true
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const accountsInfo = await connection.getMultipleAccountsInfo(addresses, { commitment });
|
|
35
|
+
const lines = [
|
|
36
|
+
`**Account Info (${addresses.length} addresses)**`,
|
|
37
|
+
``
|
|
38
|
+
];
|
|
39
|
+
addresses.forEach((addr, i) => {
|
|
40
|
+
const info = accountsInfo[i];
|
|
41
|
+
if (!info) {
|
|
42
|
+
lines.push(`${i + 1}. ${formatAddress(addr)} - Account does not exist`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
lines.push(`${i + 1}. ${formatAddress(addr)}`);
|
|
46
|
+
lines.push(` Balance: ${formatSol(info.lamports)}`);
|
|
47
|
+
lines.push(` Owner: ${formatAddress(info.owner.toBase58())}`);
|
|
48
|
+
if (info.executable) {
|
|
49
|
+
lines.push(` ⚡ Executable program`);
|
|
50
|
+
}
|
|
51
|
+
lines.push(``);
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: 'text', text: lines.join('\n') }]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
return {
|
|
59
|
+
content: [{ type: 'text', text: `**Error**\n\n${error.message}` }],
|
|
60
|
+
isError: true
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
// Get program accounts
|
|
65
|
+
server.tool('getProgramAccounts', 'Query all accounts owned by a program. Use filters to narrow results (e.g., token accounts for a specific mint, PDAs with certain data). Returns account addresses and data. Warning: Can be slow for programs with many accounts.', {
|
|
66
|
+
programId: z.string().describe('Program address to query'),
|
|
67
|
+
filters: z.array(z.object({
|
|
68
|
+
memcmp: z.object({
|
|
69
|
+
offset: z.number(),
|
|
70
|
+
bytes: z.string()
|
|
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 }) => {
|
|
76
|
+
if (!hasApiKey())
|
|
77
|
+
return noApiKeyResponse();
|
|
78
|
+
try {
|
|
79
|
+
const helius = getHeliusClient();
|
|
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
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
content: [{ type: 'text', text: lines.join('\n') }]
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
return {
|
|
129
|
+
content: [{ type: 'text', text: `**Error**\n\n${error.message}` }],
|
|
130
|
+
isError: true
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
// Get account info
|
|
135
|
+
server.tool('getAccountInfo', 'Get detailed information about a single Solana account including balance, owner, data, executable status, and rent epoch. Use this for inspecting program accounts, PDAs, or any on-chain account.', {
|
|
136
|
+
address: z.string().describe('Solana address to query'),
|
|
137
|
+
encoding: z.enum(['base58', 'base64', 'jsonParsed']).optional().default('base64')
|
|
138
|
+
}, async ({ address, encoding }) => {
|
|
139
|
+
if (!hasApiKey())
|
|
140
|
+
return noApiKeyResponse();
|
|
141
|
+
try {
|
|
142
|
+
const helius = getHeliusClient();
|
|
143
|
+
const connection = helius.connection;
|
|
144
|
+
if (!connection || !connection.getAccountInfo) {
|
|
145
|
+
return {
|
|
146
|
+
content: [{
|
|
147
|
+
type: 'text',
|
|
148
|
+
text: '**Error**\n\nAccount info lookup not available'
|
|
149
|
+
}],
|
|
150
|
+
isError: true
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const accountInfo = await connection.getAccountInfo(address, { encoding });
|
|
154
|
+
if (!accountInfo) {
|
|
155
|
+
return {
|
|
156
|
+
content: [{
|
|
157
|
+
type: 'text',
|
|
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
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
content: [{ type: 'text', text: lines.join('\n') }]
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
return {
|
|
192
|
+
content: [{ type: 'text', text: `**Error**\n\n${error.message}` }],
|
|
193
|
+
isError: true
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getHeliusClient, hasApiKey } from '../utils/helius.js';
|
|
3
|
+
import { formatAddress } from '../utils/formatters.js';
|
|
4
|
+
import { noApiKeyResponse } from './shared.js';
|
|
5
|
+
export function registerExtendedAssetTools(server) {
|
|
6
|
+
server.tool('getAssetsByGroup', 'Get NFTs in a collection', { groupKey: z.string(), groupValue: z.string(), page: z.number().optional().default(1), limit: z.number().optional().default(20) }, async ({ groupKey, groupValue, page, limit }) => {
|
|
7
|
+
if (!hasApiKey())
|
|
8
|
+
return noApiKeyResponse();
|
|
9
|
+
const response = await getHeliusClient().getAssetsByGroup({ groupKey, groupValue, page, limit });
|
|
10
|
+
const items = response.items || [];
|
|
11
|
+
const total = response.total || 0;
|
|
12
|
+
if (items.length === 0)
|
|
13
|
+
return { content: [{ type: 'text', text: 'No assets found' }] };
|
|
14
|
+
const lines = [`**Collection: ${total} total**`, ``];
|
|
15
|
+
items.slice(0, 15).forEach((a, i) => {
|
|
16
|
+
const name = a.content && a.content.metadata && a.content.metadata.name ? a.content.metadata.name : 'Unnamed';
|
|
17
|
+
lines.push(`${i + 1}. ${name}`);
|
|
18
|
+
lines.push(` ${formatAddress(a.id)}`);
|
|
19
|
+
});
|
|
20
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
21
|
+
});
|
|
22
|
+
server.tool('searchAssets', 'Search NFTs with filters', { creatorAddress: z.string().optional(), ownerAddress: z.string().optional(), burnt: z.boolean().optional(), page: z.number().optional().default(1), limit: z.number().optional().default(20) }, async (params) => {
|
|
23
|
+
if (!hasApiKey())
|
|
24
|
+
return noApiKeyResponse();
|
|
25
|
+
const response = await getHeliusClient().searchAssets(params);
|
|
26
|
+
const items = response.items || [];
|
|
27
|
+
if (items.length === 0)
|
|
28
|
+
return { content: [{ type: 'text', text: 'No results' }] };
|
|
29
|
+
const lines = [`**Found ${response.total || 0} assets**`, ``];
|
|
30
|
+
items.slice(0, 10).forEach((a, i) => {
|
|
31
|
+
const name = a.content && a.content.metadata && a.content.metadata.name ? a.content.metadata.name : 'Unnamed';
|
|
32
|
+
lines.push(`${i + 1}. ${name}`);
|
|
33
|
+
});
|
|
34
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
35
|
+
});
|
|
36
|
+
server.tool('getAssetProof', 'Get Merkle proof for cNFT', { id: z.string() }, async ({ id }) => {
|
|
37
|
+
if (!hasApiKey())
|
|
38
|
+
return noApiKeyResponse();
|
|
39
|
+
const proof = await getHeliusClient().getAssetProof({ id });
|
|
40
|
+
return { content: [{ type: 'text', text: `**Proof for ${formatAddress(id)}**\n\nLength: ${proof.proof ? proof.proof.length : 0} nodes` }] };
|
|
41
|
+
});
|
|
42
|
+
server.tool('getAssetsByCreator', 'Get NFTs by creator', { creatorAddress: z.string(), page: z.number().optional().default(1), limit: z.number().optional().default(20) }, async (params) => {
|
|
43
|
+
if (!hasApiKey())
|
|
44
|
+
return noApiKeyResponse();
|
|
45
|
+
const response = await getHeliusClient().getAssetsByCreator(params);
|
|
46
|
+
const items = response.items || [];
|
|
47
|
+
const lines = [`**${response.total || 0} assets by creator**`, ``];
|
|
48
|
+
items.slice(0, 10).forEach((a, i) => {
|
|
49
|
+
const name = a.content && a.content.metadata && a.content.metadata.name ? a.content.metadata.name : 'Unnamed';
|
|
50
|
+
lines.push(`${i + 1}. ${name}`);
|
|
51
|
+
});
|
|
52
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getHeliusClient, hasApiKey } from '../utils/helius.js';
|
|
3
|
+
import { formatAddress } from '../utils/formatters.js';
|
|
4
|
+
import { noApiKeyResponse } from './shared.js';
|
|
5
|
+
export function registerAssetTools(server) {
|
|
6
|
+
// Get Assets by Owner (NFTs and tokens via DAS)
|
|
7
|
+
server.tool('getAssetsByOwner', 'Get all NFTs and digital assets owned by a Solana wallet using the DAS (Digital Asset Standard) API. Returns asset names, types (NFT, cNFT, Fungible, etc.), and mint addresses. Supports both regular NFTs and compressed NFTs (cNFTs). Use this to see what NFTs/collectibles a wallet owns. For fungible token balances, use getTokenBalances instead.', {
|
|
8
|
+
address: z.string().describe('Solana wallet address (base58 encoded)'),
|
|
9
|
+
limit: z.number().optional().default(20).describe('Number of assets to return (default 20). Increase for wallets with many NFTs.')
|
|
10
|
+
}, async ({ address, limit }) => {
|
|
11
|
+
if (!hasApiKey())
|
|
12
|
+
return noApiKeyResponse();
|
|
13
|
+
const helius = getHeliusClient();
|
|
14
|
+
const response = await helius.getAssetsByOwner({
|
|
15
|
+
ownerAddress: address,
|
|
16
|
+
page: 1,
|
|
17
|
+
limit
|
|
18
|
+
});
|
|
19
|
+
if (!response.items || response.items.length === 0) {
|
|
20
|
+
return {
|
|
21
|
+
content: [{
|
|
22
|
+
type: 'text',
|
|
23
|
+
text: `**Assets for ${formatAddress(address)}**\n\nNo assets found.`
|
|
24
|
+
}]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const items = response.items;
|
|
28
|
+
// Enrich assets missing name/symbol
|
|
29
|
+
const unknownAssets = items.filter((asset) => !asset.content?.metadata?.name && !asset.content?.metadata?.symbol && !asset.token_info?.symbol);
|
|
30
|
+
if (unknownAssets.length > 0 && unknownAssets.length <= 10) {
|
|
31
|
+
const enrichedData = await Promise.allSettled(unknownAssets.map((asset) => helius.getAsset({ id: asset.id })));
|
|
32
|
+
enrichedData.forEach((result, idx) => {
|
|
33
|
+
if (result.status === 'fulfilled' && result.value) {
|
|
34
|
+
const enriched = result.value;
|
|
35
|
+
const original = unknownAssets[idx];
|
|
36
|
+
if (enriched.content?.metadata) {
|
|
37
|
+
original.content = original.content || {};
|
|
38
|
+
original.content.metadata = enriched.content.metadata;
|
|
39
|
+
}
|
|
40
|
+
if (enriched.token_info?.symbol) {
|
|
41
|
+
original.token_info = original.token_info || {};
|
|
42
|
+
original.token_info.symbol = enriched.token_info.symbol;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const lines = [`**Assets for ${formatAddress(address)}** (${response.total} total)`, ''];
|
|
48
|
+
items.forEach((asset) => {
|
|
49
|
+
const name = asset.content?.metadata?.name || asset.content?.metadata?.symbol || asset.token_info?.symbol || 'Unnamed';
|
|
50
|
+
lines.push(`- **${name}** (${asset.interface})`);
|
|
51
|
+
lines.push(` ID: ${formatAddress(asset.id)}`);
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
content: [{
|
|
55
|
+
type: 'text',
|
|
56
|
+
text: lines.join('\n')
|
|
57
|
+
}]
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
// Get Single Asset Details
|
|
61
|
+
server.tool('getAsset', 'Get detailed information about a specific NFT, token, or digital asset by its mint address. Returns name, symbol, description, image URL, current owner, creators/deployer addresses, authorities, token supply, decimals, royalties, and whether the token is mutable. Use this to find who created/deployed a token, verify token details, or get full NFT metadata.', {
|
|
62
|
+
id: z.string().describe('Asset mint address / ID (base58 encoded). This is the unique identifier for the NFT or token.')
|
|
63
|
+
}, async ({ id }) => {
|
|
64
|
+
if (!hasApiKey())
|
|
65
|
+
return noApiKeyResponse();
|
|
66
|
+
const helius = getHeliusClient();
|
|
67
|
+
const asset = await helius.getAsset({ id });
|
|
68
|
+
if (!asset) {
|
|
69
|
+
return {
|
|
70
|
+
content: [{
|
|
71
|
+
type: 'text',
|
|
72
|
+
text: `Asset ${formatAddress(id)} not found.`
|
|
73
|
+
}]
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const metadata = asset.content?.metadata;
|
|
77
|
+
const tokenInfo = asset.token_info;
|
|
78
|
+
const lines = [
|
|
79
|
+
`**Asset: ${metadata?.name || tokenInfo?.symbol || 'Unnamed'}**`,
|
|
80
|
+
'',
|
|
81
|
+
`**Mint Address:** ${asset.id}`,
|
|
82
|
+
`**Type:** ${asset.interface}`,
|
|
83
|
+
];
|
|
84
|
+
if (metadata?.symbol || tokenInfo?.symbol) {
|
|
85
|
+
lines.push(`**Symbol:** ${metadata?.symbol || tokenInfo?.symbol}`);
|
|
86
|
+
}
|
|
87
|
+
if (asset.ownership?.owner) {
|
|
88
|
+
lines.push(`**Owner:** ${formatAddress(asset.ownership.owner)}`);
|
|
89
|
+
}
|
|
90
|
+
if (metadata?.description) {
|
|
91
|
+
lines.push(`**Description:** ${metadata.description}`);
|
|
92
|
+
}
|
|
93
|
+
// Creators/Deployer info
|
|
94
|
+
if (asset.creators && asset.creators.length > 0) {
|
|
95
|
+
lines.push('', '**Creators:**');
|
|
96
|
+
asset.creators.forEach((creator) => {
|
|
97
|
+
const verified = creator.verified ? '✓' : '✗';
|
|
98
|
+
lines.push(`- ${formatAddress(creator.address)} (${creator.share}% share) ${verified}`);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// Authorities
|
|
102
|
+
if (asset.authorities && asset.authorities.length > 0) {
|
|
103
|
+
lines.push('', '**Authorities:**');
|
|
104
|
+
asset.authorities.forEach((auth) => {
|
|
105
|
+
lines.push(`- ${formatAddress(auth.address)} [${auth.scopes.join(', ')}]`);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// Token info (for fungible tokens)
|
|
109
|
+
if (tokenInfo) {
|
|
110
|
+
lines.push('', '**Token Info:**');
|
|
111
|
+
if (tokenInfo.decimals !== undefined) {
|
|
112
|
+
lines.push(`- Decimals: ${tokenInfo.decimals}`);
|
|
113
|
+
}
|
|
114
|
+
if (tokenInfo.supply !== undefined) {
|
|
115
|
+
const formattedSupply = tokenInfo.decimals
|
|
116
|
+
? (tokenInfo.supply / Math.pow(10, tokenInfo.decimals)).toLocaleString()
|
|
117
|
+
: tokenInfo.supply.toLocaleString();
|
|
118
|
+
lines.push(`- Supply: ${formattedSupply}`);
|
|
119
|
+
}
|
|
120
|
+
if (tokenInfo.token_program) {
|
|
121
|
+
const programName = tokenInfo.token_program === 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
|
|
122
|
+
? 'SPL Token'
|
|
123
|
+
: tokenInfo.token_program === 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'
|
|
124
|
+
? 'Token-2022'
|
|
125
|
+
: formatAddress(tokenInfo.token_program);
|
|
126
|
+
lines.push(`- Program: ${programName}`);
|
|
127
|
+
}
|
|
128
|
+
if (tokenInfo.price_info?.price_per_token) {
|
|
129
|
+
lines.push(`- Price: $${tokenInfo.price_info.price_per_token.toFixed(6)} ${tokenInfo.price_info.currency || 'USD'}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (asset.content?.links?.image) {
|
|
133
|
+
lines.push(`**Image:** ${asset.content.links.image}`);
|
|
134
|
+
}
|
|
135
|
+
if (asset.royalty && asset.royalty.percent > 0) {
|
|
136
|
+
lines.push(`**Royalty:** ${asset.royalty.percent}%`);
|
|
137
|
+
}
|
|
138
|
+
// Mutable/burnt status
|
|
139
|
+
const flags = [];
|
|
140
|
+
if (asset.mutable === false)
|
|
141
|
+
flags.push('Immutable');
|
|
142
|
+
if (asset.mutable === true)
|
|
143
|
+
flags.push('Mutable');
|
|
144
|
+
if (asset.burnt)
|
|
145
|
+
flags.push('Burnt');
|
|
146
|
+
if (flags.length > 0) {
|
|
147
|
+
lines.push(`**Status:** ${flags.join(', ')}`);
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
content: [{
|
|
151
|
+
type: 'text',
|
|
152
|
+
text: lines.join('\n')
|
|
153
|
+
}]
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
}
|