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.
- package/CHANGELOG.md +52 -0
- package/LICENSE +1 -1
- package/README.md +97 -21
- package/dist/http.d.ts +1 -0
- package/dist/http.js +2 -0
- package/dist/index.js +93 -2
- package/dist/scripts/validate-catalog.d.ts +13 -0
- package/dist/scripts/validate-catalog.js +76 -0
- package/dist/tools/accounts.js +114 -204
- package/dist/tools/assets.js +109 -123
- package/dist/tools/auth.d.ts +2 -0
- package/dist/tools/auth.js +459 -0
- package/dist/tools/balance.js +28 -32
- package/dist/tools/blocks.js +68 -87
- package/dist/tools/config.js +18 -79
- package/dist/tools/das-extras.js +56 -41
- package/dist/tools/docs.js +12 -54
- package/dist/tools/enhanced-websockets.js +104 -74
- package/dist/tools/fees.js +42 -61
- package/dist/tools/guides.js +126 -515
- package/dist/tools/index.js +50 -2
- package/dist/tools/laserstream.js +107 -53
- package/dist/tools/network.js +47 -69
- package/dist/tools/plans.d.ts +21 -0
- package/dist/tools/plans.js +105 -246
- package/dist/tools/product-catalog.d.ts +10 -0
- package/dist/tools/product-catalog.js +123 -0
- package/dist/tools/recommend.d.ts +4 -0
- package/dist/tools/recommend.js +233 -0
- package/dist/tools/shared.js +8 -3
- package/dist/tools/solana-knowledge.d.ts +2 -0
- package/dist/tools/solana-knowledge.js +544 -0
- package/dist/tools/tokens.js +17 -18
- package/dist/tools/transactions.js +232 -302
- package/dist/tools/transfers.d.ts +2 -0
- package/dist/tools/transfers.js +270 -0
- package/dist/tools/wallet.js +175 -177
- package/dist/tools/webhooks.js +80 -82
- package/dist/types/transaction-types.d.ts +1 -1
- package/dist/types/transaction-types.js +2 -1
- package/dist/utils/config.d.ts +27 -0
- package/dist/utils/config.js +76 -0
- package/dist/utils/docs.d.ts +24 -0
- package/dist/utils/docs.js +72 -0
- package/dist/utils/errors.d.ts +32 -0
- package/dist/utils/errors.js +157 -0
- package/dist/utils/feedback.d.ts +16 -0
- package/dist/utils/feedback.js +87 -0
- package/dist/utils/formatters.d.ts +0 -1
- package/dist/utils/formatters.js +0 -3
- package/dist/utils/helius.d.ts +15 -5
- package/dist/utils/helius.js +52 -45
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +17 -7
- package/system-prompts/helius/claude.system.md +170 -0
- package/system-prompts/helius/full.md +2868 -0
- package/system-prompts/helius/openai.developer.md +170 -0
- package/system-prompts/helius-dflow/claude.system.md +290 -0
- package/system-prompts/helius-dflow/full.md +3647 -0
- package/system-prompts/helius-dflow/openai.developer.md +290 -0
- package/system-prompts/helius-phantom/claude.system.md +348 -0
- package/system-prompts/helius-phantom/full.md +5472 -0
- package/system-prompts/helius-phantom/openai.developer.md +348 -0
- package/system-prompts/svm/claude.system.md +174 -0
- package/system-prompts/svm/full.md +699 -0
- package/system-prompts/svm/openai.developer.md +174 -0
|
@@ -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
|
+
}
|