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

```
├── .env.example
├── .gitignore
├── deno.json
├── deno.lock
├── Dockerfile
├── main_test.ts
├── main.ts
├── README.md
└── tool
    ├── toolHandler.ts
    └── toolInfo.ts
```

# Files

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

```
1 | .env
```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
1 | ACCESS_KEY=xxx
```

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

```markdown
  1 | # DeBanK MCP Server
  2 | 
  3 | A stateless Model Context Protocol (MCP) server for interacting with the DeBanK API to retrieve blockchain and DeFi data.
  4 | 
  5 | ## Overview
  6 | 
  7 | This project implements a Model Context Protocol (MCP) server that provides various tools for querying blockchain data, including chains, protocols, tokens, pools, and user assets. Built with the [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/mcp), it leverages the HTTP Streamable transport to provide a modern, efficient API interface.
  8 | 
  9 | The server is designed to be completely stateless, with each request being handled independently, making it highly scalable and robust for production environments.
 10 | 
 11 | ## Features
 12 | 
 13 | - **Stateless Architecture**: Each request creates a new server instance and transport
 14 | - **Comprehensive DeFi Data Tools**: Access to chains, protocols, tokens, pools, and user data
 15 | - **Pagination Support**: All list-returning endpoints support pagination
 16 | - **Error Handling**: Robust error handling and reporting
 17 | 
 18 | ## Tools Available
 19 | 
 20 | | Tool Name | Description |
 21 | |-----------|-------------|
 22 | | `get_chain_info` | Get information about blockchains |
 23 | | `get_protocol_info` | Get information about DeFi protocols |
 24 | | `get_token_info` | Get information about tokens |
 25 | | `get_pool_info` | Get detailed information about a specific liquidity pool |
 26 | | `get_user_assets` | Get information about a user's assets across different blockchains |
 27 | | `get_user_activities` | Get information about a user's protocol positions, transaction history, and balance charts |
 28 | | `get_user_authorizations` | Get information about a user's token and NFT authorizations |
 29 | | `get_collection_nft_list` | Get a list of NFTs in a specific collection |
 30 | | `wallet_tools` | Access wallet-related functionality |
 31 | 
 32 | ## Prerequisites
 33 | 
 34 | - [Deno](https://deno.land/) 1.35 or later
 35 | - DeBanK API Access Key - [Get one here](https://pro.debank.com/)
 36 | 
 37 | ## Installation
 38 | 
 39 | 1. Clone the repository
 40 | ```bash
 41 | git clone https://github.com/yourusername/debank-mcp-server.git
 42 | cd debank-mcp-server
 43 | ```
 44 | 
 45 | 2. Set up your environment variables
 46 | ```bash
 47 | export ACCESS_KEY=your_debank_api_key
 48 | ```
 49 | 
 50 | ## Running the Server
 51 | 
 52 | Start the server with the following command:
 53 | 
 54 | ```bash
 55 | deno run --allow-net --allow-env main.ts
 56 | ```
 57 | 
 58 | The server will start and listen on port 8080 by default. You can now send MCP requests to `http://localhost:8080/mcp`.
 59 | 
 60 | ## MCP HTTP Streamable Implementation
 61 | 
 62 | This project uses the StreamableHTTPServerTransport from the Model Context Protocol SDK to handle MCP requests. Every request creates a new server instance and transport, making the service completely stateless:
 63 | 
 64 | ```typescript
 65 | // Create new server instance and transport for each request
 66 | const server = createServer();
 67 | const transport = new StreamableHTTPServerTransport({
 68 |   sessionIdGenerator: () => randomUUID(),
 69 | });
 70 | 
 71 | // Connect to server
 72 | await server.connect(transport);
 73 | 
 74 | // Handle request
 75 | await transport.handleRequest(req, res, req.body);
 76 | ```
 77 | 
 78 | This approach simplifies deployment and scaling, as there's no need to manage session state across multiple instances.
 79 | 
 80 | ## Project Structure
 81 | 
 82 | ```
 83 | ├── main.ts                # Main server file with MCP endpoint handling
 84 | ├── deno.json              # Deno configuration
 85 | ├── deno.lock              # Dependency lock file
 86 | ├── tool/
 87 | │   ├── toolInfo.ts        # Tool definitions
 88 | │   └── toolHandler.ts     # Tool handler implementations
 89 | └── README.md              # This file
 90 | ```
 91 | 
 92 | ## Configuration
 93 | 
 94 | The following environment variables can be configured:
 95 | 
 96 | - `ACCESS_KEY` - Your DeBanK API access key
 97 | - `PORT` - (Optional) Port to run the server on (default: 8080)
 98 | 
 99 | ## Contributing
100 | 
101 | Contributions are welcome! Please feel free to submit a Pull Request.
102 | 
103 | ## License
104 | 
105 | This project is licensed under the MIT License - see the LICENSE file for details.
106 | 
107 | ## Acknowledgments
108 | 
109 | - [Model Context Protocol](https://github.com/modelcontextprotocol/mcp)
110 | - [DeBanK API](https://pro.debank.com/)
111 | 
```

--------------------------------------------------------------------------------
/main_test.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { assertEquals } from "@std/assert";
2 | import { add } from "./main.ts";
3 | 
4 | Deno.test(function addTest() {
5 |   assertEquals(add(2, 3), 5);
6 | });
7 | 
```

--------------------------------------------------------------------------------
/deno.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "tasks": {
 3 |     "dev": "deno run --watch main.ts",
 4 |     "start": "deno run main.ts"
 5 |   },
 6 |   "imports": {
 7 |     "@modelcontextprotocol/sdk": "npm:@modelcontextprotocol/sdk@^1.10.2",
 8 |     "@std/assert": "jsr:@std/assert@1"
 9 |   }
10 | }
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | FROM denoland/deno:1.41.3
 2 | 
 3 | # Set working directory
 4 | WORKDIR /app
 5 | 
 6 | # Copy dependency configuration files
 7 | COPY deno.json deno.lock ./
 8 | 
 9 | # Copy source code
10 | COPY . .
11 | 
12 | # Cache dependencies
13 | RUN deno cache main.ts
14 | 
15 | # Expose port (port 8080 is used in main.ts)
16 | EXPOSE 8080
17 | 
18 | # Configure health check
19 | HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
20 |   CMD deno eval "try { await fetch('http://localhost:8080/mcp'); Deno.exit(0); } catch { Deno.exit(1); }"
21 | 
22 | # Run application
23 | CMD ["deno", "run", "--allow-net", "--allow-env", "--allow-read", "main.ts"]
24 | 
```

--------------------------------------------------------------------------------
/main.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // debank.ts
  2 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  3 | import express, { Request, Response, NextFunction } from "npm:express";
  4 | import { randomUUID } from "node:crypto";
  5 | import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
  6 | import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk";
  7 | import toolsList from "./tool/toolInfo.ts";
  8 | import { toolToHandler } from "./tool/toolHandler.ts";
  9 | 
 10 | // Server configuration
 11 | const APP_NAME = "debank";
 12 | const APP_VERSION = "1.0.0";
 13 | const PORT = 8080;
 14 | 
 15 | // Create MCP server
 16 | function createServer() {
 17 |   const server = new Server(
 18 |     {
 19 |       name: APP_NAME,
 20 |       version: APP_VERSION,
 21 |     },
 22 |     {
 23 |       capabilities: {
 24 |         tools: {},
 25 |       },
 26 |     }
 27 |   );
 28 | 
 29 |   server.setRequestHandler(ListToolsRequestSchema, () => {
 30 |     console.log("Handling listTools request");
 31 |     return {
 32 |       tools: toolsList,
 33 |     };
 34 |   });
 35 | 
 36 |   server.setRequestHandler(CallToolRequestSchema, async (request) => {
 37 |     console.log(`Handling callTool request for tool: ${request.params.name}`);
 38 |     try {
 39 |       const requestToolName = request.params.name;
 40 | 
 41 |       const isMcptool = toolsList.some(
 42 |         (tool) => tool.name === requestToolName,
 43 |       );
 44 | 
 45 |       if (isMcptool) {
 46 |         const tool = toolsList.find((tool) => tool.name === requestToolName);
 47 |         if (!tool) {
 48 |           console.log(`Tool ${requestToolName} not found`);
 49 |           return { error: `Tool ${requestToolName} not found` };
 50 |         }
 51 | 
 52 |         const handler = toolToHandler[requestToolName];
 53 | 
 54 |         if (!handler) {
 55 |           console.log(`Handler for ${requestToolName} not found`);
 56 |           return { error: `Handler for ${requestToolName} not found` };
 57 |         }
 58 | 
 59 |         console.log(`Calling handler for ${requestToolName}`);
 60 |         const result = await handler(request.params.arguments as any);
 61 |         return {
 62 |           content: [
 63 |             {
 64 |               type: "text",
 65 |               text: JSON.stringify(result),
 66 |             },
 67 |           ],
 68 |         };
 69 |       }
 70 |       console.log(`Tool ${requestToolName} not found in toolsList`);
 71 |       return {
 72 |         content: [
 73 |           {
 74 |             type: "text",
 75 |             text: "Tool not found",
 76 |           },
 77 |         ],
 78 |       };
 79 |     } catch (error) {
 80 |       console.error(`Error calling tool: ${error}`);
 81 |       return {
 82 |         content: [
 83 |           {
 84 |             type: "text",
 85 |             text: `Tool ${request.params.name} failed: ${error}`,
 86 |           },
 87 |         ],
 88 |       };
 89 |     }
 90 |   });
 91 | 
 92 |   return server;
 93 | }
 94 | 
 95 | // Start server
 96 | const startServer = async () => {
 97 |   const app = express();
 98 |   app.use(express.json());
 99 | 
100 |   // Logging middleware
101 |   app.use((req: Request, res: Response, next: NextFunction) => {
102 |     console.log(`${req.method} ${req.url}`);
103 |     console.log('Headers:', JSON.stringify(req.headers));
104 |     if (req.body) {
105 |       console.log('Body:', JSON.stringify(req.body));
106 |     }
107 |     next();
108 |   });
109 | 
110 |   // Single endpoint to handle all MCP requests - stateless mode
111 |   app.all('/mcp', async (req: Request, res: Response) => {
112 |     console.log(`Received ${req.method} request to /mcp`);
113 | 
114 |     try {
115 |       // Create new server instance and transport for each request
116 |       const server = createServer();
117 |       const transport = new StreamableHTTPServerTransport({
118 |         sessionIdGenerator: () => randomUUID(),
119 |       });
120 | 
121 |       // Connect to server
122 |       await server.connect(transport);
123 | 
124 |       // Handle request
125 |       await transport.handleRequest(req, res, req.body);
126 |     } catch (error) {
127 |       console.error('Error handling MCP request:', error);
128 |       if (!res.headersSent) {
129 |         res.status(500).json({
130 |           jsonrpc: '2.0',
131 |           error: {
132 |             code: -32603,
133 |             message: 'Internal server error',
134 |           },
135 |           id: null,
136 |         });
137 |       }
138 |     }
139 |   });
140 | 
141 |   const server = app.listen(PORT, () => {
142 |     console.log(`DeBanK MCP Server listening on port ${PORT}`);
143 |   });
144 | 
145 |   // Handle server shutdown
146 |   process.on('SIGINT', () => {
147 |     console.log('Shutting down server...');
148 |     server.close(() => {
149 |       console.log('Server shutdown complete');
150 |       process.exit(0);
151 |     });
152 |   });
153 | };
154 | 
155 | // Start server
156 | startServer();
```

--------------------------------------------------------------------------------
/tool/toolInfo.ts:
--------------------------------------------------------------------------------

```typescript
  1 | export const getChainInfo = {
  2 |     name: "get_chain_info",
  3 |     description: "Get information about blockchains via GET requests to /v1/chain or /v1/chain/list. " +
  4 |         "Can retrieve details about a specific chain or list all supported chains.",
  5 |     inputSchema: {
  6 |         type: "object",
  7 |         properties: {
  8 |             id: { type: "string", description: "Optional chain identifier (e.g. eth, bsc, xdai)" },
  9 |             page: { type: "number", description: "Page number, starting from 1", default: 1 },
 10 |             page_size: { type: "number", description: "Number of records per page", default: 5 }
 11 |         },
 12 |         required: []
 13 |     }
 14 | }
 15 | 
 16 | export const getProtocolInfo = {
 17 |     name: "get_protocol_info",
 18 |     description: "Get information about DeFi protocols via GET requests to various protocol endpoints. " +
 19 |         "Can retrieve details about a specific protocol, list protocols on a chain, or fetch top holders.",
 20 |     inputSchema: {
 21 |         type: "object",
 22 |         properties: {
 23 |             id: { type: "string", description: "Protocol identifier (e.g. curve, uniswap)" },
 24 |             chain_id: { type: "string", description: "Chain identifier (e.g. eth, bsc)" },
 25 |             get_top_holders: { type: "boolean", description: "Set to True to fetch the top holders of a protocol", default: false },
 26 |             start: { type: "number", description: "Integer offset for pagination" },
 27 |             limit: { type: "number", description: "Number of results to return", default: 10 },
 28 |             page: { type: "number", description: "Page number, starting from 1", default: 1 },
 29 |             page_size: { type: "number", description: "Number of records per page", default: 5 }
 30 |         },
 31 |         required: []
 32 |     }
 33 | }
 34 | 
 35 | export const getTokenInfo = {
 36 |     name: "get_token_info",
 37 |     description: "Get information about tokens via GET requests to various token endpoints. " +
 38 |         "Can retrieve token details, top holders, or historical prices.",
 39 |     inputSchema: {
 40 |         type: "object",
 41 |         properties: {
 42 |             chain_id: { type: "string", description: "Chain identifier (e.g. eth, bsc, xdai)" },
 43 |             id: { type: "string", description: "Token identifier - either a contract address or a native token id" },
 44 |             action: { type: "string", description: "Type of information to retrieve", default: "details" },
 45 |             date_at: { type: "string", description: "UTC timezone date in YYYY-MM-DD format" },
 46 |             start: { type: "number", description: "Integer offset for pagination", default: 0 },
 47 |             limit: { type: "number", description: "Number of holders to return", default: 100 },
 48 |             page: { type: "number", description: "Page number, starting from 1", default: 1 },
 49 |             page_size: { type: "number", description: "Number of records per page", default: 5 }
 50 |         },
 51 |         required: ["chain_id", "id"]
 52 |     }
 53 | }
 54 | 
 55 | export const getPoolInfo = {
 56 |     name: "get_pool_info",
 57 |     description: "Get detailed information about a specific liquidity pool via a GET request to /v1/pool. " +
 58 |         "Returns detailed statistics about the pool including its deposits, user counts, and associated protocol.",
 59 |     inputSchema: {
 60 |         type: "object",
 61 |         properties: {
 62 |             id: { type: "string", description: "Pool identifier" },
 63 |             chain_id: { type: "string", description: "Chain identifier (e.g. eth, bsc, xdai)" }
 64 |         },
 65 |         required: ["id", "chain_id"]
 66 |     }
 67 | }
 68 | 
 69 | export const getUserAssets = {
 70 |     name: "get_user_assets",
 71 |     description: "Get information about a user's assets across different blockchains. " +
 72 |         "Can retrieve basic balance, token lists, NFTs, and more with optional chain filtering.",
 73 |     inputSchema: {
 74 |         type: "object",
 75 |         properties: {
 76 |             id: { type: "string", description: "User wallet address" },
 77 |             asset_type: { type: "string", description: "Type of asset information to retrieve", default: "balance" },
 78 |             chain_id: { type: "string", description: "Chain identifier for single-chain queries" },
 79 |             token_id: { type: "string", description: "Token identifier for specific token balance query" },
 80 |             chain_ids: { type: "string", description: "Optional comma-separated list of chain IDs" },
 81 |             page: { type: "number", description: "Page number, starting from 1", default: 1 },
 82 |             page_size: { type: "number", description: "Number of records per page", default: 5 }
 83 |         },
 84 |         required: ["id"]
 85 |     }
 86 | }
 87 | 
 88 | export const getUserActivities = {
 89 |     name: "get_user_activities",
 90 |     description: "Get information about a user's protocol positions, transaction history, and balance charts. " +
 91 |         "Supports filtering by chain and protocol.",
 92 |     inputSchema: {
 93 |         type: "object",
 94 |         properties: {
 95 |             id: { type: "string", description: "User wallet address" },
 96 |             activity_type: { type: "string", description: "Type of activity information to retrieve" },
 97 |             chain_id: { type: "string", description: "Chain identifier for single-chain queries" },
 98 |             protocol_id: { type: "string", description: "Protocol identifier for specific protocol query" },
 99 |             chain_ids: { type: "string", description: "Optional comma-separated list of chain IDs" },
100 |             page_count: { type: "number", description: "Optional number of pages to return for history queries" },
101 |             start_time: { type: "number", description: "Optional Unix timestamp to start from for history queries" },
102 |             is_simple: { type: "boolean", description: "Whether to use simple or complex protocol list", default: true },
103 |             page: { type: "number", description: "Page number, starting from 1", default: 1 },
104 |             page_size: { type: "number", description: "Number of records per page", default: 5 }
105 |         },
106 |         required: ["id", "activity_type"]
107 |     }
108 | }
109 | 
110 | export const getUserAuthorizations = {
111 |     name: "get_user_authorizations",
112 |     description: "Get information about a user's token and NFT authorizations on a specific blockchain.",
113 |     inputSchema: {
114 |         type: "object",
115 |         properties: {
116 |             id: { type: "string", description: "User wallet address" },
117 |             chain_id: { type: "string", description: "Chain identifier (e.g. eth, bsc, xdai)" },
118 |             auth_type: { type: "string", description: "Type of authorization to retrieve", default: "token" },
119 |             page: { type: "number", description: "Page number, starting from 1", default: 1 },
120 |             page_size: { type: "number", description: "Number of records per page", default: 5 }
121 |         },
122 |         required: ["id", "chain_id"]
123 |     }
124 | }
125 | 
126 | export const getCollectionNftList = {
127 |     name: "get_collection_nft_list",
128 |     description: "Get a list of NFTs in a specific collection using a GET request to /v1/collection/nft_list. " +
129 |         "Returns an array of NFT objects with details like name, description, content, and attributes.",
130 |     inputSchema: {
131 |         type: "object",
132 |         properties: {
133 |             id: { type: "string", description: "NFT contract address" },
134 |             chain_id: { type: "string", description: "Chain identifier (e.g. eth, bsc, xdai)" },
135 |             start: { type: "number", description: "Integer offset for pagination", default: 0 },
136 |             limit: { type: "number", description: "Number of NFTs to return", default: 20 },
137 |             page: { type: "number", description: "Page number, starting from 1", default: 1 },
138 |             page_size: { type: "number", description: "Number of records per page", default: 5 }
139 |         },
140 |         required: ["id", "chain_id"]
141 |     }
142 | }
143 | 
144 | export const walletTools = {
145 |     name: "wallet_tools",
146 |     description: "Access wallet-related functionality: get gas prices, analyze transactions, or simulate transactions.",
147 |     inputSchema: {
148 |         type: "object",
149 |         properties: {
150 |             action: { type: "string", description: "Type of wallet action to perform" },
151 |             chain_id: { type: "string", description: "Chain identifier (e.g. eth, bsc, xdai)" },
152 |             tx: { type: "object", description: "Transaction object" },
153 |             pending_tx_list: { type: "array", description: "Optional list of transactions to execute before the main transaction" },
154 |             page: { type: "number", description: "Page number, starting from 1", default: 1 },
155 |             page_size: { type: "number", description: "Number of records per page", default: 5 }
156 |         },
157 |         required: ["action"]
158 |     }
159 | }
160 | 
161 | export default [
162 |     getChainInfo,
163 |     getProtocolInfo,
164 |     getTokenInfo,
165 |     getPoolInfo,
166 |     getUserAssets,
167 |     getUserActivities,
168 |     getUserAuthorizations,
169 |     getCollectionNftList,
170 |     walletTools
171 | ]
172 | 
173 | 
```

--------------------------------------------------------------------------------
/tool/toolHandler.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { getChainInfo, getCollectionNftList, getPoolInfo, getProtocolInfo, getTokenInfo, getUserActivities, getUserAssets, getUserAuthorizations, walletTools } from "./toolInfo.ts";
  2 | 
  3 | // DeBanK API configuration
  4 | const ACCESS_KEY = Deno.env.get("ACCESS_KEY");
  5 | const BASE_URL = "https://pro-openapi.debank.com";
  6 | 
  7 | // Pagination function types
  8 | interface PaginationInfo {
  9 |     page: number;
 10 |     page_size: number;
 11 |     total_items: number;
 12 |     total_pages: number;
 13 | }
 14 | 
 15 | interface PaginatedResult<T> {
 16 |     data: T[];
 17 |     pagination: PaginationInfo;
 18 | }
 19 | 
 20 | // Pagination function
 21 | function paginateResults<T>(results: T[] | null | undefined, page = 1, page_size = 5): PaginatedResult<T> | null | undefined {
 22 |     if (results === null || results === undefined || !Array.isArray(results)) {
 23 |         return results as any;
 24 |     }
 25 | 
 26 |     const start_idx = (page - 1) * page_size;
 27 |     const end_idx = start_idx + page_size;
 28 |     const total_items = results.length;
 29 |     const total_pages = Math.ceil(total_items / page_size);
 30 | 
 31 |     const paginated_results = results.slice(start_idx, end_idx);
 32 | 
 33 |     const pagination_info: PaginationInfo = {
 34 |         page,
 35 |         page_size,
 36 |         total_items,
 37 |         total_pages
 38 |     };
 39 | 
 40 |     return {
 41 |         data: paginated_results,
 42 |         pagination: pagination_info
 43 |     };
 44 | }
 45 | 
 46 | // Network request function
 47 | async function makeNwsRequest(url: string): Promise<any> {
 48 |     const headers = {
 49 |         "Accept": "application/json",
 50 |         "AccessKey": ACCESS_KEY || ""
 51 |     };
 52 | 
 53 |     try {
 54 |         const response = await fetch(url, {
 55 |             method: "GET",
 56 |             headers
 57 |         });
 58 | 
 59 |         if (!response.ok) {
 60 |             throw new Error(`HTTP error ${response.status}`);
 61 |         }
 62 | 
 63 |         return await response.json();
 64 |     } catch (error) {
 65 |         console.error("Request error:", error);
 66 |         return null;
 67 |     }
 68 | }
 69 | 
 70 | // POST request function
 71 | async function makePostRequest(url: string, data: any): Promise<any> {
 72 |     const headers = {
 73 |         "Accept": "application/json",
 74 |         "Content-Type": "application/json",
 75 |         "AccessKey": ACCESS_KEY || ""
 76 |     };
 77 | 
 78 |     try {
 79 |         const response = await fetch(url, {
 80 |             method: "POST",
 81 |             headers,
 82 |             body: JSON.stringify(data)
 83 |         });
 84 | 
 85 |         if (!response.ok) {
 86 |             throw new Error(`HTTP error ${response.status}`);
 87 |         }
 88 | 
 89 |         return await response.json();
 90 |     } catch (error) {
 91 |         console.error("POST request error:", error);
 92 |         return null;
 93 |     }
 94 | }
 95 | 
 96 | 
 97 | export const getChainInfoHandler = async ({ id, page = 1, page_size = 5 }: { id?: string, page?: number, page_size?: number }) => {
 98 |     if (id) {
 99 |         return await makeNwsRequest(`${BASE_URL}/v1/chain?id=${id}`);
100 |     } else {
101 |         const results = await makeNwsRequest(`${BASE_URL}/v1/chain/list`);
102 |         return paginateResults(results, page, page_size);
103 |     }
104 | }
105 | 
106 | export const getProtocolInfoHandler = async ({ id, chain_id, get_top_holders = false, start, limit = 10, page = 1, page_size = 5 }: { id?: string, chain_id?: string, get_top_holders?: boolean, start?: number, limit?: number, page?: number, page_size?: number }) => {
107 |     if (id && get_top_holders) {
108 |         // Get top holders of a protocol
109 |         let url = `${BASE_URL}/v1/protocol/top_holders?id=${id}`;
110 |         if (start !== undefined) {
111 |             url += `&start=${start}`;
112 |         }
113 |         if (limit !== undefined) {
114 |             url += `&limit=${limit}`;
115 |         }
116 |         const results = await makeNwsRequest(url);
117 |         return paginateResults(results, page, page_size);
118 |     } else if (id) {
119 |         // Get specific protocol info
120 |         return await makeNwsRequest(`${BASE_URL}/v1/protocol?id=${id}`);
121 |     } else if (chain_id) {
122 |         // Get protocols on a chain
123 |         const protocols = await makeNwsRequest(`${BASE_URL}/v1/protocol/list?chain_id=${chain_id}`);
124 |         if (protocols) {
125 |             // Sort protocols by TVL in descending order
126 |             const sorted_protocols = [...protocols].sort((a, b) =>
127 |                 (b.tvl || 0) - (a.tvl || 0)
128 |             );
129 |             return paginateResults(sorted_protocols, page, page_size);
130 |         }
131 |         return null;
132 |     } else {
133 |         return { error: "Either id or chain_id must be provided" };
134 |     }
135 | }
136 | 
137 | export const getTokenInfoHandler = async ({ chain_id, id, action = "details", date_at, start = 0, limit = 100, page = 1, page_size = 5 }: { chain_id: string, id: string, action?: string, date_at?: string, start?: number, limit?: number, page?: number, page_size?: number }) => {
138 |     if (action === "details") {
139 |         return await makeNwsRequest(`${BASE_URL}/v1/token?chain_id=${chain_id}&id=${id}`);
140 |     } else if (action === "holders") {
141 |         const url = `${BASE_URL}/v1/token/top_holders?chain_id=${chain_id}&id=${id}&start=${start}&limit=${limit}`;
142 |         const results = await makeNwsRequest(url);
143 |         return paginateResults(results, page, page_size);
144 |     } else if (action === "history") {
145 |         if (!date_at) {
146 |             return { error: "date_at parameter is required for historical price" };
147 |         }
148 |         return await makeNwsRequest(`${BASE_URL}/v1/token/history_price?chain_id=${chain_id}&id=${id}&date_at=${date_at}`);
149 |     } else {
150 |         return { error: "Invalid action parameter. Use 'details', 'holders', or 'history'." };
151 |     }
152 | }
153 | 
154 | export const getPoolInfoHandler = async ({ id, chain_id }: { id: string, chain_id: string }) => {
155 |     return await makeNwsRequest(`${BASE_URL}/v1/pool?id=${id}&chain_id=${chain_id}`);
156 | }
157 | 
158 | export const getUserAssetsHandler = async ({ id, asset_type = "balance", chain_id, token_id, chain_ids, page = 1, page_size = 5 }: { id: string, asset_type?: string, chain_id?: string, token_id?: string, chain_ids?: string, page?: number, page_size?: number }) => {
159 |     if (asset_type === "balance") {
160 |         if (chain_id) {
161 |             return await makeNwsRequest(`${BASE_URL}/v1/user/chain_balance?id=${id}&chain_id=${chain_id}`);
162 |         } else {
163 |             let url = `${BASE_URL}/v1/user/total_balance?id=${id}`;
164 |             if (chain_ids) {
165 |                 url += `&chain_ids=${chain_ids}`;
166 |             }
167 |             return await makeNwsRequest(url);
168 |         }
169 |     } else if (asset_type === "chains") {
170 |         const results = await makeNwsRequest(`${BASE_URL}/v1/user/used_chain_list?id=${id}`);
171 |         return paginateResults(results, page, page_size);
172 |     } else if (asset_type === "tokens") {
173 |         let results;
174 |         if (chain_id) {
175 |             results = await makeNwsRequest(`${BASE_URL}/v1/user/token_list?id=${id}&chain_id=${chain_id}`);
176 |         } else {
177 |             let url = `${BASE_URL}/v1/user/all_token_list?id=${id}`;
178 |             if (chain_ids) {
179 |                 url += `&chain_ids=${chain_ids}`;
180 |             }
181 |             results = await makeNwsRequest(url);
182 |         }
183 |         return paginateResults(results, page, page_size);
184 |     } else if (asset_type === "token") {
185 |         if (!chain_id || !token_id) {
186 |             return { error: "chain_id and token_id are required for token balance query" };
187 |         }
188 |         return await makeNwsRequest(`${BASE_URL}/v1/user/token_balance?id=${id}&chain_id=${chain_id}&token_id=${token_id}`);
189 |     } else if (asset_type === "nfts") {
190 |         let results;
191 |         if (chain_id) {
192 |             results = await makeNwsRequest(`${BASE_URL}/v1/user/nft_list?id=${id}&chain_id=${chain_id}`);
193 |         } else {
194 |             let url = `${BASE_URL}/v1/user/all_nft_list?id=${id}`;
195 |             if (chain_ids) {
196 |                 url += `&chain_ids=${chain_ids}`;
197 |             }
198 |             results = await makeNwsRequest(url);
199 |         }
200 |         return paginateResults(results, page, page_size);
201 |     } else {
202 |         return { error: "Invalid asset_type parameter" };
203 |     }
204 | }
205 | 
206 | export const getUserActivitiesHandler = async ({ id, activity_type, chain_id, protocol_id, chain_ids, page_count, start_time, is_simple = true, page = 1, page_size = 5 }: { id: string, activity_type: string, chain_id?: string, protocol_id?: string, chain_ids?: string, page_count?: number, start_time?: number, is_simple?: boolean, page?: number, page_size?: number }) => {
207 |     if (activity_type === "protocols") {
208 |         if (protocol_id) {
209 |             // Get specific protocol info
210 |             return await makeNwsRequest(`${BASE_URL}/v1/user/protocol?id=${id}&protocol_id=${protocol_id}`);
211 |         } else if (chain_id) {
212 |             // Get protocol list for a specific chain
213 |             let results;
214 |             if (is_simple) {
215 |                 results = await makeNwsRequest(`${BASE_URL}/v1/user/simple_protocol_list?id=${id}&chain_id=${chain_id}`);
216 |             } else {
217 |                 results = await makeNwsRequest(`${BASE_URL}/v1/user/complex_protocol_list?id=${id}&chain_id=${chain_id}`);
218 |             }
219 |             return paginateResults(results, page, page_size);
220 |         } else {
221 |             // Get protocol list for all chains
222 |             const url_base = `${BASE_URL}/v1/user/all_${is_simple ? "simple" : "complex"}_protocol_list`;
223 |             let url = `${url_base}?id=${id}`;
224 |             if (chain_ids) {
225 |                 url += `&chain_ids=${chain_ids}`;
226 |             }
227 |             const results = await makeNwsRequest(url);
228 |             return paginateResults(results, page, page_size);
229 |         }
230 |     } else if (activity_type === "history") {
231 |         let url;
232 |         if (chain_id) {
233 |             // Get history for a specific chain
234 |             url = `${BASE_URL}/v1/user/history_list?id=${id}&chain_id=${chain_id}`;
235 |         } else {
236 |             // Get history for all chains
237 |             url = `${BASE_URL}/v1/user/history?id=${id}`;
238 |             if (chain_ids) {
239 |                 url += `&chain_ids=${chain_ids}`;
240 |             }
241 |         }
242 | 
243 |         if (page_count !== undefined) {
244 |             url += `&page_count=${page_count}`;
245 |         }
246 |         if (start_time !== undefined) {
247 |             url += `&start_time=${start_time}`;
248 |         }
249 | 
250 |         const results = await makeNwsRequest(url);
251 |         return paginateResults(results, page, page_size);
252 |     } else if (activity_type === "chart") {
253 |         let results;
254 |         if (chain_id) {
255 |             // Get chart for a specific chain
256 |             results = await makeNwsRequest(`${BASE_URL}/v1/user/chain_net_curve?id=${id}&chain_id=${chain_id}`);
257 |         } else {
258 |             // Get chart for all chains
259 |             let url = `${BASE_URL}/v1/user/total_net_curve?id=${id}`;
260 |             if (chain_ids) {
261 |                 url += `&chain_ids=${chain_ids}`;
262 |             }
263 |             results = await makeNwsRequest(url);
264 |         }
265 |         return paginateResults(results, page, page_size);
266 |     } else {
267 |         return { error: "Invalid activity_type parameter" };
268 |     }
269 | }
270 | 
271 | export const getUserAuthorizationsHandler = async ({ id, chain_id, auth_type = "token", page = 1, page_size = 5 }: { id: string, chain_id: string, auth_type?: string, page?: number, page_size?: number }) => {
272 |     if (auth_type === "token") {
273 |         const results = await makeNwsRequest(`${BASE_URL}/v1/user/token_auth_list?id=${id}&chain_id=${chain_id}`);
274 |         return paginateResults(results, page, page_size);
275 |     } else if (auth_type === "nft") {
276 |         const results = await makeNwsRequest(`${BASE_URL}/v1/user/nft_auth_list?id=${id}&chain_id=${chain_id}`);
277 |         return paginateResults(results, page, page_size);
278 |     } else {
279 |         return { error: "Invalid auth_type parameter. Use 'token' or 'nft'." };
280 |     }
281 | }
282 | 
283 | export const getCollectionNftListHandler = async ({ id, chain_id, start = 0, limit = 20, page = 1, page_size = 5 }: { id: string, chain_id: string, start?: number, limit?: number, page?: number, page_size?: number }) => {
284 |     const url = `${BASE_URL}/v1/collection/nft_list?id=${id}&chain_id=${chain_id}&start=${start}&limit=${limit}`;
285 |     const results = await makeNwsRequest(url);
286 |     return paginateResults(results, page, page_size);
287 | }
288 | 
289 | export const walletToolsHandler = async ({ action, chain_id, tx, pending_tx_list, page = 1, page_size = 5 }: { action: string, chain_id?: string, tx?: any, pending_tx_list?: any[], page?: number, page_size?: number }) => {
290 |     if (action === "gas") {
291 |         if (!chain_id) {
292 |             return { error: "chain_id parameter is required for gas price query" };
293 |         }
294 |         const results = await makeNwsRequest(`${BASE_URL}/v1/wallet/gas_market?chain_id=${chain_id}`);
295 |         return paginateResults(results, page, page_size);
296 |     } else if (action === "explain_tx") {
297 |         if (!tx) {
298 |             return { error: "tx parameter is required for transaction explanation" };
299 |         }
300 |         const data = { tx };
301 |         return await makePostRequest(`${BASE_URL}/v1/wallet/explain_tx`, data);
302 |     } else if (action === "simulate_tx") {
303 |         if (!tx) {
304 |             return { error: "tx parameter is required for transaction simulation" };
305 |         }
306 |         const data: any = { tx };
307 |         if (pending_tx_list) {
308 |             data.pending_tx_list = pending_tx_list;
309 |         }
310 |         return await makePostRequest(`${BASE_URL}/v1/wallet/pre_exec_tx`, data);
311 |     } else {
312 |         return { error: "Invalid action parameter. Use 'gas', 'explain_tx', or 'simulate_tx'." };
313 |     }
314 | }
315 | 
316 | 
317 | 
318 | export const toolToHandler = {
319 |     [getChainInfo.name]: getChainInfoHandler,
320 |     [getProtocolInfo.name]: getProtocolInfoHandler,
321 |     [getTokenInfo.name]: getTokenInfoHandler,
322 |     [getPoolInfo.name]: getPoolInfoHandler,
323 |     [getUserAssets.name]: getUserAssetsHandler,
324 |     [getUserActivities.name]: getUserActivitiesHandler,
325 |     [getUserAuthorizations.name]: getUserAuthorizationsHandler,
326 |     [getCollectionNftList.name]: getCollectionNftListHandler,
327 |     [walletTools.name]: walletToolsHandler
328 | }
```