# 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 | }
```