#
tokens: 16319/50000 18/18 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── claude_desktop_config.json
├── package.json
├── pnpm-lock.yaml
├── README.md
├── src
│   ├── actions
│   │   ├── index.ts
│   │   ├── priorityFee
│   │   │   └── getPriorityFeeEstimate.ts
│   │   ├── security
│   │   │   └── getSecurityTxt.ts
│   │   ├── transaction
│   │   │   └── getTransactionHistory.ts
│   │   └── validator
│   │       └── getValidatorInfo.ts
│   ├── index.ts
│   ├── tests
│   │   ├── test-priority-fee.ts
│   │   └── test-transaction-history.ts
│   ├── tools
│   │   ├── priorityFee.ts
│   │   ├── security.ts
│   │   ├── transaction.ts
│   │   └── validator.ts
│   └── types
│       └── action.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | .env
2 | node_modules
3 | build
4 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Solana Agent Kit MCP Server
 2 | 
 3 | [![npm version](https://badge.fury.io/js/solana-mpc.svg)](https://www.npmjs.com/package/solana-mpc)
 4 | [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
 5 | 
 6 | A Model Context Protocol (MCP) server that provides onchain tools for Claude AI, allowing it to interact with the Solana blockchain through a standardized interface. This implementation is based on the Solana Agent Kit and enables AI agents to perform blockchain operations seamlessly.
 7 | 
 8 | # DEMO VIDEO
 9 | https://www.youtube.com/watch?v=VbfSzFuIzn8
10 | 
11 | # Actions
12 | ### GET_VALIDATOR_INFO
13 | Retrieves detailed information about Solana validators
14 | Shows stake amounts, commission rates, and performance metrics
15 | 
16 | ### GET_PRIORITY_FEE_ESTIMATE
17 | Estimates optimal transaction fees based on current network conditions
18 | Provides different fee tiers (low, medium, high) with expected confirmation times
19 | 
20 | ### GET_TRANSACTION_HISTORY
21 | Fetches transaction history for any Solana wallet or token account
22 | Supports filtering by transaction types and pagination
23 | 
24 | ### GET_SECURITY_TXT
25 | Extracts security contact information from Solana programs
26 | Helps users find proper channels for reporting vulnerabilities
27 | Integrated Functions
28 | Your MCP server also includes core Solana functionality for:
29 | 
30 | Asset management (GET_ASSET, MINT_NFT)
31 | Token operations (DEPLOY_TOKEN, TRANSFER, TRADE)
32 | Network information (GET_TPS, BALANCE)
33 | Utility functions (REQUEST_FUNDS, REGISTER_DOMAIN)
34 | These functions together provide a comprehensive interface for interacting with the Solana blockchain through a unified MCP server.
35 | 
36 | 
```

--------------------------------------------------------------------------------
/src/types/action.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | import { SolanaAgentKit } from "solana-agent-kit";
 3 | 
 4 | export interface Action {
 5 |   name: string;
 6 |   similes: string[];
 7 |   description: string;
 8 |   examples: any[];
 9 |   schema: z.ZodObject<any>;
10 |   handler: (agent: SolanaAgentKit, input: Record<string, any>) => Promise<any>;
11 | }
12 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |     "compilerOptions": {
 3 |       "target": "ES2022",
 4 |       "module": "Node16",
 5 |       "moduleResolution": "Node16",
 6 |       "outDir": "./build",
 7 |       "rootDir": "./src",
 8 |       "strict": true,
 9 |       "esModuleInterop": true,
10 |       "skipLibCheck": true,
11 |       "forceConsistentCasingInFileNames": true
12 |     },
13 |     "include": ["src/**/*"],
14 |     "exclude": ["node_modules"]
15 |   }
```

--------------------------------------------------------------------------------
/claude_desktop_config.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |     "mcpServers": {
 3 |         "agent-kit": {
 4 |             "command": "node",
 5 |             "env" : {
 6 |                 "OPENAI_API_KEY": "optional_openai_api_key_here",
 7 |                 "RPC_URL": "your_rpc_url_here",
 8 |                 "SOLANA_PRIVATE_KEY": "your_private_key_here"
 9 |             },
10 |             "args": [
11 |                 "/absolute/path/to/build/index.js"
12 |             ]
13 |         }
14 |     }
15 | }
16 | 
17 | 
```

--------------------------------------------------------------------------------
/src/actions/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import getValidatorInfoAction from "./validator/getValidatorInfo.js";
 2 | import getPriorityFeeEstimateAction from "./priorityFee/getPriorityFeeEstimate.js";
 3 | import getTransactionHistoryAction from "./transaction/getTransactionHistory.js";
 4 | import getSecurityTxtAction from "./security/getSecurityTxt.js";
 5 | 
 6 | // Export all actions
 7 | export const ACTIONS = {
 8 |   GET_VALIDATOR_INFO_ACTION: getValidatorInfoAction,
 9 |   GET_PRIORITY_FEE_ESTIMATE_ACTION: getPriorityFeeEstimateAction,
10 |   GET_TRANSACTION_HISTORY_ACTION: getTransactionHistoryAction,
11 |   GET_SECURITY_TXT_ACTION: getSecurityTxtAction,
12 | };
13 | 
14 | // Export individual actions for direct imports
15 | export { default as GET_VALIDATOR_INFO_ACTION } from "./validator/getValidatorInfo.js";
16 | export { default as GET_PRIORITY_FEE_ESTIMATE_ACTION } from "./priorityFee/getPriorityFeeEstimate.js";
17 | export { default as GET_TRANSACTION_HISTORY_ACTION } from "./transaction/getTransactionHistory.js";
18 | export { default as GET_SECURITY_TXT_ACTION } from "./security/getSecurityTxt.js";
19 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "solana-mcp-sendai-hackathon",
 3 |   "version": "1.0.0",
 4 |   "description": "A Model Context Protocol server for interacting with the Solana blockchain, powered by the [Solana Agent Kit](https://github.com/sendaifun/solana-agent-kit)",
 5 |   "main": "build/index.js",
 6 |   "type": "module",
 7 |   "bin": {
 8 |     "solana-mcp": "./build/index.js"
 9 |   },
10 |   "scripts": {
11 |     "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
12 |     "start": "node build/index.js",
13 |     "dev": "tsx watch src/index.ts"
14 |   },
15 |   "files": [
16 |     "build"
17 |   ],
18 |   "repository": {
19 |     "type": "git",
20 |     "url": "https://github.com/cryptoleek/solana-mcp-sendai-hackathon.git"
21 |   },
22 |   "keywords": [
23 |     "solana",
24 |     "mcp",
25 |     "solana-agent-kit",
26 |     "solana-mcp"
27 |   ],
28 |   "author": "cryptoleek",
29 |   "license": "MIT",
30 |   "dependencies": {
31 |     "@modelcontextprotocol/sdk": "^1.6.1",
32 |     "@solana/spl-memo": "^0.2.5",
33 |     "@solana/spl-token": "^0.4.13",
34 |     "@solana/web3.js": "^1.98.0",
35 |     "bs58": "^6.0.0",
36 |     "dotenv": "^16.4.7",
37 |     "helius-sdk": "^1.4.2",
38 |     "solana-agent-kit": "1.4.8",
39 |     "zod": "^3.24.2"
40 |   },
41 |   "devDependencies": {
42 |     "@types/node": "^22.13.4",
43 |     "ts-node": "^10.9.2",
44 |     "typescript": "^5.7.3"
45 |   },
46 |   "packageManager": "[email protected]"
47 | }
48 | 
```

--------------------------------------------------------------------------------
/src/tools/validator.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Connection, PublicKey } from "@solana/web3.js";
 2 | 
 3 | export async function getValidatorInfo(validatorPubkey: PublicKey, connection: Connection) {
 4 |   try {
 5 |     // Get the current epoch info
 6 |     const epochInfo = await connection.getEpochInfo();
 7 |     
 8 |     // Get vote accounts to find our validator
 9 |     const voteAccounts = await connection.getVoteAccounts();
10 |     
11 |     // Find the validator in either current or delinquent vote accounts
12 |     const allAccounts = [...voteAccounts.current, ...voteAccounts.delinquent];
13 |     const validatorAccount = allAccounts.find(
14 |       account => account.votePubkey === validatorPubkey.toString()
15 |     );
16 |     
17 |     if (!validatorAccount) {
18 |       throw new Error("Validator not found");
19 |     }
20 |     
21 |     // Get validator's identity account balance
22 |     const balance = await connection.getBalance(new PublicKey(validatorAccount.nodePubkey));
23 |     
24 |     return {
25 |       identity: validatorAccount.nodePubkey,
26 |       vote: validatorAccount.votePubkey,
27 |       commission: validatorAccount.commission,
28 |       activatedStake: validatorAccount.activatedStake,
29 |       epochVoteAccount: validatorAccount.epochVoteAccount,
30 |       epochCredits: validatorAccount.epochCredits,
31 |       delinquent: voteAccounts.delinquent.some(
32 |         account => account.votePubkey === validatorPubkey.toString()
33 |       ),
34 |       lastVote: validatorAccount.lastVote, 
35 |       balance: balance,
36 |       currentEpoch: epochInfo.epoch,
37 |     };
38 |   } catch (error) {
39 |     throw error;
40 |   }
41 | }
42 | 
```

--------------------------------------------------------------------------------
/src/tests/test-transaction-history.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Connection, PublicKey } from '@solana/web3.js';
 2 | import * as dotenv from 'dotenv';
 3 | import { getTransactionHistory } from '../tools/transaction.js';
 4 | 
 5 | dotenv.config();
 6 | 
 7 | async function main() {
 8 |   try {
 9 |     // Validate environment variables
10 |     if (!process.env.RPC_URL) {
11 |       throw new Error('RPC_URL environment variable is required');
12 |     }
13 | 
14 |     // Create a connection to the Solana cluster
15 |     const connection = new Connection(process.env.RPC_URL);
16 |     
17 |     // Print connection info for debugging
18 |     console.log(`Using RPC URL: ${process.env.RPC_URL.substring(0, 30)}...`);
19 |     
20 |     try {
21 |       const version = await connection.getVersion();
22 |       console.log(`Connected to Solana ${version["solana-core"]}`);
23 |     } catch (err) {
24 |       console.log(`Failed to get version: ${err}`);
25 |     }
26 |     
27 |     // Test address - using the provided address
28 |     const testAddress = new PublicKey('DWdBJfMzVXJCB3TMdFzxhTeap6pMQCajamApbqXHbkQ4');
29 |     
30 |     console.log(`Fetching transaction history for address: ${testAddress.toString()}`);
31 |     
32 |     // Test with default options
33 |     console.log('\n--- Test 1: Retrieve all txns ---');
34 |     const defaultResults = await getTransactionHistory(testAddress, connection);
35 |     console.log(`Retrieved ${defaultResults.length} transactions`);
36 |     // Test with options
37 |     console.log('\n--- Test 2: Options (limit: 10, type: TRANSFER) ---');
38 |     const options = {
39 |       limit: 10,
40 |       types: ['TRANSFER']
41 |     };
42 |     const optionResults = await getTransactionHistory(testAddress, connection, options);
43 |     console.log(`Retrieved ${optionResults.length} transactions`);
44 | 
45 |     console.log('--- Printing the first 10 transactions ---');
46 |     for (const txn of optionResults.slice(0, 10)) {
47 |       console.log(txn);
48 |     }
49 |     
50 |     console.log('\nAll tests completed!');
51 |   } catch (error) {
52 |     console.error('Error running tests:', error);
53 |     process.exit(1);
54 |   }
55 | }
56 | 
57 | main();
58 | 
```

--------------------------------------------------------------------------------
/src/actions/validator/getValidatorInfo.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Action } from "../../types/action.js";
 2 | import { SolanaAgentKit } from "solana-agent-kit";
 3 | import { z } from "zod";
 4 | import { PublicKey } from "@solana/web3.js";
 5 | import { getValidatorInfo } from "../../tools/validator.js";
 6 | 
 7 | const getValidatorInfoAction: Action = {
 8 |   name: "GET_VALIDATOR_INFO",
 9 |   similes: [
10 |     "validator status",
11 |     "check validator",
12 |     "validator info",
13 |     "validator details",
14 |     "node information",
15 |   ],
16 |   description:
17 |     "Get detailed information about a Solana validator including stake, commission, and performance",
18 |   examples: [
19 |     [
20 |       {
21 |         input: {
22 |           validatorAddress: "he1iusunGwqrNtafDtLdhsUQDFvo13z9sUa36PauBtk",
23 |         },
24 |         output: {
25 |           status: "success",
26 |           info: {
27 |             identity: "HEL1USMZKAL2odpNBj2oCjffnFGaYwmbGmyewGv1e2TU",
28 |             vote: "he1iusunGwqrNtafDtLdhsUQDFvo13z9sUa36PauBtk",
29 |             commission: 10,
30 |             activatedStake: 1520000000,
31 |             delinquent: false,
32 |             skipRate: 0.0234,
33 |           },
34 |           message: "Successfully retrieved validator information",
35 |         },
36 |         explanation: "Get information about a specific Solana validator",
37 |       },
38 |     ],
39 |   ],
40 |   schema: z.object({
41 |     validatorAddress: z
42 |       .string()
43 |       .describe("The public key of the validator to get information about"),
44 |   }),
45 |   handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
46 |     try {
47 |       const validatorPubkey = new PublicKey(input.validatorAddress);
48 |       const connection = agent.connection;
49 |       
50 |       const validatorInfo = await getValidatorInfo(validatorPubkey, connection);
51 | 
52 |       return {
53 |         status: "success",
54 |         info: validatorInfo,
55 |         message: "Successfully retrieved validator information",
56 |       };
57 |     } catch (error: any) {
58 |       return {
59 |         status: "error",
60 |         message: `Failed to get validator info: ${error.message}`,
61 |       };
62 |     }
63 |   },
64 | };
65 | 
66 | export default getValidatorInfoAction;
67 | 
```

--------------------------------------------------------------------------------
/src/actions/security/getSecurityTxt.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Action } from "../../types/action.js";
 2 | import { SolanaAgentKit } from "solana-agent-kit";
 3 | import { z } from "zod";
 4 | import { PublicKey } from "@solana/web3.js";
 5 | import { getSecurityTxtInfo } from "../../tools/security.js";
 6 | 
 7 | const getSecurityTxtAction: Action = {
 8 |   name: "GET_SECURITY_TXT",
 9 |   similes: [
10 |     "security.txt",
11 |     "security contact",
12 |     "program security",
13 |     "security disclosure",
14 |     "vulnerability reporting",
15 |     "security information",
16 |     "contact maintainers",
17 |     "security policy",
18 |   ],
19 |   description:
20 |     "Extract and display the security.txt file information for a given Solana program, making it easier to contact the program's maintainers with security concerns",
21 |   examples: [
22 |     [
23 |       {
24 |         input: {
25 |           programId: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
26 |         },
27 |         output: {
28 |           status: "success",
29 |           info: {
30 |             contact: "mailto:[email protected]",
31 |             expires: "2023-12-31T23:59:59.000Z",
32 |             encryption: "https://solana.com/pgp-key.txt",
33 |             acknowledgments: "https://solana.com/hall-of-fame",
34 |             preferredLanguages: "en",
35 |           },
36 |           message: "Successfully retrieved security.txt information",
37 |         },
38 |         explanation: "Get security contact information for the Solana Token Program",
39 |       },
40 |     ],
41 |   ],
42 |   schema: z.object({
43 |     programId: z
44 |       .string()
45 |       .describe("The program ID (public key) of the Solana program to inspect"),
46 |   }),
47 |   handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
48 |     try {
49 |       const programPubkey = new PublicKey(input.programId);
50 |       const connection = agent.connection;
51 |       
52 |       const securityInfo = await getSecurityTxtInfo(programPubkey, connection);
53 | 
54 |       // If we couldn't find any security information
55 |       if (Object.keys(securityInfo).length === 0) {
56 |         return {
57 |           status: "warning",
58 |           info: null,
59 |           message: "No security.txt information found for this program",
60 |         };
61 |       }
62 | 
63 |       return {
64 |         status: "success",
65 |         info: securityInfo,
66 |         message: "Successfully retrieved security.txt information",
67 |       };
68 |     } catch (error: any) {
69 |       return {
70 |         status: "error",
71 |         message: `Failed to get security.txt info: ${error.message}`,
72 |       };
73 |     }
74 |   },
75 | };
76 | 
77 | export default getSecurityTxtAction;
78 | 
```

--------------------------------------------------------------------------------
/src/tests/test-priority-fee.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | 
 3 | /**
 4 |  * Simple test script to debug the priority fee estimation tool
 5 |  * Tests both account keys and transaction-based methods
 6 |  */
 7 | 
 8 | import { Connection } from '@solana/web3.js';
 9 | import { 
10 |   estimatePriorityFee, 
11 |   estimatePriorityFeeByAccountKeys, 
12 |   estimatePriorityFeeByTransaction, 
13 |   PriorityFeeEstimate
14 | } from '../tools/priorityFee.js'; 
15 | 
16 | import * as dotenv from "dotenv";
17 | 
18 | dotenv.config();
19 | 
20 | // Set up Solana connection (using a public RPC endpoint)
21 | // const RPC_ENDPOINT = 'https://api.mainnet-beta.solana.com';
22 | // For Helius API, you might want to use your own endpoint with API key
23 | const RPC_ENDPOINT = process.env.RPC_URL;
24 | 
25 | async function testPriorityFeeEstimate() {
26 |   try {
27 |     console.log('=== Testing Priority Fee Estimation ===');
28 |     console.log('Using RPC endpoint:', RPC_ENDPOINT);
29 |     
30 |     // Create Solana connection
31 |     const connection = new Connection(RPC_ENDPOINT || "");
32 |     
33 |     // Test 1: Default method (account keys with default Jupiter account)
34 |     console.log('\n--- Test 1: Default method ---');
35 |     const defaultEstimate = await estimatePriorityFee(connection);
36 |     printEstimate(defaultEstimate);
37 |     
38 |     // Test 2: Account keys method with custom accounts
39 |     console.log('\n--- Test 2: Account keys method with custom accounts ---');
40 |     const customAccountsEstimate = await estimatePriorityFeeByAccountKeys(
41 |       connection,
42 |       [
43 |         'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', // Token program
44 |         'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'  // Associated token program
45 |       ]
46 |     );
47 |     printEstimate(customAccountsEstimate);
48 |     
49 |     // Test 3: Transaction method
50 |     console.log('\n--- Test 3: Transaction method ---');
51 |     const transactionEstimate = await estimatePriorityFeeByTransaction(connection);
52 |     printEstimate(transactionEstimate);
53 |     
54 |     console.log('\n=== All tests completed ===');
55 |   } catch (error: any ) {
56 |     console.error('Failed to run tests:', error);
57 |     if (error.cause) {
58 |       console.error('Error cause:', error.cause);
59 |     }
60 |   }
61 | }
62 | 
63 | /**
64 |  * Helper function to print the fee estimate in a readable format
65 |  */
66 | function printEstimate(estimate: PriorityFeeEstimate) {
67 |   console.log('Priority Fee Estimate:');
68 |   console.log('- Low:', estimate.low, 'microLamports');
69 |   console.log('- Medium:', estimate.medium, 'microLamports');
70 |   console.log('- High:', estimate.high, 'microLamports');
71 |   console.log('- Suggested:', estimate.suggested, 'microLamports');
72 |   console.log('- Time Estimates:', estimate.timeEstimates);
73 |   console.log('- High Load Behavior:', estimate.highLoadBehavior);
74 | }
75 | 
76 | // Run the test
77 | await testPriorityFeeEstimate()
78 |   .catch(err => {
79 |     console.error('Unhandled error:', err);
80 |     process.exit(1);
81 |   });
82 | 
```

--------------------------------------------------------------------------------
/src/tools/transaction.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Connection, PublicKey } from '@solana/web3.js';
  2 | import { parseTransaction } from 'solana-agent-kit/dist/tools/index.js';
  3 | 
  4 | // Interface for transaction history options
  5 | interface TransactionHistoryOptions {
  6 |   limit?: number;
  7 |   before?: string;
  8 |   until?: string;
  9 |   types?: string[];
 10 |   minContextSlot?: number;
 11 | }
 12 | 
 13 | // Interface for transaction data
 14 | interface TransactionData {
 15 |   signature: string;
 16 |   slot: number;
 17 |   timestamp: number;
 18 |   err: any;
 19 |   memo: string | null;
 20 |   blockTime: number;
 21 |   type: string;
 22 |   fee: number;
 23 |   status: string;
 24 | }
 25 | 
 26 | /**
 27 |  * Get transaction history for a Solana wallet address or token account using Helius API
 28 |  * @param publicKey - The public key to get transaction history for
 29 |  * @param connection - Solana connection object (not used with Helius but kept for compatibility)
 30 |  * @param options - Optional parameters for filtering transactions
 31 |  * @returns Array of transaction data
 32 |  */
 33 | export async function getTransactionHistory(
 34 |   publicKey: PublicKey,
 35 |   connection: Connection,
 36 |   options: TransactionHistoryOptions = {}
 37 | ): Promise<TransactionData[]> {
 38 |   try {
 39 |     const address = publicKey.toString();
 40 |     
 41 |     // Helius SDK doesn't have a direct method for transaction history
 42 |     // We'll use the fetchTransactionHistory helper function to make the API call
 43 |     const transactions = await fetchTransactionHistory(connection, address, options);
 44 |     
 45 |     return transactions;
 46 |   } catch (error) {
 47 |     console.error('Error fetching transaction history:', error);
 48 |     throw error;
 49 |   }
 50 | }
 51 | 
 52 | /**
 53 |  * Helper function to fetch transaction history using Helius API directly
 54 |  * @param address - The address to get transaction history for
 55 |  * @param options - Query parameters
 56 |  * @returns Array of transaction data from Helius
 57 |  */
 58 | async function fetchTransactionHistory(
 59 |   connection: Connection,
 60 |   address: string,
 61 |   options: TransactionHistoryOptions = {}
 62 | ): Promise<TransactionData[]> {
 63 |   try {
 64 |     // Build query parameters
 65 |     const queryParams: Record<string, string> = {};
 66 |     
 67 |     if (options.limit) {
 68 |       queryParams.limit = options.limit.toString();
 69 |     }
 70 |     
 71 |     if (options.before) {
 72 |       queryParams.before = options.before;
 73 |     }
 74 |     
 75 |     if (options.until) {
 76 |       queryParams.until = options.until;
 77 |     }
 78 |     
 79 |     if (options.types && options.types.length > 0) {
 80 |       queryParams.type = options.types.join(',');
 81 |     }
 82 |     
 83 |     // Convert params to URL query string
 84 |     const queryString = Object.entries(queryParams)
 85 |       .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
 86 |       .join('&');
 87 | 
 88 |     
 89 |     const rpcUrl = connection.rpcEndpoint;
 90 |     const apiKey = rpcUrl.split('api-key=')[1];   
 91 | 
 92 |     if (!apiKey) {
 93 |       throw new Error('Missing Helius API key');
 94 |     }
 95 |     
 96 |     // Build the URL
 97 |     const url = `https://api.helius.xyz/v0/addresses/${address}/transactions?api-key=${apiKey}${queryString ? `&${queryString}` : ''}`;
 98 |     
 99 |     // Fetch data from Helius API
100 |     const response = await fetch(url);
101 |     
102 |     if (!response.ok) {
103 |       const errorText = await response.text();
104 |       throw new Error(`Helius API error: ${response.status} ${errorText}`);
105 |     }
106 |     
107 |     const data = await response.json();
108 |     
109 |     // Map the response to our expected format
110 |     return data.map((tx: any) => ({
111 |       signature: tx.signature,
112 |       slot: tx.slot,
113 |       timestamp: tx.timestamp,
114 |       err: tx.err,
115 |       memo: tx.memo || null,
116 |       blockTime: tx.timestamp,
117 |       type: tx.type || 'Unknown',
118 |       fee: tx.fee || 0,      
119 |       status: tx.err ? 'Failed' : 'Success'
120 |     }));
121 |   } catch (error) {
122 |     console.error('Error in fetchTransactionHistory:', error);
123 |     throw error;
124 |   }
125 | }
126 | 
127 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | 
 3 | import { ACTIONS as SAK_ACTIONS, SolanaAgentKit, startMcpServer } from "solana-agent-kit";
 4 | import { ACTIONS as CUSTOM_ACTIONS } from "./actions/index.js";
 5 | import * as dotenv from "dotenv";
 6 | 
 7 | dotenv.config();
 8 | 
 9 | // Validate required environment variables
10 | function validateEnvironment() {
11 |     const requiredEnvVars = {
12 |         'SOLANA_PRIVATE_KEY': process.env.SOLANA_PRIVATE_KEY,
13 |         'RPC_URL': process.env.RPC_URL
14 |     };
15 | 
16 |     const missingVars = Object.entries(requiredEnvVars)
17 |         .filter(([_, value]) => !value)
18 |         .map(([key]) => key);
19 | 
20 |     if (missingVars.length > 0) {
21 |         throw new Error(`Missing required environment variables: ${missingVars.join(', ')}`);
22 |     }
23 | }
24 | 
25 | async function main() {
26 |     try {
27 |         // Validate environment before proceeding
28 |         validateEnvironment();
29 | 
30 |         const RPC_URL = process.env.RPC_URL as string
31 |         const HELIUS_API_KEY = RPC_URL.split('api-key=')[1]
32 | 
33 |         // Initialize the agent with error handling
34 |         const agent = new SolanaAgentKit(
35 |             process.env.SOLANA_PRIVATE_KEY!,
36 |             process.env.RPC_URL!,
37 |             {
38 |                 OPENAI_API_KEY: process.env.OPENAI_API_KEY || "",
39 |                 PERPLEXITY_API_KEY: process.env.PERPLEXITY_API_KEY || "",
40 |                 HELIUS_API_KEY: HELIUS_API_KEY || ""
41 |             }
42 |         );
43 | 
44 |         // Debug: Log our custom actions
45 |         console.log("Registering custom actions:");
46 |         console.log("GET_VALIDATOR_INFO_ACTION schema:", CUSTOM_ACTIONS.GET_VALIDATOR_INFO_ACTION.schema);
47 |         console.log("GET_PRIORITY_FEE_ESTIMATE_ACTION schema:", CUSTOM_ACTIONS.GET_PRIORITY_FEE_ESTIMATE_ACTION.schema);
48 |         console.log("GET_TRANSACTION_HISTORY_ACTION schema:", CUSTOM_ACTIONS.GET_TRANSACTION_HISTORY_ACTION.schema);
49 |         console.log("GET_SECURITY_TXT_ACTION schema:", CUSTOM_ACTIONS.GET_SECURITY_TXT_ACTION.schema);
50 | 
51 |         const mcp_actions = {
52 |             GET_ASSET: SAK_ACTIONS.GET_ASSET_ACTION,
53 |             DEPLOY_TOKEN: SAK_ACTIONS.DEPLOY_TOKEN_ACTION,
54 |             FETCH_PRICE: SAK_ACTIONS.FETCH_PRICE_ACTION,
55 |             GET_WALLET_ADDRESS: SAK_ACTIONS.WALLET_ADDRESS_ACTION,
56 |             BALANCE: SAK_ACTIONS.BALANCE_ACTION,
57 |             TRANSFER: SAK_ACTIONS.TRANSFER_ACTION,
58 |             MINT_NFT: SAK_ACTIONS.MINT_NFT_ACTION,
59 |             TRADE: SAK_ACTIONS.TRADE_ACTION,
60 |             REQUEST_FUNDS: SAK_ACTIONS.REQUEST_FUNDS_ACTION,
61 |             REGISTER_DOMAIN: SAK_ACTIONS.RESOLVE_DOMAIN_ACTION,
62 |             GET_TPS: SAK_ACTIONS.GET_TPS_ACTION,
63 |             PARSE_TRANSACTION_ACTION: SAK_ACTIONS.PARSE_TRANSACTION_ACTION,
64 |             // Add our custom actions
65 |             GET_VALIDATOR_INFO: CUSTOM_ACTIONS.GET_VALIDATOR_INFO_ACTION,
66 |             GET_PRIORITY_FEE_ESTIMATE: CUSTOM_ACTIONS.GET_PRIORITY_FEE_ESTIMATE_ACTION,
67 |             GET_TRANSACTION_HISTORY: CUSTOM_ACTIONS.GET_TRANSACTION_HISTORY_ACTION,
68 |             GET_SECURITY_TXT: CUSTOM_ACTIONS.GET_SECURITY_TXT_ACTION,
69 |         };
70 | 
71 |         // Debug: Log all registered actions
72 |         console.log("All registered MCP actions:", Object.keys(mcp_actions));
73 | 
74 |         // Start the MCP server with error handling
75 |         await startMcpServer(mcp_actions, agent, { 
76 |             name: "solana-agent", 
77 |             version: "0.0.1"
78 |         });
79 | 
80 |         // Add console logging for debugging
81 |         console.log("MCP server started successfully");        
82 |     } catch (error) {
83 |         console.error('Failed to start MCP server:', error instanceof Error ? error.message : String(error));
84 |         process.exit(1);
85 |     }
86 | }
87 | 
88 | // Handle uncaught exceptions and rejections
89 | process.on('uncaughtException', (error) => {
90 |     console.error('Uncaught Exception:', error);
91 |     process.exit(1);
92 | });
93 | 
94 | process.on('unhandledRejection', (reason, promise) => {
95 |     console.error('Unhandled Rejection at:', promise, 'reason:', reason);
96 |     process.exit(1);
97 | });
98 | 
99 | main();
```

--------------------------------------------------------------------------------
/src/actions/priorityFee/getPriorityFeeEstimate.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Action } from "../../types/action.js";
  2 | import { SolanaAgentKit } from "solana-agent-kit";
  3 | import { z } from "zod";
  4 | import { estimatePriorityFee } from "../../tools/priorityFee.js";
  5 | 
  6 | const getPriorityFeeEstimateAction: Action = {
  7 |   name: "GET_PRIORITY_FEE_ESTIMATE",
  8 |   similes: [
  9 |     "estimate priority fee",
 10 |     "transaction fee estimate",
 11 |     "optimal priority fee",
 12 |     "solana fee estimate",
 13 |     "transaction cost estimate",
 14 |   ],
 15 |   description:
 16 |     "Estimates optimal priority fees for Solana transactions based on recent network activity",
 17 |   examples: [
 18 |     [
 19 |       {
 20 |         input: {},
 21 |         output: {
 22 |           status: "success",
 23 |           estimate: {
 24 |             low: 1000,
 25 |             medium: 10000,
 26 |             high: 100000,
 27 |             suggested: 12000,
 28 |             timeEstimates: {
 29 |               low: "3-5 seconds",
 30 |               medium: "2-3 seconds",
 31 |               high: "1-2 seconds"
 32 |             }
 33 |           },
 34 |           message: "Successfully estimated priority fees",
 35 |         },
 36 |         explanation: "Get current priority fee estimates for Solana transactions",
 37 |       },
 38 |     ],
 39 |     [
 40 |       {
 41 |         input: { method: "transaction" },
 42 |         output: {
 43 |           status: "success",
 44 |           estimate: {
 45 |             low: 1000,
 46 |             medium: 10000,
 47 |             high: 100000,
 48 |             suggested: 12000,
 49 |             timeEstimates: {
 50 |               low: "3-5 seconds",
 51 |               medium: "2-3 seconds",
 52 |               high: "1-2 seconds"
 53 |             }
 54 |           },
 55 |           message: "Successfully estimated priority fees using transaction method",
 56 |         },
 57 |         explanation: "Get priority fee estimates using the transaction-based method",
 58 |       },
 59 |     ],
 60 |     [
 61 |       {
 62 |         input: { 
 63 |           method: "accountKeys",
 64 |           accountKeys: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"]
 65 |         },
 66 |         output: {
 67 |           status: "success",
 68 |           estimate: {
 69 |             low: 1000,
 70 |             medium: 10000,
 71 |             high: 100000,
 72 |             suggested: 12000,
 73 |             timeEstimates: {
 74 |               low: "3-5 seconds",
 75 |               medium: "2-3 seconds",
 76 |               high: "1-2 seconds"
 77 |             }
 78 |           },
 79 |           message: "Successfully estimated priority fees for specific account keys",
 80 |         },
 81 |         explanation: "Get priority fee estimates for specific account keys",
 82 |       },
 83 |     ],
 84 |   ],
 85 |   // Define schema with optional parameters
 86 |   schema: z.object({
 87 |     method: z.enum(["accountKeys", "transaction"]).optional(),
 88 |     accountKeys: z.array(z.string()).optional(),
 89 |     serializedTransaction: z.string().optional(),
 90 |   }),
 91 |   handler: async (agent: SolanaAgentKit, params: any) => {
 92 |     try {
 93 |       const connection = agent.connection;
 94 |       
 95 |       // Extract parameters
 96 |       const method = params.method;
 97 |       const accountKeys = params.accountKeys;
 98 |       const serializedTransaction = params.serializedTransaction;
 99 |       
100 |       // Call the estimatePriorityFee function with the provided parameters
101 |       const feeEstimate = await estimatePriorityFee(connection, {
102 |         method,
103 |         accountKeys,
104 |         serializedTransaction
105 |       });
106 | 
107 |       // Prepare success message based on the method used
108 |       let message = "Successfully estimated priority fees";
109 |       if (method === "transaction") {
110 |         message = "Successfully estimated priority fees using transaction method";
111 |       } else if (method === "accountKeys" && accountKeys) {
112 |         message = "Successfully estimated priority fees for specific account keys";
113 |       }
114 | 
115 |       return {
116 |         status: "success",
117 |         estimate: feeEstimate,
118 |         message,
119 |       };
120 |     } catch (error: any) {
121 |       return {
122 |         status: "error",
123 |         message: `Failed to estimate priority fees: ${error.message}`,
124 |         error: error.toString(),
125 |         stack: error.stack
126 |       };
127 |     }
128 |   },
129 | };
130 | 
131 | export default getPriorityFeeEstimateAction;
132 | 
```

--------------------------------------------------------------------------------
/src/tools/security.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Connection, PublicKey } from "@solana/web3.js";
  2 | import * as bs58 from "bs58";
  3 | 
  4 | // Interface for security.txt content
  5 | export interface SecurityTxtInfo {
  6 |   contact?: string;
  7 |   expires?: string;
  8 |   encryption?: string;
  9 |   acknowledgments?: string;
 10 |   preferredLanguages?: string;
 11 |   canonical?: string;
 12 |   policy?: string;
 13 |   hiring?: string;
 14 |   // Any additional fields that might be in the security.txt
 15 |   [key: string]: string | undefined;
 16 | }
 17 | 
 18 | /**
 19 |  * Extracts and parses security.txt information from a Solana program
 20 |  * 
 21 |  * @param programId - The PublicKey of the Solana program to inspect
 22 |  * @param connection - The Solana connection object
 23 |  * @returns The parsed security.txt information
 24 |  */
 25 | export async function getSecurityTxtInfo(programId: PublicKey, connection: Connection): Promise<SecurityTxtInfo> {
 26 |   try {
 27 |     // Get the program account data
 28 |     const programAccount = await connection.getAccountInfo(programId);
 29 |     
 30 |     if (!programAccount) {
 31 |       throw new Error("Program account not found");
 32 |     }
 33 |     
 34 |     // Check if this is an upgradeable program
 35 |     const BPF_UPGRADEABLE_LOADER_ID = new PublicKey("BPFLoaderUpgradeab1e11111111111111111111111");
 36 |     
 37 |     let programData: Buffer;
 38 |     
 39 |     if (programAccount.owner.equals(BPF_UPGRADEABLE_LOADER_ID)) {
 40 |       // This is an upgradeable program, we need to find the program data account
 41 |       // The first 4 bytes indicate the account type
 42 |       const accountType = programAccount.data.slice(0, 4);
 43 |       
 44 |       // Check if this is a Program account (type 2)
 45 |       if (accountType[0] === 2) {
 46 |         // Extract the program data address (next 32 bytes)
 47 |         const programDataAddress = new PublicKey(programAccount.data.slice(4, 36));
 48 |         
 49 |         // Get the program data account
 50 |         const programDataAccount = await connection.getAccountInfo(programDataAddress);
 51 |         
 52 |         if (!programDataAccount) {
 53 |           throw new Error("Program data account not found");
 54 |         }
 55 |         
 56 |         // The program data starts after the header (first 8 bytes)
 57 |         // First byte is account type, next 7 bytes are the slot
 58 |         programData = programDataAccount.data.slice(8);
 59 |       } else {
 60 |         throw new Error("Not a valid upgradeable program account");
 61 |       }
 62 |     } else {
 63 |       // For non-upgradeable programs, use the account data directly
 64 |       programData = programAccount.data;
 65 |     }
 66 |     
 67 |     // Now search for security.txt in the program data
 68 |     return findAndParseSecurityTxt(programData);
 69 |   } catch (error) {
 70 |     throw error;
 71 |   }
 72 | }
 73 | 
 74 | /**
 75 |  * Finds and parses security.txt content in program data
 76 |  * 
 77 |  * @param programData - The program binary data
 78 |  * @returns Parsed security.txt information
 79 |  */
 80 | function findAndParseSecurityTxt(programData: Buffer): SecurityTxtInfo {
 81 |   try {
 82 |     // Convert the binary data to a string
 83 |     // Note: This is a simplification, as the security.txt might be encoded in various ways
 84 |     const dataString = new TextDecoder().decode(programData);
 85 |     
 86 |     // Look for security.txt pattern
 87 |     // Pattern 1: Standard security.txt format
 88 |     const securityTxtMatch = dataString.match(/(?:^|\n)# ?security\.txt(?:\n|$)([\s\S]*?)(?:\n\n|\n#|$)/i);
 89 |     
 90 |     if (securityTxtMatch && securityTxtMatch[1]) {
 91 |       return parseSecurityTxt(securityTxtMatch[1]);
 92 |     }
 93 |     
 94 |     // Pattern 2: Look for BEGIN/END SECURITY.TXT markers
 95 |     const securityTxtBlockMatch = dataString.match(/-----BEGIN SECURITY\.TXT-----([\s\S]*?)-----END SECURITY\.TXT-----/i);
 96 |     
 97 |     if (securityTxtBlockMatch && securityTxtBlockMatch[1]) {
 98 |       return parseSecurityTxt(securityTxtBlockMatch[1]);
 99 |     }
100 |     
101 |     // Pattern 3: Look for common security.txt fields
102 |     const fieldMatch = dataString.match(/Contact: ([^\n]+)/i) || 
103 |                        dataString.match(/Security-Contact: ([^\n]+)/i) ||
104 |                        dataString.match(/mailto:([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i);
105 |     
106 |     if (fieldMatch) {
107 |       const result: SecurityTxtInfo = {};
108 |       result.contact = fieldMatch[1].trim();
109 |       return result;
110 |     }
111 |     
112 |     // If we couldn't find any specific security.txt format, try to extract any security-related info
113 |     return extractSecurityInfoFromText(dataString);
114 |   } catch (error) {
115 |     // If we encounter any error during parsing, return an empty object
116 |     console.error("Error parsing security.txt:", error);
117 |     return {};
118 |   }
119 | }
120 | 
121 | /**
122 |  * Parses security.txt content into a structured object
123 |  * 
124 |  * @param content - The raw security.txt content
125 |  * @returns Structured security.txt information
126 |  */
127 | function parseSecurityTxt(content: string): SecurityTxtInfo {
128 |   const result: SecurityTxtInfo = {};
129 |   
130 |   // Split by lines and process each line
131 |   const lines = content.split('\n');
132 |   
133 |   for (const line of lines) {
134 |     // Skip empty lines and comments
135 |     if (!line.trim() || line.trim().startsWith('#')) {
136 |       continue;
137 |     }
138 |     
139 |     // Extract field and value
140 |     const match = line.match(/^([^:]+):\s*(.*)$/);
141 |     if (match) {
142 |       const [_, field, value] = match;
143 |       const fieldName = field.trim().toLowerCase();
144 |       
145 |       // Convert kebab-case to camelCase
146 |       const camelCaseField = fieldName.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
147 |       
148 |       result[camelCaseField] = value.trim();
149 |     }
150 |   }
151 |   
152 |   return result;
153 | }
154 | 
155 | /**
156 |  * Attempts to extract security information from program text when no explicit security.txt is found
157 |  * 
158 |  * @param text - The program text to analyze
159 |  * @returns Any security information found
160 |  */
161 | function extractSecurityInfoFromText(text: string): SecurityTxtInfo {
162 |   const result: SecurityTxtInfo = {};
163 |   
164 |   // Look for common patterns that might indicate security contact info
165 |   const contactMatches = [
166 |     // Email patterns
167 |     ...text.match(/security@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g) || [],
168 |     ...text.match(/mailto:[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g) || [],
169 |     // URL patterns
170 |     ...text.match(/https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\/security/g) || [],
171 |     ...text.match(/https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\/responsible-disclosure/g) || [],
172 |     ...text.match(/https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\/vulnerability/g) || [],
173 |   ];
174 |   
175 |   if (contactMatches.length > 0) {
176 |     result.contact = contactMatches[0];
177 |   }
178 |   
179 |   // Look for PGP key patterns
180 |   const pgpMatches = text.match(/-----BEGIN PGP PUBLIC KEY BLOCK-----([\s\S]*?)-----END PGP PUBLIC KEY BLOCK-----/g);
181 |   if (pgpMatches) {
182 |     result.encryption = "PGP key found in program data";
183 |   }
184 |   
185 |   return result;
186 | }
187 | 
```

--------------------------------------------------------------------------------
/src/actions/transaction/getTransactionHistory.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Action } from "../../types/action.js";
  2 | import { SolanaAgentKit } from "solana-agent-kit";
  3 | import { z } from "zod";
  4 | import { PublicKey } from "@solana/web3.js";
  5 | import { getTransactionHistory } from "../../tools/transaction.js";
  6 | import dotenv from "dotenv";
  7 | 
  8 | // Load environment variables
  9 | dotenv.config();
 10 | 
 11 | const getTransactionHistoryAction: Action = {
 12 |   name: "GET_TRANSACTION_HISTORY",
 13 |   similes: [
 14 |     "transaction history",
 15 |     "account transactions",
 16 |     "transaction list",
 17 |     "tx history",
 18 |     "recent transactions",
 19 |   ],
 20 |   description:
 21 |     "Get transaction history for a Solana wallet address or token account using Helius API",
 22 |   examples: [
 23 |     [
 24 |       {
 25 |         input: { 
 26 |           address: "8zFZHuSRuDpuAR7J6FzwyF3vKNx4CVW3DFHJerQhc7Zd"
 27 |         },
 28 |         output: {
 29 |           status: "success",
 30 |           transactions: [
 31 |             {
 32 |               signature: "5UJpjrKQ8641Q8kPudtvtqR2SgZMz5rSbdpuNP1qy6BxAw4aY3bRfyZQqGKEK5yQXi3yk4pVMRLqzYjnb3bawRn5",
 33 |               slot: 172492080,
 34 |               timestamp: 1678901234,
 35 |               err: null,
 36 |               memo: null,
 37 |               blockTime: 1678901234,
 38 |               type: "System Transfer",
 39 |               fee: 5000,
 40 |               status: "Success"
 41 |             }
 42 |           ],
 43 |           message: "Successfully retrieved transaction history"
 44 |         },
 45 |         explanation: "Get recent transaction history for a specific Solana address",
 46 |       },
 47 |     ],
 48 |     [
 49 |       {
 50 |         input: { 
 51 |           address: "8zFZHuSRuDpuAR7J6FzwyF3vKNx4CVW3DFHJerQhc7Zd",
 52 |           limit: 5
 53 |         },
 54 |         output: {
 55 |           status: "success",
 56 |           transactions: [
 57 |             {
 58 |               signature: "5UJpjrKQ8641Q8kPudtvtqR2SgZMz5rSbdpuNP1qy6BxAw4aY3bRfyZQqGKEK5yQXi3yk4pVMRLqzYjnb3bawRn5",
 59 |               slot: 172492080,
 60 |               timestamp: 1678901234,
 61 |               err: null,
 62 |               memo: null,
 63 |               blockTime: 1678901234,
 64 |               type: "System Transfer",
 65 |               fee: 5000,
 66 |               status: "Success"
 67 |             }
 68 |           ],
 69 |           message: "Successfully retrieved 5 most recent transactions"
 70 |         },
 71 |         explanation: "Get the 5 most recent transactions for a Solana address",
 72 |       },
 73 |     ],
 74 |     [
 75 |       {
 76 |         input: { 
 77 |           address: "8zFZHuSRuDpuAR7J6FzwyF3vKNx4CVW3DFHJerQhc7Zd",
 78 |           before: "5UJpjrKQ8641Q8kPudtvtqR2SgZMz5rSbdpuNP1qy6BxAw4aY3bRfyZQqGKEK5yQXi3yk4pVMRLqzYjnb3bawRn5"
 79 |         },
 80 |         output: {
 81 |           status: "success",
 82 |           transactions: [
 83 |             {
 84 |               signature: "4tSRZ8QVNfUyHuJGZQvJzuUbq3nBpZ9QFNEsrey9mEbY6iN7VDuZtFGBciogSGkAiKwbVL8YgYNJZP1XNqXhRmML",
 85 |               slot: 172492070,
 86 |               timestamp: 1678901200,
 87 |               err: null,
 88 |               memo: null,
 89 |               blockTime: 1678901200,
 90 |               type: "Token Transfer",
 91 |               fee: 5000,
 92 |               status: "Success"
 93 |             }
 94 |           ],
 95 |           message: "Successfully retrieved transaction history before specified signature"
 96 |         },
 97 |         explanation: "Get transaction history before a specific transaction signature",
 98 |       },
 99 |     ],
100 |     [
101 |       {
102 |         input: { 
103 |           address: "8zFZHuSRuDpuAR7J6FzwyF3vKNx4CVW3DFHJerQhc7Zd",
104 |           types: ["NFT_SALE", "NFT_LISTING"]
105 |         },
106 |         output: {
107 |           status: "success",
108 |           transactions: [
109 |             {
110 |               signature: "4tSRZ8QVNfUyHuJGZQvJzuUbq3nBpZ9QFNEsrey9mEbY6iN7VDuZtFGBciogSGkAiKwbVL8YgYNJZP1XNqXhRmML",
111 |               slot: 172492070,
112 |               timestamp: 1678901200,
113 |               err: null,
114 |               memo: null,
115 |               blockTime: 1678901200,
116 |               type: "NFT_SALE",
117 |               fee: 5000,
118 |               status: "Success"
119 |             }
120 |           ],
121 |           message: "Successfully retrieved NFT sales and listings transactions"
122 |         },
123 |         explanation: "Get NFT sales and listings transactions for a Solana address",
124 |       },
125 |     ],
126 |   ],
127 |   // Define schema with optional address and optional parameters
128 |   schema: z.object({
129 |     address: z.string().min(32).max(44).optional(),
130 |     limit: z.number().min(1).max(100).optional(),
131 |     before: z.string().optional(),
132 |     until: z.string().optional(),
133 |     minContextSlot: z.number().optional(),
134 |     types: z.array(z.string()).optional(),
135 |   }),
136 |   handler: async (agent: SolanaAgentKit, params: any) => {
137 |     try {
138 |       // Check if address is provided
139 |       if (!params.address) {
140 |         return {
141 |           status: "input_needed",
142 |           message: "Please provide a Solana wallet address to view transaction history.",
143 |           error: "Missing address parameter"
144 |         };
145 |       }
146 |       
147 |       const connection = agent.connection;
148 |       
149 |       // Extract parameters
150 |       const address = params.address;
151 |       const limit = params.limit || 10;
152 |       const before = params.before;
153 |       const until = params.until;
154 |       const minContextSlot = params.minContextSlot;
155 |       const types = params.types;
156 |       
157 |       // Validate address
158 |       let publicKey: PublicKey;
159 |       try {
160 |         publicKey = new PublicKey(address);
161 |       } catch (error) {
162 |         return {
163 |           status: "error",
164 |           message: "Invalid Solana address provided",
165 |           error: "Invalid public key format"
166 |         };
167 |       }
168 |       
169 |       // Call the getTransactionHistory function with the provided parameters
170 |       const transactions = await getTransactionHistory(publicKey, connection, {
171 |         limit,
172 |         before,
173 |         until,
174 |         minContextSlot,
175 |         types
176 |       });
177 |       
178 |       // Prepare success message based on the parameters used
179 |       let message = "Successfully retrieved transaction history";
180 |       if (limit) {
181 |         message = `Successfully retrieved ${limit} most recent transactions`;
182 |       }
183 |       if (before) {
184 |         message = "Successfully retrieved transaction history before specified signature";
185 |       }
186 |       if (until) {
187 |         message = "Successfully retrieved transaction history until specified signature";
188 |       }
189 |       if (types && types.length > 0) {
190 |         message = `Successfully retrieved ${types.join(', ')} transactions`;
191 |       }
192 |       
193 |       return {
194 |         status: "success",
195 |         transactions: transactions.map(tx => ({
196 |           signature: tx.signature,
197 |           slot: tx.slot,
198 |           timestamp: tx.timestamp,
199 |           err: tx.err,
200 |           memo: tx.memo,
201 |           blockTime: tx.blockTime,
202 |           type: tx.type,
203 |           fee: tx.fee,
204 |           status: tx.status
205 |         })),
206 |         message,
207 |       };
208 |     } catch (error: any) {
209 |       return {
210 |         status: "error",
211 |         message: `Failed to retrieve transaction history: ${error.message}`,
212 |         error: error.toString(),
213 |         stack: error.stack
214 |       };
215 |     }
216 |   },
217 | };
218 | 
219 | export default getTransactionHistoryAction;
220 | 
```

--------------------------------------------------------------------------------
/src/tools/priorityFee.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Connection, ComputeBudgetProgram, Transaction, PublicKey, Keypair } from "@solana/web3.js";
  2 | 
  3 | export interface PriorityFeeEstimate {
  4 |   low: number;
  5 |   medium: number;
  6 |   high: number;
  7 |   veryHigh?: number;
  8 |   min?: number;
  9 |   unsafeMax?: number;
 10 |   suggested: number;
 11 |   timeEstimates: {
 12 |     low: string;
 13 |     medium: string;
 14 |     high: string;
 15 |   };
 16 |   highLoadBehavior?: {
 17 |     low: string;
 18 |     medium: string;
 19 |     high: string;
 20 |   };
 21 |   // Raw Helius API response
 22 |   priorityFeeEstimate?: number;
 23 |   priorityFeeLevels?: {
 24 |     min?: number;
 25 |     low: number;
 26 |     medium: number;
 27 |     high: number;
 28 |     veryHigh?: number;
 29 |     unsafeMax?: number;
 30 |   };
 31 | }
 32 | 
 33 | /**
 34 |  * Estimates priority fees using Helius API via account keys method
 35 |  * @param connection Solana connection object
 36 |  * @param accountKeys Optional array of account keys to use for estimation (defaults to Jupiter account)
 37 |  * @param options Optional configuration for fee estimation
 38 |  * @returns Priority fee estimate with different fee levels
 39 |  */
 40 | export async function estimatePriorityFeeByAccountKeys(
 41 |   connection: Connection,
 42 |   accountKeys: string[] = ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
 43 |   options?: {
 44 |     lookbackSlots?: number;
 45 |     includeVote?: boolean;
 46 |   }
 47 | ): Promise<PriorityFeeEstimate> {
 48 |   try {
 49 |     // Get RPC URL from connection
 50 |     const rpcUrl = connection.rpcEndpoint;
 51 |     
 52 |     // Make request to Helius API for priority fee estimate using account keys
 53 |     const response = await fetch(rpcUrl, {
 54 |       method: "POST",
 55 |       headers: {
 56 |         "Content-Type": "application/json",
 57 |       },
 58 |       body: JSON.stringify({
 59 |         jsonrpc: "2.0",
 60 |         id: 1,
 61 |         method: "getPriorityFeeEstimate",
 62 |         params: [{
 63 |           "accountKeys": accountKeys,
 64 |           "options": {
 65 |             "includeAllPriorityFeeLevels": true,
 66 |             "lookbackSlots": options?.lookbackSlots || 150,
 67 |             "includeVote": options?.includeVote !== undefined ? options.includeVote : true
 68 |           }
 69 |         }]
 70 |       }),
 71 |     });
 72 |     
 73 |     const data = await response.json();
 74 | 
 75 |     console.log(data)
 76 |     
 77 |     if (data.error) {
 78 |       throw new Error(`Helius API error: ${data.error.message}`);
 79 |     }
 80 |     
 81 |     // Extract fee levels from the response
 82 |     const result = data.result;
 83 |     
 84 |     // If using Helius API, the response will have priorityFeeLevels
 85 |     if (result) {
 86 |       const priorityFeeEstimate = result.priorityFeeEstimate || result.priorityFee;
 87 |       const priorityFeeLevels = result.priorityFeeLevels;
 88 |       
 89 |       if (priorityFeeLevels) {
 90 |         const { min, low, medium, high, veryHigh, unsafeMax } = priorityFeeLevels;
 91 |         // Use the recommended fee or calculate a suggested value based on medium
 92 |         const suggested = priorityFeeEstimate || Math.ceil(medium * 1.2);
 93 |         
 94 |         return {
 95 |           low: low || 0,
 96 |           medium: medium || 0,
 97 |           high: high || 0,
 98 |           veryHigh: veryHigh,
 99 |           min: min,
100 |           unsafeMax: unsafeMax,
101 |           suggested,
102 |           timeEstimates: {
103 |             low: "1-2 blocks (~0.8s)",
104 |             medium: "1 block (~0.4s)",
105 |             high: "Usually immediate"
106 |           },
107 |           highLoadBehavior: {
108 |             low: "May be delayed or dropped",
109 |             medium: "More consistent inclusion",
110 |             high: "Very likely first-in"
111 |           },
112 |           // Include the raw Helius API response
113 |           priorityFeeEstimate,
114 |           priorityFeeLevels
115 |         };
116 |       }
117 |     }
118 |     
119 |     // Fallback to default values if the API doesn't return expected format
120 |     return getDefaultPriorityFees();
121 |   } catch (error) {
122 |     console.error("Error fetching priority fee estimate by account keys:", error);
123 |     return getDefaultPriorityFees();
124 |   }
125 | }
126 | 
127 | /**
128 |  * Estimates priority fees using Helius API via serialized transaction method
129 |  * @param connection Solana connection object
130 |  * @param serializedTransaction Base58 encoded serialized transaction (optional)
131 |  * @param options Optional configuration for fee estimation
132 |  * @returns Priority fee estimate with different fee levels
133 |  */
134 | export async function estimatePriorityFeeByTransaction(
135 |   connection: Connection,
136 |   serializedTransaction?: string,
137 |   options?: {
138 |     lookbackSlots?: number;
139 |     includeVote?: boolean;
140 |   }
141 | ): Promise<PriorityFeeEstimate> {
142 |   try {
143 |     // Get RPC URL from connection
144 |     const rpcUrl = connection.rpcEndpoint;
145 |     
146 |     // If no serialized transaction is provided, create a dummy transaction
147 |     let encodedTransaction = serializedTransaction;
148 |     if (!encodedTransaction) {
149 |       try {
150 |         // Since the transaction-based method is having issues with encoding,
151 |         // let's fall back to the account keys method which is more reliable
152 |         console.log("Using account keys method as fallback for transaction method");
153 |         return await estimatePriorityFeeByAccountKeys(connection);
154 |       } catch (txError) {
155 |         console.error("Error creating dummy transaction:", txError);
156 |         // If we can't create a transaction, fall back to account keys method
157 |         return estimatePriorityFeeByAccountKeys(connection);
158 |       }
159 |     }
160 |     
161 |     // If we have a serialized transaction (provided externally), use it
162 |     if (encodedTransaction) {
163 |       // Make request to Helius API for priority fee estimate using serialized transaction
164 |       const response = await fetch(rpcUrl, {
165 |         method: "POST",
166 |         headers: {
167 |           "Content-Type": "application/json",
168 |         },
169 |         body: JSON.stringify({
170 |           jsonrpc: "2.0",
171 |           id: 1,
172 |           method: "getPriorityFeeEstimate",
173 |           params: [{
174 |             "transaction": encodedTransaction,
175 |             "options": {
176 |               "includeAllPriorityFeeLevels": true,
177 |               "lookbackSlots": options?.lookbackSlots || 150,
178 |               "includeVote": options?.includeVote !== undefined ? options.includeVote : true
179 |             }
180 |           }]
181 |         }),
182 |       });
183 |       
184 |       const data = await response.json();
185 |       
186 |       if (data.error) {
187 |         throw new Error(`Helius API error: ${data.error.message}`);
188 |       }
189 |       
190 |       // Extract fee levels from the response
191 |       const result = data.result;
192 |       
193 |       // If using Helius API, the response will have priorityFeeLevels
194 |       if (result) {
195 |         const priorityFeeEstimate = result.priorityFeeEstimate || result.priorityFee;
196 |         const priorityFeeLevels = result.priorityFeeLevels;
197 |         
198 |         if (priorityFeeLevels) {
199 |           const { min, low, medium, high, veryHigh, unsafeMax } = priorityFeeLevels;
200 |           // Use the recommended fee or calculate a suggested value based on medium
201 |           const suggested = priorityFeeEstimate || Math.ceil(medium * 1.2);
202 |           
203 |           return {
204 |             low: low || 0,
205 |             medium: medium || 0,
206 |             high: high || 0,
207 |             veryHigh: veryHigh,
208 |             min: min,
209 |             unsafeMax: unsafeMax,
210 |             suggested,
211 |             timeEstimates: {
212 |               low: "3-5 seconds",
213 |               medium: "2-3 seconds",
214 |               high: "1-2 seconds"
215 |             },
216 |             highLoadBehavior: {
217 |               low: "May be delayed or dropped",
218 |               medium: "More consistent inclusion",
219 |               high: "Very likely first-in"
220 |             },
221 |             // Include the raw Helius API response
222 |             priorityFeeEstimate,
223 |             priorityFeeLevels
224 |           };
225 |         }
226 |       }
227 |     }
228 |     
229 |     // Fallback to default values if the API doesn't return expected format
230 |     return getDefaultPriorityFees();
231 |   } catch (error) {
232 |     console.error("Error fetching priority fee estimate by transaction:", error);
233 |     return getDefaultPriorityFees();
234 |   }
235 | }
236 | 
237 | /**
238 |  * Main function to estimate priority fees using the preferred method
239 |  * @param connection Solana connection object
240 |  * @param options Optional configuration for fee estimation
241 |  * @returns Priority fee estimate with different fee levels
242 |  */
243 | export async function estimatePriorityFee(
244 |   connection: Connection,
245 |   options?: {
246 |     method?: 'accountKeys' | 'transaction';
247 |     accountKeys?: string[];
248 |     serializedTransaction?: string;
249 |     lookbackSlots?: number;
250 |     includeVote?: boolean;
251 |   }
252 | ): Promise<PriorityFeeEstimate> {
253 |   try {
254 |     const method = options?.method || 'accountKeys';
255 |     
256 |     if (method === 'transaction') {
257 |       // If a serialized transaction is provided, use the transaction method
258 |       if (options?.serializedTransaction) {
259 |         return await estimatePriorityFeeByTransaction(connection, options.serializedTransaction, {
260 |           lookbackSlots: options?.lookbackSlots,
261 |           includeVote: options?.includeVote
262 |         });
263 |       } else {
264 |         // Otherwise, fall back to account keys method for reliability
265 |         console.log("No serialized transaction provided, using account keys method");
266 |         return await estimatePriorityFeeByAccountKeys(connection, options?.accountKeys, {
267 |           lookbackSlots: options?.lookbackSlots,
268 |           includeVote: options?.includeVote
269 |         });
270 |       }
271 |     } else {
272 |       return await estimatePriorityFeeByAccountKeys(connection, options?.accountKeys, {
273 |         lookbackSlots: options?.lookbackSlots,
274 |         includeVote: options?.includeVote
275 |       });
276 |     }
277 |   } catch (error) {
278 |     console.error("Error estimating priority fee:", error);
279 |     return getDefaultPriorityFees();
280 |   }
281 | }
282 | 
283 | /**
284 |  * Returns default priority fee values when API calls fail
285 |  */
286 | function getDefaultPriorityFees(): PriorityFeeEstimate {
287 |   return {
288 |     low: 1000,
289 |     medium: 10000,
290 |     high: 100000,
291 |     veryHigh: 150000,
292 |     min: 0,
293 |     unsafeMax: 200000,
294 |     suggested: 10000,
295 |     timeEstimates: {
296 |       low: "1-2 blocks (~0.8s)",
297 |       medium: "1 block (~0.4s)",
298 |       high: "Usually immediate"
299 |     },
300 |     highLoadBehavior: {
301 |       low: "May be delayed or dropped",
302 |       medium: "More consistent inclusion",
303 |       high: "Very likely first-in"
304 |     },
305 |     priorityFeeEstimate: 10000,
306 |     priorityFeeLevels: {
307 |       min: 0,
308 |       low: 1000,
309 |       medium: 10000,
310 |       high: 100000,
311 |       veryHigh: 150000,
312 |       unsafeMax: 200000
313 |     }
314 |   };
315 | }
316 | 
317 | // Helper function to create a ComputeBudgetInstruction with the priority fee
318 | export function createPriorityFeeInstruction(microLamports: number) {
319 |   return ComputeBudgetProgram.setComputeUnitPrice({
320 |     microLamports
321 |   });
322 | }
323 | 
```