diff --git a/python/x402-example/README.md b/python/x402-example/README.md
new file mode 100644
index 0000000..77c1905
--- /dev/null
+++ b/python/x402-example/README.md
@@ -0,0 +1,253 @@
+# Crypto Payment Simulator - Fermi 450ms Block Time
+
+A demonstration of BNB Chain's Fermi upgrade showcasing sub-second transaction finality with 450ms block times. This interactive application allows you to send payments and measure real-time transaction confirmation speeds.
+
+## Overview
+
+This example demonstrates the speed improvements brought by BNB Chain's Fermi hard fork, which reduced block times from 0.75 seconds to 0.45 seconds (450ms). With fast finality, transactions can be confirmed in approximately 1.125 seconds, making BNB Chain one of the fastest EVM-compatible chains.
+
+## Screenshot
+
+
+
+## Features
+
+- **Real-time Payment Processing**: Send BNB payments and see instant confirmation
+- **Timing Metrics**: Measure actual vs theoretical confirmation times
+- **Interactive Frontend**: Beautiful web interface for testing payments
+- **Transaction History**: View recent transactions with timing data
+- **Network Statistics**: Monitor current block time and network status
+- **MCP Integration**: Full Model Context Protocol support for AI agents
+
+## Prerequisites
+
+- Python 3.8 or higher
+- A BSC testnet wallet with TBNB for testing
+- Access to BSC testnet RPC endpoint
+
+## Installation
+
+1. Navigate to the 450ms-payment-simulator directory:
+```bash
+cd 450ms-payment-simulator
+```
+
+2. Run the setup script:
+```bash
+./run.sh
+```
+
+The script will:
+- Create a virtual environment
+- Install all dependencies
+- Run tests
+- Start the server
+
+## Configuration
+
+Create a `.env` file (or copy from `.env.example`):
+
+```bash
+PRIVATE_KEY=0xYourPrivateKeyHere
+BSC_TESTNET_RPC=https://data-seed-prebsc-1-s1.binance.org:8545/
+```
+
+## Usage
+
+### Running the Application
+
+```bash
+./run.sh
+```
+
+The application will start on `http://localhost:5000`
+
+### Available Tools
+
+#### `send_payment`
+
+Send a payment and measure finality time.
+
+**Parameters:**
+- `recipient_address` (string, required): BSC testnet address to receive payment
+- `amount` (float, required): Amount of BNB to send
+- `note` (string, optional): Payment description
+
+**Example:**
+```json
+{
+ "recipient_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
+ "amount": 0.1,
+ "note": "Payment for services"
+}
+```
+
+**Returns:**
+```json
+{
+ "success": true,
+ "transaction_hash": "0x...",
+ "recipient": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
+ "amount": 0.1,
+ "block_number": 12345,
+ "status": "confirmed",
+ "timing": {
+ "total_time_seconds": 0.523,
+ "confirmation_time_seconds": 0.478,
+ "blocks_to_confirm": 2,
+ "theoretical_time_450ms_blocks": 0.9,
+ "efficiency_percent": 188.28
+ },
+ "timestamp": "2026-01-24T10:30:00"
+}
+```
+
+#### `get_payment_history`
+
+Get recent payment transaction history.
+
+**Parameters:**
+- `limit` (int, optional): Maximum number of transactions (default: 10)
+
+**Returns:**
+```json
+{
+ "success": true,
+ "count": 5,
+ "transactions": [...]
+}
+```
+
+#### `get_balance`
+
+Get balance of an address.
+
+**Parameters:**
+- `address` (string, optional): Address to check (defaults to sender)
+
+**Returns:**
+```json
+{
+ "success": true,
+ "address": "0x...",
+ "balance_wei": "1000000000000000000",
+ "balance_bnb": 1.0,
+ "network": "BSC Testnet (Fermi - 450ms blocks)"
+}
+```
+
+#### `get_network_stats`
+
+Get current network statistics.
+
+**Returns:**
+```json
+{
+ "success": true,
+ "network": "BSC Testnet",
+ "chain_id": 97,
+ "current_block": 50000,
+ "fermi_block_time_seconds": 0.45,
+ "theoretical_finality_seconds": 1.125
+}
+```
+
+## Demo Example
+
+### Input
+
+1. Open the web interface at `http://localhost:5000`
+2. Enter recipient address: `0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb`
+3. Enter amount: `0.1` BNB
+4. Add note: `"Test payment"`
+5. Click "Send Payment"
+
+### Expected Output
+
+```
+Payment Successful!
+Transaction: 0x1234567890abcdef...
+Confirmed in 0.478s
+Block: 50001
+```
+
+### Timing Breakdown
+
+- **Submission Delay**: ~0.045s (network latency)
+- **Confirmation Time**: ~0.478s (actual block confirmation)
+- **Total Time**: ~0.523s
+- **Blocks to Confirm**: 2 blocks
+- **Theoretical Time**: 0.9s (2 ร 450ms)
+- **Efficiency**: 188% (faster than theoretical due to network optimization)
+
+## Network Information
+
+- **Network**: Binance Smart Chain Testnet
+- **Chain ID**: 97
+- **Block Time**: 450ms (Fermi upgrade)
+- **Fast Finality**: ~1.125 seconds
+- **RPC Endpoint**: https://data-seed-prebsc-1-s1.binance.org:8545/
+- **Explorer**: https://testnet.bscscan.com/
+
+## Testing
+
+Run unit tests:
+
+```bash
+source venv/bin/activate
+pytest test_payment_simulator.py -v
+```
+
+Run tests with coverage:
+
+```bash
+pytest test_payment_simulator.py --cov=payment_simulator --cov-report=html
+```
+
+## Architecture
+
+- **Backend**: Flask web server with MCP integration
+- **Frontend**: HTML/CSS/JavaScript single-page application
+- **Blockchain**: Web3.py for BSC testnet interaction
+- **Protocol**: Model Context Protocol (MCP) for AI agent integration
+
+## Key Metrics Demonstrated
+
+1. **Block Time**: 450ms (down from 750ms)
+2. **Fast Finality**: ~1.125 seconds (2.5 blocks)
+3. **Throughput**: Up to 5,000 DEX swaps per second
+4. **Confirmation Speed**: Sub-second transaction finality
+
+## Security Notes
+
+- โ ๏ธ **Never commit your private key** to version control
+- Use environment variables for sensitive data
+- This example is for testnet use only
+- Always verify addresses before sending payments
+
+## Troubleshooting
+
+### Connection Issues
+- Verify RPC endpoint is accessible
+- Check internet connection
+- Ensure BSC testnet is operational
+
+### Transaction Failures
+- Verify sufficient balance for gas fees
+- Check recipient address is valid
+- Ensure private key is correctly configured
+
+### Frontend Not Loading
+- Verify Flask server is running on port 5000
+- Check browser console for errors
+- Ensure templates directory exists
+
+## License
+
+This project is provided as-is for educational and demonstration purposes.
+
+## References
+
+- [BNB Chain Fermi Upgrade](https://docs.bnbchain.org/announce/fermi-bsc/)
+- [Model Context Protocol](https://modelcontextprotocol.io)
+- [BNB Chain Documentation](https://docs.bnbchain.org)
diff --git a/python/x402-example/payment_simulator.py b/python/x402-example/payment_simulator.py
new file mode 100644
index 0000000..f9bb0b5
--- /dev/null
+++ b/python/x402-example/payment_simulator.py
@@ -0,0 +1,304 @@
+#!/usr/bin/env python3
+"""
+Crypto Payment Simulator - Showcasing Fermi 450ms Block Time
+Demonstrates fast transaction finality on BNB Chain with sub-second block times
+"""
+
+import os
+import time
+import json
+from datetime import datetime
+from typing import Any, Dict, List
+from flask import Flask, render_template, request, jsonify
+from web3 import Web3
+from mcp.server.fastmcp import FastMCP
+
+app = Flask(__name__)
+
+# Initialize MCP server
+mcp = FastMCP("Crypto Payment Simulator - Fermi Block Time")
+
+# Configuration
+BSC_TESTNET_RPC = os.getenv("BSC_TESTNET_RPC", "https://data-seed-prebsc-1-s1.binance.org:8545/")
+PRIVATE_KEY = os.getenv("PRIVATE_KEY", "")
+FERMI_BLOCK_TIME = 0.45 # 450ms block time
+
+# Initialize Web3
+w3 = Web3(Web3.HTTPProvider(BSC_TESTNET_RPC))
+
+if not w3.is_connected():
+ raise ConnectionError("Failed to connect to BSC Testnet")
+
+# Get sender account
+sender_account = None
+sender_address = None
+if PRIVATE_KEY:
+ sender_account = w3.eth.account.from_key(PRIVATE_KEY)
+ sender_address = sender_account.address
+
+# Transaction history
+transaction_history: List[Dict[str, Any]] = []
+
+
+@mcp.tool()
+def send_payment(recipient_address: str, amount: float, note: str = "") -> Dict[str, Any]:
+ """
+ Send a payment and measure finality time with Fermi's 450ms block time.
+
+ Args:
+ recipient_address: The BSC testnet address to receive the payment
+ amount: Amount of BNB to send (in BNB)
+ note: Optional payment note/description
+
+ Returns:
+ Transaction details with timing metrics
+ """
+ if not PRIVATE_KEY:
+ return {
+ "success": False,
+ "error": "Private key not configured"
+ }
+
+ try:
+ # Validate address
+ if not w3.is_address(recipient_address):
+ return {
+ "success": False,
+ "error": f"Invalid address: {recipient_address}"
+ }
+
+ recipient_address = w3.to_checksum_address(recipient_address)
+
+ # Validate amount
+ if amount <= 0:
+ return {
+ "success": False,
+ "error": "Amount must be greater than 0"
+ }
+
+ # Record start time
+ start_time = time.time()
+ tx_submitted_time = None
+ tx_confirmed_time = None
+
+ # Get current block number
+ initial_block = w3.eth.block_number
+
+ # Get current nonce
+ nonce = w3.eth.get_transaction_count(sender_address)
+
+ # Get gas price
+ gas_price = w3.eth.gas_price
+
+ # Build transaction
+ amount_wei = w3.to_wei(amount, 'ether')
+
+ transaction = {
+ 'to': recipient_address,
+ 'value': amount_wei,
+ 'gas': 21000,
+ 'gasPrice': gas_price,
+ 'nonce': nonce,
+ 'chainId': 97 # BSC Testnet
+ }
+
+ # Sign transaction
+ signed_txn = w3.eth.account.sign_transaction(transaction, PRIVATE_KEY)
+
+ # Submit transaction
+ tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
+ tx_submitted_time = time.time()
+ submission_delay = tx_submitted_time - start_time
+
+ # Wait for confirmation (with Fermi, should be very fast)
+ receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=10)
+ tx_confirmed_time = time.time()
+
+ # Calculate timing metrics
+ total_time = tx_confirmed_time - start_time
+ confirmation_time = tx_confirmed_time - tx_submitted_time
+ blocks_to_confirm = receipt.blockNumber - initial_block
+
+ # Calculate theoretical vs actual
+ theoretical_time = blocks_to_confirm * FERMI_BLOCK_TIME
+ efficiency = (theoretical_time / confirmation_time * 100) if confirmation_time > 0 else 0
+
+ result = {
+ "success": True,
+ "transaction_hash": tx_hash.hex(),
+ "recipient": recipient_address,
+ "amount": amount,
+ "amount_wei": str(amount_wei),
+ "block_number": receipt.blockNumber,
+ "status": "confirmed" if receipt.status == 1 else "failed",
+ "timing": {
+ "total_time_seconds": round(total_time, 3),
+ "submission_delay_seconds": round(submission_delay, 3),
+ "confirmation_time_seconds": round(confirmation_time, 3),
+ "blocks_to_confirm": blocks_to_confirm,
+ "theoretical_time_450ms_blocks": round(theoretical_time, 3),
+ "efficiency_percent": round(efficiency, 2)
+ },
+ "note": note,
+ "timestamp": datetime.now().isoformat()
+ }
+
+ # Store in history
+ transaction_history.append(result)
+
+ return result
+
+ except Exception as e:
+ return {
+ "success": False,
+ "error": str(e)
+ }
+
+
+@mcp.tool()
+def get_payment_history(limit: int = 10) -> Dict[str, Any]:
+ """
+ Get recent payment transaction history.
+
+ Args:
+ limit: Maximum number of transactions to return
+
+ Returns:
+ List of recent transactions
+ """
+ recent = transaction_history[-limit:] if len(transaction_history) > limit else transaction_history
+ return {
+ "success": True,
+ "count": len(recent),
+ "transactions": list(reversed(recent))
+ }
+
+
+@mcp.tool()
+def get_balance(address: str = None) -> Dict[str, Any]:
+ """
+ Get balance of an address. If no address provided, returns sender balance.
+
+ Args:
+ address: Address to check (optional, defaults to sender)
+
+ Returns:
+ Balance information
+ """
+ try:
+ if address:
+ if not w3.is_address(address):
+ return {
+ "success": False,
+ "error": f"Invalid address: {address}"
+ }
+ address = w3.to_checksum_address(address)
+ else:
+ if not sender_address:
+ return {
+ "success": False,
+ "error": "No sender address configured"
+ }
+ address = sender_address
+
+ balance_wei = w3.eth.get_balance(address)
+ balance_bnb = w3.from_wei(balance_wei, 'ether')
+
+ return {
+ "success": True,
+ "address": address,
+ "balance_wei": str(balance_wei),
+ "balance_bnb": float(balance_bnb),
+ "network": "BSC Testnet (Fermi - 450ms blocks)"
+ }
+ except Exception as e:
+ return {
+ "success": False,
+ "error": str(e)
+ }
+
+
+@mcp.tool()
+def get_network_stats() -> Dict[str, Any]:
+ """
+ Get current network statistics including block time information.
+
+ Returns:
+ Network statistics
+ """
+ try:
+ current_block = w3.eth.block_number
+ latest_block = w3.eth.get_block('latest')
+ previous_block = w3.eth.get_block(current_block - 1) if current_block > 0 else latest_block
+
+ # Calculate actual block time
+ if previous_block and latest_block:
+ time_diff = latest_block.timestamp - previous_block.timestamp
+ else:
+ time_diff = FERMI_BLOCK_TIME
+
+ return {
+ "success": True,
+ "network": "BSC Testnet",
+ "chain_id": 97,
+ "current_block": current_block,
+ "fermi_block_time_seconds": FERMI_BLOCK_TIME,
+ "actual_block_time_seconds": time_diff,
+ "theoretical_finality_seconds": FERMI_BLOCK_TIME * 2.5, # ~1.125s for fast finality
+ "rpc_endpoint": BSC_TESTNET_RPC
+ }
+ except Exception as e:
+ return {
+ "success": False,
+ "error": str(e)
+ }
+
+
+# Flask routes for frontend
+@app.route('/')
+def index():
+ """Render the payment simulator frontend"""
+ return render_template('index.html')
+
+
+@app.route('/api/send_payment', methods=['POST'])
+def api_send_payment():
+ """API endpoint for sending payments"""
+ data = request.json
+ result = send_payment(
+ data.get('recipient_address', ''),
+ data.get('amount', 0),
+ data.get('note', '')
+ )
+ return jsonify(result)
+
+
+@app.route('/api/history', methods=['GET'])
+def api_history():
+ """API endpoint for transaction history"""
+ limit = request.args.get('limit', 10, type=int)
+ result = get_payment_history(limit)
+ return jsonify(result)
+
+
+@app.route('/api/balance', methods=['GET'])
+def api_balance():
+ """API endpoint for balance check"""
+ address = request.args.get('address', None)
+ result = get_balance(address)
+ return jsonify(result)
+
+
+@app.route('/api/network_stats', methods=['GET'])
+def api_network_stats():
+ """API endpoint for network statistics"""
+ result = get_network_stats()
+ return jsonify(result)
+
+
+if __name__ == "__main__":
+ # Create templates directory if it doesn't exist
+ os.makedirs('templates', exist_ok=True)
+
+ # Run Flask app
+ app.run(host='0.0.0.0', port=5000, debug=True)
diff --git a/python/x402-example/requirements.txt b/python/x402-example/requirements.txt
new file mode 100644
index 0000000..e3b0a1f
--- /dev/null
+++ b/python/x402-example/requirements.txt
@@ -0,0 +1,5 @@
+flask>=3.0.0
+web3>=6.0.0
+mcp>=1.0.0
+pytest>=7.0.0
+pytest-cov>=4.0.0
diff --git a/python/x402-example/run.sh b/python/x402-example/run.sh
new file mode 100644
index 0000000..10cf425
--- /dev/null
+++ b/python/x402-example/run.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+
+# Crypto Payment Simulator - Run Script
+# This script sets up the virtual environment and runs the payment simulator
+
+set -e
+
+echo "๐ Setting up Crypto Payment Simulator (Fermi 450ms Block Time)"
+echo "================================================================"
+
+# Check if Python 3 is available
+if ! command -v python3 &> /dev/null; then
+ echo "โ Python 3 is required but not installed."
+ exit 1
+fi
+
+# Create virtual environment if it doesn't exist
+if [ ! -d "venv" ]; then
+ echo "๐ฆ Creating virtual environment..."
+ python3 -m venv venv
+fi
+
+# Activate virtual environment
+echo "๐ง Activating virtual environment..."
+source venv/bin/activate
+
+# Install dependencies
+echo "๐ฅ Installing dependencies..."
+pip install --upgrade pip
+pip install -r requirements.txt
+
+# Check if .env file exists
+if [ ! -f ".env" ]; then
+ echo "โ ๏ธ .env file not found. Creating from .env.example..."
+ cp .env.example .env
+ echo "โ ๏ธ Please edit .env file and add your PRIVATE_KEY before running!"
+ exit 1
+fi
+
+# Load environment variables
+export $(cat .env | grep -v '^#' | xargs)
+
+# Check if PRIVATE_KEY is set
+if [ -z "$PRIVATE_KEY" ] || [ "$PRIVATE_KEY" == "0xYourPrivateKeyHere" ]; then
+ echo "โ PRIVATE_KEY not set in .env file. Please configure it first."
+ exit 1
+fi
+
+# Run tests
+echo "๐งช Running tests..."
+python -m pytest test_payment_simulator.py -v
+
+# Start the server
+echo "โ
Starting payment simulator server..."
+echo "๐ Frontend will be available at http://localhost:5000"
+echo "๐ MCP server is running..."
+echo ""
+python payment_simulator.py
diff --git a/python/x402-example/templates/index.html b/python/x402-example/templates/index.html
new file mode 100644
index 0000000..a5e7e83
--- /dev/null
+++ b/python/x402-example/templates/index.html
@@ -0,0 +1,391 @@
+
+
+
+
+
+ Fermi Payment Simulator - 450ms Block Time
+
+
+
+
+
+
+
+
+
Block Time
+
450ms
+
Fermi Upgrade
+
+
+
Fast Finality
+
~1.1s
+
Theoretical
+
+
+
Current Block
+
-
+
BSC Testnet
+
+
+
Your Balance
+
-
+
BNB
+
+
+
+
+
+
+
+
Recent Transactions
+
+
Loading transaction history...
+
+
+
+
+
+
+
+
diff --git a/python/x402-example/test_payment_simulator.py b/python/x402-example/test_payment_simulator.py
new file mode 100644
index 0000000..8afefd4
--- /dev/null
+++ b/python/x402-example/test_payment_simulator.py
@@ -0,0 +1,177 @@
+#!/usr/bin/env python3
+"""
+Unit tests for Crypto Payment Simulator
+"""
+
+import os
+import sys
+import pytest
+from unittest.mock import Mock, patch, MagicMock
+
+# Set test environment variables before importing
+os.environ["PRIVATE_KEY"] = "0x" + "1" * 64
+os.environ["BSC_TESTNET_RPC"] = "https://test-rpc.example.com"
+
+# Mock Web3 before importing
+mock_w3 = MagicMock()
+mock_w3.is_connected.return_value = True
+
+def _is_address(addr):
+ """Return False for invalid addresses, True for valid-looking ones."""
+ if not addr or not isinstance(addr, str):
+ return False
+ addr = addr.strip()
+ return addr.startswith("0x") and len(addr) == 42 and all(c in "0123456789abcdefABCDEFx" for c in addr[2:])
+
+mock_w3.is_address.side_effect = _is_address
+mock_w3.to_checksum_address.side_effect = lambda x: x if isinstance(x, str) else str(x)
+mock_w3.to_wei.return_value = 100000000000000000 # 0.1 ETH in Wei
+mock_w3.from_wei.return_value = 0.1
+
+mock_account = MagicMock()
+VALID_ADDR = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0"
+OTHER_ADDR = "0x1234567890123456789012345678901234567890"
+mock_account.address = VALID_ADDR
+mock_w3.eth.account.from_key.return_value = mock_account
+
+MockWeb3 = MagicMock(return_value=mock_w3)
+MockWeb3.HTTPProvider = MagicMock()
+
+_web3_patcher = patch("web3.Web3", MockWeb3)
+_web3_patcher.start()
+
+import payment_simulator # noqa: E402
+
+
+class TestSendPayment:
+ """Tests for send_payment tool"""
+
+ def test_missing_private_key(self):
+ """Test send_payment when private key is not configured"""
+ with patch.object(payment_simulator, "PRIVATE_KEY", ""):
+ result = payment_simulator.send_payment(VALID_ADDR, 0.1)
+ assert result["success"] is False
+ assert "not configured" in result["error"].lower()
+
+ def test_invalid_address(self):
+ """Test send_payment with invalid address"""
+ result = payment_simulator.send_payment("invalid_address", 0.1)
+ assert result["success"] is False
+ assert "invalid" in result["error"].lower()
+
+ def test_invalid_amount(self):
+ """Test send_payment with invalid amount"""
+ result = payment_simulator.send_payment(OTHER_ADDR, 0)
+ assert result["success"] is False
+ assert "greater than 0" in result["error"].lower()
+
+ @patch("payment_simulator.w3")
+ def test_successful_payment(self, mock_w3):
+ """Test successful payment with timing metrics"""
+ mock_w3.is_address.return_value = True
+ mock_w3.to_checksum_address.return_value = OTHER_ADDR
+ mock_w3.eth.block_number = 1000
+ mock_w3.eth.get_transaction_count.return_value = 0
+ mock_w3.eth.gas_price = 1000000000
+
+ mock_signed = Mock()
+ mock_signed.rawTransaction = b"raw_tx_data"
+ mock_w3.eth.account.sign_transaction.return_value = mock_signed
+
+ mock_tx_hash = Mock()
+ mock_tx_hash.hex.return_value = "0x1234567890abcdef"
+ mock_w3.eth.send_raw_transaction.return_value = mock_tx_hash
+
+ mock_receipt = Mock()
+ mock_receipt.blockNumber = 1002
+ mock_receipt.status = 1
+ mock_w3.eth.wait_for_transaction_receipt.return_value = mock_receipt
+
+ result = payment_simulator.send_payment(OTHER_ADDR, 0.1, "Test payment")
+
+ assert result["success"] is True
+ assert result["transaction_hash"] == "0x1234567890abcdef"
+ assert result["recipient"] == OTHER_ADDR
+ assert result["amount"] == 0.1
+ assert result["note"] == "Test payment"
+ assert "timing" in result
+ assert "confirmation_time_seconds" in result["timing"]
+ assert "blocks_to_confirm" in result["timing"]
+ assert result["timing"]["blocks_to_confirm"] == 2
+
+
+class TestGetPaymentHistory:
+ """Tests for get_payment_history tool"""
+
+ def test_empty_history(self):
+ """Test getting history when no transactions exist"""
+ payment_simulator.transaction_history.clear()
+ result = payment_simulator.get_payment_history()
+ assert result["success"] is True
+ assert result["count"] == 0
+ assert result["transactions"] == []
+
+ def test_history_with_limit(self):
+ """Test getting history with limit"""
+ # Add some mock transactions
+ payment_simulator.transaction_history.clear()
+ for i in range(5):
+ payment_simulator.transaction_history.append({
+ "transaction_hash": f"0x{i}",
+ "amount": 0.1 * i
+ })
+
+ result = payment_simulator.get_payment_history(limit=3)
+ assert result["success"] is True
+ assert result["count"] == 3
+
+
+class TestGetBalance:
+ """Tests for get_balance tool"""
+
+ def test_balance_with_address(self):
+ """Test getting balance for specific address"""
+ test_address = VALID_ADDR
+ test_balance_wei = 1000000000000000000
+
+ mock_w3.is_address.return_value = True
+ mock_w3.to_checksum_address.return_value = test_address
+ mock_w3.eth.get_balance.return_value = test_balance_wei
+ mock_w3.from_wei.return_value = 1.0
+
+ result = payment_simulator.get_balance(test_address)
+
+ assert result["success"] is True
+ assert result["address"] == test_address
+ assert result["balance_bnb"] == 1.0
+
+ def test_balance_no_sender(self):
+ """Test getting balance when no sender configured"""
+ with patch.object(payment_simulator, "sender_address", None):
+ result = payment_simulator.get_balance()
+ assert result["success"] is False
+ assert "no sender address configured" in result["error"].lower()
+
+
+class TestGetNetworkStats:
+ """Tests for get_network_stats tool"""
+
+ @patch("payment_simulator.w3")
+ def test_network_stats(self, mock_w3):
+ """Test getting network statistics"""
+ mock_w3.eth.block_number = 5000
+ mock_block = Mock()
+ mock_block.timestamp = 1000000
+ mock_w3.eth.get_block.return_value = mock_block
+
+ result = payment_simulator.get_network_stats()
+
+ assert result["success"] is True
+ assert result["network"] == "BSC Testnet"
+ assert result["chain_id"] == 97
+ assert result["current_block"] == 5000
+ assert result["fermi_block_time_seconds"] == 0.45
+
+
+if __name__ == "__main__":
+ pytest.main([__file__, "-v"])
diff --git a/typescript/faucet-mcp/README.md b/typescript/faucet-mcp/README.md
new file mode 100644
index 0000000..d3b4287
--- /dev/null
+++ b/typescript/faucet-mcp/README.md
@@ -0,0 +1,211 @@
+# TBNB Faucet MCP Server (Node.js)
+
+A Node.js implementation of a Model Context Protocol (MCP) server that provides a faucet service for distributing testnet BNB tokens on the Binance Smart Chain testnet.
+
+## About
+
+This MCP server enables AI agents and other MCP clients to request and receive testnet BNB tokens by interacting with the BSC testnet blockchain. It follows the Anthropic MCP specification and can be integrated with any MCP-compatible client.
+
+## Prerequisites
+
+- Node.js 18.0 or higher
+- npm or yarn package manager
+- A BSC testnet wallet with TBNB for the faucet
+- Access to BSC testnet RPC endpoint
+
+## Installation
+
+1. Navigate to the project directory:
+```bash
+cd faucet-mcp
+```
+
+2. Install dependencies:
+```bash
+npm install
+```
+
+## Configuration
+
+Set the following environment variables before running:
+
+- `FAUCET_PRIVATE_KEY`: The private key of your faucet wallet (required)
+- `BSC_TESTNET_RPC`: BSC testnet RPC endpoint URL (optional, has default)
+
+Example:
+
+```bash
+export FAUCET_PRIVATE_KEY="0x..."
+export BSC_TESTNET_RPC="https://data-seed-prebsc-1-s1.binance.org:8545/"
+```
+
+Or create a `.env` file (not included in repo for security):
+
+```
+FAUCET_PRIVATE_KEY=0x...
+BSC_TESTNET_RPC=https://data-seed-prebsc-1-s1.binance.org:8545/
+```
+
+## Usage
+
+Run the server:
+
+```bash
+node index.js
+```
+
+Or using npm:
+
+```bash
+npm start
+```
+
+The server communicates via stdio, which is the standard transport for MCP servers.
+
+## Available Tools
+
+### send_tbnb
+
+Sends testnet BNB tokens to a recipient address.
+
+**Parameters:**
+- `recipient` (string, required): BSC testnet address to receive tokens
+- `amount` (number, optional): Amount of TBNB to send (default: 0.1, max: 1.0)
+
+**Returns:**
+- Transaction hash
+- Recipient address
+- Amount sent
+- Block number
+- Transaction status
+
+### get_faucet_info
+
+Retrieves information about the faucet wallet.
+
+**Parameters:** None
+
+**Returns:**
+- Faucet wallet address
+- Current balance (Wei and TBNB)
+- Network information
+- RPC endpoint
+
+### get_balance
+
+Queries the TBNB balance of any BSC testnet address.
+
+**Parameters:**
+- `address` (string, required): BSC testnet address to check
+
+**Returns:**
+- Address (checksummed)
+- Balance in Wei and TBNB
+- Network name
+
+## MCP Client Integration
+
+To integrate this server with an MCP client, add it to your client configuration:
+
+```json
+{
+ "mcpServers": {
+ "tbnb-faucet": {
+ "command": "node",
+ "args": ["/absolute/path/to/index.js"],
+ "env": {
+ "FAUCET_PRIVATE_KEY": "0x...",
+ "BSC_TESTNET_RPC": "https://data-seed-prebsc-1-s1.binance.org:8545/"
+ }
+ }
+ }
+}
+```
+
+## Network Configuration
+
+- **Network**: Binance Smart Chain Testnet
+- **Chain ID**: 97
+- **Default RPC**: https://data-seed-prebsc-1-s1.binance.org:8545/
+- **Block Explorer**: https://testnet.bscscan.com
+
+## Error Handling
+
+The server includes comprehensive error handling:
+
+- Address validation (checksum and format)
+- Amount validation (0 to 1.0 TBNB)
+- Self-transfer prevention
+- Network connectivity checks
+- Transaction error reporting
+
+All errors are returned as structured JSON responses.
+
+## Security
+
+๐ **Security Best Practices:**
+
+- Never commit your private key to version control
+- Use environment variables or secure secret management systems
+- This server is intended for testnet use only
+- Consider implementing rate limiting for production deployments
+- Monitor faucet balance and set up alerts
+
+## Troubleshooting
+
+### Common Issues
+
+**"Faucet wallet not configured"**
+- Ensure `FAUCET_PRIVATE_KEY` environment variable is set
+- Verify the private key is valid and starts with `0x`
+
+**Connection errors**
+- Check your internet connection
+- Verify the RPC endpoint is accessible
+- Try an alternative BSC testnet RPC endpoint
+
+**Transaction failures**
+- Ensure the faucet wallet has sufficient TBNB balance
+- Verify the recipient address is valid
+- Check network conditions (gas prices, congestion)
+
+**Module not found errors**
+- Run `npm install` to install dependencies
+- Ensure you're using Node.js 18.0 or higher
+
+## Development
+
+The server uses:
+- `@modelcontextprotocol/sdk` for MCP protocol implementation
+- `ethers.js` v6 for blockchain interactions
+
+## License
+
+MIT License
+
+## Testing
+
+Run unit tests:
+
+```bash
+npm install
+npm test
+```
+
+Run tests with coverage:
+
+```bash
+npm run test:coverage
+```
+
+Run tests in watch mode:
+
+```bash
+npm run test:watch
+```
+
+## Resources
+
+- [Model Context Protocol Documentation](https://modelcontextprotocol.io)
+- [BNB Chain Documentation](https://docs.bnbchain.org)
+- [Ethers.js Documentation](https://docs.ethers.org)
diff --git a/typescript/faucet-mcp/index.js b/typescript/faucet-mcp/index.js
new file mode 100644
index 0000000..6414234
--- /dev/null
+++ b/typescript/faucet-mcp/index.js
@@ -0,0 +1,299 @@
+#!/usr/bin/env node
+
+/**
+ * TBNB Faucet MCP Server
+ * Model Context Protocol server for distributing testnet BNB tokens
+ */
+
+import { Server } from "@modelcontextprotocol/sdk/server/index.js";
+import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
+import {
+ CallToolRequestSchema,
+ ListToolsRequestSchema,
+} from "@modelcontextprotocol/sdk/types.js";
+import { ethers } from "ethers";
+
+// Configuration
+const BSC_TESTNET_RPC = process.env.BSC_TESTNET_RPC || "https://data-seed-prebsc-1-s1.binance.org:8545/";
+const FAUCET_PRIVATE_KEY = process.env.FAUCET_PRIVATE_KEY || "";
+
+if (!FAUCET_PRIVATE_KEY) {
+ console.error("WARNING: FAUCET_PRIVATE_KEY environment variable is not set");
+}
+
+// Initialize provider and wallet
+const provider = new ethers.JsonRpcProvider(BSC_TESTNET_RPC);
+let wallet = null;
+let walletAddress = null;
+
+if (FAUCET_PRIVATE_KEY) {
+ wallet = new ethers.Wallet(FAUCET_PRIVATE_KEY, provider);
+ walletAddress = wallet.address;
+}
+
+// MCP Server
+const server = new Server(
+ {
+ name: "tbnb-faucet",
+ version: "1.0.0",
+ },
+ {
+ capabilities: {
+ tools: {},
+ },
+ }
+);
+
+// List available tools
+server.setRequestHandler(ListToolsRequestSchema, async () => {
+ return {
+ tools: [
+ {
+ name: "send_tbnb",
+ description: "Send testnet BNB tokens to a specified address on BSC testnet",
+ inputSchema: {
+ type: "object",
+ properties: {
+ recipient: {
+ type: "string",
+ description: "The BSC testnet address that will receive the TBNB tokens",
+ },
+ amount: {
+ type: "number",
+ description: "Amount of TBNB to send (default: 0.1, maximum: 1.0)",
+ default: 0.1,
+ },
+ },
+ required: ["recipient"],
+ },
+ },
+ {
+ name: "get_faucet_info",
+ description: "Get information about the faucet including current balance",
+ inputSchema: {
+ type: "object",
+ properties: {},
+ },
+ },
+ {
+ name: "get_balance",
+ description: "Get the TBNB balance of a BSC testnet address",
+ inputSchema: {
+ type: "object",
+ properties: {
+ address: {
+ type: "string",
+ description: "The BSC testnet address to check",
+ },
+ },
+ required: ["address"],
+ },
+ },
+ ],
+ };
+});
+
+// Handle tool calls
+server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ const { name, arguments: args } = request.params;
+
+ try {
+ switch (name) {
+ case "send_tbnb":
+ return await handleSendTbnb(args);
+ case "get_faucet_info":
+ return await handleGetFaucetInfo();
+ case "get_balance":
+ return await handleGetBalance(args);
+ default:
+ throw new Error(`Unknown tool: ${name}`);
+ }
+ } catch (error) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify(
+ {
+ error: error.message,
+ },
+ null,
+ 2
+ ),
+ },
+ ],
+ isError: true,
+ };
+ }
+});
+
+/**
+ * Send TBNB to a recipient address
+ */
+async function handleSendTbnb(args) {
+ if (!wallet) {
+ throw new Error("Faucet wallet not configured. Please set FAUCET_PRIVATE_KEY.");
+ }
+
+ const recipient = args?.recipient;
+ const amount = args?.amount || 0.1;
+
+ if (!recipient) {
+ throw new Error("Recipient address is required");
+ }
+
+ // Validate address
+ if (!ethers.isAddress(recipient)) {
+ throw new Error(`Invalid address: ${recipient}`);
+ }
+
+ const recipientAddress = ethers.getAddress(recipient);
+
+ // Prevent self-transfer
+ if (recipientAddress.toLowerCase() === walletAddress.toLowerCase()) {
+ throw new Error("Cannot send tokens to the faucet address itself");
+ }
+
+ // Validate amount
+ if (amount <= 0 || amount > 1.0) {
+ throw new Error("Amount must be between 0 and 1.0 TBNB");
+ }
+
+ try {
+ // Convert amount to Wei
+ const amountWei = ethers.parseEther(amount.toString());
+
+ // Get current gas price
+ const feeData = await provider.getFeeData();
+
+ // Send transaction
+ const tx = await wallet.sendTransaction({
+ to: recipientAddress,
+ value: amountWei,
+ gasLimit: 21000,
+ gasPrice: feeData.gasPrice,
+ });
+
+ // Wait for transaction to be mined
+ const receipt = await tx.wait();
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify(
+ {
+ success: true,
+ transactionHash: tx.hash,
+ recipient: recipientAddress,
+ amount: amount,
+ amountWei: amountWei.toString(),
+ blockNumber: receipt.blockNumber,
+ status: receipt.status === 1 ? "confirmed" : "failed",
+ },
+ null,
+ 2
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ throw new Error(`Transaction failed: ${error.message}`);
+ }
+}
+
+/**
+ * Get faucet information and balance
+ */
+async function handleGetFaucetInfo() {
+ if (!walletAddress) {
+ throw new Error("Faucet wallet not configured");
+ }
+
+ try {
+ const balance = await provider.getBalance(walletAddress);
+ const balanceTbnb = ethers.formatEther(balance);
+
+ // Get network info
+ const network = await provider.getNetwork();
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify(
+ {
+ faucetAddress: walletAddress,
+ balanceWei: balance.toString(),
+ balanceTbnb: parseFloat(balanceTbnb),
+ network: {
+ name: "BSC Testnet",
+ chainId: network.chainId.toString(),
+ },
+ rpcEndpoint: BSC_TESTNET_RPC,
+ },
+ null,
+ 2
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ throw new Error(`Failed to get faucet info: ${error.message}`);
+ }
+}
+
+/**
+ * Get balance of an address
+ */
+async function handleGetBalance(args) {
+ const address = args?.address;
+
+ if (!address) {
+ throw new Error("Address is required");
+ }
+
+ // Validate address
+ if (!ethers.isAddress(address)) {
+ throw new Error(`Invalid address: ${address}`);
+ }
+
+ try {
+ const addressChecksum = ethers.getAddress(address);
+ const balance = await provider.getBalance(addressChecksum);
+ const balanceTbnb = ethers.formatEther(balance);
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify(
+ {
+ address: addressChecksum,
+ balanceWei: balance.toString(),
+ balanceTbnb: parseFloat(balanceTbnb),
+ network: "BSC Testnet",
+ },
+ null,
+ 2
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ throw new Error(`Failed to get balance: ${error.message}`);
+ }
+}
+
+// Start server
+async function main() {
+ const transport = new StdioServerTransport();
+ await server.connect(transport);
+
+ console.error("TBNB Faucet MCP Server running on stdio");
+}
+
+main().catch((error) => {
+ console.error("Fatal error:", error);
+ process.exit(1);
+});
diff --git a/typescript/faucet-mcp/index.test.js b/typescript/faucet-mcp/index.test.js
new file mode 100644
index 0000000..9f47c94
--- /dev/null
+++ b/typescript/faucet-mcp/index.test.js
@@ -0,0 +1,277 @@
+/**
+ * Unit tests for TBNB Faucet MCP Server (Example 3 - Node.js)
+ *
+ * Note: These tests focus on the business logic and validation
+ * rather than full integration with the MCP server, since the server
+ * initializes on import and uses stdio transport.
+ */
+
+import { describe, test, expect, beforeEach, jest } from '@jest/globals';
+
+describe('Address Validation Logic', () => {
+ test('should validate correct Ethereum address format', () => {
+ const validAddress = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0';
+ // Ethereum addresses are 42 characters (0x + 40 hex chars)
+ expect(validAddress.length).toBe(42);
+ expect(validAddress.startsWith('0x')).toBe(true);
+ expect(/^0x[a-fA-F0-9]{40}$/.test(validAddress)).toBe(true);
+ });
+
+ test('should reject invalid address formats', () => {
+ const invalidAddresses = [
+ 'invalid',
+ '0x123',
+ '742d35Cc6634C0532925a3b844Bc9e7595f0bEb', // Missing 0x
+ '0xGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', // Invalid hex
+ '',
+ ];
+
+ invalidAddresses.forEach((addr) => {
+ const isValid = addr.length === 42 && addr.startsWith('0x') && /^0x[a-fA-F0-9]{40}$/.test(addr);
+ expect(isValid).toBe(false);
+ });
+ });
+});
+
+describe('Amount Validation Logic', () => {
+ test('should accept valid amounts between 0 and 1.0', () => {
+ const validAmounts = [0.01, 0.1, 0.5, 1.0];
+
+ validAmounts.forEach((amount) => {
+ const isValid = amount > 0 && amount <= 1.0;
+ expect(isValid).toBe(true);
+ });
+ });
+
+ test('should reject zero or negative amounts', () => {
+ const invalidAmounts = [0, -0.1, -1.0];
+
+ invalidAmounts.forEach((amount) => {
+ const isValid = amount > 0 && amount <= 1.0;
+ expect(isValid).toBe(false);
+ });
+ });
+
+ test('should reject amounts greater than 1.0', () => {
+ const invalidAmounts = [1.1, 2.0, 10.0];
+
+ invalidAmounts.forEach((amount) => {
+ const isValid = amount > 0 && amount <= 1.0;
+ expect(isValid).toBe(false);
+ });
+ });
+});
+
+describe('Self-Transfer Prevention Logic', () => {
+ test('should detect self-transfer attempts', () => {
+ const faucetAddress = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
+ const recipient = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
+
+ const isSelfTransfer = recipient.toLowerCase() === faucetAddress.toLowerCase();
+ expect(isSelfTransfer).toBe(true);
+ });
+
+ test('should allow transfers to different addresses', () => {
+ const faucetAddress = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
+ const recipient = '0x1234567890123456789012345678901234567890';
+
+ const isSelfTransfer = recipient.toLowerCase() === faucetAddress.toLowerCase();
+ expect(isSelfTransfer).toBe(false);
+ });
+});
+
+describe('Wei Conversion Logic', () => {
+ test('should convert TBNB to Wei correctly', () => {
+ // 1 TBNB = 10^18 Wei
+ const tbnbAmounts = [
+ { tbnb: 0.1, wei: '100000000000000000' },
+ { tbnb: 1.0, wei: '1000000000000000000' },
+ { tbnb: 0.01, wei: '10000000000000000' },
+ ];
+
+ tbnbAmounts.forEach(({ tbnb, wei }) => {
+ // Manual conversion for testing
+ const calculatedWei = (tbnb * Math.pow(10, 18)).toString();
+ expect(calculatedWei).toBe(wei);
+ });
+ });
+
+ test('should handle Wei to TBNB conversion', () => {
+ const weiAmounts = [
+ { wei: '100000000000000000', tbnb: 0.1 },
+ { wei: '1000000000000000000', tbnb: 1.0 },
+ { wei: '500000000000000000', tbnb: 0.5 },
+ ];
+
+ weiAmounts.forEach(({ wei, tbnb }) => {
+ // Manual conversion for testing
+ const calculatedTbnb = parseFloat(wei) / Math.pow(10, 18);
+ expect(calculatedTbnb).toBeCloseTo(tbnb, 10);
+ });
+ });
+});
+
+describe('Transaction Response Format', () => {
+ test('should format successful transaction response correctly', () => {
+ const mockResponse = {
+ success: true,
+ transactionHash: '0xabcdef1234567890',
+ recipient: '0x1234567890123456789012345678901234567890',
+ amount: 0.1,
+ amountWei: '100000000000000000',
+ blockNumber: 12345,
+ status: 'confirmed',
+ };
+
+ expect(mockResponse).toHaveProperty('success');
+ expect(mockResponse).toHaveProperty('transactionHash');
+ expect(mockResponse).toHaveProperty('recipient');
+ expect(mockResponse).toHaveProperty('amount');
+ expect(mockResponse).toHaveProperty('blockNumber');
+ expect(mockResponse).toHaveProperty('status');
+ expect(mockResponse.success).toBe(true);
+ expect(mockResponse.status).toBe('confirmed');
+ });
+
+ test('should format error response correctly', () => {
+ const mockErrorResponse = {
+ error: 'Invalid address: invalid_address',
+ };
+
+ expect(mockErrorResponse).toHaveProperty('error');
+ expect(typeof mockErrorResponse.error).toBe('string');
+ });
+});
+
+describe('Faucet Info Response Format', () => {
+ test('should format faucet info response correctly', () => {
+ const mockFaucetInfo = {
+ faucetAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
+ balanceWei: '1000000000000000000',
+ balanceTbnb: 1.0,
+ network: {
+ name: 'BSC Testnet',
+ chainId: '97',
+ },
+ rpcEndpoint: 'https://data-seed-prebsc-1-s1.binance.org:8545/',
+ };
+
+ expect(mockFaucetInfo).toHaveProperty('faucetAddress');
+ expect(mockFaucetInfo).toHaveProperty('balanceWei');
+ expect(mockFaucetInfo).toHaveProperty('balanceTbnb');
+ expect(mockFaucetInfo).toHaveProperty('network');
+ expect(mockFaucetInfo).toHaveProperty('rpcEndpoint');
+ expect(mockFaucetInfo.network.chainId).toBe('97');
+ });
+});
+
+describe('Balance Query Response Format', () => {
+ test('should format balance query response correctly', () => {
+ const mockBalanceResponse = {
+ address: '0x1234567890123456789012345678901234567890',
+ balanceWei: '500000000000000000',
+ balanceTbnb: 0.5,
+ network: 'BSC Testnet',
+ };
+
+ expect(mockBalanceResponse).toHaveProperty('address');
+ expect(mockBalanceResponse).toHaveProperty('balanceWei');
+ expect(mockBalanceResponse).toHaveProperty('balanceTbnb');
+ expect(mockBalanceResponse).toHaveProperty('network');
+ expect(mockBalanceResponse.balanceTbnb).toBe(0.5);
+ });
+});
+
+describe('Error Handling', () => {
+ test('should handle missing recipient address', () => {
+ const recipient = '';
+ if (!recipient) {
+ expect(() => {
+ throw new Error('Recipient address is required');
+ }).toThrow('Recipient address is required');
+ }
+ });
+
+ test('should handle missing wallet configuration', () => {
+ const wallet = null;
+ if (!wallet) {
+ expect(() => {
+ throw new Error('Faucet wallet not configured. Please set FAUCET_PRIVATE_KEY.');
+ }).toThrow('Faucet wallet not configured');
+ }
+ });
+
+ test('should handle network errors gracefully', () => {
+ const networkError = new Error('Network error');
+ expect(networkError.message).toBe('Network error');
+ expect(networkError).toBeInstanceOf(Error);
+ });
+});
+
+describe('MCP Tool Schema Validation', () => {
+ test('send_tbnb tool should have correct schema structure', () => {
+ const toolSchema = {
+ name: 'send_tbnb',
+ description: 'Send testnet BNB tokens to a specified address on BSC testnet',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ recipient: {
+ type: 'string',
+ description: 'The BSC testnet address that will receive the TBNB tokens',
+ },
+ amount: {
+ type: 'number',
+ description: 'Amount of TBNB to send (default: 0.1, maximum: 1.0)',
+ default: 0.1,
+ },
+ },
+ required: ['recipient'],
+ },
+ };
+
+ expect(toolSchema).toHaveProperty('name');
+ expect(toolSchema).toHaveProperty('description');
+ expect(toolSchema).toHaveProperty('inputSchema');
+ expect(toolSchema.inputSchema.type).toBe('object');
+ expect(toolSchema.inputSchema.required).toContain('recipient');
+ });
+
+ test('get_faucet_info tool should have correct schema structure', () => {
+ const toolSchema = {
+ name: 'get_faucet_info',
+ description: 'Get information about the faucet including current balance',
+ inputSchema: {
+ type: 'object',
+ properties: {},
+ },
+ };
+
+ expect(toolSchema).toHaveProperty('name');
+ expect(toolSchema).toHaveProperty('description');
+ expect(toolSchema).toHaveProperty('inputSchema');
+ expect(toolSchema.inputSchema.type).toBe('object');
+ });
+
+ test('get_balance tool should have correct schema structure', () => {
+ const toolSchema = {
+ name: 'get_balance',
+ description: 'Get the TBNB balance of a BSC testnet address',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ address: {
+ type: 'string',
+ description: 'The BSC testnet address to check',
+ },
+ },
+ required: ['address'],
+ },
+ };
+
+ expect(toolSchema).toHaveProperty('name');
+ expect(toolSchema).toHaveProperty('description');
+ expect(toolSchema).toHaveProperty('inputSchema');
+ expect(toolSchema.inputSchema.required).toContain('address');
+ });
+});
diff --git a/typescript/faucet-mcp/package.json b/typescript/faucet-mcp/package.json
new file mode 100644
index 0000000..8e7ca75
--- /dev/null
+++ b/typescript/faucet-mcp/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "tbnb-faucet-mcp",
+ "version": "1.0.0",
+ "description": "MCP server for distributing testnet BNB tokens on BSC testnet",
+ "type": "module",
+ "main": "index.js",
+ "scripts": {
+ "start": "node index.js",
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
+ "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
+ "test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage"
+ },
+ "keywords": [
+ "mcp",
+ "model-context-protocol",
+ "bnb",
+ "testnet",
+ "faucet",
+ "blockchain"
+ ],
+ "author": "",
+ "license": "MIT",
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^0.5.0",
+ "ethers": "^6.9.0"
+ },
+ "devDependencies": {
+ "@jest/globals": "^29.7.0",
+ "jest": "^29.7.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "jest": {
+ "testEnvironment": "node",
+ "transform": {},
+ "moduleNameMapper": {
+ "^(\\.{1,2}/.*)\\.js$": "$1"
+ }
+ }
+}