helius-mcp 1.3.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.
Files changed (101) hide show
  1. package/CHANGELOG.md +79 -79
  2. package/LICENSE +21 -21
  3. package/README.md +144 -132
  4. package/dist/http.d.ts +1 -1
  5. package/dist/index.js +2 -56
  6. package/dist/results/store.d.ts +8 -0
  7. package/dist/results/store.js +72 -0
  8. package/dist/results/types.d.ts +47 -0
  9. package/dist/results/types.js +1 -0
  10. package/dist/router/action-groups.d.ts +6 -0
  11. package/dist/router/action-groups.js +32 -0
  12. package/dist/router/action-handlers.d.ts +20 -0
  13. package/dist/router/action-handlers.js +125 -0
  14. package/dist/router/actions.d.ts +12 -0
  15. package/dist/router/actions.js +123 -0
  16. package/dist/router/catalog.d.ts +6 -0
  17. package/dist/router/catalog.js +388 -0
  18. package/dist/router/context.d.ts +5 -0
  19. package/dist/router/context.js +10 -0
  20. package/dist/router/dispatch.d.ts +4 -0
  21. package/dist/router/dispatch.js +276 -0
  22. package/dist/router/instructions.d.ts +1 -0
  23. package/dist/router/instructions.js +25 -0
  24. package/dist/router/register.d.ts +2 -0
  25. package/dist/router/register.js +15 -0
  26. package/dist/router/required-params.d.ts +9 -0
  27. package/dist/router/required-params.js +66 -0
  28. package/dist/router/responses.d.ts +29 -0
  29. package/dist/router/responses.js +186 -0
  30. package/dist/router/schemas.d.ts +216 -0
  31. package/dist/router/schemas.js +195 -0
  32. package/dist/router/telemetry.d.ts +27 -0
  33. package/dist/router/telemetry.js +52 -0
  34. package/dist/router/types.d.ts +46 -0
  35. package/dist/router/types.js +1 -0
  36. package/dist/scripts/validate-catalog.d.ts +2 -2
  37. package/dist/scripts/validate-catalog.js +10 -10
  38. package/dist/tools/accounts.js +5 -5
  39. package/dist/tools/assets.js +5 -5
  40. package/dist/tools/auth.js +392 -319
  41. package/dist/tools/config.js +3 -3
  42. package/dist/tools/das-extras.js +6 -6
  43. package/dist/tools/docs.js +55 -41
  44. package/dist/tools/enhanced-websockets.js +13 -13
  45. package/dist/tools/fees.js +3 -3
  46. package/dist/tools/index.d.ts +1 -1
  47. package/dist/tools/index.js +2 -80
  48. package/dist/tools/laserstream.js +20 -23
  49. package/dist/tools/network.js +10 -4
  50. package/dist/tools/plans.d.ts +0 -5
  51. package/dist/tools/plans.js +167 -12
  52. package/dist/tools/product-catalog.d.ts +1 -0
  53. package/dist/tools/product-catalog.js +51 -16
  54. package/dist/tools/recommend.d.ts +0 -1
  55. package/dist/tools/recommend.js +9 -28
  56. package/dist/tools/shared.d.ts +1 -0
  57. package/dist/tools/shared.js +21 -13
  58. package/dist/tools/solana-knowledge.js +23 -7
  59. package/dist/tools/staking.d.ts +2 -0
  60. package/dist/tools/staking.js +268 -0
  61. package/dist/tools/transactions.js +167 -3
  62. package/dist/tools/transfers.js +38 -43
  63. package/dist/tools/wallet.js +27 -16
  64. package/dist/tools/webhooks.js +3 -3
  65. package/dist/tools/zk-compression.d.ts +2 -0
  66. package/dist/tools/zk-compression.js +781 -0
  67. package/dist/utils/config.d.ts +2 -2
  68. package/dist/utils/config.js +68 -6
  69. package/dist/utils/errors.d.ts +10 -1
  70. package/dist/utils/errors.js +46 -12
  71. package/dist/utils/feedback.js +1 -4
  72. package/dist/utils/helius.js +2 -1
  73. package/dist/utils/ows.d.ts +74 -0
  74. package/dist/utils/ows.js +155 -0
  75. package/dist/version.d.ts +1 -1
  76. package/dist/version.js +1 -1
  77. package/package.json +64 -64
  78. package/system-prompts/helius/claude.system.md +200 -170
  79. package/system-prompts/helius/full.md +3212 -2869
  80. package/system-prompts/helius/openai.developer.md +200 -170
  81. package/system-prompts/helius-dflow/claude.system.md +324 -290
  82. package/system-prompts/helius-dflow/full.md +4136 -3648
  83. package/system-prompts/helius-dflow/openai.developer.md +324 -290
  84. package/system-prompts/helius-jupiter/claude.system.md +333 -0
  85. package/system-prompts/helius-jupiter/full.md +5109 -0
  86. package/system-prompts/helius-jupiter/openai.developer.md +333 -0
  87. package/system-prompts/helius-okx/claude.system.md +182 -0
  88. package/system-prompts/helius-okx/full.md +584 -0
  89. package/system-prompts/helius-okx/openai.developer.md +182 -0
  90. package/system-prompts/helius-phantom/claude.system.md +345 -333
  91. package/system-prompts/helius-phantom/full.md +5625 -5473
  92. package/system-prompts/helius-phantom/openai.developer.md +345 -333
  93. package/system-prompts/svm/claude.system.md +159 -159
  94. package/system-prompts/svm/full.md +631 -631
  95. package/system-prompts/svm/openai.developer.md +159 -159
  96. package/dist/scripts/test-htmltotext.d.ts +0 -5
  97. package/dist/scripts/test-htmltotext.js +0 -67
  98. package/dist/scripts/test-solana-knowledge.d.ts +0 -9
  99. package/dist/scripts/test-solana-knowledge.js +0 -272
  100. package/dist/scripts/validate-templates.d.ts +0 -12
  101. package/dist/scripts/validate-templates.js +0 -94
@@ -1,10 +1,11 @@
1
1
  import { z } from 'zod';
2
- import { createKeyPairSignerFromBytes, address } from '@solana/kit';
2
+ import { address } from '@solana/kit';
3
3
  import { getTransferSolInstruction } from '@solana-program/system';
4
4
  import { findAssociatedTokenPda, getCloseAccountInstruction, getCreateAssociatedTokenIdempotentInstruction, getTransferCheckedInstruction, TOKEN_PROGRAM_ADDRESS, } from '@solana-program/token';
5
- import { getHeliusClient, hasApiKey, loadSignerOrFail } from '../utils/helius.js';
5
+ import { getHeliusClient, hasApiKey } from '../utils/helius.js';
6
6
  import { mcpText, mcpError, handleToolError, isValidAddressFormat } from '../utils/errors.js';
7
7
  import { noApiKeyResponse } from './shared.js';
8
+ import { resolveOwsOrKeypairSigner } from '../utils/ows.js';
8
9
  const TOKEN_2022_PROGRAM_ID = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
9
10
  // ── Tool Registration ──
10
11
  export function registerTransferTools(server) {
@@ -19,24 +20,22 @@ export function registerTransferTools(server) {
19
20
  recipientAddress: z.string().describe('Recipient Solana wallet address (base58 encoded)'),
20
21
  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
22
  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
+ owsWallet: z.string().optional().describe('OWS wallet name for policy-gated signing (requires `ows` CLI installed). When provided, signs via Open Wallet Standard instead of the local keypair.'),
24
+ }, async ({ recipientAddress, amount, sendMax, owsWallet }) => {
23
25
  if (!hasApiKey())
24
26
  return noApiKeyResponse();
25
27
  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
- }
28
+ // Load signer — OWS wallet or local keypair
29
+ const resolved = await resolveOwsOrKeypairSigner(owsWallet);
30
+ if (!resolved.ok)
31
+ return resolved.error;
32
+ const { signer, walletAddress } = resolved;
34
33
  // Validate recipient address
35
34
  if (!isValidAddressFormat(recipientAddress)) {
36
- return mcpError(`Invalid recipient address "${recipientAddress}". Expected a valid Solana address (32-44 base58 characters).`);
35
+ return mcpError(`Invalid recipient address "${recipientAddress}". Expected a valid Solana address (32-44 base58 characters).`, { type: 'VALIDATION', code: 'INVALID_ADDRESS', retryable: false, recovery: 'Provide a valid base58-encoded Solana address (32-44 characters).' });
37
36
  }
38
37
  const helius = getHeliusClient();
39
- const balanceResult = await helius.getBalance(signerData.walletAddress);
38
+ const balanceResult = await helius.getBalance(walletAddress);
40
39
  const balanceLamports = BigInt(balanceResult.value);
41
40
  let lamports;
42
41
  let sendAmount;
@@ -56,13 +55,13 @@ export function registerTransferTools(server) {
56
55
  if (lamports <= 0n) {
57
56
  const available = Number(balanceLamports) / 1_000_000_000;
58
57
  return mcpError(`Balance too low to cover the transaction fee. You have ${available} SOL.\n\n` +
59
- `Wallet: \`${signerData.walletAddress}\``);
58
+ `Wallet: \`${walletAddress}\``, { type: 'INSUFFICIENT_FUNDS', code: 'LOW_SOL', retryable: false, recovery: `Fund the wallet with more SOL. Current balance: ${available} SOL.` });
60
59
  }
61
60
  sendAmount = Number(lamports) / 1_000_000_000;
62
61
  }
63
62
  else {
64
63
  if (amount === undefined) {
65
- return mcpError('Either `amount` must be specified or `sendMax` must be true.');
64
+ return mcpError('Either `amount` must be specified or `sendMax` must be true.', { type: 'VALIDATION', code: 'MISSING_PARAM', retryable: false, recovery: 'Provide `amount` or set `sendMax` to true.' });
66
65
  }
67
66
  lamports = BigInt(Math.round(amount * 1_000_000_000));
68
67
  sendAmount = amount;
@@ -71,11 +70,9 @@ export function registerTransferTools(server) {
71
70
  if (balanceLamports < lamports + reserveLamports) {
72
71
  const available = Number(balanceLamports) / 1_000_000_000;
73
72
  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}\``);
73
+ `Wallet: \`${walletAddress}\``, { type: 'INSUFFICIENT_FUNDS', code: 'LOW_SOL', retryable: false, recovery: `Fund the wallet with at least ${sendAmount + 0.005} SOL.` });
75
74
  }
76
75
  }
77
- // Create signer from keypair bytes
78
- const signer = await createKeyPairSignerFromBytes(signerData.secretKey);
79
76
  // Build transfer instruction
80
77
  const ix = getTransferSolInstruction({
81
78
  source: signer,
@@ -100,8 +97,9 @@ export function registerTransferTools(server) {
100
97
  region: 'Default',
101
98
  });
102
99
  }
100
+ const signerLabel = owsWallet ? `\`${walletAddress}\` (OWS: ${owsWallet})` : `\`${walletAddress}\``;
103
101
  return mcpText(`**SOL Transfer Sent**\n\n` +
104
- `- **From:** \`${signerData.walletAddress}\`\n` +
102
+ `- **From:** ${signerLabel}\n` +
105
103
  `- **To:** \`${recipientAddress}\`\n` +
106
104
  `- **Amount:** ${sendAmount} SOL${sendMax ? ' (max)' : ''}\n` +
107
105
  `- **Signature:** \`${signature}\`\n` +
@@ -124,24 +122,22 @@ export function registerTransferTools(server) {
124
122
  mintAddress: z.string().describe('Token mint address (base58 encoded, e.g., EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v for USDC)'),
125
123
  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
124
  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 }) => {
125
+ owsWallet: z.string().optional().describe('OWS wallet name for policy-gated signing (requires `ows` CLI installed). When provided, signs via Open Wallet Standard instead of the local keypair.'),
126
+ }, async ({ recipientAddress, mintAddress, amount, sendMax, owsWallet }) => {
128
127
  if (!hasApiKey())
129
128
  return noApiKeyResponse();
130
129
  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
- }
130
+ // Load signer — OWS wallet or local keypair
131
+ const resolved = await resolveOwsOrKeypairSigner(owsWallet);
132
+ if (!resolved.ok)
133
+ return resolved.error;
134
+ const { signer, walletAddress } = resolved;
139
135
  // Validate addresses
140
136
  if (!isValidAddressFormat(recipientAddress)) {
141
- return mcpError(`Invalid recipient address "${recipientAddress}". Expected a valid Solana address (32-44 base58 characters).`);
137
+ return mcpError(`Invalid recipient address "${recipientAddress}". Expected a valid Solana address (32-44 base58 characters).`, { type: 'VALIDATION', code: 'INVALID_ADDRESS', retryable: false, recovery: 'Provide a valid base58-encoded Solana address (32-44 characters).' });
142
138
  }
143
139
  if (!isValidAddressFormat(mintAddress)) {
144
- return mcpError(`Invalid mint address "${mintAddress}". Expected a valid Solana address (32-44 base58 characters).`);
140
+ return mcpError(`Invalid mint address "${mintAddress}". Expected a valid Solana address (32-44 base58 characters).`, { type: 'VALIDATION', code: 'INVALID_ADDRESS', retryable: false, recovery: 'Provide a valid base58-encoded token mint address (32-44 characters).' });
145
141
  }
146
142
  // Fetch token metadata via DAS to get decimals and token program
147
143
  const helius = getHeliusClient();
@@ -150,10 +146,10 @@ export function registerTransferTools(server) {
150
146
  asset = await helius.getAsset({ id: mintAddress });
151
147
  }
152
148
  catch {
153
- return mcpError(`Could not fetch token metadata for mint \`${mintAddress}\`. Verify the mint address is correct.`);
149
+ return mcpError(`Could not fetch token metadata for mint \`${mintAddress}\`. Verify the mint address is correct.`, { type: 'NOT_FOUND', code: 'RESOURCE_NOT_FOUND', retryable: false, recovery: 'Verify the mint address is correct. Use `getAsset` to check if the mint exists.' });
154
150
  }
155
151
  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.`);
152
+ return mcpError(`Mint \`${mintAddress}\` does not appear to be a fungible token (no decimals info found). Use \`getAsset\` to inspect it.`, { type: 'NOT_FOUND', code: 'RESOURCE_NOT_FOUND', retryable: false, recovery: 'This mint may not be a fungible token. Use `getAsset` to inspect it.' });
157
153
  }
158
154
  const decimals = asset.token_info.decimals;
159
155
  const tokenProgram = asset.token_info.token_program;
@@ -161,10 +157,8 @@ export function registerTransferTools(server) {
161
157
  const tokenSymbol = asset.content?.metadata?.symbol || '';
162
158
  // Token-2022 check
163
159
  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.`);
160
+ 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.`, { type: 'UNSUPPORTED', code: 'TOKEN_2022', retryable: false, recovery: 'Token-2022 transfers are not yet supported. Only standard SPL Token transfers are available.' });
165
161
  }
166
- // Create signer from keypair bytes
167
- const signer = await createKeyPairSignerFromBytes(signerData.secretKey);
168
162
  const mint = address(mintAddress);
169
163
  const recipient = address(recipientAddress);
170
164
  // Derive ATAs
@@ -188,20 +182,20 @@ export function registerTransferTools(server) {
188
182
  }
189
183
  catch {
190
184
  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}\``);
185
+ `Wallet: \`${walletAddress}\`\n` +
186
+ `Mint: \`${mintAddress}\``, { type: 'INSUFFICIENT_FUNDS', code: 'LOW_TOKEN', retryable: false, recovery: `You may not hold ${tokenSymbol || tokenName}. Check your token balances with getTokenBalances.` });
193
187
  }
194
188
  rawAmount = BigInt(tokenBalance.value.amount);
195
189
  if (rawAmount === 0n) {
196
190
  return mcpError(`${tokenSymbol || tokenName} balance is 0. Nothing to send.\n\n` +
197
- `Wallet: \`${signerData.walletAddress}\`\n` +
198
- `Mint: \`${mintAddress}\``);
191
+ `Wallet: \`${walletAddress}\`\n` +
192
+ `Mint: \`${mintAddress}\``, { type: 'INSUFFICIENT_FUNDS', code: 'ZERO_BALANCE', retryable: false, recovery: `${tokenSymbol || tokenName} balance is zero. Fund the wallet with tokens before sending.` });
199
193
  }
200
194
  sendAmount = Number(rawAmount) / 10 ** decimals;
201
195
  }
202
196
  else {
203
197
  if (amount === undefined) {
204
- return mcpError('Either `amount` must be specified or `sendMax` must be true.');
198
+ return mcpError('Either `amount` must be specified or `sendMax` must be true.', { type: 'VALIDATION', code: 'MISSING_PARAM', retryable: false, recovery: 'Provide `amount` or set `sendMax` to true.' });
205
199
  }
206
200
  rawAmount = BigInt(Math.round(amount * 10 ** decimals));
207
201
  sendAmount = amount;
@@ -212,8 +206,8 @@ export function registerTransferTools(server) {
212
206
  if (currentRaw < rawAmount) {
213
207
  const currentHuman = Number(currentRaw) / 10 ** decimals;
214
208
  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}\``);
209
+ `Wallet: \`${walletAddress}\`\n` +
210
+ `Mint: \`${mintAddress}\``, { type: 'INSUFFICIENT_FUNDS', code: 'LOW_TOKEN', retryable: false, recovery: `You have ${currentHuman} ${tokenSymbol || tokenName} but need ${sendAmount}. Fund the wallet with more tokens.` });
217
211
  }
218
212
  }
219
213
  catch {
@@ -255,8 +249,9 @@ export function registerTransferTools(server) {
255
249
  const displayName = tokenSymbol
256
250
  ? `${sendAmount} ${tokenSymbol} (${tokenName})`
257
251
  : `${sendAmount} ${tokenName}`;
252
+ const signerLabel = owsWallet ? `\`${walletAddress}\` (OWS: ${owsWallet})` : `\`${walletAddress}\``;
258
253
  return mcpText(`**Token Transfer Sent**\n\n` +
259
- `- **From:** \`${signerData.walletAddress}\`\n` +
254
+ `- **From:** ${signerLabel}\n` +
260
255
  `- **To:** \`${recipientAddress}\`\n` +
261
256
  `- **Amount:** ${displayName}${sendMax ? ' (max)' : ''}\n` +
262
257
  `- **Mint:** \`${mintAddress}\`\n` +
@@ -1,20 +1,23 @@
1
1
  import { z } from 'zod';
2
2
  import { getHeliusClient, hasApiKey } from '../utils/helius.js';
3
- import { formatAddress, formatSol, formatTimestamp } from '../utils/formatters.js';
3
+ import { formatAddress, formatTimestamp } from '../utils/formatters.js';
4
4
  import { noApiKeyResponse } from './shared.js';
5
5
  import { mcpText, mcpError, validateEnum, handleToolError, http404Error, addressError } from '../utils/errors.js';
6
6
  export function registerWalletTools(server) {
7
7
  // ─── Get Wallet Identity ───
8
- server.tool('getWalletIdentity', 'BEST FOR: identifying a single known wallet. PREFER batchWalletIdentity for multiple addresses. Identify known wallets (exchanges, protocols, institutions). Returns name, type, category, tags. Credit cost: 100 credits.', {
9
- address: z.string().describe('Solana wallet address (base58 encoded)')
8
+ server.tool('getWalletIdentity', 'BEST FOR: identifying a single known wallet. PREFER batchWalletIdentity for multiple addresses. Identify known wallets (exchanges, protocols, institutions). Returns name, type, category, tags. Also accepts SNS/ANS domains (e.g., `toly.sol`, `helius.bonk`) — mainnet only. Credit cost: 100 credits.', {
9
+ address: z.string().describe('Solana wallet address (base58) or SNS/ANS domain (e.g., toly.sol, helius.bonk — mainnet only)')
10
10
  }, async ({ address }) => {
11
11
  if (!hasApiKey())
12
12
  return noApiKeyResponse();
13
13
  try {
14
14
  const client = getHeliusClient();
15
+ // TODO: drop `any` once helius-sdk types include inputDomain on the identity response
15
16
  const data = await client.wallet.getIdentity({ wallet: address });
16
17
  const lines = ['**Wallet Identity**', ''];
17
- lines.push(`**Address:** ${formatAddress(address)}`);
18
+ if (data.inputDomain)
19
+ lines.push(`**Input:** ${data.inputDomain}`);
20
+ lines.push(`**Address:** ${formatAddress(data.address || address)}`);
18
21
  if (data.name)
19
22
  lines.push(`**Name:** ${data.name}`);
20
23
  if (data.type)
@@ -32,25 +35,33 @@ export function registerWalletTools(server) {
32
35
  }
33
36
  });
34
37
  // ─── Batch Wallet Identity ───
35
- server.tool('batchWalletIdentity', 'BEST FOR: identifying multiple wallets at once (up to 100). PREFER getWalletIdentity for a single address. Look up identities for up to 100 Solana addresses. Returns names, types, and categories. Credit cost: 100 credits.', {
36
- addresses: z.array(z.string()).describe('Array of Solana wallet addresses (base58 encoded, max 100)')
37
- }, async ({ addresses }) => {
38
+ server.tool('batchWalletIdentity', 'BEST FOR: identifying multiple wallets at once (up to 100). PREFER getWalletIdentity for a single address. Look up identities for up to 100 entries. Returns names, types, and categories. Entries may be addresses or SNS/ANS domains (mainnet only); unresolved domains are returned with `unresolved: true`. Credit cost: 100 credits.', {
39
+ entries: z.array(z.string()).describe('Array of up to 100 entries — each a base58 address or SNS/ANS domain')
40
+ }, async ({ entries }) => {
38
41
  if (!hasApiKey())
39
42
  return noApiKeyResponse();
40
- if (addresses.length > 100) {
41
- return mcpError('Error: Maximum 100 addresses per batch request.');
43
+ if (entries.length > 100) {
44
+ return mcpError('Maximum 100 entries per batch request.', { type: 'VALIDATION', code: 'TOO_MANY_ITEMS', retryable: false, recovery: 'Reduce batch to 100 or fewer entries.' });
42
45
  }
43
46
  try {
44
47
  const client = getHeliusClient();
45
- const results = await client.wallet.getBatchIdentity({ addresses });
48
+ const results = await client.wallet.getBatchIdentity({ addresses: entries });
46
49
  if (results.length === 0) {
47
- return mcpText(`**Batch Identity Lookup** (${addresses.length} addresses)\n\nNo identities found.`);
50
+ return mcpText(`**Batch Identity Lookup** (${entries.length} entries)\n\nNo identities found.`);
48
51
  }
49
52
  const lines = [`**Batch Identity Lookup** (${results.length} results)`, ''];
53
+ // TODO: drop `Array<any>` once helius-sdk types include inputDomain/unresolved on the batch response
50
54
  for (const entry of results) {
55
+ if (entry.unresolved) {
56
+ lines.push(`- \`${entry.inputDomain || '?'}\` — Unresolved domain`);
57
+ continue;
58
+ }
51
59
  const addr = formatAddress(entry.address || '');
60
+ const subject = entry.inputDomain && entry.address
61
+ ? `\`${entry.inputDomain}\` → ${addr}`
62
+ : addr;
52
63
  if (entry.name) {
53
- lines.push(`- **${entry.name}** — ${addr}`);
64
+ lines.push(`- **${entry.name}** — ${subject}`);
54
65
  const details = [];
55
66
  if (entry.type)
56
67
  details.push(entry.type);
@@ -60,14 +71,14 @@ export function registerWalletTools(server) {
60
71
  lines.push(` ${details.join(' | ')}`);
61
72
  }
62
73
  else {
63
- lines.push(`- ${addr} — Unknown`);
74
+ lines.push(`- ${subject} — Unknown`);
64
75
  }
65
76
  }
66
77
  return mcpText(lines.join('\n'));
67
78
  }
68
79
  catch (err) {
69
80
  return handleToolError(err, 'Error fetching batch identities', [
70
- addressError('Batch Identity'),
81
+ addressError('Batch Identity', 'Entries must be base58 addresses or SNS/ANS domains.'),
71
82
  ]);
72
83
  }
73
84
  });
@@ -164,7 +175,7 @@ export function registerWalletTools(server) {
164
175
  transactions.forEach((tx, i) => {
165
176
  const time = tx.timestamp ? formatTimestamp(tx.timestamp) : 'N/A';
166
177
  const status = tx.error ? 'Failed' : 'Success';
167
- const fee = tx.fee ? formatSol(tx.fee) : 'N/A';
178
+ const fee = tx.fee != null ? `${tx.fee} SOL` : 'N/A';
168
179
  lines.push(`${i + 1}. ${time} — ${status} — Fee: ${fee}`);
169
180
  lines.push(` Signature: \`${tx.signature}\``);
170
181
  // Balance changes
@@ -252,7 +263,7 @@ export function registerWalletTools(server) {
252
263
  if (data.funderType)
253
264
  lines.push(`**Type:** ${data.funderType}`);
254
265
  if (data.amount !== undefined) {
255
- lines.push(`**Amount:** ${formatSol(data.amount)}`);
266
+ lines.push(`**Amount:** ${data.amount} SOL`);
256
267
  }
257
268
  if (data.date)
258
269
  lines.push(`**Date:** ${data.date}`);
@@ -3,7 +3,7 @@ import { getHeliusClient, hasApiKey } from '../utils/helius.js';
3
3
  import { formatAddress } from '../utils/formatters.js';
4
4
  import { noApiKeyResponse } from './shared.js';
5
5
  import { TRANSACTION_TYPES } from '../types/transaction-types.js';
6
- import { mcpText, validateEnum, handleToolError, http404Error, http400Error } from '../utils/errors.js';
6
+ import { mcpText, mcpError, validateEnum, handleToolError, http404Error, http400Error } from '../utils/errors.js';
7
7
  export function registerWebhookTools(server) {
8
8
  server.tool('getAllWebhooks', 'List all active webhooks for your Helius account. Shows webhook IDs, URLs, and monitored addresses. Credit cost: 100 credits/call (management operation).', {}, async () => {
9
9
  if (!hasApiKey())
@@ -77,7 +77,7 @@ export function registerWebhookTools(server) {
77
77
  if (transactionTypes) {
78
78
  const invalid = transactionTypes.filter(t => !TRANSACTION_TYPES.includes(t));
79
79
  if (invalid.length > 0) {
80
- return mcpText(`**Create Webhook Error**\n\nInvalid transaction type(s): ${invalid.join(', ')}. See valid types: https://www.helius.dev/docs/webhooks/transaction-types`);
80
+ return mcpError(`**Create Webhook Error**\n\nInvalid transaction type(s): ${invalid.join(', ')}. See valid types: https://www.helius.dev/docs/webhooks/transaction-types`, { type: 'VALIDATION', code: 'INVALID_ENUM', retryable: false, recovery: `Remove invalid transaction types: ${invalid.join(', ')}. See https://www.helius.dev/docs/webhooks/transaction-types for valid types.` });
81
81
  }
82
82
  }
83
83
  try {
@@ -113,7 +113,7 @@ export function registerWebhookTools(server) {
113
113
  if (transactionTypes) {
114
114
  const invalid = transactionTypes.filter(t => !TRANSACTION_TYPES.includes(t));
115
115
  if (invalid.length > 0) {
116
- return mcpText(`**Update Webhook Error**\n\nInvalid transaction type(s): ${invalid.join(', ')}. See valid types: https://www.helius.dev/docs/webhooks/transaction-types`);
116
+ return mcpError(`**Update Webhook Error**\n\nInvalid transaction type(s): ${invalid.join(', ')}. See valid types: https://www.helius.dev/docs/webhooks/transaction-types`, { type: 'VALIDATION', code: 'INVALID_ENUM', retryable: false, recovery: `Remove invalid transaction types: ${invalid.join(', ')}. See https://www.helius.dev/docs/webhooks/transaction-types for valid types.` });
117
117
  }
118
118
  }
119
119
  try {
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerZkCompressionTools(server: McpServer): void;