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

```
├── .gitignore
├── config.example.json
├── jest.config.mjs
├── package-lock.json
├── package.json
├── README.md
├── README.npm.md
├── scripts
│   └── publish.cjs
├── sources
│   ├── BUSD_TRANSFER_MCP.mp4
│   └── example.ts
├── src
│   ├── addressConfig.ts
│   ├── cli
│   │   ├── help.ts
│   │   ├── init.ts
│   │   └── version.ts
│   ├── config.ts
│   ├── functions
│   │   ├── fetchBalanceTool.ts
│   │   ├── memeTokenDetails.ts
│   │   ├── pancakeAddLiquidityTool.ts
│   │   ├── pancakeRemoveLiquidityTool.ts
│   │   ├── pancakeSwapPosition.ts
│   │   └── pancakeSwapTool.ts
│   ├── index.ts
│   ├── init.ts
│   ├── lib
│   │   └── bep20Abi.ts
│   ├── main.ts
│   ├── PrivateAES.ts
│   ├── responseUtils.ts
│   ├── test
│   │   └── privateAES.test.ts
│   ├── tools
│   │   ├── buyMemeToken.ts
│   │   ├── getWalletInfo.ts
│   │   ├── goplusSecurityCheck.ts
│   │   ├── pancakeAddLiquidity.ts
│   │   ├── pancakeMyPosition.ts
│   │   ├── pancakeRemovePosition.ts
│   │   ├── pancakeSwap.ts
│   │   ├── queryMemeTokenDetails.ts
│   │   ├── sellMemeToken.ts
│   │   ├── transferBEP20Token.ts
│   │   └── transferNativeToken.ts
│   ├── types
│   │   └── types.ts
│   └── util.ts
└── tsconfig.json
```

# Files

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

```
 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
 2 | 
 3 | # dependencies
 4 | /node_modules
 5 | /.pnp
 6 | .pnp.js
 7 | 
 8 | # testing
 9 | /coverage
10 | 
11 | # database
12 | /prisma/db.sqlite
13 | /prisma/db.sqlite-journal
14 | db.sqlite
15 | 
16 | # next.js
17 | /.next/
18 | /out/
19 | next-env.d.ts
20 | 
21 | # production
22 | /build
23 | 
24 | # misc
25 | .DS_Store
26 | *.pem
27 | 
28 | # debug
29 | npm-debug.log*
30 | yarn-debug.log*
31 | yarn-error.log*
32 | .pnpm-debug.log*
33 | 
34 | # local env files
35 | # do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
36 | .env
37 | .env*.local
38 | 
39 | # vercel
40 | .vercel
41 | 
42 | # typescript
43 | *.tsbuildinfo
44 | 
45 | # idea files
46 | .idea
47 | yarn.lock
48 | 
49 | .env*
50 | dist/
```

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

```markdown
  1 | 
  2 | 
  3 | ---
  4 | 
  5 | ## 📦 BNBChain MCP – Binance Smart Chain Tool Server (MCP + CLI Ready)
  6 | 
  7 | > A plug-and-play MCP tool server to **send BNB**, **transfer BEP-20 tokens**, **deploy tokens**, and **interact with smart contracts** on the **Binance Smart Chain (BSC)** — built for **Claude Desktop**, **AI agents**, and **developers.**
  8 | 
  9 | ---
 10 | 
 11 | ### ⚙️ Core Capabilities
 12 | 
 13 | - 🔐 Secure token & native transfers via CLI or MCP
 14 | - 🧱 Interact with smart contracts (ABI/function-based)
 15 | - 🔄 PancakeSwap integration for swaps & liquidity
 16 | - ⚙️ Create meme tokens & deploy BEP-20 smart contracts
 17 | - 🧠 Native Claude Desktop integration via MCP
 18 | - 🔧 CLI-ready, MCP-compliant, developer-friendly
 19 | - 🔑 Password-protected private keys
 20 | 
 21 | ---
 22 | 
 23 | ## 🛠 Installation & Setup
 24 | 
 25 | ### 1. Install
 26 | 
 27 | ```bash
 28 | npm install -g bnbchain-mcp
 29 | ```
 30 | 
 31 | ### 2. Run the CLI Setup Wizard
 32 | 
 33 | ```bash
 34 | bnbchain-mcp --init
 35 | ```
 36 | 
 37 | You’ll be prompted to enter:
 38 | 
 39 | - ✅ **BSC Wallet Private Key** *(required)* 
 40 | - ✅ **Wallet Password** *(required, must be 6 characters)*
 41 | - ✅ **Custom RPC URL** *(optional, defaults to:* `https://bsc-dataseed.binance.org` *)
 42 | 
 43 | ---
 44 | 
 45 | ## 🧠 Claude Desktop Integration
 46 | 
 47 | After CLI setup, the tool can **auto-configure itself into Claude Desktop**.
 48 | 
 49 | 📍 File modified:
 50 | 
 51 | ```
 52 | ~/Library/Application Support/Claude/claude_desktop_config.json
 53 | ```
 54 | 
 55 | Claude will detect and run this MCP server with your selected tools.
 56 | 
 57 | ---
 58 | 
 59 | ## 🔨 Supported MCP Tools
 60 | 
 61 | | Tool Name             | Description                              |
 62 | |----------------------|------------------------------------------|
 63 | | `transferNativeToken` | Send BNB to a wallet                     |
 64 | | `transferBEP20Token`  | Transfer BEP-20 token via symbol/address |
 65 | | `pancakeSwap`         | Swap tokens via PancakeSwap              |
 66 | | `createFourMeme`      | Create meme token on Four.Meme           |
 67 | | `createBEP20Token`    | Deploy a BEP-20 contract                 |
 68 | | `getBalance`          | Get token + native balance               |
 69 | | `callContractFunction`| Custom contract calls via ABI            |
 70 | | `getWalletInfo`       | Get wallet info for an address           |
 71 | | `securityCheck`       | Check token security of BSC tokens       |
 72 | | `pancakeAddLiquidity` | Add liquidity to PancakeSwap             |
 73 | | `pancakeMyPosition`   | View your PancakeSwap positions          |
 74 | | `pancakeRemovePosition`| Remove liquidity from PancakeSwap        |
 75 | | `sellMemeToken`        | Sell meme token on Four.Meme             |
 76 | | ...and more coming soon 🔧 |
 77 | 
 78 | ---
 79 | 
 80 | ## 🧪 Development Workflow
 81 | 
 82 | ### Compile TypeScript:
 83 | ```bash
 84 | npm run build
 85 | ```
 86 | 
 87 | ### Start MCP Server:
 88 | ```bash
 89 | npm start
 90 | # or
 91 | node build/index.js
 92 | ```
 93 | 
 94 | ### Re-configure:
 95 | ```bash
 96 | bnbchain-mcp --init
 97 | ```
 98 | 
 99 | ---
100 | 
101 | ## 📘 Model Context Protocol (MCP)
102 | 
103 | This project is built on **Model Context Protocol** – a standard to help agents and models interact with structured tool APIs.
104 | 
105 | **MCP Benefits**:
106 | - ✅ Structured input/output
107 | - ✅ Claude + OpenAI compatible
108 | - ✅ Secure + serverless-ready
109 | 
110 | ---
111 | 
112 | ## ✅ Roadmap
113 | 
114 | - [x] CLI Configuration Wizard
115 | - [x] Claude Desktop Integration
116 | - [x] Token Deploy + Transfer
117 | - [ ] Token charting tools (DEXTools, Gecko)
118 | - [ ] Telegram auto-trading agent
119 | - [ ] AI assistant with BSC on-chain brain
120 | 
121 | ---
122 | 
123 | ## 🤝 Contributing
124 | 
125 | Feel free to fork, PR, or raise issues.
126 | We're building **tool-first, AI-ready infrastructure** for the next wave of Web3 agents. Join us!
127 | 
128 | ---
129 | 
130 | ## 🛡️ License
131 | 
132 | MIT — Use freely, contribute openly.
133 | 
134 | ---
135 | 
```

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

```markdown
  1 | 
  2 | 
  3 | ---
  4 | 
  5 | ## 📦 BNBChain MCP – Binance Smart Chain Tool Server (MCP + CLI Ready)
  6 | 
  7 | > A comprehensive blockchain tool server for BNB, BEP-20 tokens, smart contract deployment and interaction built on BNB Smart Chain (BSC) and compatible with other EVM networks.
  8 | 
  9 | ---
 10 | 
 11 | ## Technology Stack
 12 | 
 13 | - **Blockchain**: BNB Smart Chain (BSC)  
 14 | - **Web3 Libraries**: Viem 2.23.11, PancakeSwap SDK 5.8.8  
 15 | - **CLI/Backend**: TypeScript, Node.js (ESM)  
 16 | - **Protocol**: Model Context Protocol (MCP) SDK 1.4.0  
 17 | - **Security**: AES encryption with bcrypt for private key protection
 18 | - **Token Security**: GoPlus SDK for security checks
 19 | - **Data Provider**: Moralis SDK 2.27.2 for blockchain data
 20 | 
 21 | ---
 22 | 
 23 | ## Supported Networks
 24 | 
 25 | - **BNB Smart Chain Mainnet** (Chain ID: 56)  
 26 |   - RPC: https://bsc-dataseed.binance.org (default)
 27 |   - Custom RPC supported via environment configuration
 28 | 
 29 | ---
 30 | 
 31 | ## Contract Addresses
 32 | 
 33 | | Contract Type | Address | Description |
 34 | |--------------|---------|-------------|
 35 | | Four.Meme Try Buy | 0xF251F83e40a78868FcfA3FA4599Dad6494E46034 | Four.Meme token purchase contract |
 36 | | Four.Meme Buy/Sell AMAP | 0x5c952063c7fc8610FFDB798152D69F0B9550762b | Four.Meme auto-market-adjusted pricing |
 37 | | Four.Meme Create Token | 0x5c952063c7fc8610FFDB798152D69F0B9550762b | Four.Meme token factory |
 38 | | PancakeSwap Router V2 | Integrated via SDK | DEX routing and swaps |
 39 | | PancakeSwap V3 Pools | Accessed via SDK | Liquidity pools management |
 40 | 
 41 | ---
 42 | 
 43 | ## Features
 44 | 
 45 | - **Low-cost BNB & BEP-20 transfers** - Optimized for BSC's low gas fees
 46 | - **PancakeSwap V2/V3 integration** - Automated swaps, liquidity management, and position tracking
 47 | - **Four.Meme platform support** - Create, buy, and sell meme tokens directly
 48 | - **Security-first architecture** - AES-256 encrypted private keys with bcrypt password protection
 49 | - **Token security analysis** - Built-in GoPlus security checks for token verification
 50 | - **Gas-efficient operations** - Smart routing for optimal gas usage on BSC
 51 | - **AI-ready MCP protocol** - Seamless integration with Claude Desktop and AI agents
 52 | - **Real-time wallet monitoring** - Track balances and positions across multiple tokens
 53 | 
 54 | ---
 55 | 
 56 | ## 🛠 Installation & Setup
 57 | 
 58 | ### 1. Install
 59 | 
 60 | ```bash
 61 | npm install -g bnbchain-mcp
 62 | ```
 63 | 
 64 | ### 2. Run the CLI Setup Wizard
 65 | 
 66 | ```bash
 67 | bnbchain-mcp --init
 68 | ```
 69 | 
 70 | You’ll be prompted to enter:
 71 | 
 72 | - ✅ **BSC Wallet Private Key** *(required)* 
 73 | - ✅ **Wallet Password** *(required, must be 6 characters)*
 74 | - ✅ **Custom RPC URL** *(optional, defaults to:* `https://bsc-dataseed.binance.org` *)
 75 | 
 76 | ---
 77 | 
 78 | ## 🧠 Claude Desktop Integration
 79 | 
 80 | After CLI setup, the tool can **auto-configure itself into Claude Desktop**.
 81 | 
 82 | 📍 File modified:
 83 | 
 84 | ```
 85 | ~/Library/Application Support/Claude/claude_desktop_config.json
 86 | ```
 87 | 
 88 | Claude will detect and run this MCP server with your selected tools.
 89 | 
 90 | ---
 91 | 
 92 | ## 🔨 Supported MCP Tools
 93 | 
 94 | | Tool Name             | Description                              |
 95 | |----------------------|------------------------------------------|
 96 | | `transferNativeToken` | Send BNB to a wallet                     |
 97 | | `transferBEP20Token`  | Transfer BEP-20 token via symbol/address |
 98 | | `pancakeSwap`         | Swap tokens via PancakeSwap              |
 99 | | `createFourMeme`      | Create meme token on Four.Meme           |
100 | | `createBEP20Token`    | Deploy a BEP-20 contract                 |
101 | | `getBalance`          | Get token + native balance               |
102 | | `callContractFunction`| Custom contract calls via ABI            |
103 | | `getWalletInfo`       | Get wallet info for an address           |
104 | | `securityCheck`       | Check token security of BSC tokens       |
105 | | `pancakeAddLiquidity` | Add liquidity to PancakeSwap             |
106 | | `pancakeMyPosition`   | View your PancakeSwap positions          |
107 | | `pancakeRemovePosition`| Remove liquidity from PancakeSwap        |
108 | | `sellMemeToken`        | Sell meme token on Four.Meme             |
109 | | ...and more coming soon 🔧 |
110 | 
111 | ---
112 | 
113 | ## 🧪 Development Workflow
114 | 
115 | ### Compile TypeScript:
116 | ```bash
117 | npm run build
118 | ```
119 | 
120 | ### Start MCP Server:
121 | ```bash
122 | npm start
123 | # or
124 | node build/index.js
125 | ```
126 | 
127 | ### Re-configure:
128 | ```bash
129 | bnbchain-mcp --init
130 | ```
131 | 
132 | ---
133 | 
134 | ## 📘 Model Context Protocol (MCP)
135 | 
136 | This project is built on **Model Context Protocol** – a standard to help agents and models interact with structured tool APIs.
137 | 
138 | **MCP Benefits**:
139 | - ✅ Structured input/output
140 | - ✅ Claude + OpenAI compatible
141 | - ✅ Secure + serverless-ready
142 | 
143 | ---
144 | 
145 | ## ✅ Roadmap
146 | 
147 | - [x] CLI Configuration Wizard
148 | - [x] Claude Desktop Integration
149 | - [x] Token Deploy + Transfer
150 | - [ ] Token charting tools (DEXTools, Gecko)
151 | - [ ] Telegram auto-trading agent
152 | - [ ] AI assistant with BSC on-chain brain
153 | 
154 | ---
155 | 
156 | ## 🤝 Contributing
157 | 
158 | Feel free to fork, PR, or raise issues.
159 | We're building **tool-first, AI-ready infrastructure** for the next wave of Web3 agents. Join us!
160 | 
161 | ---
162 | 
163 | ## 🛡️ License
164 | 
165 | MIT — Use freely, contribute openly.
166 | 
167 | ---
168 | 
```

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

```typescript
 1 | /**
 2 |  * Fetch tokens from PancakeSwap token list
 3 |  * @returns Array of token data
 4 |  */
 5 | 
 6 | import { Address } from "viem";
 7 | 
 8 | export interface TokenInfo {
 9 |   address: Address;
10 |   decimals: number;
11 |   symbol?: string;
12 |   name?: string;
13 | }
14 | 
```

--------------------------------------------------------------------------------
/src/cli/help.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // src/cli/help.ts
 2 | export function printHelp(): void {
 3 |     console.log(`
 4 |   📦 BNB Chain MCP CLI
 5 |   
 6 |   Usage:
 7 |     bnbchain-mcp [options]
 8 |   
 9 |   Options:
10 |     -i, --init      Initialize configuration
11 |     -v, --version   Show CLI version
12 |     -h, --help      Show help info
13 |   
14 |   Examples:
15 |     bnbchain-mcp --init
16 |     bnbchain-mcp --version
17 |     bnbchain-mcp
18 |     `);
19 | }
```

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

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

--------------------------------------------------------------------------------
/config.example.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |     "mcpServers": {
 3 |         "bsc-mcp": {
 4 |             "command": "node",
 5 |             "args": [
 6 |                 "/Users/Username/Desktop/bsc-mpc/build/index.js"
 7 |             ],
 8 |             "env": {
 9 |                 "BSC_WALLET_PRIVATE_KEY": "BSC_WALLET_PRIVATE_KEY",
10 |                 "BSC_RPC_URL": "BSC_RPC_URL"
11 |             },
12 |             "disabled": false,
13 |             "autoApprove": []
14 |         }
15 |     }
16 | }
```

--------------------------------------------------------------------------------
/jest.config.mjs:
--------------------------------------------------------------------------------

```
 1 | export default {
 2 |   transform: {
 3 |     '^.+\\.tsx?$': 'ts-jest',
 4 |     '^.+\\.mjs$': 'ts-jest',
 5 |   },
 6 |   moduleFileExtensions: ['js', 'json', 'ts', 'tsx', 'mjs'],
 7 |   testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[tj]s?(x)'],
 8 |   moduleNameMapper: {
 9 |     '^(\\.{1,2}/.*)\\.js$': '$1',
10 |   },
11 |   extensionsToTreatAsEsm: ['.ts', '.tsx'],
12 |   globals: {
13 |     'ts-jest': {
14 |       useESM: true,
15 |     },
16 |   },
17 | };
```

--------------------------------------------------------------------------------
/src/addressConfig.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Address } from "viem"
 2 | 
 3 | export const AddressConfig: {
 4 |     [key: string]: Address
 5 | } = {
 6 |     "FourMemeTryBuyContract": "0xF251F83e40a78868FcfA3FA4599Dad6494E46034",
 7 |     "FourMemeBuyTokenAMAPContract": "0x5c952063c7fc8610FFDB798152D69F0B9550762b",
 8 |     "FourMemeSellTokenAMAPContract": "0x5c952063c7fc8610FFDB798152D69F0B9550762b",
 9 |     "FourMemeCreateTokenContract": "0x5c952063c7fc8610FFDB798152D69F0B9550762b",
10 | }
```

--------------------------------------------------------------------------------
/src/cli/version.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import fs from 'fs';
 2 | import path from 'path';
 3 | import { fileURLToPath } from 'url';
 4 | 
 5 | const __filename = fileURLToPath(import.meta.url);
 6 | const __dirname = path.dirname(__filename);
 7 | 
 8 | let version = '1.0.0'; // fallback
 9 | 
10 | try {
11 |   const pkgPath = path.resolve(__dirname, '../../package.json');
12 |   const pkgJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
13 |   version = pkgJson.version || version;
14 | } catch {
15 | }
16 | 
17 | export { version };
18 | 
```

--------------------------------------------------------------------------------
/src/cli/init.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { execSync } from 'child_process';
 2 | import { fileURLToPath } from 'url';
 3 | import path from 'path';
 4 | 
 5 | // __dirname workaround for ES Modules
 6 | const __filename = fileURLToPath(import.meta.url);
 7 | const __dirname = path.dirname(__filename);
 8 | 
 9 | export async function init() {
10 |   const initPath = path.resolve(__dirname, '../init.js');
11 | 
12 |   try {
13 |     execSync(`node "${initPath}"`, { stdio: 'inherit' });
14 |   } catch (err) {
15 |     console.error('❌ Failed to run init:', err);
16 |     process.exit(1);
17 |   }
18 | }
```

--------------------------------------------------------------------------------
/src/test/privateAES.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { encryptPrivateKey, decryptPrivateKey } from "../PrivateAES.js";
 2 | 
 3 | describe.only("privateAES", () => {
 4 |     
 5 |     test("test", async () => {
 6 |         const privateKey = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef';
 7 |         const password = '12345678';
 8 |         const encrypted =await encryptPrivateKey(privateKey, password);
 9 |         console.log(encrypted);
10 |         const decrypted =await decryptPrivateKey(encrypted, password);
11 |         console.log(decrypted);
12 |         expect(decrypted).toBe(privateKey);
13 |     })
14 | })
```

--------------------------------------------------------------------------------
/src/lib/bep20Abi.ts:
--------------------------------------------------------------------------------

```typescript
 1 | export const bep20abi = [
 2 |   {
 3 |     name: "transfer",
 4 |     type: "function",
 5 |     stateMutability: "nonpayable",
 6 |     inputs: [
 7 |       { name: "recipient", type: "address" },
 8 |       { name: "amount", type: "uint256" },
 9 |     ],
10 |     outputs: [{ type: "bool" }],
11 |   },
12 |   {
13 |     name: "decimals",
14 |     type: "function",
15 |     stateMutability: "view",
16 |     inputs: [],
17 |     outputs: [{ type: "uint8" }],
18 |   },
19 |   {
20 |     name: "symbol",
21 |     type: "function",
22 |     stateMutability: "view",
23 |     inputs: [],
24 |     outputs: [{ type: "string" }],
25 |   },
26 |   {
27 |     name: "name",
28 |     type: "function",
29 |     stateMutability: "view",
30 |     inputs: [],
31 |     outputs: [{ type: "string" }],
32 |   },
33 | ] as const;
34 | 
```

--------------------------------------------------------------------------------
/src/functions/fetchBalanceTool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { sanitizeData } from "../responseUtils";
 2 | 
 3 | const BALANCE_API_URL = "https://app.termix.ai/api/bscBalanceCheck";
 4 | 
 5 | type BalanceData = {
 6 |   address: string;
 7 |   nativeBalance: string;
 8 |   tokenBalances: {
 9 |     token_address: string;
10 |     symbol: string;
11 |     name: string;
12 |     logo: string;
13 |     decimals: string;
14 |     balance: string;
15 |   }[];
16 | };
17 | 
18 | export async function getBalance(address: string) {
19 |   const response = await fetch(`${BALANCE_API_URL}?address=${address}`);
20 |   if (!response.ok) {
21 |     throw new Error(`Balance Fetch Error: ${response.statusText}`);
22 |   }
23 |   const result = await response.json() as BalanceData;
24 | 
25 |   return sanitizeData(result, {
26 |     strictMode: true,
27 |     maxLength: 200,
28 |     allowMarkdown: false
29 |   });
30 | 
31 | }
32 | 
```

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

```typescript
 1 | #!/usr/bin/env node
 2 | // src/index.ts
 3 | import { parseArgs } from 'node:util';
 4 | import { init } from './cli/init.js';
 5 | import { version } from './cli/version.js';
 6 | import { printHelp } from './cli/help.js';
 7 | import { main } from './main.js';
 8 | 
 9 | 
10 | process.on('uncaughtException', (error: Error) => {
11 |   console.error('❌ Uncaught exception:', error);
12 | });
13 | 
14 | process.on('unhandledRejection', (error: Error | unknown) => {
15 |   console.error('❌ Unhandled rejection:', error);
16 | });
17 | 
18 | interface CliOptions {
19 |   init?: boolean;
20 |   help?: boolean;
21 |   version?: boolean;
22 | }
23 | 
24 | let values: CliOptions;
25 | 
26 | try {
27 |   const args = parseArgs({
28 |     options: {
29 |       init: { type: 'boolean', short: 'i' },
30 |       help: { type: 'boolean', short: 'h' },
31 |       version: { type: 'boolean', short: 'v' },
32 |     },
33 |   });
34 |   values = args.values as CliOptions;
35 | } catch (err) {
36 |   console.error('❌ Unrecognized argument. For help, run `bnbchain-mcp --help`.');
37 |   process.exit(1);
38 | }
39 | 
40 | if (values.help) {
41 |   printHelp();
42 |   process.exit(0);
43 | }
44 | 
45 | if (values.version) {
46 |   console.log(version);
47 |   process.exit(0);
48 | }
49 | 
50 | if (values.init) {
51 |   await init(); // run init.js logic
52 | } else {
53 |   main().catch((error: Error) => {
54 |     console.error('❌ Fatal error in main():', error);
55 |     process.exit(1);
56 |   });
57 | }
58 | 
```

--------------------------------------------------------------------------------
/src/tools/getWalletInfo.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { z } from "zod";
 3 | import { getBalance } from "../functions/fetchBalanceTool.js";
 4 | import { getAccount } from "../config.js";
 5 | 
 6 | export function registerGetWalletInfo(server: McpServer) {
 7 |   server.tool("Get_Wallet_Info", "👛View detailed balance and holdings for any wallet address", {
 8 |       address: z.string().optional().describe("When querying the user's own wallet value, it is null"),
 9 |     },
10 |     async ({ address }) => {
11 |       try {
12 |         if (address === '' || !address || address === 'null') {
13 |           const account = await getAccount();
14 |           address = account.address
15 |         }
16 |         const balance = await getBalance(address);
17 | 
18 |         return {
19 |           content: [
20 |             {
21 |               type: "text",
22 |               text: `Native Balance (BNB): ${balance.nativeBalance}\n\nToken Balances:\n${JSON.stringify(balance.tokenBalances)}\n\nWallet Address: ${address}`,
23 |             },
24 |           ],
25 |         };
26 |       } catch (error) {
27 |         const errorMessage =
28 |           error instanceof Error ? error.message : String(error);
29 |         return {
30 |           content: [
31 |             { type: "text", text: `Failed to fetch balance: ${errorMessage}` },
32 |           ],
33 |           isError: true,
34 |         };
35 |       }
36 |     }
37 |   );
38 | }
39 | 
```

--------------------------------------------------------------------------------
/src/tools/pancakeMyPosition.ts:
--------------------------------------------------------------------------------

```typescript
 1 | 
 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 3 | import { myPosition } from "../functions/pancakeSwapPosition.js";
 4 | import { bigIntReplacer } from "../util.js";
 5 | import { getAccount } from "../config.js";
 6 | 
 7 | export function registerPancakeMyPosition(server: McpServer) {
 8 | 
 9 |     server.tool("View_PancakeSwap_Positions", "📊View your active liquidity positions on PancakeSwap", {}, async ({}) => {
10 | 
11 |             try {
12 |             
13 |                 const account = await getAccount();
14 |                 const positions = await myPosition(account.address);
15 |                 return {
16 |                     content: [
17 |                         {
18 |                             type: "text",
19 |                             text: `get user potitions successfully. ${JSON.stringify(positions, bigIntReplacer)}`
20 |                         },
21 |                     ],
22 |                 };
23 |             } catch (error) {
24 |                 const errorMessage =
25 |                     error instanceof Error ? error.message : String(error);
26 |                 return {
27 |                     content: [
28 |                         {
29 |                             type: "text",
30 |                             text: `get user potitions failed: ${errorMessage}`,
31 |                         },
32 |                     ],
33 |                     isError: true,
34 |                 };
35 |             }
36 |         }
37 |     );
38 | }
```

--------------------------------------------------------------------------------
/src/tools/queryMemeTokenDetails.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { z } from "zod";
 3 | import { memeTokenDetail } from "../functions/memeTokenDetails.js";
 4 | 
 5 | export function registerQueryMemeTokenDetails(server: McpServer) {
 6 |     server.tool(
 7 |         "QueryMemeTokenDetails",
 8 |         "Fetches token details for a given meme token using the four.meme API. Default price in USDT.",
 9 |         {
10 |             tokenName: z.string().describe("The name of the token to query (e.g., HGUSDT)")
11 |         },
12 |         async ({ tokenName }) => {
13 |             try {
14 |                 const data = await memeTokenDetail(tokenName);
15 | 
16 |                 return {
17 |                     content: [
18 |                         {
19 |                             type: "text",
20 |                             text: `Token details for "${tokenName}": ${JSON.stringify(data, null, 2)}`
21 |                         }
22 |                     ]
23 |                 };
24 |             } catch (error) {
25 |                 const errorMessage = error instanceof Error ? error.message : String(error);
26 |                 return {
27 |                     content: [
28 |                         {
29 |                             type: "text",
30 |                             text: `Failed to fetch token details: ${errorMessage}`
31 |                         }
32 |                     ],
33 |                     isError: true
34 |                 };
35 |             }
36 |         }
37 |     );
38 | }
39 | 
```

--------------------------------------------------------------------------------
/src/functions/memeTokenDetails.ts:
--------------------------------------------------------------------------------

```typescript
 1 | export const memeTokenDetail = async (tokenName: string) => {
 2 |     // Fetch both BNB price and token details in parallel
 3 |     const [bnbPriceRes, tokenRes] = await Promise.all([
 4 |         fetch("https://api.binance.com/api/v3/ticker/price?symbol=BNBUSDT"),
 5 |         fetch(`https://www.four.meme/meme-api/v1/private/token/query?tokenName=${encodeURIComponent(tokenName)}`, {
 6 |             method: "GET",
 7 |             headers: {
 8 |                 "Content-Type": "application/json",
 9 |                 Accept: "application/json"
10 |             }
11 |         })
12 |     ]);
13 | 
14 |     // Validate both responses
15 |     if (!bnbPriceRes.ok) throw new Error(`Failed to fetch BNB price: ${bnbPriceRes.status}`);
16 |     if (!tokenRes.ok) throw new Error(`Failed to fetch token details: ${tokenRes.status}`);
17 | 
18 |     const [bnbPriceJson, tokenJson] = await Promise.all([bnbPriceRes.json(), tokenRes.json()]);
19 | 
20 |     const BNB_TO_USDT = parseFloat(bnbPriceJson.price);
21 |     const token = tokenJson.data?.[0];
22 | 
23 |     if (!token || !token.tokenPrice?.price) {
24 |         return {
25 |             content: [{ type: "text", text: "Token not found or price unavailable." }],
26 |             isError: true
27 |         };
28 |     }
29 | 
30 |     const bnbPrice = parseFloat(token.tokenPrice.price);
31 |     const priceInUsdt = bnbPrice * BNB_TO_USDT;
32 | 
33 |     const data = {
34 |         ...token,
35 |         tokenPrice: {
36 |             ...token.tokenPrice,
37 |             priceInUsdt: priceInUsdt.toFixed(10)
38 |         }
39 |     };
40 | 
41 |     return data;
42 | };
43 | 
```

--------------------------------------------------------------------------------
/src/tools/transferNativeToken.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { z } from "zod";
 3 | import { parseEther } from "viem";
 4 | import { getAccount, walletClient } from "../config.js";
 5 | import { buildTxUrl, checkTransactionHash } from "../util.js";
 6 | 
 7 | export function registerTransferNativeToken(server: McpServer) {
 8 |   server.tool("Send_BNB", "💎Transfer native token (BNB), Before execution, check the wallet information first", {
 9 |       recipientAddress: z.string(),
10 |       amount: z.string(),
11 |     },
12 |     async ({ recipientAddress, amount }) => {
13 |       let txHash = undefined;
14 |       try {
15 | 
16 |         const account = await getAccount();
17 |         txHash = await walletClient(account).sendTransaction({
18 |           to: recipientAddress as `0x${string}`,
19 |           value: parseEther(amount),
20 |         });
21 | 
22 |         const txUrl = await checkTransactionHash(txHash)
23 |         
24 |         return {
25 |           content: [
26 |             {
27 |               type: "text",
28 |               text: `Transaction sent successfully. ${txUrl}`,
29 |               url: txUrl,
30 |             },
31 |           ],
32 |         };
33 |       } catch (error) {
34 |         const errorMessage =
35 |           error instanceof Error ? error.message : String(error);
36 |         const txUrl = buildTxUrl(txHash);
37 |         return {
38 |           content: [
39 |             {
40 |               type: "text",
41 |               text: `transaction failed: ${errorMessage}`,
42 |               url: txUrl,
43 |             },
44 |           ],
45 |           isError: true,
46 |         };
47 |       }
48 |     }
49 |   );
50 | }
51 | 
```

--------------------------------------------------------------------------------
/src/tools/pancakeSwap.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { z } from "zod";
 3 | import { pancakeSwap } from "../functions/pancakeSwapTool.js";
 4 | import { getAccount, publicClient } from "../config.js";
 5 | import { buildTxUrl, checkTransactionHash } from "../util.js";
 6 | 
 7 | export function registerPancakeSwap(server: McpServer) {
 8 |   server.tool("PancakeSwap_Token_Exchange", "💱Exchange tokens on BNBChain using PancakeSwap DEX", {
 9 |       inputToken: z.string(),
10 |       outputToken: z.string(),
11 |       amount: z.string(),
12 |     },
13 |     async ({ inputToken, outputToken, amount }) => {
14 |       let txHash = undefined;
15 |       try {
16 |         const account = await getAccount();
17 |         txHash = await pancakeSwap({
18 |           account,
19 |           inputToken,
20 |           outputToken,
21 |           amount,
22 |         });
23 |         const txUrl = await checkTransactionHash(txHash)
24 |         
25 |         return {
26 |           content: [
27 |             {
28 |               type: "text",
29 |               text: `PancakeSwap transaction sent successfully. ${txUrl}`,
30 |               url: txUrl,
31 |             },
32 |           ],
33 |         };
34 |       } catch (error) {
35 |         const errorMessage =
36 |           error instanceof Error ? error.message : String(error);
37 |         const txUrl = buildTxUrl(txHash);
38 |         return {
39 |           content: [
40 |             {
41 |               type: "text",
42 |               text: `transaction failed: ${errorMessage}`,
43 |               url: txUrl,
44 |             },
45 |           ],
46 |           isError: true,
47 |         };
48 |       }
49 |     }
50 |   );
51 | }
52 | 
```

--------------------------------------------------------------------------------
/src/tools/pancakeRemovePosition.ts:
--------------------------------------------------------------------------------

```typescript
 1 | 
 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 3 | import { z } from "zod";
 4 | import { removeLiquidityV3 } from "../functions/pancakeRemoveLiquidityTool.js";
 5 | import { getAccount } from "../config.js";
 6 | import { buildTxUrl, checkTransactionHash } from "../util.js";
 7 | 
 8 | export function registerPancakeRemovePosition(server: McpServer) {
 9 | 
10 |     server.tool("Remove_PancakeSwap_Liquidity", "🔄Withdraw your liquidity from PancakeSwap pools", {
11 |             positionId: z.string(),
12 |             percent: z.number().max(100).min(1),
13 |         },
14 |         async ({ positionId, percent }) => {
15 | 
16 |             let txHash = undefined;
17 |             try {
18 | 
19 |                 const account = await getAccount();
20 |                 txHash = await removeLiquidityV3(account, BigInt(positionId), percent);
21 |                 const txUrl = await checkTransactionHash(txHash)
22 |                 
23 |                 return {
24 |                     content: [
25 |                         {
26 |                             type: "text",
27 |                             text: `remove liquidity position on panceke successfully. ${txUrl}`,
28 |                             url: txUrl,
29 |                         },
30 |                     ],
31 |                 };
32 |             } catch (error) {
33 |                 const errorMessage =
34 |                   error instanceof Error ? error.message : String(error);
35 |                 const txUrl = buildTxUrl(txHash);
36 |                 return {
37 |                   content: [
38 |                     {
39 |                       type: "text",
40 |                       text: `transaction failed: ${errorMessage}`,
41 |                       url: txUrl,
42 |                     },
43 |                   ],
44 |                   isError: true,
45 |                 };
46 |             }
47 |         }
48 |     );
49 | }
```

--------------------------------------------------------------------------------
/src/tools/goplusSecurityCheck.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // @ts-ignore
 2 | import { GoPlus, ErrorCode } from "@goplus/sdk-node";
 3 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 4 | import { ChainId } from "@pancakeswap/sdk";
 5 | import { z } from "zod";
 6 | import { sanitizeData } from "../responseUtils";
 7 | 
 8 | export function registerGoplusSecurityCheck(server: McpServer) {
 9 |   server.tool("Token_Security_Check", "🔒Analyze BNBChain tokens for potential security risks powered by GoPlus", {
10 |       tokenAddress: z.string(),
11 |     },
12 |     async ({ tokenAddress }) => {
13 |       try {
14 |         const chainId = ChainId.BSC.toString(); // BSC chain ID
15 |         const addresses = [tokenAddress];
16 | 
17 |         // Call GoPlus API to check token security
18 |         let res = await (GoPlus as any).tokenSecurity(chainId, addresses, 30);
19 | 
20 |         if (res.code !== (ErrorCode as any).SUCCESS) {
21 |           return {
22 |             content: [
23 |               {
24 |                 type: "text",
25 |                 text: `Security check failed: ${res.message}`,
26 |               },
27 |             ],
28 |             isError: true,
29 |           };
30 |         }
31 |         
32 | 
33 |         const securityData = res.result[tokenAddress] || {};
34 | 
35 |         const sanitizedData = sanitizeData(securityData, {
36 |           strictMode: true,
37 |           maxLength: 200,
38 |           allowMarkdown: false
39 |         });
40 |         return {
41 |           content: [
42 |             {
43 |               type: "text",
44 |               text: `Security check successful for ${tokenAddress}: ${JSON.stringify(
45 |                 sanitizedData,
46 |                 null,
47 |                 2
48 |               )}`,
49 |             },
50 |           ],
51 |         };
52 |       } catch (error) {
53 |         const errorMessage =
54 |           error instanceof Error ? error.message : String(error);
55 |         return {
56 |           content: [
57 |             {
58 |               type: "text",
59 |               text: `Security check failed: ${errorMessage}`,
60 |             },
61 |           ],
62 |           isError: true,
63 |         };
64 |       }
65 |     }
66 |   );
67 | }
68 | 
```

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

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 3 | import dotenv from "dotenv";
 4 | dotenv.config();
 5 | 
 6 | // Import tool registrations
 7 | import { registerTransferNativeToken } from "./tools/transferNativeToken.js";
 8 | import { registerTransferBEP20Token } from "./tools/transferBEP20Token.js";
 9 | import { registerPancakeSwap } from "./tools/pancakeSwap.js";
10 | import { registerGetWalletInfo } from "./tools/getWalletInfo.js";
11 | import { registerBuyMemeToken } from "./tools/buyMemeToken.js";
12 | import { registerSellMemeToken } from "./tools/sellMemeToken.js";
13 | import { registerPancakeAddLiquidity } from "./tools/pancakeAddLiquidity.js";
14 | import { registerPancakeMyPosition } from "./tools/pancakeMyPosition.js";
15 | import { registerPancakeRemovePosition } from "./tools/pancakeRemovePosition.js";
16 | import { registerGoplusSecurityCheck } from "./tools/goplusSecurityCheck.js";
17 | import { registerQueryMemeTokenDetails } from "./tools/queryMemeTokenDetails.js";
18 | 
19 | // Main server entry
20 | export async function main() {
21 |     const server = new McpServer({
22 |         name: "bsc-mcp",
23 |         version: "1.0.0"
24 |     });
25 | 
26 |     // Register all tools
27 |     registerTransferNativeToken(server);
28 |     registerTransferBEP20Token(server);
29 |     registerPancakeSwap(server);
30 |     registerGetWalletInfo(server);
31 |     registerBuyMemeToken(server);
32 |     registerSellMemeToken(server);
33 |     registerPancakeAddLiquidity(server);
34 |     registerPancakeMyPosition(server);
35 |     registerPancakeRemovePosition(server);
36 |     registerGoplusSecurityCheck(server);
37 |     registerQueryMemeTokenDetails(server);
38 | 
39 |     const transport = new StdioServerTransport();
40 | 
41 |     transport.onmessage = (message /** @type {JSONRPCMessage} */) => {
42 |         console.log("📩 Received message:", JSON.stringify(message, null, 2));
43 |     };
44 | 
45 |     transport.onerror = (error) => {
46 |         console.error("🚨 Transport error:", error);
47 |     };
48 | 
49 |     await server.connect(transport);
50 | }
51 | 
```

--------------------------------------------------------------------------------
/src/tools/transferBEP20Token.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { z } from "zod";
 3 | import { parseUnits, getContract, Address, publicActions } from "viem"; 
 4 | import { bep20abi } from "../lib/bep20Abi.js";
 5 | import { getAccount, walletClient } from "../config.js";
 6 | import { buildTxUrl, checkTransactionHash } from "../util.js";
 7 | 
 8 | export function registerTransferBEP20Token(server: McpServer) {
 9 |   server.tool("Send_BEP20_Token", "📤Send any BEP-20 token to another wallet (requires wallet check first)", {
10 |       recipientAddress: z.string(),
11 |       amount: z.string(),
12 |       address: z.string(),
13 |     },
14 |     async ({ recipientAddress, amount, address }) => {
15 |       let txHash = undefined;
16 |       try {
17 |         // Get token details including address and decimals
18 | 
19 |         const account = await getAccount();
20 |         const client = walletClient(account).extend(publicActions)
21 | 
22 |         const contract = getContract({
23 |           address: address as Address,
24 |           abi: bep20abi,
25 |           client,
26 |         });
27 | 
28 |         const decimals = await contract.read.decimals();
29 |         // Parse the amount based on token decimals
30 |         const parsedAmount = parseUnits(amount, decimals);
31 | 
32 |         txHash = await contract.write.transfer([
33 |           `0x${recipientAddress.replace("0x", "")}`,
34 |           parsedAmount,
35 |         ], {
36 |           gas: BigInt(100000),
37 |         });
38 |         const txUrl = await checkTransactionHash(txHash)
39 |         
40 | 
41 |         return {
42 |           content: [
43 |             {
44 |               type: "text",
45 |               text: `BEP-20 token transfer sent successfully. ${txUrl}`,
46 |               url: txUrl,
47 |             },
48 |           ],
49 |         };
50 |       } catch (error) {
51 |         const errorMessage =
52 |           error instanceof Error ? error.message : String(error);
53 |         const txUrl = buildTxUrl(txHash);
54 |         return {
55 |           content: [
56 |             {
57 |               type: "text",
58 |               text: `transaction failed: ${errorMessage}`,
59 |               url: txUrl,
60 |             },
61 |           ],
62 |           isError: true,
63 |         };
64 |       }
65 |     }
66 |   );
67 | }
68 | 
```

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

```json
 1 | {
 2 |   "name": "bnbchain-mcp",
 3 |   "version": "1.0.12",
 4 |   "main": "index.js",
 5 |   "type": "module",
 6 |   "bin": {
 7 |     "bnbchain-mcp": "build/index.js"
 8 |   },
 9 |   "scripts": {
10 |     "test": "node test/testServer.js",
11 |     "test:privateAES": "npx jest src/test/privateAES.test.ts -t \"test\"",
12 |     "start": "node build/index.js",
13 |     "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
14 |     "init": "node ./build/init.js",
15 |     "init:build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\" && node ./build/init.js",
16 |     "publish:auto": "node scripts/publish.cjs"
17 |   },
18 |   "files": [
19 |     "build"
20 |   ],
21 |   "keywords": [
22 |     "binance-smart-chain",
23 |     "bsc",
24 |     "blockchain",
25 |     "web3",
26 |     "cryptocurrency",
27 |     "defi",
28 |     "model-context-protocol",
29 |     "mcp",
30 |     "claude-integration",
31 |     "ai-agents",
32 |     "token-transfers",
33 |     "smart-contracts",
34 |     "pancakeswap",
35 |     "bep20",
36 |     "swap",
37 |     "liquidity",
38 |     "wallet-management",
39 |     "blockchain-tools",
40 |     "crypto-cli",
41 |     "defi-tools",
42 |     "meme-tokens",
43 |     "token-deployment",
44 |     "blockchain-development",
45 |     "web3-infrastructure",
46 |     "crypto-automation",
47 |     "token-security",
48 |     "moralis-integration",
49 |     "bnb-chain"
50 |   ],
51 |   "author": "",
52 |   "license": "MIT",
53 |   "description": "",
54 |   "devDependencies": {
55 |     "@types/bcrypt": "^5.0.2",
56 |     "@types/jest": "^29.5.14",
57 |     "@types/figlet": "^1.7.0",
58 |     "@types/fs-extra": "^11.0.4",
59 |     "@types/node": "^22.10.0",
60 |     "@types/prompts": "^2.4.9",
61 |     "jest": "^29.7.0",
62 |     "ts-jest": "^29.3.0",
63 |     "typescript": "^5.8.2"
64 |   },
65 |   "dependencies": {
66 |     "@goplus/sdk-node": "^1.0.12",
67 |     "@modelcontextprotocol/sdk": "^1.4.0",
68 |     "@pancakeswap/sdk": "^5.8.8",
69 |     "@pancakeswap/smart-router": "6.1.6",
70 |     "@pancakeswap/tokens": "^0.6.24",
71 |     "@pancakeswap/v3-sdk": "^3.9.0",
72 |     "bcrypt": "^5.1.1",
73 |     "chalk": "^5.4.1",
74 |     "dotenv": "^16.4.7",
75 |     "figlet": "^1.8.0",
76 |     "fs-extra": "^11.3.0",
77 |     "graphql-request": "^7.1.2",
78 |     "moralis": "^2.27.2",
79 |     "ora": "^8.2.0",
80 |     "prompts": "^2.4.2",
81 |     "ts-node-dev": "^2.0.0",
82 |     "viem": "^2.23.11"
83 |   }
84 | }
85 | 
```

--------------------------------------------------------------------------------
/src/tools/pancakeAddLiquidity.ts:
--------------------------------------------------------------------------------

```typescript
 1 | 
 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 3 | import { z } from "zod";
 4 | import {
 5 |     parseUnits,
 6 | } from "viem";
 7 | import { addLiquidityV3 } from "../functions/pancakeAddLiquidityTool.js";
 8 | import { CurrencyAmount, } from "@pancakeswap/sdk";
 9 | import {
10 |     FeeAmount
11 | } from '@pancakeswap/v3-sdk';
12 | import { getToken } from "../functions/pancakeSwapTool.js";
13 | import { getAccount, } from "../config.js";
14 | import { buildTxUrl, checkTransactionHash } from "../util.js";
15 | 
16 | export function registerPancakeAddLiquidity(server: McpServer) {
17 | 
18 |     server.tool("Add_PancakeSwap_Liquidity", "💧Provide liquidity to PancakeSwap trading pairs", {
19 |             token0: z.string(),
20 |             token1: z.string(),
21 |             token0Amount: z.string(),
22 |             token1Amount: z.string(),
23 |         },
24 |         async ({ token0, token1, token0Amount, token1Amount }) => {
25 | 
26 |             let txHash = undefined;
27 |             try {
28 |             
29 |                 // Define tokens
30 |                 const tokenA = await getToken(token0);
31 |                 const tokenB = await getToken(token1);
32 |             
33 |                 // Amounts to add
34 |                 const amountTokenA = CurrencyAmount.fromRawAmount(tokenA, parseUnits(token0Amount, tokenA.decimals).toString());
35 |                 const amountTokenB = CurrencyAmount.fromRawAmount(tokenB, parseUnits(token1Amount, tokenB.decimals).toString());
36 |             
37 |                 const account = await getAccount();
38 | 
39 |                 txHash = await addLiquidityV3(
40 |                     tokenA,
41 |                     tokenB,
42 |                     FeeAmount.MEDIUM, // 0.3%
43 |                     amountTokenA,
44 |                     amountTokenB,
45 |                     account,
46 |                 );
47 | 
48 |                 const txUrl = await checkTransactionHash(txHash)
49 |                 return {
50 |                     content: [
51 |                         {
52 |                             type: "text",
53 |                             text: `add liquidity to pancake successfully. ${txUrl}`,
54 |                             url: txUrl,
55 |                         },
56 |                     ],
57 |                 };
58 |             } catch (error) {
59 |                 const errorMessage =
60 |                     error instanceof Error ? error.message : String(error);
61 |                 const txUrl = buildTxUrl(txHash);
62 |                 return {
63 |                     content: [
64 |                         {
65 |                             type: "text",
66 |                             text: `transaction failed: ${errorMessage}`,
67 |                             url: txUrl,
68 |                         },
69 |                     ],
70 |                     isError: true,
71 |                 };
72 |             }
73 |         }
74 |     );
75 | }
```

--------------------------------------------------------------------------------
/scripts/publish.cjs:
--------------------------------------------------------------------------------

```
 1 | #!/usr/bin/env node
 2 | 
 3 | const { execSync } = require('child_process');
 4 | const fs = require('fs');
 5 | const path = require('path');
 6 | 
 7 | // Logging helpers
 8 | const log = (msg) => console.log(`\n🟡 ${msg}`);
 9 | const success = (msg) => console.log(`✅ ${msg}`);
10 | const error = (msg) => {
11 |     console.error(`❌ ${msg}`);
12 |     process.exit(1);
13 | };
14 | 
15 | // Paths
16 | const pkgPath = path.resolve('package.json');
17 | const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
18 | const branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
19 | const args = process.argv.slice(2);
20 | 
21 | // Run shell command
22 | const run = (cmd) => {
23 |     log(`Running: ${cmd}`);
24 |     execSync(cmd, { stdio: 'inherit' });
25 | };
26 | 
27 | // Auto-increment patch version
28 | const bumpPatchVersion = (version) => {
29 |     const [major, minor, patch] = version.split('.').map(Number);
30 |     return `${major}.${minor}.${patch + 1}`;
31 | };
32 | 
33 | // Swap README for NPM
34 | const swapReadmeForNpm = () => {
35 |     const npmReadme = path.resolve('README.npm.md');
36 |     const targetReadme = path.resolve('README.md');
37 | 
38 |     if (fs.existsSync(npmReadme)) {
39 |         fs.copyFileSync(targetReadme, path.resolve('README.github.md'));
40 |         fs.copyFileSync(npmReadme, targetReadme);
41 |         success('Swapped README.npm.md → README.md for NPM publish');
42 |     } else {
43 |         log('No README.npm.md found. Using default README.md');
44 |     }
45 | };
46 | 
47 | // Restore original README
48 | const restoreReadme = () => {
49 |     const ghReadme = path.resolve('README.github.md');
50 |     const targetReadme = path.resolve('README.md');
51 | 
52 |     if (fs.existsSync(ghReadme)) {
53 |         fs.copyFileSync(ghReadme, targetReadme);
54 |         fs.unlinkSync(ghReadme);
55 |         success('Restored original GitHub README.md');
56 |     }
57 | };
58 | 
59 | // Main flow
60 | const main = () => {
61 |     console.log(`📍 Current Git Branch: ${branch}`);
62 | 
63 |     if (!fs.existsSync('build/index.js')) {
64 |         error('Build not found. Run `npm run build` first.');
65 |     }
66 | 
67 |     // Check for version override
68 |     const versionFlagIndex = args.indexOf('--version');
69 |     let newVersion = '';
70 | 
71 |     if (versionFlagIndex !== -1 && args[versionFlagIndex + 1]) {
72 |         newVersion = args[versionFlagIndex + 1];
73 |         success(`Custom version passed via CLI: ${newVersion}`);
74 |     } else {
75 |         newVersion = bumpPatchVersion(pkg.version);
76 |         success(`Auto-incremented patch version: ${pkg.version} → ${newVersion}`);
77 |     }
78 | 
79 |     pkg.version = newVersion;
80 |     fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
81 | 
82 |     swapReadmeForNpm();
83 | 
84 |     run('npm run build');
85 | 
86 |     const tag = branch === 'develop' ? '--tag alpha' : '';
87 |     run(`npm publish ${tag}`);
88 | 
89 |     restoreReadme();
90 | 
91 |     success(`Published ${pkg.name}@${newVersion} to NPM (${tag || 'latest'})`);
92 | };
93 | 
94 | main();
95 | 
```

--------------------------------------------------------------------------------
/src/PrivateAES.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import * as crypto from 'crypto';
 2 | import * as bcrypt from 'bcrypt';
 3 | 
 4 | const algorithm = 'aes-256-gcm';
 5 | 
 6 | /**
 7 |  * Encrypts an EVM private key using bcrypt and user password
 8 |  * @param privateKey The EVM private key to encrypt
 9 |  * @param password User-provided password
10 |  * @returns Encrypted string (Base64 encoded)
11 |  */
12 | export async function encryptPrivateKey(privateKey: string, password: string): Promise<string> {
13 |   // bcrypt work factor - higher is more secure but slower
14 |   const saltRounds = 12;
15 |   
16 |   // Generate bcrypt hash as key material
17 |   const hashedPassword = await bcrypt.hash(password, saltRounds);
18 |   
19 |   // Extract bcrypt salt - it's in the first 29 characters of the hash
20 |   const bcryptSalt = hashedPassword.substring(0, 29);
21 |   
22 |   // Generate symmetric encryption key using SHA-256 from bcrypt hash
23 |   const key = crypto.createHash('sha256').update(hashedPassword).digest();
24 |   
25 |   // Generate random initialization vector
26 |   const iv = crypto.randomBytes(12);
27 |   
28 |   // Create cipher object
29 |   const cipher = crypto.createCipheriv(algorithm, key, iv);
30 |   
31 |   // Encrypt private key
32 |   let encrypted = cipher.update(privateKey, 'utf8', 'base64');
33 |   encrypted += cipher.final('base64');
34 |   
35 |   // Get authentication tag
36 |   const authTag = cipher.getAuthTag();
37 |   
38 |   // Concatenate all necessary components and encode as Base64 string
39 |   const result = Buffer.concat([
40 |     Buffer.from(bcryptSalt), // Store bcrypt salt
41 |     iv,
42 |     authTag,
43 |     Buffer.from(encrypted, 'base64')
44 |   ]);
45 |   
46 |   return result.toString('base64');
47 | }
48 | 
49 | /**
50 |  * Decrypts an EVM private key using bcrypt and user password
51 |  * @param encryptedData Encrypted private key data (Base64 encoded)
52 |  * @param password User-provided password
53 |  * @returns Decrypted private key or null (if password is incorrect)
54 |  */
55 | export async function decryptPrivateKey(encryptedData: string, password: string): Promise<string | null> {
56 |   try {
57 |     // Decode Base64
58 |     const data = Buffer.from(encryptedData, 'base64');
59 |     
60 |     // Extract components
61 |     const bcryptSalt = data.subarray(0, 29).toString();
62 |     const iv = data.subarray(29, 41);
63 |     const authTag = data.subarray(41, 57);
64 |     const encrypted = data.subarray(57).toString('base64');
65 |     
66 |     // Use bcrypt to check password
67 |     // Reconstruct salt and password to create the same hash as during encryption
68 |     const hashedPassword = await bcrypt.hash(password, bcryptSalt);
69 |     
70 |     // Generate key using the same method from the hash
71 |     const key = crypto.createHash('sha256').update(hashedPassword).digest();
72 |     
73 |     // Create decipher
74 |     const decipher = crypto.createDecipheriv(algorithm, key, iv);
75 |     decipher.setAuthTag(authTag);
76 |     
77 |     // Decrypt
78 |     let decrypted = decipher.update(encrypted, 'base64', 'utf8');
79 |     decrypted += decipher.final('utf8');
80 |     
81 |     return decrypted;
82 |   } catch (error) {
83 |     // Decryption failed (incorrect password or data corruption)
84 |     return null;
85 |   }
86 | }
87 | 
```

--------------------------------------------------------------------------------
/src/tools/sellMemeToken.ts:
--------------------------------------------------------------------------------

```typescript
  1 | 
  2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  3 | import { z } from "zod";
  4 | import {
  5 |     parseUnits,
  6 |     type Hex,
  7 | } from "viem";
  8 | import { getAccount, publicClient, walletClient } from "../config.js";
  9 | import { AddressConfig } from "../addressConfig.js";
 10 | import { buildTxUrl, checkTransactionHash } from "../util.js";
 11 | 
 12 | const tokenAbi = [
 13 |     { "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" }], "name": "allowance", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" },
 14 |     { "inputs": [{ "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }], "name": "approve", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "nonpayable", "type": "function" },
 15 | 
 16 | ]
 17 | 
 18 | export function registerSellMemeToken(server: McpServer) {
 19 | 
 20 |     server.tool("Sell_Meme_Token", "💰Sell meme tokens for other currencies", {
 21 |             token: z.string(),
 22 |             tokenValue: z.string(),
 23 |         },
 24 |         async ({ token, tokenValue }) => {
 25 | 
 26 |             let txHash = undefined;
 27 |             try {
 28 | 
 29 |                 const account = await getAccount();
 30 |                 const allowanceAmount = await publicClient.readContract({
 31 |                     address: token as Hex,
 32 |                     abi: tokenAbi,
 33 |                     functionName: 'allowance',
 34 |                     args: [account.address, AddressConfig.FourMemeSellTokenAMAPContract],
 35 |                 }) as bigint;
 36 |                 if (allowanceAmount < parseUnits(tokenValue, 18)) {
 37 | 
 38 |                     const hash = await walletClient(account).writeContract({
 39 |                         account,
 40 |                         address: token as Hex,
 41 |                         abi: tokenAbi,
 42 |                         functionName: 'approve',
 43 |                         args: [AddressConfig.FourMemeSellTokenAMAPContract, parseUnits(tokenValue, 18)],
 44 |                     });
 45 | 
 46 |                     await publicClient.waitForTransactionReceipt({
 47 |                         hash: hash,
 48 |                         retryCount: 300,
 49 |                         retryDelay: 100,
 50 |                     });
 51 |                 }
 52 | 
 53 | 
 54 |                 txHash = await walletClient(account).writeContract({
 55 |                     account,
 56 |                     address: AddressConfig.FourMemeSellTokenAMAPContract,
 57 |                     abi: [{
 58 |                         "inputs": [
 59 |                             {
 60 |                                 "internalType": "address",
 61 |                                 "name": "token",
 62 |                                 "type": "address"
 63 |                             },
 64 |                             {
 65 |                                 "internalType": "uint256",
 66 |                                 "name": "amount",
 67 |                                 "type": "uint256"
 68 |                             }
 69 |                         ],
 70 |                         "name": "sellToken",
 71 |                         "outputs": [],
 72 |                         "stateMutability": "nonpayable",
 73 |                         "type": "function"
 74 |                     }],
 75 |                     functionName: 'sellToken',
 76 |                     args: [token as Hex, parseUnits(tokenValue, 18)],
 77 |                 });
 78 | 
 79 |                 const txUrl = await checkTransactionHash(txHash)
 80 |         
 81 |                 return {
 82 |                     content: [
 83 |                         {
 84 |                             type: "text",
 85 |                             text: `sell meme token successfully. ${txUrl}`,
 86 |                             url: txUrl,
 87 |                         },
 88 |                     ],
 89 |                 };
 90 |             } catch (error) {
 91 |                 const errorMessage =
 92 |                   error instanceof Error ? error.message : String(error);
 93 |                 const txUrl = buildTxUrl(txHash);
 94 |                 return {
 95 |                   content: [
 96 |                     {
 97 |                       type: "text",
 98 |                       text: `transaction failed: ${errorMessage}`,
 99 |                       url: txUrl,
100 |                     },
101 |                   ],
102 |                   isError: true,
103 |                 };
104 |             }
105 |         }
106 |     );
107 | }
```

--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Hex, http, publicActions, createWalletClient, createPublicClient, PrivateKeyAccount } from "viem";
  2 | import { privateKeyToAccount } from "viem/accounts";
  3 | import { bsc } from "viem/chains";
  4 | import { getPassword, } from "./util.js";
  5 | import { decryptPrivateKey, } from "./PrivateAES.js";
  6 | 
  7 | export const rpcUrl = process.env.BSC_RPC_URL || "https://bsc-dataseed.binance.org";
  8 | 
  9 | class ObfuscatedSecureBuffer {
 10 |     private buffers: Uint8Array[] = [];
 11 |     private indexMap: number[] = [];
 12 |     private salt: Uint8Array = new Uint8Array(32);
 13 |     private length: number = 0;
 14 |     private isActive = false;
 15 | 
 16 |     constructor() {
 17 |     }
 18 |     updata(data: Uint8Array | string) {
 19 |         let originalData: Uint8Array;
 20 |         if (typeof data === 'string') {
 21 |             const hexString = data.startsWith('0x') ? data.slice(2) : data;
 22 |             originalData = new Uint8Array(hexString.length / 2);
 23 |             for (let i = 0; i < hexString.length; i += 2) {
 24 |                 originalData[i / 2] = parseInt(hexString.substr(i, 2), 16);
 25 |             }
 26 |         } else {
 27 |             originalData = new Uint8Array(data);
 28 |         }
 29 | 
 30 |         this.length = originalData.length;
 31 | 
 32 |         this.salt = new Uint8Array(32);
 33 |         crypto.getRandomValues(this.salt);
 34 | 
 35 |         const bufferCount = 3;
 36 |         this.buffers = [];
 37 | 
 38 |         for (let i = 0; i < bufferCount; i++) {
 39 |             const buffer = new Uint8Array(this.length);
 40 |             crypto.getRandomValues(buffer);
 41 |             this.buffers.push(buffer);
 42 |         }
 43 | 
 44 |         this.indexMap = Array.from({ length: this.length }, (_, i) => i)
 45 |             .sort(() => 0.5 - Math.random());
 46 | 
 47 |         for (let i = 0; i < this.length; i++) {
 48 |             const targetIndex = this.indexMap[i];
 49 |             const bufferIndex = i % bufferCount;
 50 | 
 51 |             const saltByte = this.salt[i % this.salt.length];
 52 |             const obfuscatedByte = originalData[i] ^ saltByte ^ (i & 0xff);
 53 | 
 54 |             this.buffers[bufferIndex][targetIndex] = obfuscatedByte;
 55 |         }
 56 |         this.isActive = true;
 57 |     }
 58 | 
 59 |     getData(): Uint8Array {
 60 |         const result = new Uint8Array(this.length);
 61 |         const bufferCount = this.buffers.length;
 62 | 
 63 |         for (let i = 0; i < this.length; i++) {
 64 |             const targetIndex = this.indexMap[i];
 65 |             const bufferIndex = i % bufferCount;
 66 | 
 67 |             const saltByte = this.salt[i % this.salt.length];
 68 |             const obfuscatedByte = this.buffers[bufferIndex][targetIndex];
 69 |             result[i] = obfuscatedByte ^ saltByte ^ (i & 0xff);
 70 |         }
 71 | 
 72 |         return result;
 73 |     }
 74 | 
 75 |     getHexString(): string {
 76 |         const data = this.getData();
 77 |         return '0x' + Array.from(data)
 78 |             .map(b => b.toString(16).padStart(2, '0'))
 79 |             .join('');
 80 |     }
 81 | 
 82 |     zeroize(): void {
 83 |         for (const buffer of this.buffers) {
 84 |             buffer.fill(0);
 85 |             crypto.getRandomValues(buffer);
 86 |             buffer.fill(0xff);
 87 |             buffer.fill(0);
 88 |         }
 89 | 
 90 |         this.salt.fill(0);
 91 |         this.indexMap.fill(0);
 92 | 
 93 |         this.buffers = [];
 94 |         this.indexMap = [];
 95 | 
 96 |         this.isActive = false;
 97 |     }
 98 | 
 99 |     active(): boolean {
100 |         return this.isActive;
101 |     }
102 | }
103 | 
104 | 
105 | let obfuscatedPrivateKey = new ObfuscatedSecureBuffer();;
106 | export const getAccount = async () => {
107 |     const BSC_WALLET_PRIVATE_KEY = process.env.BSC_WALLET_PRIVATE_KEY as Hex
108 |     if (!BSC_WALLET_PRIVATE_KEY) {
109 |         throw new Error("BSC_WALLET_PRIVATE_KEY is not defined");
110 |     }
111 |     if (obfuscatedPrivateKey.active()) {
112 |         const pk = obfuscatedPrivateKey.getHexString();
113 |         return privateKeyToAccount(
114 |             pk as Hex
115 |         );
116 |     }
117 | 
118 |     const { agreed, value: password } = await getPassword()
119 |     if (!password) {
120 |         throw new Error("You did not enter a password.");
121 |     }
122 | 
123 |     const pk = await decryptPrivateKey(BSC_WALLET_PRIVATE_KEY, password);
124 | 
125 |     if (agreed) {
126 | 
127 |         obfuscatedPrivateKey.updata(pk as string);
128 |         setTimeout(() => {
129 |             obfuscatedPrivateKey.zeroize();
130 |         }, 1000 * 60 * 60);
131 |         return privateKeyToAccount(
132 |             pk as Hex
133 |         );
134 |     } else {
135 | 
136 |         return privateKeyToAccount(
137 |             pk as Hex
138 |         );
139 |     }
140 | 
141 | };
142 | 
143 | export const getClient = async () => {
144 |     const account = await getAccount()
145 |     const client = createWalletClient({
146 |         account,
147 |         chain: bsc,
148 |         transport: http(rpcUrl),
149 |     }).extend(publicActions);
150 | 
151 |     return client;
152 | };
153 | 
154 | export const publicClient = createPublicClient({
155 |     chain: bsc,
156 |     transport: http(rpcUrl),
157 | });
158 | 
159 | export const walletClient = (account: PrivateKeyAccount) => createWalletClient({
160 |     chain: bsc,
161 |     transport: http(rpcUrl),
162 |     account: account,
163 | });
```

--------------------------------------------------------------------------------
/src/responseUtils.ts:
--------------------------------------------------------------------------------

```typescript
  1 | 
  2 | /**
  3 |  * Clean the string, remove or escape characters and patterns that may cause prompt injection
  4 |  * 
  5 |  * @param input The input string to be cleaned
  6 |  * @param options Cleaning options
  7 |  * @returns Cleaned safe string
  8 |  */
  9 | export function sanitizeString(
 10 |     input: string, 
 11 |     options: {
 12 |       maxLength?: number,         // Maximum allowed length
 13 |       strictMode?: boolean,       // Strict mode (more aggressive filtering)
 14 |       allowMarkdown?: boolean,    // Whether to allow markdown syntax
 15 |       escapeQuotes?: boolean      // Whether to escape quotes instead of removing
 16 |     } = {}
 17 |   ): string {
 18 |     // Set default values
 19 |     const {
 20 |       maxLength = 500,
 21 |       strictMode = true,
 22 |       allowMarkdown = false,
 23 |       escapeQuotes = true
 24 |     } = options;
 25 |     
 26 |     if (!input || typeof input !== 'string') {
 27 |       return '';
 28 |     }
 29 |     
 30 |     let sanitized = input;
 31 |     
 32 |     // 1. Remove possible code blocks and formatted text
 33 |     if (!allowMarkdown) {
 34 |       // Remove code blocks
 35 |       sanitized = sanitized.replace(/```[\s\S]*?```/g, "[Code block removed]");
 36 |       // Remove inline code
 37 |       sanitized = sanitized.replace(/`[^`]*`/g, "[Code removed]");
 38 |     }
 39 |     
 40 |     // 2. Handle possible closing symbols and instruction patterns
 41 |     
 42 |     // Handle HTML/XML tags
 43 |     sanitized = sanitized.replace(/<[^>]*>/g, "");
 44 |     
 45 |     // Handle various bracket pairs
 46 |     sanitized = sanitized.replace(/\{[\s\S]*?\}/g, "[Content filtered]"); // Curly brackets
 47 |     sanitized = sanitized.replace(/\[[\s\S]*?\]/g, "[Content filtered]"); // Square brackets
 48 |     sanitized = sanitized.replace(/\([\s\S]*?\)/g, "[Content filtered]"); // Parentheses
 49 |     
 50 |     // 3. Handle potential instruction keywords
 51 |     const aiKeywords = [
 52 |       "system", "user", "assistant", "model", "prompt", "instruction", 
 53 |       "context", "token", "function", "completion", "response", "davinci", 
 54 |       "claude", "gpt", "llm", "api", "openai", "anthropic"
 55 |     ];
 56 |     
 57 |     const keywordPattern = new RegExp(`\\b(${aiKeywords.join('|')})\\b`, 'gi');
 58 |     sanitized = sanitized.replace(keywordPattern, (match) => `_${match}_`);
 59 |     
 60 |     // 4. Handle quotes (escape or remove)
 61 |     if (escapeQuotes) {
 62 |       // Escape quotes
 63 |       sanitized = sanitized.replace(/"/g, '\\"').replace(/'/g, "\\'");
 64 |     } else {
 65 |       // Remove quotes
 66 |       sanitized = sanitized.replace(/["']/g, "");
 67 |     }
 68 |     
 69 |     // 5. Additional processing in strict mode
 70 |     if (strictMode) {
 71 |       // Remove all possible control characters and special characters
 72 |       sanitized = sanitized.replace(/[\u0000-\u001F\u007F-\u009F\u2000-\u200F\u2028-\u202F]/g, "");
 73 |       
 74 |       // Handle possible injection separators and special patterns
 75 |       sanitized = sanitized.replace(/\.\.\./g, "…"); // Ellipsis
 76 |       sanitized = sanitized.replace(/\-\-\-+/g, "—"); // Em dash
 77 |       sanitized = sanitized.replace(/={2,}/g, "==");  // Equal sign sequence
 78 |       
 79 |       // Handle URL and link patterns
 80 |       sanitized = sanitized.replace(/(https?:\/\/[^\s]+)/g, "[Link removed]");
 81 |     }
 82 |     
 83 |     // 6. Handle possible JSON structure markers
 84 |     sanitized = sanitized
 85 |       .replace(/(\s*"\w+"\s*:)/g, "【Property】:")  // JSON property name
 86 |       .replace(/(\[\s*\]|\{\s*\})/g, "【Empty】"); // Empty array or object
 87 |     
 88 |     // 7. Limit length
 89 |     if (sanitized.length > maxLength) {
 90 |       sanitized = sanitized.substring(0, maxLength) + "...";
 91 |     }
 92 |     
 93 |     return sanitized;
 94 |   }
 95 |   
 96 |   /**
 97 |    * Recursively process the object, applying the sanitizeString function to all string values
 98 |    * 
 99 |    * @param data The data object to be processed
100 |    * @param options Options to be passed to sanitizeString
101 |    * @param maxDepth Maximum recursion depth
102 |    * @param currentDepth Current recursion depth
103 |    * @returns Processed safe data object
104 |    */
105 |   export function sanitizeData(
106 |     data: any,
107 |     options = {},
108 |     maxDepth = 5,
109 |     currentDepth = 0
110 |   ): any {
111 |     // Handle recursion depth limit
112 |     if (currentDepth >= maxDepth) {
113 |       return typeof data === 'object' && data !== null 
114 |         ? "[Nested object simplified]" 
115 |         : data;
116 |     }
117 |     
118 |     // Basic types return directly
119 |     if (data === null || data === undefined) {
120 |       return data;
121 |     }
122 |     
123 |     // Handle string
124 |     if (typeof data === 'string') {
125 |       return sanitizeString(data, options);
126 |     }
127 |     
128 |     // Handle numbers and booleans
129 |     if (typeof data === 'number' || typeof data === 'boolean') {
130 |       return data;
131 |     }
132 |     
133 |     // Handle array
134 |     if (Array.isArray(data)) {
135 |       return data.map(item => sanitizeData(item, options, maxDepth, currentDepth + 1));
136 |     }
137 |     
138 |     // Handle object
139 |     if (typeof data === 'object') {
140 |       const result: Record<string, any> = {};
141 |       for (const key in data) {
142 |         if (Object.prototype.hasOwnProperty.call(data, key)) {
143 |           // Also clean the property name
144 |           const safeKey = key.replace(/[<>{}\[\]]/g, "");
145 |           result[safeKey] = sanitizeData(data[key], options, maxDepth, currentDepth + 1);
146 |         }
147 |       }
148 |       return result;
149 |     }
150 |     
151 |     // Other types
152 |     return String(data);
153 |   }
```

--------------------------------------------------------------------------------
/src/functions/pancakeSwapTool.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {
  2 |   Hash,
  3 |   getContract,
  4 |   Hex,
  5 |   parseUnits,
  6 |   isAddress,
  7 |   PrivateKeyAccount,
  8 |   Address,
  9 | } from "viem";
 10 | import { erc20Abi, hexToBigInt, maxUint256 } from "viem";
 11 | import {
 12 |   ChainId,
 13 |   CurrencyAmount,
 14 |   Native,
 15 |   Percent,
 16 |   Token,
 17 |   TradeType,
 18 | } from "@pancakeswap/sdk";
 19 | import {
 20 |   SMART_ROUTER_ADDRESSES,
 21 |   SmartRouter,
 22 |   SmartRouterTrade,
 23 |   SwapRouter,
 24 | } from "@pancakeswap/smart-router";
 25 | import { GraphQLClient } from "graphql-request";
 26 | import { publicClient, walletClient } from "../config.js";
 27 | import { bep20abi } from "../lib/bep20Abi.js";
 28 | 
 29 | 
 30 | export const getToken = async (
 31 |   token: string,
 32 | ) => {
 33 |   if (token.toUpperCase() === "BNB") {
 34 |     return Native.onChain(ChainId.BSC)
 35 |   }
 36 |   let address = token.toLowerCase()
 37 |   let decimal;
 38 | 
 39 |   const url = "https://tokens.pancakeswap.finance/pancakeswap-extended.json";
 40 | 
 41 |   const resp = await fetch(url);
 42 |   const data = await resp.json();
 43 |   let tokens = data.tokens
 44 |   let symbol;
 45 | 
 46 |   if (!isAddress(address)) {
 47 |     const tokenInfo = tokens.find((item: any) => item.symbol.toLowerCase() === address)
 48 |     if (!tokenInfo) {
 49 |       throw new Error("Token not found");
 50 |     }
 51 |     address = tokenInfo.address
 52 |     decimal = tokenInfo.decimals
 53 |     symbol = tokenInfo.symbol
 54 |   } else {
 55 |     const tokenInfo = tokens.find((item: any) => item.address.toLowerCase() === address)
 56 |     if (!tokenInfo) {
 57 |       
 58 |       const contract = getContract({
 59 |         address: address as Address,
 60 |         abi: bep20abi,
 61 |         client: publicClient,
 62 |       });
 63 | 
 64 |       decimal = await contract.read.decimals();
 65 |       symbol = await contract.read.symbol();
 66 |     } else {
 67 |         
 68 |       decimal = tokenInfo.decimals
 69 |       symbol = tokenInfo.symbol
 70 |     }
 71 |   }
 72 |   
 73 |   return new Token(
 74 |     ChainId.BSC,
 75 |     address as Hex,
 76 |     decimal,
 77 |     symbol,
 78 |   )
 79 | 
 80 | 
 81 | }
 82 | 
 83 | export const pancakeSwap = async ({
 84 |   account,
 85 |   inputToken,
 86 |   outputToken,
 87 |   amount,
 88 | }: {
 89 |   // privateKey: string;
 90 |   amount: string;
 91 |   inputToken: string;
 92 |   outputToken: string;
 93 |   account: PrivateKeyAccount;
 94 | }): Promise<Hash> => {
 95 | 
 96 |   const chainId = ChainId.BSC
 97 | 
 98 | 
 99 |   let currencyA = await getToken(inputToken);
100 |   let currencyB = await getToken(outputToken);
101 |   let amountDecimal = currencyA.decimals;
102 | 
103 |   const parseAmountIn = parseUnits(amount, amountDecimal);
104 |   const amountValue = CurrencyAmount.fromRawAmount(currencyA, parseAmountIn)
105 | 
106 |   if (!currencyA.isNative) {
107 |     const TokenContract = getContract({
108 |       address: currencyA.address,
109 |       abi: erc20Abi,
110 |       client: {
111 |         wallet: walletClient(account),
112 |         public: publicClient,
113 |       },
114 |     });
115 |     if (
116 |       !TokenContract.write ||
117 |       !TokenContract.write.approve ||
118 |       !TokenContract.read.allowance
119 |     ) {
120 |       throw new Error("Unable to Swap Tokens");
121 |     }
122 | 
123 |     amountDecimal = await TokenContract.read.decimals();
124 | 
125 |     const smartRouterAddress = SMART_ROUTER_ADDRESSES[ChainId.BSC]
126 |     const allowance = await TokenContract.read.allowance([account.address, smartRouterAddress]) as bigint
127 |     if (allowance < parseAmountIn) {
128 |       const approveResult = await TokenContract.write.approve([smartRouterAddress, parseAmountIn])
129 | 
130 |       await publicClient.waitForTransactionReceipt({
131 |         hash: approveResult,
132 |       });
133 |     }
134 |   }
135 | 
136 |   const quoteProvider = SmartRouter.createQuoteProvider({
137 |     onChainProvider: () => publicClient,
138 |   })
139 |   const v3SubgraphClient = new GraphQLClient('https://api.thegraph.com/subgraphs/name/pancakeswap/exchange-v3-bsc1')
140 |   const v2SubgraphClient = new GraphQLClient('https://proxy-worker-api.pancakeswap.com/bsc-exchange1')
141 | 
142 |   const [v2Pools, v3Pools] = await Promise.all([
143 |     SmartRouter.getV3CandidatePools({
144 |       onChainProvider: () => publicClient,
145 |       // @ts-ignore
146 |       subgraphProvider: () => v3SubgraphClient,
147 |       currencyA: currencyA,
148 |       currencyB: currencyB,
149 |       subgraphFallback: false,
150 |     }),
151 |     SmartRouter.getV2CandidatePools({
152 |       onChainProvider: () => publicClient,
153 |       // @ts-ignore
154 |       v2SubgraphProvider: () => v2SubgraphClient,
155 |       // @ts-ignore
156 |       v3SubgraphProvider: () => v3SubgraphClient,
157 |       currencyA: currencyA,
158 |       currencyB: currencyB,
159 |     }),
160 |   ])
161 | 
162 |   const pools = [...v2Pools, ...v3Pools]
163 |   const trade = await SmartRouter.getBestTrade(amountValue, currencyB, TradeType.EXACT_INPUT, {
164 |     gasPriceWei: () => publicClient.getGasPrice(),
165 |     maxHops: 2,
166 |     maxSplits: 2,
167 |     poolProvider: SmartRouter.createStaticPoolProvider(pools),
168 |     quoteProvider,
169 |     quoterOptimization: true,
170 |     
171 |   }) as SmartRouterTrade<TradeType>
172 | 
173 | 
174 |   const { value, calldata } = SwapRouter.swapCallParameters(trade, {
175 |     recipient: account.address,
176 |     slippageTolerance: new Percent(500, 10000),
177 |   })
178 | 
179 |   const tx = {
180 |     account: account.address,
181 |     // @ts-ignore
182 |     to: SMART_ROUTER_ADDRESSES[chainId],
183 |     data: calldata,
184 |     value: hexToBigInt(value),
185 |   };
186 |   const gasEstimate = await publicClient.estimateGas(tx);
187 | 
188 |   const calculateGasMargin = (value: bigint, margin = 1000n): bigint => {
189 |     return (value * (10000n + margin)) / 10000n;
190 |   };
191 | 
192 |   const txHash = await walletClient(account).sendTransaction({
193 |     account: account,
194 |     chainId,
195 |     // @ts-ignore
196 |     to: SMART_ROUTER_ADDRESSES[chainId],
197 |     data: calldata,
198 |     value: hexToBigInt(value),
199 |     gas: calculateGasMargin(gasEstimate),
200 |   });
201 |   return txHash;
202 | };
203 | 
204 | 
205 | 
206 | 
207 | 
208 | 
209 | 
210 | 
211 | 
```

--------------------------------------------------------------------------------
/src/init.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import prompts, { PromptObject } from 'prompts';
  2 | import figlet from 'figlet';
  3 | import chalk from 'chalk';
  4 | import path from 'path';
  5 | import fs from 'fs-extra';
  6 | import os from 'os';
  7 | import { fileURLToPath } from 'url';
  8 | import { encryptPrivateKey } from './PrivateAES.js';
  9 | 
 10 | import dotenv from "dotenv";
 11 | import { Hex } from 'viem';
 12 | import { privateKeyToAccount } from 'viem/accounts';
 13 | dotenv.config();
 14 | 
 15 | // Binance Gold Color
 16 | const yellow = chalk.hex('#F0B90B');
 17 | 
 18 | // ESModule __dirname workaround
 19 | const __filename = fileURLToPath(import.meta.url);
 20 | const __dirname = path.dirname(__filename);
 21 | 
 22 | // Cancel handler
 23 | const onCancel = () => {
 24 |     console.log(chalk.red('\n❌ Configuration cancelled by user (Ctrl+C or ESC). Exiting...'));
 25 |     process.exit(0);
 26 | };
 27 | 
 28 | // Show Banner
 29 | const showBanner = () => {
 30 |     const banner = figlet.textSync('BNB Chain MCP ', { font: 'Big' });
 31 |     console.log(yellow(banner));
 32 |     console.log(yellow('🚀 Welcome to the BNB Chain MCP Configurator\n'));
 33 | };
 34 | 
 35 | // User Input Types
 36 | interface UserInputs {
 37 |     walletPassword: string;
 38 |     privateKey: string;
 39 |     rpcUrl?: string;
 40 | }
 41 | function validatePassword(password: string) {
 42 |     // At least 8 characters
 43 |     if (password.trim() === '') return 'Wallet Password is required!';
 44 |     if (password.length < 8 || password.length > 128) return 'Wallet Password must be between 8 and 128 characters!';
 45 |     
 46 |     // Check if it contains at least one lowercase letter
 47 |     if (!/[a-z]/.test(password)) return 'Wallet Password must contain at least one lowercase letter!';
 48 |     
 49 |     // Check if it contains at least one uppercase letter
 50 |     if (!/[A-Z]/.test(password)) return 'Wallet Password must contain at least one uppercase letter!';
 51 |     
 52 |     // Check if it contains at least one number
 53 |     if (!/[0-9]/.test(password)) return 'Wallet Password must contain at least one number!';
 54 |     
 55 |     // Check if it contains at least one special character
 56 |     if (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)) return 'Wallet Password must contain at least one special character! (!@#$%^&*()_+-=[]{};\':\\"|,.<>\/?)';
 57 |     
 58 |     return true;
 59 | }
 60 | // Ask for credentials
 61 | const getInputs = async (): Promise<UserInputs> => {
 62 |     const questions: PromptObject[] = [
 63 |         {
 64 |             type: 'password',
 65 |             name: 'walletPassword',
 66 |             message: '🔐 Enter your Wallet Password (The password must be between 8 and 128 characters):',
 67 |             validate: (val: string) => {
 68 |                 return validatePassword(val);
 69 |             },
 70 |         },
 71 |         {
 72 |             type: 'password',
 73 |             name: 'privateKey',
 74 |             message: '🔑 Enter your BNB Chain Wallet Private Key:',
 75 |             validate: (val: string) =>
 76 |                 val.trim() === '' ? 'Private key is required!' : true,
 77 |         },
 78 |         {
 79 |             type: 'text',
 80 |             name: 'rpcUrl',
 81 |             message: '🌐 Enter your BNB Chain RPC URL (optional):',
 82 |         },
 83 |     ];
 84 | 
 85 |     return await prompts(questions, { onCancel }) as UserInputs;
 86 | };
 87 | 
 88 | // Generate .env file
 89 | const generateEnvFile = async (privateKey: string, address: string, rpcUrl?: string, ): Promise<void> => {
 90 |     const envContent = `
 91 | BSC_WALLET_PRIVATE_KEY=${privateKey}
 92 | BSC_WALLET_ADDRESS=${address}
 93 | BSC_RPC_URL=${rpcUrl || ''}
 94 | `.trim();
 95 | 
 96 |     await fs.writeFile('.env', envContent);
 97 |     console.log(yellow('✅ .env file generated.'));
 98 | };
 99 | 
100 | // Generate config object
101 | const generateConfig = async (privateKey: string, address: string, rpcUrl?: string, ): Promise<any> => {
102 |     const indexPath = path.resolve(__dirname, '..', 'build', 'index.js'); // one level up from cli/
103 | 
104 |     return {
105 |         'bsc-mcp': {
106 |             command: 'node',
107 |             args: [indexPath],
108 |             env: {
109 |                 BSC_WALLET_PRIVATE_KEY: privateKey,
110 |                 BSC_WALLET_ADDRESS: address,
111 |                 BSC_RPC_URL: rpcUrl || '',
112 |             },
113 |             disabled: false,
114 |             autoApprove: []
115 |         }
116 |     };
117 | };
118 | 
119 | // Configure Claude Desktop
120 | const configureClaude = async (config: object): Promise<boolean> => {
121 |     const userHome = os.homedir();
122 |     let claudePath;
123 |     const platform = os.platform();
124 |     if (platform == "darwin") {
125 |         claudePath = path.join(userHome, 'Library/Application Support/Claude/claude_desktop_config.json');
126 |     } else if (platform == "win32") {
127 |         claudePath = path.join(userHome, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
128 |     } else {
129 |         console.log(chalk.red('❌ Unsupported platform.'));
130 |         return false;
131 |     }
132 |     
133 |     if (!fs.existsSync(claudePath)) {
134 |         console.log(chalk.yellow('⚠️ Claude config file not found. Creating a new one with default configuration.'));
135 |         // Create a default configuration object
136 |         const defaultConfig = {
137 |             mcpServers: {}
138 |         };
139 |         // Write the default configuration to the file
140 |         await fs.writeJSON(claudePath, defaultConfig, { spaces: 2 });
141 |     }
142 | 
143 |     
144 |     const jsonData = fs.readFileSync(claudePath, 'utf8');
145 |     const data = JSON.parse(jsonData);
146 |     
147 |     data.mcpServers = {
148 |         ...data.mcpServers,
149 |         ...config,
150 |     };
151 |     
152 |     await fs.writeJSON(claudePath, data, { spaces: 2 });
153 |     console.log(yellow('✅ BNB Chain MCP configured for Claude Desktop. Please RESTART your Claude to enjoy it 🎉'));
154 |     return true;
155 | };
156 | 
157 | // Save fallback config file
158 | const saveFallbackConfig = async (config: object): Promise<void> => {
159 |     await fs.writeJSON('config.json', config, { spaces: 2 });
160 |     console.log(yellow('📁 Saved config.json in root project folder.'));
161 | };
162 | 
163 | // Main logic
164 | const init = async () => {
165 |     showBanner();
166 | 
167 |     const { privateKey, rpcUrl, walletPassword } = await getInputs();
168 |     const _0xPrivateKey = privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`
169 |     const account = privateKeyToAccount(
170 |         _0xPrivateKey as Hex
171 |     );
172 | 
173 |     const privateKeyEncrypt = await encryptPrivateKey(_0xPrivateKey, walletPassword);
174 | 
175 |     await generateEnvFile(privateKeyEncrypt, account.address, rpcUrl);
176 | 
177 |     const config = await generateConfig(privateKeyEncrypt, account.address, rpcUrl);
178 | 
179 |     const { setupClaude } = await prompts({
180 |         type: 'confirm',
181 |         name: 'setupClaude',
182 |         message: '🧠 Do you want to configure in Claude Desktop?',
183 |         initial: true
184 |     }, { onCancel });
185 | 
186 |     if (setupClaude) {
187 |         const success = await configureClaude(config);
188 |         if (!success) {
189 |             await saveFallbackConfig(config);
190 |         }
191 |     } else {
192 |         await saveFallbackConfig(config);
193 |     }
194 | };
195 | 
196 | init(); 
```

--------------------------------------------------------------------------------
/src/functions/pancakeSwapPosition.ts:
--------------------------------------------------------------------------------

```typescript
  1 | 
  2 | import dotenv from 'dotenv';
  3 | dotenv.config();
  4 | 
  5 | import { TickMath, SqrtPriceMath } from '@pancakeswap/v3-sdk';
  6 | import {
  7 |     Address,
  8 |     PrivateKeyAccount,
  9 |     formatUnits,
 10 |     parseAbi,
 11 | } from "viem";
 12 | import { publicClient, } from '../config.js';
 13 | 
 14 | 
 15 | 
 16 | const POSITION_MANAGER_ADDRESS = '0x46A15B0b27311cedF172AB29E4f4766fbE7F4364' as Address;
 17 | const FACTORY_ADDRESS = '0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865' as Address;
 18 | 
 19 | const FACTORY_ABI = parseAbi([
 20 |     'function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool)'
 21 | ]);
 22 | 
 23 | const POOL_ABI = parseAbi([
 24 |     'function liquidity() external view returns (uint128)',
 25 |     'function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)'
 26 | ]);
 27 | 
 28 | const ERC20_ABI = parseAbi([
 29 |     'function decimals() external view returns (uint256)',
 30 |     'function symbol() external view returns (string)',
 31 |     'function name() external view returns (string)',
 32 | ]);
 33 | 
 34 | const masterChefV3ABI = parseAbi([
 35 |     'function balanceOf(address account) external view returns (uint256)',
 36 |     'function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256)',
 37 |     'function positions(uint256) external view returns (uint96 nonce, address operator, address token0, address token1, uint24 fee, int24 tickLower, int24 tickUpper, uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, uint128 tokensOwed0, uint128 tokensOwed1)',
 38 | ]);
 39 | 
 40 | export const myPosition = async (accountAddress: Address) => {
 41 | 
 42 | 
 43 |     const balance = await publicClient.readContract({
 44 |         abi: masterChefV3ABI,
 45 |         address: POSITION_MANAGER_ADDRESS,
 46 |         functionName: 'balanceOf',
 47 |         args: [accountAddress],
 48 |     })
 49 | 
 50 | 
 51 |     if (Number(balance) === 0) {
 52 |         return;
 53 |     }
 54 | 
 55 |     const nftCalls = []
 56 |     for (let i = 0; i < Number(balance); i++) {
 57 |         const nftCall = {
 58 |             abi: masterChefV3ABI,
 59 |             address: POSITION_MANAGER_ADDRESS,
 60 |             functionName: 'tokenOfOwnerByIndex',
 61 |             args: [accountAddress, BigInt(i)],
 62 |         }
 63 |         nftCalls.push(nftCall)
 64 |     }
 65 | 
 66 | 
 67 |     const nftIds = await publicClient.multicall<BigInt[]>({
 68 |         contracts: nftCalls,
 69 |         allowFailure: false,
 70 |     })
 71 | 
 72 | 
 73 |     const positionCalls = nftIds.map((nftId) => {
 74 |         return {
 75 |             abi: masterChefV3ABI,
 76 |             address: POSITION_MANAGER_ADDRESS,
 77 |             functionName: 'positions',
 78 |             args: [nftId],
 79 |         }
 80 |     })
 81 | 
 82 |     const positions = await publicClient.multicall<any[]>({
 83 |         contracts: positionCalls,
 84 |         allowFailure: false,
 85 |     }) as any[]
 86 | 
 87 |     const getTokenInfo = async (token: Address) => {
 88 |         const infoCalls = [
 89 |             {
 90 |                 address: token,
 91 |                 abi: ERC20_ABI,
 92 |                 functionName: 'symbol',
 93 |                 args: [],
 94 |             },
 95 |             {
 96 |                 address: token,
 97 |                 abi: ERC20_ABI,
 98 |                 functionName: 'name',
 99 |                 args: [],
100 |             },
101 |             {
102 |                 address: token,
103 |                 abi: ERC20_ABI,
104 |                 functionName: 'decimals',
105 |                 args: [],
106 |             },
107 |         ]
108 | 
109 |         const tokenInfo = await publicClient.multicall<any[]>({
110 |             contracts: infoCalls,
111 |             allowFailure: false,
112 |         }) as any[]
113 |         return {
114 |             token,
115 |             symbol: tokenInfo[0] as string,
116 |             name: tokenInfo[1] as string,
117 |             decimals: Number(tokenInfo[2]),
118 |         }
119 |     }
120 | 
121 |     const poolTokenInfos = await Promise.all(positions.map(async (position) => {
122 |         const tokenInfos = await Promise.all([
123 |             getTokenInfo(position[2] as Address),
124 |             getTokenInfo(position[3] as Address),
125 |         ]);
126 |         return {
127 |             token0: tokenInfos[0],
128 |             token1: tokenInfos[1],
129 |         }
130 |     })) as any[]
131 | 
132 |     const poolCalls = []
133 |     for (const position of positions) {
134 |         const poolCall = {
135 |             address: FACTORY_ADDRESS,
136 |             abi: FACTORY_ABI,
137 |             functionName: 'getPool',
138 |             args: [
139 |                 position[2] as Address,
140 |                 position[3] as Address,
141 |                 BigInt(position[4])
142 |             ]
143 |         }
144 |         poolCalls.push(poolCall);
145 |     }
146 | 
147 |     const pools = await publicClient.multicall<any[]>({
148 |         contracts: poolCalls,
149 |         allowFailure: false,
150 |     }) as any[]
151 | 
152 |     const slot0Calls = []
153 |     for (const pool of pools) {
154 |         const slot0Call = {
155 |             address: pool as Address,
156 |             abi: POOL_ABI,
157 |             functionName: 'slot0',
158 |         }
159 |         slot0Calls.push(slot0Call);
160 |     }
161 |     const slot0s = await publicClient.multicall<any[]>({
162 |         contracts: slot0Calls,
163 |         allowFailure: false,
164 |     }) as any[]
165 | 
166 |     const positionInfos = []
167 |     for (let i = 0; i < pools.length; i++) {
168 |         const positionInfo = {
169 |             tickCurrent: slot0s[i][1],
170 |             tickLower: positions[i][5],
171 |             tickUpper: positions[i][6],
172 |             liquidity: positions[i][7],
173 |             ...poolTokenInfos[i],
174 |             feeTier: positions[i][4],
175 |             positionId: nftIds[i],
176 |         }
177 |         const sqrtPriceX96 = TickMath.getSqrtRatioAtTick(positionInfo.tickCurrent);
178 |         const sqrtPriceAX96 = TickMath.getSqrtRatioAtTick(positionInfo.tickLower);
179 |         const sqrtPriceBX96 = TickMath.getSqrtRatioAtTick(positionInfo.tickUpper);
180 |         const liquidity = BigInt(positionInfo.liquidity);
181 |         let amount0;
182 |         let amount1;
183 |         if (positionInfo.tickCurrent < positionInfo.tickLower) {
184 |             amount0 = SqrtPriceMath.getAmount0Delta(sqrtPriceAX96, sqrtPriceBX96, liquidity,
185 |                 false);
186 |             amount1 = BigInt(0);
187 |         } else if (positionInfo.tickCurrent > positionInfo.tickUpper) {
188 |             amount0 = BigInt(0);
189 |             amount1 = SqrtPriceMath.getAmount1Delta(sqrtPriceAX96, sqrtPriceBX96, liquidity,
190 |                 false);
191 |         } else {
192 |             amount0 = SqrtPriceMath.getAmount0Delta(sqrtPriceX96, sqrtPriceBX96, liquidity,
193 |                 false);
194 |             amount1 = SqrtPriceMath.getAmount1Delta(sqrtPriceAX96, sqrtPriceX96, liquidity,
195 |                 false);
196 |         }
197 |         positionInfos.push({
198 |             ...positionInfo,
199 |             amount0,
200 |             amount1,
201 |             amount0Format: formatUnits(amount0, Number(positionInfo.token0.decimals)),
202 |             amount1Format: formatUnits(amount1, Number(positionInfo.token1.decimals)),
203 |         });
204 | 
205 |     }
206 | 
207 |     return positionInfos;
208 | }
209 | 
210 | 
211 | 
```

--------------------------------------------------------------------------------
/src/tools/buyMemeToken.ts:
--------------------------------------------------------------------------------

```typescript
  1 | 
  2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  3 | import { z } from "zod";
  4 | import {
  5 |     parseUnits,
  6 |     type Hex,
  7 | } from "viem";
  8 | import { getAccount, publicClient, walletClient } from "../config.js";
  9 | import { AddressConfig } from "../addressConfig.js";
 10 | import { buildTxUrl, checkTransactionHash } from "../util.js";
 11 | 
 12 | 
 13 | export function registerBuyMemeToken(server: McpServer) {
 14 | 
 15 |     server.tool("Buy_Meme_Token", "🚀Purchase meme tokens on BNBChain", {
 16 |             token: z.string(),
 17 |             tokenValue: z.string().default("0"),
 18 |             bnbValue: z.string().default("0"),
 19 |         },
 20 |         async ({ token, tokenValue, bnbValue }) => {
 21 | 
 22 |             let txHash = undefined;
 23 |             try {
 24 |                 
 25 | 
 26 |                 const account = await getAccount();
 27 |                 
 28 |                 const [,,estimatedAmount,,,amountMsgValue,,] = await publicClient.readContract({
 29 |                         address: AddressConfig.FourMemeTryBuyContract,
 30 |                         abi: [
 31 |                             {
 32 |                                 "inputs": [
 33 |                                     {
 34 |                                         "internalType": "address",
 35 |                                         "name": "token",
 36 |                                         "type": "address"
 37 |                                     },
 38 |                                     {
 39 |                                         "internalType": "uint256",
 40 |                                         "name": "amount",
 41 |                                         "type": "uint256"
 42 |                                     },
 43 |                                     {
 44 |                                         "internalType": "uint256",
 45 |                                         "name": "funds",
 46 |                                         "type": "uint256"
 47 |                                     }
 48 |                                 ],
 49 |                                 "name": "tryBuy",
 50 |                                 "outputs": [
 51 |                                     {
 52 |                                         "internalType": "address",
 53 |                                         "name": "tokenManager",
 54 |                                         "type": "address"
 55 |                                     },
 56 |                                     {
 57 |                                         "internalType": "address",
 58 |                                         "name": "quote",
 59 |                                         "type": "address"
 60 |                                     },
 61 |                                     {
 62 |                                         "internalType": "uint256",
 63 |                                         "name": "estimatedAmount",
 64 |                                         "type": "uint256"
 65 |                                     },
 66 |                                     {
 67 |                                         "internalType": "uint256",
 68 |                                         "name": "estimatedCost",
 69 |                                         "type": "uint256"
 70 |                                     },
 71 |                                     {
 72 |                                         "internalType": "uint256",
 73 |                                         "name": "estimatedFee",
 74 |                                         "type": "uint256"
 75 |                                     },
 76 |                                     {
 77 |                                         "internalType": "uint256",
 78 |                                         "name": "amountMsgValue",
 79 |                                         "type": "uint256"
 80 |                                     },
 81 |                                     {
 82 |                                         "internalType": "uint256",
 83 |                                         "name": "amountApproval",
 84 |                                         "type": "uint256"
 85 |                                     },
 86 |                                     {
 87 |                                         "internalType": "uint256",
 88 |                                         "name": "amountFunds",
 89 |                                         "type": "uint256"
 90 |                                     }
 91 |                                 ],
 92 |                                 "stateMutability": "view",
 93 |                                 "type": "function"
 94 |                             }],
 95 |                         functionName: 'tryBuy',
 96 |                         args: [token as Hex, parseUnits(tokenValue, 18), parseUnits(bnbValue, 18)],
 97 |                     });
 98 | 
 99 |                 let outputAmount;
100 |                 let inputAmount;
101 |                 if (tokenValue == "0") {
102 |                     outputAmount = (BigInt(estimatedAmount) * BigInt(100 - 20)) / 100n
103 |                     inputAmount = amountMsgValue
104 |                 } else {
105 |                     outputAmount = estimatedAmount;
106 |                     inputAmount =  (BigInt(amountMsgValue) * BigInt(100 + 5)) / 100n
107 |                 }
108 | 
109 |                 txHash = await walletClient(account).writeContract({
110 |                     account,
111 |                     address: AddressConfig.FourMemeBuyTokenAMAPContract,
112 |                     abi: [{
113 |                         "inputs": [
114 |                             {
115 |                                 "internalType": "address",
116 |                                 "name": "token",
117 |                                 "type": "address"
118 |                             },
119 |                             {
120 |                                 "internalType": "uint256",
121 |                                 "name": "funds",
122 |                                 "type": "uint256"
123 |                             },
124 |                             {
125 |                                 "internalType": "uint256",
126 |                                 "name": "minAmount",
127 |                                 "type": "uint256"
128 |                             }
129 |                         ],
130 |                         "name": "buyTokenAMAP",
131 |                         "outputs": [],
132 |                         "stateMutability": "payable",
133 |                         "type": "function"
134 |                     }],
135 |                     functionName: 'buyTokenAMAP',
136 |                     args: [token as Hex, BigInt(inputAmount), outputAmount],
137 |                     value: BigInt(inputAmount),
138 |                 });
139 | 
140 |                 const txUrl = await checkTransactionHash(txHash)
141 |                 return {
142 |                     content: [
143 |                         {
144 |                             type: "text",
145 |                             text: `buy meme token successfully. ${txUrl}`,
146 |                             url: txUrl,
147 |                         },
148 |                     ],
149 |                 };
150 |             } catch (error) {
151 |                 const errorMessage =
152 |                   error instanceof Error ? error.message : String(error);
153 |                 const txUrl = buildTxUrl(txHash);
154 |                 return {
155 |                   content: [
156 |                     {
157 |                       type: "text",
158 |                       text: `transaction failed: ${errorMessage}`,
159 |                       url: txUrl,
160 |                     },
161 |                   ],
162 |                   isError: true,
163 |                 };
164 |             }
165 |         }
166 |     );
167 | }
```

--------------------------------------------------------------------------------
/src/functions/pancakeRemoveLiquidityTool.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ChainId, Native, Percent } from "@pancakeswap/sdk";
  2 | import { PositionMath, Multicall } from "@pancakeswap/v3-sdk";
  3 | import { 
  4 |     Address, 
  5 |     Hex, 
  6 |     encodeFunctionData, 
  7 |     getAddress, 
  8 |     zeroAddress, 
  9 |     maxUint128, 
 10 |     parseAbi,
 11 |     PrivateKeyAccount,
 12 |     publicActions, 
 13 | } from "viem";
 14 | 
 15 | import dotenv from 'dotenv';
 16 | import { publicClient, walletClient } from "../config.js";
 17 | 
 18 | dotenv.config();
 19 | 
 20 | 
 21 | const positionManagerABI = [
 22 | 
 23 |     {
 24 |         inputs: [
 25 |             {
 26 |                 components: [
 27 |                     { internalType: 'uint256', name: 'tokenId', type: 'uint256' },
 28 |                     { internalType: 'uint128', name: 'liquidity', type: 'uint128' },
 29 |                     { internalType: 'uint256', name: 'amount0Min', type: 'uint256' },
 30 |                     { internalType: 'uint256', name: 'amount1Min', type: 'uint256' },
 31 |                     { internalType: 'uint256', name: 'deadline', type: 'uint256' },
 32 |                 ],
 33 |                 internalType: 'struct INonfungiblePositionManager.DecreaseLiquidityParams',
 34 |                 name: 'params',
 35 |                 type: 'tuple',
 36 |             },
 37 |         ],
 38 |         name: 'decreaseLiquidity',
 39 |         outputs: [
 40 |             { internalType: 'uint256', name: 'amount0', type: 'uint256' },
 41 |             { internalType: 'uint256', name: 'amount1', type: 'uint256' },
 42 |         ],
 43 |         stateMutability: 'payable',
 44 |         type: 'function',
 45 |     },
 46 | 
 47 |     {
 48 |         inputs: [
 49 |             {
 50 |                 components: [
 51 |                     { internalType: 'uint256', name: 'tokenId', type: 'uint256' },
 52 |                     { internalType: 'address', name: 'recipient', type: 'address' },
 53 |                     { internalType: 'uint128', name: 'amount0Max', type: 'uint128' },
 54 |                     { internalType: 'uint128', name: 'amount1Max', type: 'uint128' },
 55 |                 ],
 56 |                 internalType: 'struct INonfungiblePositionManager.CollectParams',
 57 |                 name: 'params',
 58 |                 type: 'tuple',
 59 |             },
 60 |         ],
 61 |         name: 'collect',
 62 |         outputs: [
 63 |             { internalType: 'uint256', name: 'amount0', type: 'uint256' },
 64 |             { internalType: 'uint256', name: 'amount1', type: 'uint256' },
 65 |         ],
 66 |         stateMutability: 'payable',
 67 |         type: 'function',
 68 |     },
 69 |     {
 70 |         inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }],
 71 |         name: 'positions',
 72 |         outputs: [
 73 |             { internalType: 'uint96', name: 'nonce', type: 'uint96' },
 74 |             { internalType: 'address', name: 'operator', type: 'address' },
 75 |             { internalType: 'address', name: 'token0', type: 'address' },
 76 |             { internalType: 'address', name: 'token1', type: 'address' },
 77 |             { internalType: 'uint24', name: 'fee', type: 'uint24' },
 78 |             { internalType: 'int24', name: 'tickLower', type: 'int24' },
 79 |             { internalType: 'int24', name: 'tickUpper', type: 'int24' },
 80 |             { internalType: 'uint128', name: 'liquidity', type: 'uint128' },
 81 |             { internalType: 'uint256', name: 'feeGrowthInside0LastX128', type: 'uint256' },
 82 |             { internalType: 'uint256', name: 'feeGrowthInside1LastX128', type: 'uint256' },
 83 |             { internalType: 'uint128', name: 'tokensOwed0', type: 'uint128' },
 84 |             { internalType: 'uint128', name: 'tokensOwed1', type: 'uint128' },
 85 |         ],
 86 |         stateMutability: 'view',
 87 |         type: 'function',
 88 |     },
 89 | ]
 90 | 
 91 | const FACTORY_ABI = parseAbi([
 92 |     'function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool)'
 93 | ]);
 94 | 
 95 | const POOL_ABI = parseAbi([
 96 |     'function liquidity() external view returns (uint128)',
 97 |     'function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)'
 98 | ]);
 99 | 
100 | const Payments_ABI = [
101 | 
102 | 
103 |     {
104 |         inputs: [
105 |             {
106 |                 internalType: 'uint256',
107 |                 name: 'amountMinimum',
108 |                 type: 'uint256',
109 |             },
110 |             {
111 |                 internalType: 'address',
112 |                 name: 'recipient',
113 |                 type: 'address',
114 |             },
115 |         ],
116 |         name: 'unwrapWETH9',
117 |         outputs: [],
118 |         stateMutability: 'payable',
119 |         type: 'function',
120 |     },
121 |     {
122 |         inputs: [
123 |             {
124 |                 internalType: 'address',
125 |                 name: 'token',
126 |                 type: 'address',
127 |             },
128 |             {
129 |                 internalType: 'uint256',
130 |                 name: 'amountMinimum',
131 |                 type: 'uint256',
132 |             },
133 |             {
134 |                 internalType: 'address',
135 |                 name: 'recipient',
136 |                 type: 'address',
137 |             },
138 |         ],
139 |         name: 'sweepToken',
140 |         outputs: [],
141 |         stateMutability: 'payable',
142 |         type: 'function',
143 |     },
144 | ];
145 | const POSITION_MANAGER_ADDRESS = '0x46A15B0b27311cedF172AB29E4f4766fbE7F4364' as Address;
146 | const FACTORY_ADDRESS = '0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865' as Address;
147 | 
148 | export const removeLiquidityV3 = async (account: PrivateKeyAccount, tokenId: BigInt, percent: number) => {
149 | 
150 |     const calldatas: Hex[] = []
151 | 
152 |     const client = walletClient(account).extend(publicActions)
153 | 
154 |     const positionInfo = await publicClient.readContract({
155 |         address: POSITION_MANAGER_ADDRESS,
156 |         abi: positionManagerABI,
157 |         functionName: 'positions',
158 |         args: [tokenId]
159 |     }) as any[]
160 | 
161 |     const bnb = Native.onChain(ChainId.BSC)
162 | 
163 |     const liquidity: bigint = new Percent(percent!, 100).multiply(positionInfo[7]).quotient
164 | 
165 |     // remove liquidity
166 |     calldatas.push(encodeFunctionData({
167 |         abi: positionManagerABI,
168 |         functionName: 'decreaseLiquidity',
169 |         args: [
170 |             {
171 |                 tokenId,
172 |                 liquidity,
173 |                 amount0Min: BigInt(1),
174 |                 amount1Min: BigInt(1),
175 |                 deadline: Math.floor(Date.now() / 1000) + 1200,
176 |             },
177 |         ],
178 |     }))
179 | 
180 | 
181 |     const involvesETH = getAddress(bnb.wrapped.address) === getAddress(positionInfo[2])
182 |         || getAddress(bnb.wrapped.address) === getAddress(positionInfo[3])
183 | 
184 | 
185 | 
186 |     calldatas.push(encodeFunctionData({
187 |         abi: positionManagerABI,
188 |         functionName: 'collect',
189 |         args: [
190 |             {
191 |                 tokenId,
192 |                 recipient: involvesETH ? zeroAddress : account.address,
193 |                 amount0Max: maxUint128,
194 |                 amount1Max: maxUint128,
195 |             },
196 |         ],
197 |     }))
198 | 
199 |     if (involvesETH) {
200 | 
201 | 
202 |         const poolAddrs = await client.readContract({
203 |             address: FACTORY_ADDRESS,
204 |             abi: FACTORY_ABI,
205 |             functionName: 'getPool',
206 |             args: [
207 |                 positionInfo[2] as Address,
208 |                 positionInfo[3] as Address,
209 |                 positionInfo[4]
210 |             ]
211 |         })
212 | 
213 |         const slot0 = await client.readContract({
214 |             address: poolAddrs as Address,
215 |             abi: POOL_ABI,
216 |             functionName: 'slot0',
217 |         })
218 | 
219 |         const token0Amount = PositionMath.getToken0Amount(
220 |             slot0[1],
221 |             positionInfo[5],
222 |             positionInfo[6],
223 |             slot0[0],
224 |             positionInfo[7],
225 |         )
226 |         const discountedAmount0 = new Percent(percent!, 100).multiply(token0Amount).quotient
227 | 
228 |         const token1Amount = PositionMath.getToken1Amount(
229 |             slot0[1],
230 |             positionInfo[5],
231 |             positionInfo[6],
232 |             slot0[0],
233 |             positionInfo[7],
234 |         )
235 | 
236 |         const discountedAmount1 = new Percent(percent!, 100).multiply(token1Amount).quotient
237 | 
238 |         const ethAmount = getAddress(bnb.wrapped.address) === getAddress(positionInfo[2])
239 |             ? discountedAmount0
240 |             : discountedAmount1
241 |         const token = getAddress(bnb.wrapped.address) === getAddress(positionInfo[2])
242 |             ? positionInfo[3]
243 |             : positionInfo[2]
244 |         const tokenAmount = getAddress(bnb.wrapped.address) === getAddress(positionInfo[2])
245 |             ? discountedAmount1
246 |             : discountedAmount0
247 | 
248 |         calldatas.push(encodeFunctionData({
249 |             abi: Payments_ABI,
250 |             functionName: 'unwrapWETH9',
251 |             args: [ethAmount, account.address],
252 |         }))
253 |         calldatas.push(encodeFunctionData({
254 |             abi: Payments_ABI,
255 |             functionName: 'sweepToken',
256 |             args: [token, tokenAmount, account.address],
257 |         }))
258 |     }
259 | 
260 |     const data = Multicall.encodeMulticall(calldatas)
261 |     
262 |     const tx = await client.sendTransaction({
263 |         to: POSITION_MANAGER_ADDRESS,
264 |         data: data,
265 |         value: BigInt(0),
266 |         account: account,
267 |         chain: client.chain as any,
268 |     })
269 |     return tx
270 | }
```

--------------------------------------------------------------------------------
/src/functions/pancakeAddLiquidityTool.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {
  2 |     parseAbi,
  3 |     type Address,
  4 |     Hex,
  5 |     PrivateKeyAccount,
  6 | } from 'viem';
  7 | import {
  8 |     Pool,
  9 |     Position,
 10 |     nearestUsableTick,
 11 |     FeeAmount,
 12 |     encodeSqrtRatioX96
 13 | } from '@pancakeswap/v3-sdk';
 14 | import { ChainId, Currency, CurrencyAmount, Percent, Token } from '@pancakeswap/sdk';
 15 | 
 16 | import dotenv from 'dotenv';
 17 | import { publicClient, walletClient } from '../config.js';
 18 | import { TICK_SPACINGS, TickMath } from "@pancakeswap/v3-sdk";
 19 | import { FACTORY_ADDRESSES, NFT_POSITION_MANAGER_ADDRESSES } from '@pancakeswap/v3-sdk';
 20 | 
 21 | const POSITION_MANAGER_ADDRESS = NFT_POSITION_MANAGER_ADDRESSES[ChainId.BSC];
 22 | const FACTORY_ADDRESS = FACTORY_ADDRESSES[ChainId.BSC];
 23 | 
 24 | dotenv.config();
 25 | 
 26 | 
 27 | // Contract ABI definitions
 28 | const FACTORY_ABI = parseAbi([
 29 |     'function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool)'
 30 | ]);
 31 | 
 32 | const POOL_ABI = parseAbi([
 33 |     'function liquidity() external view returns (uint128)',
 34 |     'function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)'
 35 | ]);
 36 | 
 37 | const ERC20_ABI = parseAbi([
 38 |     'function allowance(address owner, address spender) external view returns (uint256)',
 39 |     'function approve(address spender, uint256 amount) external returns (bool)',
 40 |     'function balanceOf(address account) external view returns (uint256)'
 41 | ]);
 42 | 
 43 | const POSITION_MANAGER_ABI = [
 44 |     { "inputs": [{ "components": [{ "internalType": "address", "name": "token0", "type": "address" }, { "internalType": "address", "name": "token1", "type": "address" }, { "internalType": "uint24", "name": "fee", "type": "uint24" }, { "internalType": "int24", "name": "tickLower", "type": "int24" }, { "internalType": "int24", "name": "tickUpper", "type": "int24" }, { "internalType": "uint256", "name": "amount0Desired", "type": "uint256" }, { "internalType": "uint256", "name": "amount1Desired", "type": "uint256" }, { "internalType": "uint256", "name": "amount0Min", "type": "uint256" }, { "internalType": "uint256", "name": "amount1Min", "type": "uint256" }, { "internalType": "address", "name": "recipient", "type": "address" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }], "internalType": "struct INonfungiblePositionManager.MintParams", "name": "params", "type": "tuple" }], "name": "mint", "outputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "uint128", "name": "liquidity", "type": "uint128" }, { "internalType": "uint256", "name": "amount0", "type": "uint256" }, { "internalType": "uint256", "name": "amount1", "type": "uint256" }], "stateMutability": "payable", "type": "function" }
 45 | ];
 46 | 
 47 | async function approveTokensIfNeeded(
 48 |     account: PrivateKeyAccount,
 49 |     token: Currency,
 50 |     spender: Address,
 51 |     amount: string,
 52 | ): Promise<void> {
 53 | 
 54 |     if (!token.isNative) {
 55 |         const tokenAddress = token.address as Address;
 56 |         const accountAddress = account.address;
 57 | 
 58 |         const allowance = await publicClient.readContract({
 59 |             address: tokenAddress,
 60 |             abi: ERC20_ABI,
 61 |             functionName: 'allowance',
 62 |             args: [accountAddress, spender]
 63 |         });
 64 | 
 65 |         if (BigInt(allowance.toString()) < BigInt(amount)) {
 66 |             
 67 |             const hash = await walletClient(account).writeContract({
 68 |                 account: account,
 69 |                 address: tokenAddress,
 70 |                 abi: ERC20_ABI,
 71 |                 functionName: 'approve',
 72 |                 args: [spender, BigInt(amount)],
 73 |             });
 74 | 
 75 |             await publicClient.waitForTransactionReceipt({ hash });
 76 |         }
 77 |     }
 78 | }
 79 | 
 80 | function sortTokens(
 81 |     tokenA: Currency,
 82 |     tokenB: Currency,
 83 |     amountA: CurrencyAmount<Currency>,
 84 |     amountB: CurrencyAmount<Currency>,
 85 | ): [Token, Token, CurrencyAmount<Currency>, CurrencyAmount<Currency>] {
 86 |     let token0 = tokenA.isNative ? tokenA.wrapped : tokenA;
 87 |     let token1 = tokenB.isNative ? tokenB.wrapped : tokenB;
 88 |     if (token0.sortsBefore(token1)) {
 89 |         return [token0, token1, amountA, amountB];
 90 |     } else {
 91 |         return [token1, token0, amountB, amountA];
 92 |     }
 93 | }
 94 | 
 95 | async function checkBalance(
 96 |     account: PrivateKeyAccount,
 97 |     token: Currency,
 98 |     amount: CurrencyAmount<Currency>
 99 | ) {
100 |     const accountAddress = account.address;
101 |     if (token.isNative) {
102 |         const balance = await publicClient.getBalance({ address: accountAddress });
103 |         const balanceAmount = CurrencyAmount.fromRawAmount(token, balance.toString());
104 |         if (balanceAmount.lessThan(amount)) {
105 |             throw new Error(`Insufficient balance of ${token.symbol}`);
106 |         }
107 |         return
108 |     }
109 |     const balance = await publicClient.readContract({
110 |         address: token.address as Address,
111 |         abi: ERC20_ABI,
112 |         functionName: 'balanceOf',
113 |         args: [accountAddress]
114 |     });
115 |     const balanceAmount = CurrencyAmount.fromRawAmount(token, balance.toString());
116 |     if (balanceAmount.lessThan(amount)) {
117 |         throw new Error(`Insufficient balance of ${token.symbol}`);
118 |     }
119 | }
120 | 
121 | /**
122 |  * Add V3 liquidity
123 |  * @param tokenA first token
124 |  * @param tokenB second token
125 |  * @param fee fee tier
126 |  * @param amountA amount of tokenA
127 |  * @param amountB amount of tokenB
128 |  * @param account account
129 |  * @param slippageTolerance slippage tolerance
130 |  * @param deadline transaction deadline
131 |  * @param priceLower lower price bound percentage (default: 80% of current price)
132 |  * @param priceUpper upper price bound percentage (default: 120% of current price)
133 |  * @returns transaction receipt
134 |  */
135 | 
136 | export async function addLiquidityV3(
137 |     tokenA: Currency,
138 |     tokenB: Currency,
139 |     fee: FeeAmount,
140 |     amountA: CurrencyAmount<Currency>,
141 |     amountB: CurrencyAmount<Currency>,
142 |     account: PrivateKeyAccount,
143 |     slippageTolerance: Percent = new Percent('50', '10000'), // default 0.5%
144 |     deadline: number = Math.floor(Date.now() / 1000) + 20 * 60, // default 20 minutes
145 |     priceLower: number = 0.8,
146 |     priceUpper: number = 1.2
147 | ): Promise<Hex> {
148 |     
149 | 
150 |     await Promise.all([
151 |         approveTokensIfNeeded(account, tokenA, POSITION_MANAGER_ADDRESS, amountA.quotient.toString()),
152 |         approveTokensIfNeeded(account, tokenB, POSITION_MANAGER_ADDRESS, amountB.quotient.toString()),
153 |     ]);
154 | 
155 |     await checkBalance(account, tokenA, amountA)
156 |     await checkBalance(account, tokenB, amountB)
157 | 
158 |     const [token0, token1, amount0, amount1] = sortTokens(tokenA, tokenB, amountA, amountB);
159 | 
160 |     const poolAddress = await publicClient.readContract({
161 |         address: FACTORY_ADDRESS,
162 |         abi: FACTORY_ABI,
163 |         functionName: 'getPool',
164 |         args: [
165 |             token0.address as Address,
166 |             token1.address as Address,
167 |             fee
168 |         ]
169 |     }) as Address;
170 | 
171 |     if (!poolAddress || poolAddress === '0x0000000000000000000000000000000000000000') {
172 |         throw new Error(`Pool for ${tokenA.symbol}/${tokenB.symbol} not found`);
173 |     }
174 | 
175 |     const [liquidity, slot0] = await Promise.all([
176 |         publicClient.readContract({
177 |             address: poolAddress,
178 |             abi: POOL_ABI,
179 |             functionName: 'liquidity'
180 |         }),
181 |         publicClient.readContract({
182 |             address: poolAddress,
183 |             abi: POOL_ABI,
184 |             functionName: 'slot0'
185 |         })
186 |     ]);
187 | 
188 |     const pool = new Pool(
189 |         token0,
190 |         token1,
191 |         fee,
192 |         (slot0 as any)[0].toString(), // sqrtPriceX96
193 |         liquidity.toString(),
194 |         (slot0 as any)[1] // tick
195 |     );
196 |     // Retrieve tickSpacing from the SDK constants
197 |     const tickSpacing = TICK_SPACINGS[fee]; // fee should correspond to a valid
198 |     // Convert prices to square root ratio and then to ticks
199 |     const priceLowerRatio = encodeSqrtRatioX96(priceLower * 1e18, 1e18);
200 |     const priceUpperRatio = encodeSqrtRatioX96(priceUpper * 1e18, 1e18);
201 |     const lowerPriceTick = TickMath.getTickAtSqrtRatio(priceLowerRatio);
202 |     const upperPriceTick = TickMath.getTickAtSqrtRatio(priceUpperRatio);
203 |     // Round ticks to the nearest valid tick
204 |     const tickLower = nearestUsableTick(lowerPriceTick, tickSpacing);
205 |     const tickUpper = nearestUsableTick(upperPriceTick, tickSpacing);
206 | 
207 | 
208 |     const position = Position.fromAmounts({
209 |         pool,
210 |         tickLower,
211 |         tickUpper,
212 |         amount0: amount0.quotient.toString(),
213 |         amount1: amount1.quotient.toString(),
214 |         useFullPrecision: true
215 |     });
216 | 
217 |     const { amount0: amount0Min, amount1: amount1Min } = position.mintAmountsWithSlippage(slippageTolerance);
218 | 
219 |     const value = tokenA.isNative
220 |         ? amountA.quotient.toString()
221 |         : tokenB.isNative
222 |             ? amountB.quotient.toString()
223 |             : '0';
224 | 
225 |     const mintParams = {
226 |         token0: token0.address as Address,
227 |         token1: token1.address as Address,
228 |         fee,
229 |         tickLower,
230 |         tickUpper,
231 |         amount0Desired: BigInt(amount0.quotient.toString()),
232 |         amount1Desired: BigInt(amount1.quotient.toString()),
233 |         amount0Min: BigInt(amount0Min.toString()),
234 |         amount1Min: BigInt(amount1Min.toString()),
235 |         recipient: account.address,
236 |         deadline: BigInt(deadline)
237 |     };
238 | 
239 |     const hash = await walletClient(account).writeContract({
240 |         address: POSITION_MANAGER_ADDRESS,
241 |         abi: POSITION_MANAGER_ABI,
242 |         functionName: 'mint',
243 |         args: [mintParams],
244 |         value: BigInt(value),
245 |         account: account
246 |     });
247 |     return hash;
248 | }
```

--------------------------------------------------------------------------------
/src/util.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { type Hex } from "viem";
  2 | import { exec } from 'child_process';
  3 | import fs from 'fs';
  4 | import path from 'path';
  5 | import os from 'os';
  6 | 
  7 | import { decryptPrivateKey, } from "./PrivateAES.js";
  8 | import { privateKeyToAccount } from "viem/accounts";
  9 | import { publicClient } from "./config.js";
 10 | 
 11 | const platform = os.platform();
 12 | export function buildTxUrl(txHash: Hex | undefined): string | undefined {
 13 |   if (!txHash) {
 14 |     return undefined;
 15 |   }
 16 |   const txUrl = `https://bscscan.com/tx/${txHash}`;
 17 |   return txUrl
 18 | }
 19 | export async function checkTransactionHash(txHash: Hex): Promise<string> {
 20 | 
 21 |   const txReceipt = await publicClient.waitForTransactionReceipt({
 22 |     hash: txHash,
 23 |     retryCount: 300,
 24 |     retryDelay: 100,
 25 |   });
 26 |   const txUrl = `https://bscscan.com/tx/${txHash}`;
 27 |   if (txReceipt.status !== "success") {
 28 |     throw new Error(`Please check the transaction results on bscscan, ${txUrl}`);
 29 |   }
 30 |   return txUrl;
 31 | }
 32 | 
 33 | export function bigIntReplacer(key: string, value: any) {
 34 |   return typeof value === 'bigint' ? value.toString() : value;
 35 | }
 36 | 
 37 | export interface InputBoxOptions {
 38 |   title?: string;
 39 |   message?: string;
 40 |   defaultValue?: string;
 41 |   termsText?: string;
 42 | }
 43 | 
 44 | export interface InputResult {
 45 |   value: string | null;
 46 |   agreed: boolean;
 47 | }
 48 | 
 49 | let passwordLock = false
 50 | 
 51 | export async function getPassword(isRetry?: boolean, num = 0): Promise<InputResult> {
 52 |   if (passwordLock) {
 53 |     throw new Error("Password lock is enabled. Try again in 24 hours");
 54 |   }
 55 |   if (num > 10) {
 56 |     passwordLock = true;
 57 | 
 58 |     setTimeout(() => {
 59 |       passwordLock = false;
 60 |     }, 1000 * 60 * 60 * 24);
 61 |     throw new Error("You have entered the wrong password too many times.");
 62 |   }
 63 | 
 64 |   const passwordResp = await showInputBoxWithTerms(isRetry);
 65 |   if (!passwordResp.value) {
 66 |     throw new Error("You did not enter a password.");
 67 |   }
 68 |   if (passwordResp.value.length < 8 || passwordResp.value.length > 128) {
 69 |     throw new Error("The password must be between 8 and 128 characters.");
 70 |   }
 71 |   const password = passwordResp.value;
 72 | 
 73 |   const BSC_WALLET_PRIVATE_KEY = process.env.BSC_WALLET_PRIVATE_KEY as Hex
 74 |   if (!BSC_WALLET_PRIVATE_KEY) {
 75 |     throw new Error("BSC_WALLET_PRIVATE_KEY is not defined");
 76 |   }
 77 |   try {
 78 | 
 79 |     const pk = await decryptPrivateKey(BSC_WALLET_PRIVATE_KEY, password)
 80 |     const account = privateKeyToAccount(
 81 |       pk as Hex
 82 |     );
 83 |     const address = process.env.BSC_WALLET_ADDRESS
 84 |     if (!address) {
 85 |       throw new Error("BSC_WALLET_ADDRESS is not defined");
 86 |     }
 87 | 
 88 |     if (account.address != address) {
 89 |       return await getPassword(true, ++num);
 90 |     }
 91 | 
 92 |   } catch (error) {
 93 |     if (error instanceof Error) {
 94 |       if (error.message === "Password lock is enabled. Try again in 24 hours") {
 95 |         throw error;
 96 |       }
 97 |       if (error.message === "You have entered the wrong password too many times.") {
 98 |         throw error;
 99 |       }
100 |     }
101 |     return await getPassword(true, ++num);
102 |   }
103 |   return passwordResp;
104 | }
105 | 
106 | export function showInputBoxWithTerms(isRetry?: boolean): Promise<InputResult> {
107 | 
108 |   let message = "Enter your Wallet Password:";
109 |   if (isRetry) {
110 |     message = "Wrong password, please try again:";
111 |   }
112 |   return new Promise((resolve, reject) => {
113 | 
114 |     switch (platform) {
115 |       case 'darwin':
116 |         // For macOS, we use AppleScript to show a dialog with both input and checkbox
117 |         // The AppleScript is more complex but allows for a better UX
118 |         if (isRetry) {
119 |           message = "❌" + message
120 |         }
121 |         const appleScript = `
122 |         tell application "System Events"
123 |         set userPassword to ""
124 |         set buttonPressed to ""
125 |         
126 |         repeat
127 |             try
128 |                 set userInput to display dialog "${message}" default answer "" with hidden answer buttons {"cancel", "confirm"} default button "confirm" with icon note
129 |                 set userPassword to text returned of userInput
130 |                 set buttonPressed to button returned of userInput
131 |                 
132 |                 if buttonPressed is "cancel" then
133 |                     exit repeat
134 |                 end if
135 |                 
136 |                 if (length of userPassword >= 8) and (length of userPassword <= 128) then
137 |                     exit repeat
138 |                 end if
139 |                 
140 |                 display dialog "Wallet Password must be between 8 and 128 characters!" buttons {"confirm"} default button "confirm" with icon caution
141 |             on error
142 |                 -- Handle any errors (like when user clicks the red close button)
143 |                 exit repeat
144 |             end try
145 |         end repeat
146 |         
147 |         if buttonPressed is not "cancel" then
148 |             set agreeToTerms to button returned of (display dialog "🔒 You will stay signed in for the next hour." buttons {"no", "yes"} default button "no" with icon caution)
149 |             return userPassword & "============" & agreeToTerms
150 |         else
151 |             return "canceled"
152 |         end if
153 |     end tell
154 |         `;
155 | 
156 |         exec(`osascript -e '${appleScript}'`, (error, stdout, stderr) => {
157 |           if (error) {
158 |             // User cancelled
159 |             if (error.code === 1 || error.code === 255) {
160 |               resolve({ value: null, agreed: false });
161 |             } else {
162 |               reject(error);
163 |             }
164 |             return;
165 |           }
166 | 
167 |           if (stdout.trim() === "canceled") {
168 |             reject(new Error("Please enter the password before using ❕"));
169 |             return;
170 |           }
171 |           const [password, agree] = stdout.trim().split("============");
172 |           resolve({
173 |             value: password,
174 |             agreed: agree === "yes"
175 |           });
176 |         });
177 |         break;
178 | 
179 |       case 'win32':
180 | 
181 |         const winCommand = `
182 |         Add-Type -AssemblyName System.Windows.Forms
183 |         Add-Type -AssemblyName System.Drawing
184 |         
185 |         $form = New-Object System.Windows.Forms.Form
186 |         $form.Text = 'wallet password'
187 |         $form.Size = New-Object System.Drawing.Size(450,300)
188 |         $form.StartPosition = 'CenterScreen'
189 |         
190 |         $label = New-Object System.Windows.Forms.Label
191 |         $label.Location = New-Object System.Drawing.Point(10,20)
192 |         $label.Size = New-Object System.Drawing.Size(380,40)
193 |         $label.Text = '${message}'
194 |         $form.Controls.Add($label)
195 |         
196 |         # User input label
197 |         $userLabel = New-Object System.Windows.Forms.Label
198 |         $userLabel.Location = New-Object System.Drawing.Point(10,70)
199 |         $userLabel.Size = New-Object System.Drawing.Size(150,20)
200 |         $userLabel.Text = 'Input Password:'
201 |         $form.Controls.Add($userLabel)
202 |         
203 |         # User input textbox
204 |         $passwordTextBox = New-Object System.Windows.Forms.TextBox
205 |         $passwordTextBox.Location = New-Object System.Drawing.Point(160,70)
206 |         $passwordTextBox.Size = New-Object System.Drawing.Size(250,20)
207 |         $passwordTextBox.PasswordChar = '*' 
208 |         $form.Controls.Add($passwordTextBox)
209 |         
210 |         # Error message label
211 |         $errorLabel = New-Object System.Windows.Forms.Label
212 |         $errorLabel.Location = New-Object System.Drawing.Point(160,95)
213 |         $errorLabel.Size = New-Object System.Drawing.Size(250,20)
214 |         $errorLabel.ForeColor = [System.Drawing.Color]::Red
215 |         $errorLabel.Text = ''
216 |         $form.Controls.Add($errorLabel)
217 |         
218 |         $checkbox = New-Object System.Windows.Forms.CheckBox
219 |         $checkbox.Location = New-Object System.Drawing.Point(10,130)
220 |         $checkbox.Size = New-Object System.Drawing.Size(350,20)
221 |         $checkbox.Text = 'You will stay signed in for the next hour.'
222 |         $form.Controls.Add($checkbox)
223 |         
224 |         $button = New-Object System.Windows.Forms.Button
225 |         $button.Location = New-Object System.Drawing.Point(175,190)
226 |         $button.Size = New-Object System.Drawing.Size(100,30)
227 |         $button.Text = 'Confirm'
228 |         $button.Add_Click({
229 |             # Validate password length
230 |             if ($passwordTextBox.Text.Length -lt 8 -or $passwordTextBox.Text.Length -gt 128) {
231 |                 $errorLabel.Text = 'Wallet Password must be between 8 and 128 characters!'
232 |             } else {
233 |                 $form.DialogResult = [System.Windows.Forms.DialogResult]::OK
234 |                 $form.Close()
235 |             }
236 |         })
237 |         $form.Controls.Add($button)
238 |         
239 |         $form.AcceptButton = $button
240 |         $form.Add_Shown({$form.Activate()})
241 |         [void]$form.ShowDialog()
242 |         
243 |         if ($form.DialogResult -eq [System.Windows.Forms.DialogResult]::OK) {
244 |             $result = @{
245 |               agreed = $checkbox.Checked
246 |               value = $passwordTextBox.Text
247 |             }
248 |         
249 |             $jsonResult = ConvertTo-Json -InputObject $result
250 |             Write-Output $jsonResult
251 |         }
252 |         exit 0
253 | `
254 | 
255 |         const tempScriptPath = path.join('.', 'terms_form.ps1');
256 |         fs.writeFileSync(tempScriptPath, winCommand);
257 | 
258 |         exec(`powershell -ExecutionPolicy Bypass -File "${tempScriptPath}"`, (error, stdout, stderr) => {
259 |           fs.unlinkSync(tempScriptPath);
260 | 
261 |           if (error && error.code !== 1) {
262 |             resolve({
263 |               value: null,
264 |               agreed: false
265 |             });
266 |             return;
267 |           }
268 |           if (!stdout) {
269 |             reject(new Error("Please enter the password before using ❕"));
270 |             return;
271 |           }
272 |           const stdoutJSON = JSON.parse(stdout);
273 |           resolve({
274 |             value: stdoutJSON.value as string,
275 |             agreed: stdoutJSON.agreed as boolean
276 |           });
277 |         });
278 |         break;
279 | 
280 |       default:
281 |         reject(new Error(`Unsupported platform and command-line input is not available: ${platform}`));
282 |     }
283 |   });
284 | }
285 | 
```