helius-mcp 0.5.3 → 1.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE +1 -1
  3. package/README.md +97 -21
  4. package/dist/http.d.ts +1 -0
  5. package/dist/http.js +2 -0
  6. package/dist/index.js +93 -2
  7. package/dist/scripts/validate-catalog.d.ts +13 -0
  8. package/dist/scripts/validate-catalog.js +76 -0
  9. package/dist/tools/accounts.js +114 -204
  10. package/dist/tools/assets.js +109 -123
  11. package/dist/tools/auth.d.ts +2 -0
  12. package/dist/tools/auth.js +459 -0
  13. package/dist/tools/balance.js +28 -32
  14. package/dist/tools/blocks.js +68 -87
  15. package/dist/tools/config.js +18 -79
  16. package/dist/tools/das-extras.js +56 -41
  17. package/dist/tools/docs.js +12 -54
  18. package/dist/tools/enhanced-websockets.js +104 -74
  19. package/dist/tools/fees.js +42 -61
  20. package/dist/tools/guides.js +126 -515
  21. package/dist/tools/index.js +50 -2
  22. package/dist/tools/laserstream.js +107 -53
  23. package/dist/tools/network.js +47 -69
  24. package/dist/tools/plans.d.ts +21 -0
  25. package/dist/tools/plans.js +105 -246
  26. package/dist/tools/product-catalog.d.ts +10 -0
  27. package/dist/tools/product-catalog.js +123 -0
  28. package/dist/tools/recommend.d.ts +4 -0
  29. package/dist/tools/recommend.js +233 -0
  30. package/dist/tools/shared.js +8 -3
  31. package/dist/tools/solana-knowledge.d.ts +2 -0
  32. package/dist/tools/solana-knowledge.js +544 -0
  33. package/dist/tools/tokens.js +17 -18
  34. package/dist/tools/transactions.js +232 -302
  35. package/dist/tools/transfers.d.ts +2 -0
  36. package/dist/tools/transfers.js +270 -0
  37. package/dist/tools/wallet.js +175 -177
  38. package/dist/tools/webhooks.js +80 -82
  39. package/dist/types/transaction-types.d.ts +1 -1
  40. package/dist/types/transaction-types.js +2 -1
  41. package/dist/utils/config.d.ts +27 -0
  42. package/dist/utils/config.js +76 -0
  43. package/dist/utils/docs.d.ts +24 -0
  44. package/dist/utils/docs.js +72 -0
  45. package/dist/utils/errors.d.ts +32 -0
  46. package/dist/utils/errors.js +157 -0
  47. package/dist/utils/feedback.d.ts +16 -0
  48. package/dist/utils/feedback.js +87 -0
  49. package/dist/utils/formatters.d.ts +0 -1
  50. package/dist/utils/formatters.js +0 -3
  51. package/dist/utils/helius.d.ts +15 -5
  52. package/dist/utils/helius.js +52 -45
  53. package/dist/version.d.ts +1 -0
  54. package/dist/version.js +1 -0
  55. package/package.json +17 -7
  56. package/system-prompts/helius/claude.system.md +170 -0
  57. package/system-prompts/helius/full.md +2868 -0
  58. package/system-prompts/helius/openai.developer.md +170 -0
  59. package/system-prompts/helius-dflow/claude.system.md +290 -0
  60. package/system-prompts/helius-dflow/full.md +3647 -0
  61. package/system-prompts/helius-dflow/openai.developer.md +290 -0
  62. package/system-prompts/helius-phantom/claude.system.md +348 -0
  63. package/system-prompts/helius-phantom/full.md +5472 -0
  64. package/system-prompts/helius-phantom/openai.developer.md +348 -0
  65. package/system-prompts/svm/claude.system.md +174 -0
  66. package/system-prompts/svm/full.md +699 -0
  67. package/system-prompts/svm/openai.developer.md +174 -0
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerTransferTools(server: McpServer): void;
@@ -0,0 +1,270 @@
1
+ import { z } from 'zod';
2
+ import { createKeyPairSignerFromBytes, address } from '@solana/kit';
3
+ import { getTransferSolInstruction } from '@solana-program/system';
4
+ import { findAssociatedTokenPda, getCloseAccountInstruction, getCreateAssociatedTokenIdempotentInstruction, getTransferCheckedInstruction, TOKEN_PROGRAM_ADDRESS, } from '@solana-program/token';
5
+ import { getHeliusClient, hasApiKey, loadSignerOrFail } from '../utils/helius.js';
6
+ import { mcpText, mcpError, handleToolError, isValidAddressFormat } from '../utils/errors.js';
7
+ import { noApiKeyResponse } from './shared.js';
8
+ const TOKEN_2022_PROGRAM_ID = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
9
+ // ── Tool Registration ──
10
+ export function registerTransferTools(server) {
11
+ // ── Transfer SOL ──
12
+ server.tool('transferSol', 'BEST FOR: sending native SOL from your MCP wallet to another address. ' +
13
+ 'PREFER transferToken for SPL tokens. ' +
14
+ 'Transfers native SOL using Helius Sender for optimal landing rates. ' +
15
+ 'Requires a configured keypair (call generateKeypair if needed). ' +
16
+ 'This is an irreversible on-chain transaction. ' +
17
+ 'Set sendMax to true to drain the entire SOL balance (sends balance minus transaction fees). ' +
18
+ 'Credit cost: ~3 credits (CU simulation + priority fee estimate + send).', {
19
+ recipientAddress: z.string().describe('Recipient Solana wallet address (base58 encoded)'),
20
+ amount: z.number().positive().optional().describe('Amount of SOL to send (e.g., 0.5 for half a SOL). Required unless sendMax is true.'),
21
+ sendMax: z.boolean().optional().default(false).describe('Send the maximum possible amount (entire balance minus transaction fees). When true, amount is ignored.'),
22
+ }, async ({ recipientAddress, amount, sendMax }) => {
23
+ if (!hasApiKey())
24
+ return noApiKeyResponse();
25
+ try {
26
+ // Load keypair
27
+ let signerData;
28
+ try {
29
+ signerData = await loadSignerOrFail();
30
+ }
31
+ catch {
32
+ return mcpError('No keypair found. Call `generateKeypair` first to create a wallet, then fund it before sending.');
33
+ }
34
+ // Validate recipient address
35
+ if (!isValidAddressFormat(recipientAddress)) {
36
+ return mcpError(`Invalid recipient address "${recipientAddress}". Expected a valid Solana address (32-44 base58 characters).`);
37
+ }
38
+ const helius = getHeliusClient();
39
+ const balanceResult = await helius.getBalance(signerData.walletAddress);
40
+ const balanceLamports = BigInt(balanceResult.value);
41
+ let lamports;
42
+ let sendAmount;
43
+ if (sendMax) {
44
+ // WARNING: This assumes sendSmartTransaction with priorityFeeCap=0 produces a
45
+ // single-signer transaction whose only lamport cost is the 5000-lamport base fee.
46
+ // If the SDK ever adds additional signers, compute budget instructions, or other
47
+ // lamport-consuming operations, this will under-transfer and leave a dust balance
48
+ // (or over-transfer and fail). Verify this invariant after any SDK upgrade.
49
+ //
50
+ // The account must reach exactly 0 lamports — any non-zero balance below the
51
+ // rent-exempt minimum (~0.00089 SOL) is rejected by the runtime. We cap the
52
+ // priority fee at 0 so the total fee is deterministically 5000 lamports
53
+ // (the base fee per signature), and transfer balance - 5000 to drain to zero.
54
+ const txFee = 5000n;
55
+ lamports = balanceLamports - txFee;
56
+ if (lamports <= 0n) {
57
+ const available = Number(balanceLamports) / 1_000_000_000;
58
+ return mcpError(`Balance too low to cover the transaction fee. You have ${available} SOL.\n\n` +
59
+ `Wallet: \`${signerData.walletAddress}\``);
60
+ }
61
+ sendAmount = Number(lamports) / 1_000_000_000;
62
+ }
63
+ else {
64
+ if (amount === undefined) {
65
+ return mcpError('Either `amount` must be specified or `sendMax` must be true.');
66
+ }
67
+ lamports = BigInt(Math.round(amount * 1_000_000_000));
68
+ sendAmount = amount;
69
+ // Reserve ~0.005 SOL for tx fees (priority fee + sender tip)
70
+ const reserveLamports = 5000000n;
71
+ if (balanceLamports < lamports + reserveLamports) {
72
+ const available = Number(balanceLamports) / 1_000_000_000;
73
+ return mcpError(`Insufficient SOL balance. You have ${available} SOL but need ${sendAmount} SOL plus ~0.005 SOL for transaction fees.\n\n` +
74
+ `Wallet: \`${signerData.walletAddress}\``);
75
+ }
76
+ }
77
+ // Create signer from keypair bytes
78
+ const signer = await createKeyPairSignerFromBytes(signerData.secretKey);
79
+ // Build transfer instruction
80
+ const ix = getTransferSolInstruction({
81
+ source: signer,
82
+ destination: address(recipientAddress),
83
+ amount: lamports,
84
+ });
85
+ let signature;
86
+ if (sendMax) {
87
+ // Use sendSmartTransaction (no sender tip) with priorityFeeCap=0 so the
88
+ // total fee is exactly 5000 lamports, draining the account to zero.
89
+ signature = await helius.tx.sendSmartTransaction({
90
+ signers: [signer],
91
+ instructions: [ix],
92
+ priorityFeeCap: 0,
93
+ });
94
+ }
95
+ else {
96
+ // Use Helius Sender for optimal landing rates
97
+ signature = await helius.tx.sendTransactionWithSender({
98
+ signers: [signer],
99
+ instructions: [ix],
100
+ region: 'Default',
101
+ });
102
+ }
103
+ return mcpText(`**SOL Transfer Sent**\n\n` +
104
+ `- **From:** \`${signerData.walletAddress}\`\n` +
105
+ `- **To:** \`${recipientAddress}\`\n` +
106
+ `- **Amount:** ${sendAmount} SOL${sendMax ? ' (max)' : ''}\n` +
107
+ `- **Signature:** \`${signature}\`\n` +
108
+ `- **Explorer:** https://orbmarkets.io/tx/${signature}`);
109
+ }
110
+ catch (err) {
111
+ return handleToolError(err, 'Error sending SOL transfer');
112
+ }
113
+ });
114
+ // ── Transfer SPL Token ──
115
+ server.tool('transferToken', 'BEST FOR: sending SPL tokens (USDC, BONK, JUP, etc.) from your MCP wallet to another address. ' +
116
+ 'PREFER transferSol for native SOL. ' +
117
+ 'Automatically creates recipient token account if needed. ' +
118
+ 'Uses Helius Sender for optimal landing rates. ' +
119
+ 'Requires a configured keypair. ' +
120
+ 'This is an irreversible on-chain transaction. ' +
121
+ 'Set sendMax to true to send the entire token balance and close the sender token account (reclaims rent). ' +
122
+ 'Credit cost: ~13 credits (10 DAS for mint info + ~3 for CU simulation, priority fee, send).', {
123
+ recipientAddress: z.string().describe('Recipient Solana wallet address (base58 encoded)'),
124
+ mintAddress: z.string().describe('Token mint address (base58 encoded, e.g., EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v for USDC)'),
125
+ amount: z.number().positive().optional().describe('Amount of tokens to send in human-readable units (e.g., 10 for 10 USDC). Required unless sendMax is true.'),
126
+ sendMax: z.boolean().optional().default(false).describe('Send the entire token balance and close the sender token account to reclaim rent. When true, amount is ignored.'),
127
+ }, async ({ recipientAddress, mintAddress, amount, sendMax }) => {
128
+ if (!hasApiKey())
129
+ return noApiKeyResponse();
130
+ try {
131
+ // Load keypair
132
+ let signerData;
133
+ try {
134
+ signerData = await loadSignerOrFail();
135
+ }
136
+ catch {
137
+ return mcpError('No keypair found. Call `generateKeypair` first to create a wallet, then fund it before sending.');
138
+ }
139
+ // Validate addresses
140
+ if (!isValidAddressFormat(recipientAddress)) {
141
+ return mcpError(`Invalid recipient address "${recipientAddress}". Expected a valid Solana address (32-44 base58 characters).`);
142
+ }
143
+ if (!isValidAddressFormat(mintAddress)) {
144
+ return mcpError(`Invalid mint address "${mintAddress}". Expected a valid Solana address (32-44 base58 characters).`);
145
+ }
146
+ // Fetch token metadata via DAS to get decimals and token program
147
+ const helius = getHeliusClient();
148
+ let asset;
149
+ try {
150
+ asset = await helius.getAsset({ id: mintAddress });
151
+ }
152
+ catch {
153
+ return mcpError(`Could not fetch token metadata for mint \`${mintAddress}\`. Verify the mint address is correct.`);
154
+ }
155
+ if (!asset?.token_info?.decimals && asset?.token_info?.decimals !== 0) {
156
+ return mcpError(`Mint \`${mintAddress}\` does not appear to be a fungible token (no decimals info found). Use \`getAsset\` to inspect it.`);
157
+ }
158
+ const decimals = asset.token_info.decimals;
159
+ const tokenProgram = asset.token_info.token_program;
160
+ const tokenName = asset.content?.metadata?.name || 'Unknown Token';
161
+ const tokenSymbol = asset.content?.metadata?.symbol || '';
162
+ // Token-2022 check
163
+ if (tokenProgram === TOKEN_2022_PROGRAM_ID) {
164
+ return mcpError(`Token-2022 transfers are not yet supported. This token (\`${tokenName}\`${tokenSymbol ? ` / ${tokenSymbol}` : ''}) uses the Token-2022 program. Standard SPL Token transfers are supported.`);
165
+ }
166
+ // Create signer from keypair bytes
167
+ const signer = await createKeyPairSignerFromBytes(signerData.secretKey);
168
+ const mint = address(mintAddress);
169
+ const recipient = address(recipientAddress);
170
+ // Derive ATAs
171
+ const [senderAta] = await findAssociatedTokenPda({
172
+ owner: signer.address,
173
+ mint,
174
+ tokenProgram: TOKEN_PROGRAM_ADDRESS,
175
+ });
176
+ const [recipientAta] = await findAssociatedTokenPda({
177
+ owner: recipient,
178
+ mint,
179
+ tokenProgram: TOKEN_PROGRAM_ADDRESS,
180
+ });
181
+ let rawAmount;
182
+ let sendAmount;
183
+ if (sendMax) {
184
+ // Fetch full token balance for max send
185
+ let tokenBalance;
186
+ try {
187
+ tokenBalance = await helius.getTokenAccountBalance(senderAta);
188
+ }
189
+ catch {
190
+ return mcpError(`No token account found for ${tokenSymbol || tokenName}. You may not hold this token.\n\n` +
191
+ `Wallet: \`${signerData.walletAddress}\`\n` +
192
+ `Mint: \`${mintAddress}\``);
193
+ }
194
+ rawAmount = BigInt(tokenBalance.value.amount);
195
+ if (rawAmount === 0n) {
196
+ return mcpError(`${tokenSymbol || tokenName} balance is 0. Nothing to send.\n\n` +
197
+ `Wallet: \`${signerData.walletAddress}\`\n` +
198
+ `Mint: \`${mintAddress}\``);
199
+ }
200
+ sendAmount = Number(rawAmount) / 10 ** decimals;
201
+ }
202
+ else {
203
+ if (amount === undefined) {
204
+ return mcpError('Either `amount` must be specified or `sendMax` must be true.');
205
+ }
206
+ rawAmount = BigInt(Math.round(amount * 10 ** decimals));
207
+ sendAmount = amount;
208
+ // Pre-flight token balance check
209
+ try {
210
+ const tokenBalance = await helius.getTokenAccountBalance(senderAta);
211
+ const currentRaw = BigInt(tokenBalance.value.amount);
212
+ if (currentRaw < rawAmount) {
213
+ const currentHuman = Number(currentRaw) / 10 ** decimals;
214
+ return mcpError(`Insufficient ${tokenSymbol || tokenName} balance. You have ${currentHuman} but are trying to send ${sendAmount}.\n\n` +
215
+ `Wallet: \`${signerData.walletAddress}\`\n` +
216
+ `Mint: \`${mintAddress}\``);
217
+ }
218
+ }
219
+ catch {
220
+ // Token account may not exist yet — let the on-chain error handle it
221
+ }
222
+ }
223
+ // Build instructions: idempotent ATA creation + transfer
224
+ const createAtaIx = getCreateAssociatedTokenIdempotentInstruction({
225
+ payer: signer,
226
+ ata: recipientAta,
227
+ owner: recipient,
228
+ mint,
229
+ tokenProgram: TOKEN_PROGRAM_ADDRESS,
230
+ });
231
+ const transferIx = getTransferCheckedInstruction({
232
+ source: senderAta,
233
+ mint,
234
+ destination: recipientAta,
235
+ authority: signer,
236
+ amount: rawAmount,
237
+ decimals,
238
+ });
239
+ // When sending max, close the sender's ATA to reclaim rent
240
+ const closeIx = sendMax
241
+ ? getCloseAccountInstruction({
242
+ account: senderAta,
243
+ destination: signer.address,
244
+ owner: signer,
245
+ })
246
+ : undefined;
247
+ // Send via Helius Sender
248
+ const signature = await helius.tx.sendTransactionWithSender({
249
+ signers: [signer],
250
+ instructions: closeIx
251
+ ? [createAtaIx, transferIx, closeIx]
252
+ : [createAtaIx, transferIx],
253
+ region: 'Default',
254
+ });
255
+ const displayName = tokenSymbol
256
+ ? `${sendAmount} ${tokenSymbol} (${tokenName})`
257
+ : `${sendAmount} ${tokenName}`;
258
+ return mcpText(`**Token Transfer Sent**\n\n` +
259
+ `- **From:** \`${signerData.walletAddress}\`\n` +
260
+ `- **To:** \`${recipientAddress}\`\n` +
261
+ `- **Amount:** ${displayName}${sendMax ? ' (max)' : ''}\n` +
262
+ `- **Mint:** \`${mintAddress}\`\n` +
263
+ `- **Signature:** \`${signature}\`\n` +
264
+ `- **Explorer:** https://orbmarkets.io/tx/${signature}`);
265
+ }
266
+ catch (err) {
267
+ return handleToolError(err, 'Error sending token transfer');
268
+ }
269
+ });
270
+ }