From 3f6c780874ac38ec8743d1a44046d98d8033bba0 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Fri, 16 May 2025 13:58:55 -0700 Subject: [PATCH 01/12] remove non-sei network references --- .cursor/mcp.json | 12 +- README.md | 178 +++++------------ package.json | 14 +- src/core/chains.ts | 300 +++-------------------------- src/core/prompts.ts | 34 ++-- src/core/resources.ts | 92 ++++----- src/core/services/balance.ts | 38 ++-- src/core/services/blocks.ts | 16 +- src/core/services/clients.ts | 30 +-- src/core/services/contracts.ts | 22 +-- src/core/services/ens.ts | 24 +-- src/core/services/tokens.ts | 12 +- src/core/services/transactions.ts | 18 +- src/core/services/transfer.ts | 82 ++++---- src/core/tools.ts | 308 +++++++++++++++--------------- 15 files changed, 428 insertions(+), 752 deletions(-) diff --git a/.cursor/mcp.json b/.cursor/mcp.json index 42a6a03..fedcf96 100644 --- a/.cursor/mcp.json +++ b/.cursor/mcp.json @@ -1,19 +1,19 @@ { "mcpServers": { - "evm-mcp-server": { + "sei-mcp-server": { "command": "npx", "args": [ "-y", - "@mcpdotdirect/evm-mcp-server" + "@sei-protocol/sei-mcp-server" ] }, - "evm-mcp-http": { + "sei-mcp-http": { "command": "npx", "args": [ - "-y", - "@mcpdotdirect/evm-mcp-server", + "-y", + "@sei-protocol/sei-mcp-server", "--http" ] } } -} \ No newline at end of file +} diff --git a/README.md b/README.md index 6f4f47a..298f4e1 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ -# EVM MCP Server +# SEI MCP Server ![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg) ![EVM Networks](https://img.shields.io/badge/Networks-30+-green) ![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-3178C6) ![Viem](https://img.shields.io/badge/Viem-1.0+-green) -A comprehensive Model Context Protocol (MCP) server that provides blockchain services across multiple EVM-compatible networks. This server enables AI agents to interact with Ethereum, Optimism, Arbitrum, Base, Polygon, and many other EVM chains with a unified interface. +Sei Model Context Protocol (Sei MCP) server provides blockchain services for Sei blockchain. +This server enables AI assistants and agents to interact via unified interface. ## 📋 Contents @@ -26,27 +27,24 @@ A comprehensive Model Context Protocol (MCP) server that provides blockchain ser ## 🔭 Overview -The MCP EVM Server leverages the Model Context Protocol to provide blockchain services to AI agents. It supports a wide range of services including: +The MCP EVM Server leverages the Model Context Protocol to provide blockchain services to AI agents. +It supports a wide range of services including: - Reading blockchain state (balances, transactions, blocks, etc.) - Interacting with smart contracts - Transferring tokens (native, ERC20, ERC721, ERC1155) - Querying token metadata and balances -- Chain-specific services across 30+ EVM networks -- **ENS name resolution** for all address parameters (use human-readable names like 'vitalik.eth' instead of addresses) - -All services are exposed through a consistent interface of MCP tools and resources, making it easy for AI agents to discover and use blockchain functionality. **Every tool that accepts Ethereum addresses also supports ENS names**, automatically resolving them to addresses behind the scenes. +All services are exposed through a consistent interface of MCP tools and resources, making it easy for AI agents to +discover and use blockchain functionality. ## ✨ Features ### Blockchain Data Access -- **Multi-chain support** for 30+ EVM-compatible networks - **Chain information** including blockNumber, chainId, and RPCs - **Block data** access by number, hash, or latest - **Transaction details** and receipts with decoded logs - **Address balances** for native tokens and all token standards -- **ENS resolution** for human-readable Ethereum addresses (use 'vitalik.eth' instead of '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045') ### Token services @@ -83,63 +81,9 @@ All services are exposed through a consistent interface of MCP tools and resourc ## 🌐 Supported Networks -### Mainnets -- Ethereum (ETH) -- Optimism (OP) -- Arbitrum (ARB) -- Arbitrum Nova -- Base -- Polygon (MATIC) -- Polygon zkEVM -- Avalanche (AVAX) -- Binance Smart Chain (BSC) -- zkSync Era -- Linea -- Celo -- Gnosis (xDai) -- Fantom (FTM) -- Filecoin (FIL) -- Moonbeam -- Moonriver -- Cronos -- Scroll -- Mantle -- Manta -- Blast -- Fraxtal -- Mode -- Metis -- Kroma -- Zora -- Aurora -- Canto -- Flow -- Lumia - -### Testnets -- Sepolia -- Optimism Sepolia -- Arbitrum Sepolia -- Base Sepolia -- Polygon Amoy -- Avalanche Fuji -- BSC Testnet -- zkSync Sepolia -- Linea Sepolia -- Scroll Sepolia -- Mantle Sepolia -- Manta Sepolia -- Blast Sepolia -- Fraxtal Testnet -- Mode Testnet -- Metis Sepolia -- Kroma Sepolia -- Zora Sepolia -- Celo Alfajores -- Goerli -- Holesky -- Flow Testnet -- Lumia Testnet +- Sei Mainnet +- Sei Testnet +- Sei Localnet ## 🛠️ Prerequisites @@ -150,8 +94,8 @@ All services are exposed through a consistent interface of MCP tools and resourc ```bash # Clone the repository -git clone https://github.com/mcpdotdirect/mcp-evm-server.git -cd mcp-evm-server +git clone https://github.com/sei-protocol/sei-mcp-server.git +cd sei-mcp-server # Install dependencies with Bun bun install @@ -164,7 +108,7 @@ npm install The server uses the following default configuration: -- **Default Chain ID**: 1 (Ethereum Mainnet) +- **Default Chain ID**: 1329 (Sei Mainnet) - **Server Port**: 3001 - **Server Host**: 0.0.0.0 (accessible from any network interface) @@ -177,14 +121,14 @@ These values are hardcoded in the application. If you need to modify them, you c ### Using npx (No Installation Required) -You can run the MCP EVM Server directly without installation using npx: +You can run the Sei MCP Server directly without installation using npx: ```bash # Run the server in stdio mode (for CLI tools) -npx @mcpdotdirect/evm-mcp-server +npx @sei-js/sei-mcp-server # Run the server in HTTP mode (for web applications) -npx @mcpdotdirect/evm-mcp-server --http +npx @sei-js/sei-mcp-server --http ``` ### Running the Server Locally @@ -222,9 +166,9 @@ To connect to the MCP server from Cursor: 3. Scroll down to "MCP Servers" section 4. Click "Add new MCP server" 5. Enter the following details: - - Server name: `evm-mcp-server` + - Server name: `sei-mcp-server` - Type: `command` - - Command: `npx @mcpdotdirect/evm-mcp-server` + - Command: `npx @sei-js/sei-mcp-server` 6. Click "Save" @@ -241,14 +185,14 @@ For a more portable configuration that you can share with your team or use acros "command": "npx", "args": [ "-y", - "@mcpdotdirect/evm-mcp-server" + "@sei-protocol/sei-mcp-server" ] }, "evm-mcp-http": { "command": "npx", "args": [ "-y", - "@mcpdotdirect/evm-mcp-server", + "@sei-protocol/sei-mcp-server", "--http" ] } @@ -297,12 +241,12 @@ After configuring the MCP server with `mcp.json`, you can easily use it in Curso // blockchain-example.js async function main() { try { - // Get ETH balance for an address using ENS - console.log("Getting ETH balance for vitalik.eth..."); + // Get Sei balance for an address using ENS + console.log("Getting Sei balance for 0x1234..."); // When using with Cursor, you can simply ask Cursor to: - // "Check the ETH balance of vitalik.eth on mainnet" - // Or "Transfer 0.1 ETH from my wallet to vitalik.eth" + // "Check the Sei balance of 0x1234 on mainnet" + // Or "Transfer 0.1 Sei from my wallet to 0x1234" // Cursor will use the MCP server to execute these operations // without requiring any additional code from you @@ -319,9 +263,9 @@ main(); 2. With the file open in Cursor, you can ask Cursor to: - - "Check the current ETH balance of vitalik.eth" - - "Look up the price of USDC on Ethereum" - - "Show me the latest block on Optimism" + - "Check the current Sei balance of 0x1234 on mainnet" + - "Look up the price of USDC on Sei" + - "Show me the latest block on Sei" - "Check if 0x1234... is a contract address" 3. Cursor will use the MCP server to execute these operations and return the results directly in your conversation. @@ -334,7 +278,7 @@ If you're using Claude CLI, you can connect to the MCP server with just two comm ```bash # Add the MCP server -claude mcp add evm-mcp-server npx @mcpdotdirect/evm-mcp-server +claude mcp add evm-mcp-server npx @sei-protocol/sei-mcp-server # Start Claude with the MCP server enabled claude @@ -347,16 +291,16 @@ claude const mcp = new McpClient("http://localhost:3000"); const result = await mcp.invokeTool("get-token-balance", { - tokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum - ownerAddress: "vitalik.eth", // ENS name instead of address - network: "ethereum" + tokenAddress: "0x3894085ef7ff0f0aedf52e2a2704928d1ec074f1", // USDC on Sei + ownerAddress: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", + network: "sei" }); console.log(result); // { -// tokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", +// tokenAddress: "0x3894085ef7ff0f0aedf52e2a2704928d1ec074f1", // owner: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", -// network: "ethereum", +// network: "sei", // raw: "1000000000", // formatted: "1000", // symbol: "USDC", @@ -364,31 +308,11 @@ console.log(result); // } ``` -### Example: Resolving an ENS Name - -```javascript -// Example of using the MCP client to resolve an ENS name to an address -const mcp = new McpClient("http://localhost:3000"); - -const result = await mcp.invokeTool("resolve-ens", { - ensName: "vitalik.eth", - network: "ethereum" -}); - -console.log(result); -// { -// ensName: "vitalik.eth", -// normalizedName: "vitalik.eth", -// resolvedAddress: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", -// network: "ethereum" -// } -``` - ## 📚 API Reference ### Tools -The server provides the following MCP tools for agents. **All tools that accept address parameters support both Ethereum addresses and ENS names.** +The server provides the following MCP tools for agents. #### Token services @@ -408,16 +332,16 @@ The server provides the following MCP tools for agents. **All tools that accept #### Blockchain services -| Tool Name | Description | Key Parameters | -|-----------|-------------|----------------| -| `get-chain-info` | Get network information | `network` | -| `get-balance` | Get native token balance | `address` (address/ENS), `network` | -| `transfer-eth` | Send native tokens | `privateKey`, `to` (address/ENS), `amount`, `network` | +| Tool Name | Description | Key Parameters | +|-------------------|-------------|----------------| +| `get-chain-info` | Get network information | `network` | +| `get-balance` | Get native token balance | `address` (address/ENS), `network` | +| `transfer-sei` | Send native tokens | `privateKey`, `to` (address/ENS), `amount`, `network` | | `get-transaction` | Get transaction details | `txHash`, `network` | -| `read-contract` | Read smart contract state | `contractAddress` (address/ENS), `abi`, `functionName`, `args`, `network` | -| `write-contract` | Write to smart contract | `contractAddress` (address/ENS), `abi`, `functionName`, `args`, `privateKey`, `network` | -| `is-contract` | Check if address is a contract | `address` (address/ENS), `network` | -| `resolve-ens` | Resolve ENS name to address | `ensName`, `network` | +| `read-contract` | Read smart contract state | `contractAddress` (address/ENS), `abi`, `functionName`, `args`, `network` | +| `write-contract` | Write to smart contract | `contractAddress` (address/ENS), `abi`, `functionName`, `args`, `privateKey`, `network` | +| `is-contract` | Check if address is a contract | `address` (address/ENS), `network` | +| `resolve-ens` | Resolve ENS name to address | `ensName`, `network` | ### Resources @@ -425,15 +349,15 @@ The server exposes blockchain data through the following MCP resource URIs. All #### Blockchain Resources -| Resource URI Pattern | Description | -|-----------|-------------| +| Resource URI Pattern | Description | +|-----------|------------------------------------------| | `evm://{network}/chain` | Chain information for a specific network | -| `evm://chain` | Ethereum mainnet chain information | -| `evm://{network}/block/{blockNumber}` | Block data by number | -| `evm://{network}/block/latest` | Latest block data | -| `evm://{network}/address/{address}/balance` | Native token balance | -| `evm://{network}/tx/{txHash}` | Transaction details | -| `evm://{network}/tx/{txHash}/receipt` | Transaction receipt with logs | +| `evm://chain` | Sei mainnet chain information | +| `evm://{network}/block/{blockNumber}` | Block data by number | +| `evm://{network}/block/latest` | Latest block data | +| `evm://{network}/address/{address}/balance` | Native token balance | +| `evm://{network}/tx/{txHash}` | Transaction details | +| `evm://{network}/tx/{txHash}/receipt` | Transaction receipt with logs | #### Token Resources diff --git a/package.json b/package.json index 37ab0a7..fbf22a8 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { - "name": "@mcpdotdirect/evm-mcp-server", + "name": "@sei-protocol/sei-mcp-server", "module": "src/index.ts", "type": "module", - "version": "1.1.3", + "version": "1.0.0", "description": "Model Context Protocol (MCP) server for interacting with EVM-compatible networks", "bin": { - "evm-mcp-server": "./bin/cli.js" + "sei-mcp-server": "./bin/cli.js" }, "main": "build/index.js", "files": [ @@ -50,7 +50,7 @@ "model-context-protocol", "evm", "blockchain", - "ethereum", + "sei", "web3", "smart-contracts", "ai", @@ -63,12 +63,12 @@ }, "repository": { "type": "git", - "url": "https://github.com/mcpdotdirect/evm-mcp-server" + "url": "https://github.com/sei-protocol/sei-mcp-server" }, "bugs": { - "url": "https://github.com/mcpdotdirect/evm-mcp-server/issues" + "url": "https://github.com/sei-protocol/sei-mcp-server/issues" }, - "homepage": "https://github.com/mcpdotdirect/evm-mcp-server#readme", + "homepage": "https://github.com/sei-protocol/seo-mcp-server#readme", "publishConfig": { "access": "public" } diff --git a/src/core/chains.ts b/src/core/chains.ts index 43bac2c..39e6e66 100644 --- a/src/core/chains.ts +++ b/src/core/chains.ts @@ -1,281 +1,33 @@ import { type Chain } from 'viem'; import { - // Mainnets - mainnet, - optimism, - arbitrum, - arbitrumNova, - base, - polygon, - polygonZkEvm, - avalanche, - bsc, - zksync, - linea, - celo, - gnosis, - fantom, - filecoin, - moonbeam, - moonriver, - cronos, - lumiaMainnet, - scroll, - mantle, - manta, - blast, - fraxtal, - mode, - metis, - kroma, - zora, - aurora, - canto, - flowMainnet, - - // Testnets - sepolia, - optimismSepolia, - arbitrumSepolia, - baseSepolia, - polygonAmoy, - avalancheFuji, - bscTestnet, - zksyncSepoliaTestnet, - lineaSepolia, - lumiaTestnet, - scrollSepolia, - mantleSepoliaTestnet, - mantaSepoliaTestnet, - blastSepolia, - fraxtalTestnet, - modeTestnet, - metisSepolia, - kromaSepolia, - zoraSepolia, - celoAlfajores, - goerli, - holesky, - flowTestnet + sei, + seiTestnet, + seiDevnet } from 'viem/chains'; // Default configuration values -export const DEFAULT_RPC_URL = 'https://eth.llamarpc.com'; -export const DEFAULT_CHAIN_ID = 1; +export const DEFAULT_RPC_URL = 'https://evm-rpc.sei-apis.com'; +export const DEFAULT_CHAIN_ID = 1329; // Map chain IDs to chains export const chainMap: Record = { - // Mainnets - 1: mainnet, - 10: optimism, - 42161: arbitrum, - 42170: arbitrumNova, - 8453: base, - 137: polygon, - 1101: polygonZkEvm, - 43114: avalanche, - 56: bsc, - 324: zksync, - 59144: linea, - 42220: celo, - 100: gnosis, - 250: fantom, - 314: filecoin, - 1284: moonbeam, - 1285: moonriver, - 25: cronos, - 534352: scroll, - 5000: mantle, - 169: manta, - 994873017: lumiaMainnet, - 81457: blast, - 252: fraxtal, - 34443: mode, - 1088: metis, - 255: kroma, - 7777777: zora, - 1313161554: aurora, - 7700: canto, - 747: flowMainnet, - - // Testnets - 11155111: sepolia, - 11155420: optimismSepolia, - 421614: arbitrumSepolia, - 84532: baseSepolia, - 80002: polygonAmoy, - 43113: avalancheFuji, - 97: bscTestnet, - 300: zksyncSepoliaTestnet, - 59141: lineaSepolia, - 1952959480: lumiaTestnet, - 534351: scrollSepolia, - 5003: mantleSepoliaTestnet, - 3441006: mantaSepoliaTestnet, - 168587773: blastSepolia, - 2522: fraxtalTestnet, - 919: modeTestnet, - 59902: metisSepolia, - 2358: kromaSepolia, - 999999999: zoraSepolia, - 44787: celoAlfajores, - 5: goerli, - 17000: holesky, - 545: flowTestnet, + 1329: sei, + 1328: seiTestnet, + 713_715: seiDevnet }; // Map network names to chain IDs for easier reference export const networkNameMap: Record = { - // Mainnets - 'ethereum': 1, - 'mainnet': 1, - 'eth': 1, - 'optimism': 10, - 'op': 10, - 'arbitrum': 42161, - 'arb': 42161, - 'arbitrum-nova': 42170, - 'arbitrumnova': 42170, - 'base': 8453, - 'polygon': 137, - 'matic': 137, - 'polygon-zkevm': 1101, - 'polygonzkevm': 1101, - 'avalanche': 43114, - 'avax': 43114, - 'binance': 56, - 'bsc': 56, - 'zksync': 324, - 'linea': 59144, - 'celo': 42220, - 'gnosis': 100, - 'xdai': 100, - 'fantom': 250, - 'ftm': 250, - 'filecoin': 314, - 'fil': 314, - 'moonbeam': 1284, - 'moonriver': 1285, - 'cronos': 25, - 'scroll': 534352, - 'mantle': 5000, - 'manta': 169, - 'lumia': 994873017, - 'blast': 81457, - 'fraxtal': 252, - 'mode': 34443, - 'metis': 1088, - 'kroma': 255, - 'zora': 7777777, - 'aurora': 1313161554, - 'canto': 7700, - 'flow': 747, - - // Testnets - 'sepolia': 11155111, - 'optimism-sepolia': 11155420, - 'optimismsepolia': 11155420, - 'arbitrum-sepolia': 421614, - 'arbitrumsepolia': 421614, - 'base-sepolia': 84532, - 'basesepolia': 84532, - 'polygon-amoy': 80002, - 'polygonamoy': 80002, - 'avalanche-fuji': 43113, - 'avalanchefuji': 43113, - 'fuji': 43113, - 'bsc-testnet': 97, - 'bsctestnet': 97, - 'zksync-sepolia': 300, - 'zksyncsepolia': 300, - 'linea-sepolia': 59141, - 'lineasepolia': 59141, - 'lumia-testnet': 1952959480, - 'scroll-sepolia': 534351, - 'scrollsepolia': 534351, - 'mantle-sepolia': 5003, - 'mantlesepolia': 5003, - 'manta-sepolia': 3441006, - 'mantasepolia': 3441006, - 'blast-sepolia': 168587773, - 'blastsepolia': 168587773, - 'fraxtal-testnet': 2522, - 'fraxtaltestnet': 2522, - 'mode-testnet': 919, - 'modetestnet': 919, - 'metis-sepolia': 59902, - 'metissepolia': 59902, - 'kroma-sepolia': 2358, - 'kromasepolia': 2358, - 'zora-sepolia': 999999999, - 'zorasepolia': 999999999, - 'celo-alfajores': 44787, - 'celoalfajores': 44787, - 'alfajores': 44787, - 'goerli': 5, - 'holesky': 17000, - 'flow-testnet': 545, + 'sei': 1329, + 'sei-testnet': 1328, + 'sei-devnet': 713_715, }; // Map chain IDs to RPC URLs export const rpcUrlMap: Record = { - // Mainnets - 1: 'https://eth.llamarpc.com', - 10: 'https://mainnet.optimism.io', - 42161: 'https://arb1.arbitrum.io/rpc', - 42170: 'https://nova.arbitrum.io/rpc', - 8453: 'https://mainnet.base.org', - 137: 'https://polygon-rpc.com', - 1101: 'https://zkevm-rpc.com', - 43114: 'https://api.avax.network/ext/bc/C/rpc', - 56: 'https://bsc-dataseed.binance.org', - 324: 'https://mainnet.era.zksync.io', - 59144: 'https://rpc.linea.build', - 42220: 'https://forno.celo.org', - 100: 'https://rpc.gnosischain.com', - 250: 'https://rpc.ftm.tools', - 314: 'https://api.node.glif.io/rpc/v1', - 1284: 'https://rpc.api.moonbeam.network', - 1285: 'https://rpc.api.moonriver.moonbeam.network', - 25: 'https://evm.cronos.org', - 534352: 'https://rpc.scroll.io', - 5000: 'https://rpc.mantle.xyz', - 169: 'https://pacific-rpc.manta.network/http', - 81457: 'https://rpc.blast.io', - 252: 'https://rpc.frax.com', - 994873017: 'https://mainnet-rpc.lumia.org', - 34443: 'https://mainnet.mode.network', - 1088: 'https://andromeda.metis.io/?owner=1088', - 255: 'https://api.kroma.network', - 7777777: 'https://rpc.zora.energy', - 1313161554: 'https://mainnet.aurora.dev', - 7700: 'https://canto.gravitychain.io', - 747: 'https://mainnet.evm.nodes.onflow.org', - - // Testnets - 11155111: 'https://sepolia.drpc.org', - 11155420: 'https://sepolia.optimism.io', - 421614: 'https://sepolia-rpc.arbitrum.io/rpc', - 84532: 'https://sepolia.base.org', - 80002: 'https://rpc-amoy.polygon.technology', - 43113: 'https://api.avax-test.network/ext/bc/C/rpc', - 97: 'https://data-seed-prebsc-1-s1.binance.org:8545', - 300: 'https://sepolia.era.zksync.dev', - 59141: 'https://rpc.sepolia.linea.build', - 534351: 'https://sepolia-rpc.scroll.io', - 5003: 'https://rpc.sepolia.mantle.xyz', - 3441006: 'https://pacific-rpc.sepolia.manta.network/http', - 1952959480: 'https://testnet-rpc.lumia.org', - 168587773: 'https://sepolia.blast.io', - 2522: 'https://rpc.testnet.frax.com', - 919: 'https://sepolia.mode.network', - 59902: 'https://sepolia.metis.io/?owner=59902', - 2358: 'https://api.sepolia.kroma.network', - 999999999: 'https://sepolia.rpc.zora.energy', - 44787: 'https://alfajores-forno.celo-testnet.org', - 5: 'https://rpc.ankr.com/eth_goerli', - 17000: 'https://ethereum-holesky.publicnode.com', - 545: 'https://testnet.evm.nodes.onflow.org', + 1329: 'https://evm-rpc.sei-apis.com', + 1328: 'https://evm-rpc-atlantic-2.sei-apis.com', + 713_715: 'https://evm-rpc-arctic-1.sei-apis.com' }; /** @@ -287,21 +39,21 @@ export function resolveChainId(chainIdentifier: number | string): number { if (typeof chainIdentifier === 'number') { return chainIdentifier; } - + // Convert to lowercase for case-insensitive matching const networkName = chainIdentifier.toLowerCase(); - + // Check if the network name is in our map if (networkName in networkNameMap) { return networkNameMap[networkName]; } - + // Try parsing as a number const parsedId = parseInt(networkName); if (!isNaN(parsedId)) { return parsedId; } - + // Default to mainnet if not found return DEFAULT_CHAIN_ID; } @@ -317,15 +69,15 @@ export function getChain(chainIdentifier: number | string = DEFAULT_CHAIN_ID): C const networkName = chainIdentifier.toLowerCase(); // Try to get from direct network name mapping first if (networkNameMap[networkName]) { - return chainMap[networkNameMap[networkName]] || mainnet; + return chainMap[networkNameMap[networkName]] || sei; } - + // If not found, throw an error throw new Error(`Unsupported network: ${chainIdentifier}`); } - + // If it's a number, return the chain from chainMap - return chainMap[chainIdentifier] || mainnet; + return chainMap[chainIdentifier] || sei; } /** @@ -334,10 +86,10 @@ export function getChain(chainIdentifier: number | string = DEFAULT_CHAIN_ID): C * @returns The RPC URL for the specified chain */ export function getRpcUrl(chainIdentifier: number | string = DEFAULT_CHAIN_ID): string { - const chainId = typeof chainIdentifier === 'string' - ? resolveChainId(chainIdentifier) + const chainId = typeof chainIdentifier === 'string' + ? resolveChainId(chainIdentifier) : chainIdentifier; - + return rpcUrlMap[chainId] || DEFAULT_RPC_URL; } @@ -349,4 +101,4 @@ export function getSupportedNetworks(): string[] { return Object.keys(networkNameMap) .filter(name => name.length > 2) // Filter out short aliases .sort(); -} +} diff --git a/src/core/prompts.ts b/src/core/prompts.ts index b7f7bce..61b7586 100644 --- a/src/core/prompts.ts +++ b/src/core/prompts.ts @@ -12,14 +12,14 @@ export function registerEVMPrompts(server: McpServer) { "Explore information about a specific block", { blockNumber: z.string().optional().describe("Block number to explore. If not provided, latest block will be used."), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet' etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, - ({ blockNumber, network = "ethereum" }) => ({ + ({ blockNumber, network = "sei" }) => ({ messages: [{ role: "user", content: { type: "text", - text: blockNumber + text: blockNumber ? `Please analyze block #${blockNumber} on the ${network} network and provide information about its key metrics, transactions, and significance.` : `Please analyze the latest block on the ${network} network and provide information about its key metrics, transactions, and significance.` } @@ -33,9 +33,9 @@ export function registerEVMPrompts(server: McpServer) { "Analyze a specific transaction", { txHash: z.string().describe("Transaction hash to analyze"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet' etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, - ({ txHash, network = "ethereum" }) => ({ + ({ txHash, network = "sei" }) => ({ messages: [{ role: "user", content: { @@ -51,10 +51,10 @@ export function registerEVMPrompts(server: McpServer) { "analyze_address", "Analyze an EVM address", { - address: z.string().describe("Ethereum address to analyze"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet.") + address: z.string().describe("Sei 0x address to analyze"), + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet' etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, - ({ address, network = "ethereum" }) => ({ + ({ address, network = "sei" }) => ({ messages: [{ role: "user", content: { @@ -72,9 +72,9 @@ export function registerEVMPrompts(server: McpServer) { { contractAddress: z.string().describe("The contract address"), abiJson: z.string().optional().describe("The contract ABI as a JSON string"), - network: z.string().optional().describe("Network name or chain ID. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.") }, - ({ contractAddress, abiJson, network = "ethereum" }) => ({ + ({ contractAddress, abiJson, network = "sei" }) => ({ messages: [{ role: "user", content: { @@ -108,9 +108,9 @@ export function registerEVMPrompts(server: McpServer) { // Network comparison server.prompt( "compare_networks", - "Compare different EVM-compatible networks", + "Compare Sei networks", { - networkList: z.string().describe("Comma-separated list of networks to compare (e.g., 'ethereum,optimism,arbitrum')") + networkList: z.string().describe("Comma-separated list of networks to compare (e.g., 'sei,sei-testnet,sei-devnet')") }, ({ networkList }) => { const networks = networkList.split(',').map(n => n.trim()); @@ -119,7 +119,7 @@ export function registerEVMPrompts(server: McpServer) { role: "user", content: { type: "text", - text: `Please compare the following EVM-compatible networks: ${networks.join(', ')}. Include information about their architecture, gas fees, transaction speed, security, and any other relevant differences.` + text: `Please compare the following Sei networks: ${networks.join(', ')}. Include information about their architecture, gas fees, transaction speed, security, and any other relevant differences.` } }] }; @@ -134,11 +134,11 @@ export function registerEVMPrompts(server: McpServer) { tokenAddress: z.string().describe("Token contract address to analyze"), tokenType: z.string().optional().describe("Type of token to analyze (erc20, erc721/nft, or auto-detect). Defaults to auto."), tokenId: z.string().optional().describe("Token ID (required for NFT analysis)"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, - ({ tokenAddress, tokenType = "auto", tokenId, network = "ethereum" }) => { + ({ tokenAddress, tokenType = "auto", tokenId, network = "sei" }) => { let promptText = ""; - + if (tokenType === "erc20" || tokenType === "auto") { promptText = `Please analyze the ERC20 token at address ${tokenAddress} on the ${network} network. Provide information about its name, symbol, total supply, and any other relevant details. If possible, explain the token's purpose, utility, and market context.`; } else if ((tokenType === "erc721" || tokenType === "nft") && tokenId) { @@ -159,4 +159,4 @@ export function registerEVMPrompts(server: McpServer) { } ); -} \ No newline at end of file +} diff --git a/src/core/resources.ts b/src/core/resources.ts index f8afca9..4a08b85 100644 --- a/src/core/resources.ts +++ b/src/core/resources.ts @@ -10,7 +10,7 @@ import type { Address, Hash } from "viem"; export function registerEVMResources(server: McpServer) { // Get EVM info for a specific network server.resource( - "chain_info_by_network", + "chain_info_by_network", new ResourceTemplate("evm://{network}/chain", { list: undefined }), async (uri, params) => { try { @@ -18,7 +18,7 @@ export function registerEVMResources(server: McpServer) { const chainId = await services.getChainId(network); const blockNumber = await services.getBlockNumber(network); const rpcUrl = getRpcUrl(network); - + return { contents: [{ uri: uri.href, @@ -41,17 +41,17 @@ export function registerEVMResources(server: McpServer) { } ); - // Default chain info (Ethereum mainnet) + // Default chain info (Sei mainnet) server.resource( - "ethereum_chain_info", + "sei_chain_info", "evm://chain", async (uri) => { try { - const network = "ethereum"; + const network = "sei"; const chainId = await services.getChainId(network); const blockNumber = await services.getBlockNumber(network); const rpcUrl = getRpcUrl(network); - + return { contents: [{ uri: uri.href, @@ -83,7 +83,7 @@ export function registerEVMResources(server: McpServer) { const network = params.network as string; const blockNumber = params.blockNumber as string; const block = await services.getBlockByNumber(parseInt(blockNumber), network); - + return { contents: [{ uri: uri.href, @@ -110,7 +110,7 @@ export function registerEVMResources(server: McpServer) { const network = params.network as string; const blockHash = params.blockHash as string; const block = await services.getBlockByHash(blockHash as Hash, network); - + return { contents: [{ uri: uri.href, @@ -136,7 +136,7 @@ export function registerEVMResources(server: McpServer) { try { const network = params.network as string; const block = await services.getLatestBlock(network); - + return { contents: [{ uri: uri.href, @@ -154,15 +154,15 @@ export function registerEVMResources(server: McpServer) { } ); - // Default latest block (Ethereum mainnet) + // Default latest block (Sei mainnet) server.resource( "default_latest_block", "evm://block/latest", async (uri) => { try { - const network = "ethereum"; + const network = "sei"; const block = await services.getLatestBlock(network); - + return { contents: [{ uri: uri.href, @@ -188,8 +188,8 @@ export function registerEVMResources(server: McpServer) { try { const network = params.network as string; const address = params.address as string; - const balance = await services.getETHBalance(address as Address, network); - + const balance = await services.getBalance(address as Address, network); + return { contents: [{ uri: uri.href, @@ -207,23 +207,23 @@ export function registerEVMResources(server: McpServer) { return { contents: [{ uri: uri.href, - text: `Error fetching ETH balance: ${error instanceof Error ? error.message : String(error)}` + text: `Error fetching Sei balance: ${error instanceof Error ? error.message : String(error)}` }] }; } } ); - // Default ETH balance (Ethereum mainnet) + // Default ETH balance (Sei mainnet) server.resource( - "default_eth_balance", + "default_sei_balance", new ResourceTemplate("evm://address/{address}/eth-balance", { list: undefined }), async (uri, params) => { try { - const network = "ethereum"; + const network = "sei"; const address = params.address as string; - const balance = await services.getETHBalance(address as Address, network); - + const balance = await services.getBalance(address as Address, network); + return { contents: [{ uri: uri.href, @@ -241,7 +241,7 @@ export function registerEVMResources(server: McpServer) { return { contents: [{ uri: uri.href, - text: `Error fetching ETH balance: ${error instanceof Error ? error.message : String(error)}` + text: `Error fetching Sei balance: ${error instanceof Error ? error.message : String(error)}` }] }; } @@ -257,13 +257,13 @@ export function registerEVMResources(server: McpServer) { const network = params.network as string; const address = params.address as string; const tokenAddress = params.tokenAddress as string; - + const balance = await services.getERC20Balance( tokenAddress as Address, address as Address, network ); - + return { contents: [{ uri: uri.href, @@ -290,22 +290,22 @@ export function registerEVMResources(server: McpServer) { } ); - // Default ERC20 balance (Ethereum mainnet) + // Default ERC20 balance (Sei mainnet) server.resource( "default_erc20_balance", new ResourceTemplate("evm://address/{address}/token/{tokenAddress}/balance", { list: undefined }), async (uri, params) => { try { - const network = "ethereum"; + const network = "sei"; const address = params.address as string; const tokenAddress = params.tokenAddress as string; - + const balance = await services.getERC20Balance( tokenAddress as Address, address as Address, network ); - + return { contents: [{ uri: uri.href, @@ -341,7 +341,7 @@ export function registerEVMResources(server: McpServer) { const network = params.network as string; const txHash = params.txHash as string; const tx = await services.getTransaction(txHash as Hash, network); - + return { contents: [{ uri: uri.href, @@ -359,16 +359,16 @@ export function registerEVMResources(server: McpServer) { } ); - // Default transaction by hash (Ethereum mainnet) + // Default transaction by hash (Sei mainnet) server.resource( "default_transaction_by_hash", new ResourceTemplate("evm://tx/{txHash}", { list: undefined }), async (uri, params) => { try { - const network = "ethereum"; + const network = "sei"; const txHash = params.txHash as string; const tx = await services.getTransaction(txHash as Hash, network); - + return { contents: [{ uri: uri.href, @@ -393,7 +393,7 @@ export function registerEVMResources(server: McpServer) { async (uri) => { try { const networks = getSupportedNetworks(); - + return { contents: [{ uri: uri.href, @@ -421,9 +421,9 @@ export function registerEVMResources(server: McpServer) { try { const network = params.network as string; const tokenAddress = params.tokenAddress as Address; - + const tokenInfo = await services.getERC20TokenInfo(tokenAddress, network); - + return { contents: [{ uri: uri.href, @@ -454,9 +454,9 @@ export function registerEVMResources(server: McpServer) { const network = params.network as string; const tokenAddress = params.tokenAddress as Address; const address = params.address as Address; - + const balance = await services.getERC20Balance(tokenAddress, address, network); - + return { contents: [{ uri: uri.href, @@ -491,9 +491,9 @@ export function registerEVMResources(server: McpServer) { const network = params.network as string; const tokenAddress = params.tokenAddress as Address; const tokenId = BigInt(params.tokenId as string); - + const nftInfo = await services.getERC721TokenMetadata(tokenAddress, tokenId, network); - + // Get owner separately let owner = "Unknown"; try { @@ -504,7 +504,7 @@ export function registerEVMResources(server: McpServer) { } catch (e) { // Owner info not available } - + return { contents: [{ uri: uri.href, @@ -538,9 +538,9 @@ export function registerEVMResources(server: McpServer) { const tokenAddress = params.tokenAddress as Address; const tokenId = BigInt(params.tokenId as string); const address = params.address as Address; - + const isOwner = await services.isNFTOwner(tokenAddress, address, tokenId, network); - + return { contents: [{ uri: uri.href, @@ -573,9 +573,9 @@ export function registerEVMResources(server: McpServer) { const network = params.network as string; const tokenAddress = params.tokenAddress as Address; const tokenId = BigInt(params.tokenId as string); - + const tokenURI = await services.getERC1155TokenURI(tokenAddress, tokenId, network); - + return { contents: [{ uri: uri.href, @@ -608,9 +608,9 @@ export function registerEVMResources(server: McpServer) { const tokenAddress = params.tokenAddress as Address; const tokenId = BigInt(params.tokenId as string); const address = params.address as Address; - + const balance = await services.getERC1155Balance(tokenAddress, address, tokenId, network); - + return { contents: [{ uri: uri.href, @@ -633,4 +633,4 @@ export function registerEVMResources(server: McpServer) { } } ); -} \ No newline at end of file +} diff --git a/src/core/services/balance.ts b/src/core/services/balance.ts index 0221b9d..389e842 100644 --- a/src/core/services/balance.ts +++ b/src/core/services/balance.ts @@ -1,4 +1,4 @@ -import { +import { formatEther, formatUnits, type Address, @@ -67,21 +67,21 @@ const erc1155Abi = [ ] as const; /** - * Get the ETH balance for an address - * @param addressOrEns Ethereum address or ENS name + * Get the Sei balance for an address + * @param addressOrEns Sei address or ENS name * @param network Network name or chain ID - * @returns Balance in wei and ether + * @returns Balance in wei and sei */ -export async function getETHBalance( - addressOrEns: string, - network = 'ethereum' +export async function getBalance( + addressOrEns: string, + network = 'sei' ): Promise<{ wei: bigint; ether: string }> { // Resolve ENS name to address if needed const address = await resolveAddress(addressOrEns, network); - + const client = getPublicClient(network); const balance = await client.getBalance({ address }); - + return { wei: balance, ether: formatEther(balance) @@ -98,7 +98,7 @@ export async function getETHBalance( export async function getERC20Balance( tokenAddressOrEns: string, ownerAddressOrEns: string, - network = 'ethereum' + network = 'sei' ): Promise<{ raw: bigint; formatted: string; @@ -110,7 +110,7 @@ export async function getERC20Balance( // Resolve ENS names to addresses if needed const tokenAddress = await resolveAddress(tokenAddressOrEns, network); const ownerAddress = await resolveAddress(ownerAddressOrEns, network); - + const publicClient = getPublicClient(network); const contract = getContract({ @@ -147,12 +147,12 @@ export async function isNFTOwner( tokenAddressOrEns: string, ownerAddressOrEns: string, tokenId: bigint, - network = 'ethereum' + network = 'sei' ): Promise { // Resolve ENS names to addresses if needed const tokenAddress = await resolveAddress(tokenAddressOrEns, network); const ownerAddress = await resolveAddress(ownerAddressOrEns, network); - + try { const actualOwner = await readContract({ address: tokenAddress, @@ -160,7 +160,7 @@ export async function isNFTOwner( functionName: 'ownerOf', args: [tokenId] }, network) as Address; - + return actualOwner.toLowerCase() === ownerAddress.toLowerCase(); } catch (error: any) { console.error(`Error checking NFT ownership: ${error.message}`); @@ -178,12 +178,12 @@ export async function isNFTOwner( export async function getERC721Balance( tokenAddressOrEns: string, ownerAddressOrEns: string, - network = 'ethereum' + network = 'sei' ): Promise { // Resolve ENS names to addresses if needed const tokenAddress = await resolveAddress(tokenAddressOrEns, network); const ownerAddress = await resolveAddress(ownerAddressOrEns, network); - + return readContract({ address: tokenAddress, abi: erc721Abi, @@ -204,16 +204,16 @@ export async function getERC1155Balance( tokenAddressOrEns: string, ownerAddressOrEns: string, tokenId: bigint, - network = 'ethereum' + network = 'sei' ): Promise { // Resolve ENS names to addresses if needed const tokenAddress = await resolveAddress(tokenAddressOrEns, network); const ownerAddress = await resolveAddress(ownerAddressOrEns, network); - + return readContract({ address: tokenAddress, abi: erc1155Abi, functionName: 'balanceOf', args: [ownerAddress, tokenId] }, network) as Promise; -} \ No newline at end of file +} diff --git a/src/core/services/blocks.ts b/src/core/services/blocks.ts index aaf8eb9..7b1c951 100644 --- a/src/core/services/blocks.ts +++ b/src/core/services/blocks.ts @@ -1,4 +1,4 @@ -import { +import { type Hash, type Block } from 'viem'; @@ -7,7 +7,7 @@ import { getPublicClient } from './clients.js'; /** * Get the current block number for a specific network */ -export async function getBlockNumber(network = 'ethereum'): Promise { +export async function getBlockNumber(network = 'sei'): Promise { const client = getPublicClient(network); return await client.getBlockNumber(); } @@ -16,8 +16,8 @@ export async function getBlockNumber(network = 'ethereum'): Promise { * Get a block by number for a specific network */ export async function getBlockByNumber( - blockNumber: number, - network = 'ethereum' + blockNumber: number, + network = 'sei' ): Promise { const client = getPublicClient(network); return await client.getBlock({ blockNumber: BigInt(blockNumber) }); @@ -27,8 +27,8 @@ export async function getBlockByNumber( * Get a block by hash for a specific network */ export async function getBlockByHash( - blockHash: Hash, - network = 'ethereum' + blockHash: Hash, + network = 'sei' ): Promise { const client = getPublicClient(network); return await client.getBlock({ blockHash }); @@ -37,7 +37,7 @@ export async function getBlockByHash( /** * Get the latest block for a specific network */ -export async function getLatestBlock(network = 'ethereum'): Promise { +export async function getLatestBlock(network = 'sei'): Promise { const client = getPublicClient(network); return await client.getBlock(); -} \ No newline at end of file +} diff --git a/src/core/services/clients.ts b/src/core/services/clients.ts index c74c125..41594ee 100644 --- a/src/core/services/clients.ts +++ b/src/core/services/clients.ts @@ -1,7 +1,7 @@ -import { - createPublicClient, - createWalletClient, - http, +import { + createPublicClient, + createWalletClient, + http, type PublicClient, type WalletClient, type Hex, @@ -16,37 +16,37 @@ const clientCache = new Map(); /** * Get a public client for a specific network */ -export function getPublicClient(network = 'ethereum'): PublicClient { +export function getPublicClient(network = 'sei'): PublicClient { const cacheKey = String(network); - + // Return cached client if available if (clientCache.has(cacheKey)) { return clientCache.get(cacheKey)!; } - + // Create a new client const chain = getChain(network); const rpcUrl = getRpcUrl(network); - + const client = createPublicClient({ chain, transport: http(rpcUrl) }); - + // Cache the client clientCache.set(cacheKey, client); - + return client; } /** * Create a wallet client for a specific network and private key */ -export function getWalletClient(privateKey: Hex, network = 'ethereum'): WalletClient { +export function getWalletClient(privateKey: Hex, network = 'sei'): WalletClient { const chain = getChain(network); const rpcUrl = getRpcUrl(network); const account = privateKeyToAccount(privateKey); - + return createWalletClient({ account, chain, @@ -55,11 +55,11 @@ export function getWalletClient(privateKey: Hex, network = 'ethereum'): WalletCl } /** - * Get an Ethereum address from a private key + * Get an EVM address from a private key * @param privateKey The private key in hex format (with or without 0x prefix) - * @returns The Ethereum address derived from the private key + * @returns The EVM address derived from the private key */ export function getAddressFromPrivateKey(privateKey: Hex): Address { const account = privateKeyToAccount(privateKey); return account.address; -} \ No newline at end of file +} diff --git a/src/core/services/contracts.ts b/src/core/services/contracts.ts index faebb67..2e97d85 100644 --- a/src/core/services/contracts.ts +++ b/src/core/services/contracts.ts @@ -1,6 +1,6 @@ -import { - type Address, - type Hash, +import { + type Address, + type Hash, type Hex, type ReadContractParameters, type GetLogsParameters, @@ -12,7 +12,7 @@ import { resolveAddress } from './ens.js'; /** * Read from a contract for a specific network */ -export async function readContract(params: ReadContractParameters, network = 'ethereum') { +export async function readContract(params: ReadContractParameters, network = 'sei') { const client = getPublicClient(network); return await client.readContract(params); } @@ -21,9 +21,9 @@ export async function readContract(params: ReadContractParameters, network = 'et * Write to a contract for a specific network */ export async function writeContract( - privateKey: Hex, - params: Record, - network = 'ethereum' + privateKey: Hex, + params: Record, + network = 'sei' ): Promise { const client = getWalletClient(privateKey, network); return await client.writeContract(params as any); @@ -32,7 +32,7 @@ export async function writeContract( /** * Get logs for a specific network */ -export async function getLogs(params: GetLogsParameters, network = 'ethereum'): Promise { +export async function getLogs(params: GetLogsParameters, network = 'sei'): Promise { const client = getPublicClient(network); return await client.getLogs(params); } @@ -43,11 +43,11 @@ export async function getLogs(params: GetLogsParameters, network = 'ethereum'): * @param network Network name or chain ID * @returns True if the address is a contract, false if it's an EOA */ -export async function isContract(addressOrEns: string, network = 'ethereum'): Promise { +export async function isContract(addressOrEns: string, network = 'sei'): Promise { // Resolve ENS name to address if needed const address = await resolveAddress(addressOrEns, network); - + const client = getPublicClient(network); const code = await client.getBytecode({ address }); return code !== undefined && code !== '0x'; -} \ No newline at end of file +} diff --git a/src/core/services/ens.ts b/src/core/services/ens.ts index b51784c..4a70bd3 100644 --- a/src/core/services/ens.ts +++ b/src/core/services/ens.ts @@ -3,16 +3,16 @@ import { getPublicClient } from './clients.js'; import { type Address } from 'viem'; /** - * Resolves an ENS name to an Ethereum address or returns the original address if it's already valid - * @param addressOrEns An Ethereum address or ENS name - * @param network The network to use for ENS resolution (defaults to Ethereum mainnet) - * @returns The resolved Ethereum address + * Resolves an ENS name to a Sei address or returns the original address if it's already valid + * @param addressOrEns An Sei address or ENS name + * @param network The network to use for ENS resolution (defaults to Sei mainnet) + * @returns The resolved Seu address */ export async function resolveAddress( addressOrEns: string, - network = 'ethereum' + network = 'sei' ): Promise
{ - // If it's already a valid Ethereum address (0x followed by 40 hex chars), return it + // If it's already a valid Sei 0x address (0x followed by 40 hex chars), return it if (/^0x[a-fA-F0-9]{40}$/.test(addressOrEns)) { return addressOrEns as Address; } @@ -22,25 +22,25 @@ export async function resolveAddress( try { // Normalize the ENS name first const normalizedEns = normalize(addressOrEns); - + // Get the public client for the network const publicClient = getPublicClient(network); - + // Resolve the ENS name to an address const address = await publicClient.getEnsAddress({ name: normalizedEns, }); - + if (!address) { throw new Error(`ENS name ${addressOrEns} could not be resolved to an address`); } - + return address; } catch (error: any) { throw new Error(`Failed to resolve ENS name ${addressOrEns}: ${error.message}`); } } - + // If it's neither a valid address nor an ENS name, throw an error throw new Error(`Invalid address or ENS name: ${addressOrEns}`); -} \ No newline at end of file +} diff --git a/src/core/services/tokens.ts b/src/core/services/tokens.ts index e0d8306..aef9b0d 100644 --- a/src/core/services/tokens.ts +++ b/src/core/services/tokens.ts @@ -1,5 +1,5 @@ -import { - type Address, +import { + type Address, type Hex, type Hash, formatUnits, @@ -80,7 +80,7 @@ const erc1155Abi = [ */ export async function getERC20TokenInfo( tokenAddress: Address, - network: string = 'ethereum' + network: string = 'sei' ): Promise<{ name: string; symbol: string; @@ -118,7 +118,7 @@ export async function getERC20TokenInfo( export async function getERC721TokenMetadata( tokenAddress: Address, tokenId: bigint, - network: string = 'ethereum' + network: string = 'sei' ): Promise<{ name: string; symbol: string; @@ -151,7 +151,7 @@ export async function getERC721TokenMetadata( export async function getERC1155TokenURI( tokenAddress: Address, tokenId: bigint, - network: string = 'ethereum' + network: string = 'sei' ): Promise { const publicClient = getPublicClient(network); @@ -162,4 +162,4 @@ export async function getERC1155TokenURI( }); return contract.read.uri([tokenId]); -} \ No newline at end of file +} diff --git a/src/core/services/transactions.ts b/src/core/services/transactions.ts index 52954e6..667b073 100644 --- a/src/core/services/transactions.ts +++ b/src/core/services/transactions.ts @@ -1,6 +1,6 @@ -import { - type Address, - type Hash, +import { + type Address, + type Hash, type TransactionReceipt, type EstimateGasParameters } from 'viem'; @@ -9,7 +9,7 @@ import { getPublicClient } from './clients.js'; /** * Get a transaction by hash for a specific network */ -export async function getTransaction(hash: Hash, network = 'ethereum') { +export async function getTransaction(hash: Hash, network = 'sei') { const client = getPublicClient(network); return await client.getTransaction({ hash }); } @@ -17,7 +17,7 @@ export async function getTransaction(hash: Hash, network = 'ethereum') { /** * Get a transaction receipt by hash for a specific network */ -export async function getTransactionReceipt(hash: Hash, network = 'ethereum'): Promise { +export async function getTransactionReceipt(hash: Hash, network = 'sei'): Promise { const client = getPublicClient(network); return await client.getTransactionReceipt({ hash }); } @@ -25,7 +25,7 @@ export async function getTransactionReceipt(hash: Hash, network = 'ethereum'): P /** * Get the transaction count for an address for a specific network */ -export async function getTransactionCount(address: Address, network = 'ethereum'): Promise { +export async function getTransactionCount(address: Address, network = 'sei'): Promise { const client = getPublicClient(network); const count = await client.getTransactionCount({ address }); return Number(count); @@ -34,7 +34,7 @@ export async function getTransactionCount(address: Address, network = 'ethereum' /** * Estimate gas for a transaction for a specific network */ -export async function estimateGas(params: EstimateGasParameters, network = 'ethereum'): Promise { +export async function estimateGas(params: EstimateGasParameters, network = 'sei'): Promise { const client = getPublicClient(network); return await client.estimateGas(params); } @@ -42,8 +42,8 @@ export async function estimateGas(params: EstimateGasParameters, network = 'ethe /** * Get the chain ID for a specific network */ -export async function getChainId(network = 'ethereum'): Promise { +export async function getChainId(network = 'sei'): Promise { const client = getPublicClient(network); const chainId = await client.getChainId(); return Number(chainId); -} \ No newline at end of file +} diff --git a/src/core/services/transfer.ts b/src/core/services/transfer.ts index 06f8f75..10ea9d1 100644 --- a/src/core/services/transfer.ts +++ b/src/core/services/transfer.ts @@ -1,9 +1,9 @@ -import { +import { parseEther, parseUnits, formatUnits, - type Address, - type Hash, + type Address, + type Hash, type Hex, type Abi, getContract, @@ -115,30 +115,30 @@ const erc1155TransferAbi = [ ] as const; /** - * Transfer ETH to an address + * Transfer Sei to an address * @param privateKey Sender's private key * @param toAddressOrEns Recipient address or ENS name - * @param amount Amount to send in ETH + * @param amount Amount to send in Sei * @param network Network name or chain ID * @returns Transaction hash */ -export async function transferETH( +export async function transferSei( privateKey: string | Hex, toAddressOrEns: string, amount: string, // in ether - network = 'ethereum' + network = 'sei' ): Promise { // Resolve ENS name to address if needed const toAddress = await resolveAddress(toAddressOrEns, network); - + // Ensure the private key has 0x prefix const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') ? `0x${privateKey}` as Hex : privateKey as Hex; - + const client = getWalletClient(formattedKey, network); const amountWei = parseEther(amount); - + return client.sendTransaction({ to: toAddress, value: amountWei, @@ -161,7 +161,7 @@ export async function transferERC20( toAddressOrEns: string, amount: string, privateKey: string | `0x${string}`, - network: string = 'ethereum' + network: string = 'sei' ): Promise<{ txHash: Hash; amount: { @@ -176,12 +176,12 @@ export async function transferERC20( // Resolve ENS names to addresses if needed const tokenAddress = await resolveAddress(tokenAddressOrEns, network) as Address; const toAddress = await resolveAddress(toAddressOrEns, network) as Address; - + // Ensure the private key has 0x prefix const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') ? `0x${privateKey}` as `0x${string}` : privateKey as `0x${string}`; - + // Get token details const publicClient = getPublicClient(network); const contract = getContract({ @@ -189,17 +189,17 @@ export async function transferERC20( abi: erc20TransferAbi, client: publicClient, }); - + // Get token decimals and symbol const decimals = await contract.read.decimals(); const symbol = await contract.read.symbol(); - + // Parse the amount with the correct number of decimals const rawAmount = parseUnits(amount, decimals); - + // Create wallet client for sending the transaction const walletClient = getWalletClient(formattedKey, network); - + // Send the transaction const hash = await walletClient.writeContract({ address: tokenAddress, @@ -209,7 +209,7 @@ export async function transferERC20( account: walletClient.account!, chain: walletClient.chain }); - + return { txHash: hash, amount: { @@ -237,7 +237,7 @@ export async function approveERC20( spenderAddressOrEns: string, amount: string, privateKey: string | `0x${string}`, - network: string = 'ethereum' + network: string = 'sei' ): Promise<{ txHash: Hash; amount: { @@ -252,12 +252,12 @@ export async function approveERC20( // Resolve ENS names to addresses if needed const tokenAddress = await resolveAddress(tokenAddressOrEns, network) as Address; const spenderAddress = await resolveAddress(spenderAddressOrEns, network) as Address; - + // Ensure the private key has 0x prefix const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') ? `0x${privateKey}` as `0x${string}` : privateKey as `0x${string}`; - + // Get token details const publicClient = getPublicClient(network); const contract = getContract({ @@ -265,17 +265,17 @@ export async function approveERC20( abi: erc20TransferAbi, client: publicClient, }); - + // Get token decimals and symbol const decimals = await contract.read.decimals(); const symbol = await contract.read.symbol(); - + // Parse the amount with the correct number of decimals const rawAmount = parseUnits(amount, decimals); - + // Create wallet client for sending the transaction const walletClient = getWalletClient(formattedKey, network); - + // Send the transaction const hash = await walletClient.writeContract({ address: tokenAddress, @@ -285,7 +285,7 @@ export async function approveERC20( account: walletClient.account!, chain: walletClient.chain }); - + return { txHash: hash, amount: { @@ -313,7 +313,7 @@ export async function transferERC721( toAddressOrEns: string, tokenId: bigint, privateKey: string | `0x${string}`, - network: string = 'ethereum' + network: string = 'sei' ): Promise<{ txHash: Hash; tokenId: string; @@ -325,16 +325,16 @@ export async function transferERC721( // Resolve ENS names to addresses if needed const tokenAddress = await resolveAddress(tokenAddressOrEns, network) as Address; const toAddress = await resolveAddress(toAddressOrEns, network) as Address; - + // Ensure the private key has 0x prefix const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') ? `0x${privateKey}` as `0x${string}` : privateKey as `0x${string}`; - + // Create wallet client for sending the transaction const walletClient = getWalletClient(formattedKey, network); const fromAddress = walletClient.account!.address; - + // Send the transaction const hash = await walletClient.writeContract({ address: tokenAddress, @@ -344,7 +344,7 @@ export async function transferERC721( account: walletClient.account!, chain: walletClient.chain }); - + // Get token metadata const publicClient = getPublicClient(network); const contract = getContract({ @@ -352,11 +352,11 @@ export async function transferERC721( abi: erc721TransferAbi, client: publicClient, }); - + // Get token name and symbol let name = 'Unknown'; let symbol = 'NFT'; - + try { [name, symbol] = await Promise.all([ contract.read.name(), @@ -365,7 +365,7 @@ export async function transferERC721( } catch (error) { console.error('Error fetching NFT metadata:', error); } - + return { txHash: hash, tokenId: tokenId.toString(), @@ -392,7 +392,7 @@ export async function transferERC1155( tokenId: bigint, amount: string, privateKey: string | `0x${string}`, - network: string = 'ethereum' + network: string = 'sei' ): Promise<{ txHash: Hash; tokenId: string; @@ -401,19 +401,19 @@ export async function transferERC1155( // Resolve ENS names to addresses if needed const tokenAddress = await resolveAddress(tokenAddressOrEns, network) as Address; const toAddress = await resolveAddress(toAddressOrEns, network) as Address; - + // Ensure the private key has 0x prefix const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') ? `0x${privateKey}` as `0x${string}` : privateKey as `0x${string}`; - + // Create wallet client for sending the transaction const walletClient = getWalletClient(formattedKey, network); const fromAddress = walletClient.account!.address; - + // Parse amount to bigint const amountBigInt = BigInt(amount); - + // Send the transaction const hash = await walletClient.writeContract({ address: tokenAddress, @@ -423,10 +423,10 @@ export async function transferERC1155( account: walletClient.account!, chain: walletClient.chain }); - + return { txHash: hash, tokenId: tokenId.toString(), amount }; -} \ No newline at end of file +} diff --git a/src/core/tools.ts b/src/core/tools.ts index a01d7a7..9309adf 100644 --- a/src/core/tools.ts +++ b/src/core/tools.ts @@ -7,28 +7,28 @@ import { normalize } from 'viem/ens'; /** * Register all EVM-related tools with the MCP server - * - * All tools that accept Ethereum addresses also support ENS names (e.g., 'vitalik.eth'). + * + * All tools that accept EVM addresses also support ENS names (e.g., 'vitalik.eth'). * ENS names are automatically resolved to addresses using the Ethereum Name Service. - * + * * @param server The MCP server instance */ export function registerEVMTools(server: McpServer) { // NETWORK INFORMATION TOOLS - + // Get chain information server.tool( "get_chain_info", - "Get information about an EVM network", + "Get information about Sei network", { - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, - async ({ network = "ethereum" }) => { + async ({ network = "sei" }) => { try { const chainId = await services.getChainId(network); const blockNumber = await services.getBlockNumber(network); const rpcUrl = getRpcUrl(network); - + return { content: [{ type: "text", @@ -53,34 +53,34 @@ export function registerEVMTools(server: McpServer) { ); // ENS LOOKUP TOOL - + // Resolve ENS name to address server.tool( "resolve_ens", - "Resolve an ENS name to an Ethereum address", + "Resolve an ENS name to an EVM address", { - ensName: z.string().describe("ENS name to resolve (e.g., 'vitalik.eth')"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. ENS resolution works best on Ethereum mainnet. Defaults to Ethereum mainnet.") + ensName: z.string().describe("ENS name to resolve (e.g., 'jay.sei')"), + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. ENS resolution works best on Sei mainnet. Defaults to Sei mainnet.") }, - async ({ ensName, network = "ethereum" }) => { + async ({ ensName, network = "sei" }) => { try { // Validate that the input is an ENS name if (!ensName.includes('.')) { return { content: [{ type: "text", - text: `Error: Input "${ensName}" is not a valid ENS name. ENS names must contain a dot (e.g., 'name.eth').` + text: `Error: Input "${ensName}" is not a valid ENS name. ENS names must contain a dot (e.g., 'name.sei').` }], isError: true }; } - + // Normalize the ENS name const normalizedEns = normalize(ensName); - + // Resolve the ENS name to an address const address = await services.resolveAddress(ensName, network); - + return { content: [{ type: "text", @@ -112,7 +112,7 @@ export function registerEVMTools(server: McpServer) { async () => { try { const networks = getSupportedNetworks(); - + return { content: [{ type: "text", @@ -134,19 +134,19 @@ export function registerEVMTools(server: McpServer) { ); // BLOCK TOOLS - + // Get block by number server.tool( "get_block_by_number", "Get a block by its block number", { blockNumber: z.number().describe("The block number to fetch"), - network: z.string().optional().describe("Network name or chain ID. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.") }, - async ({ blockNumber, network = "ethereum" }) => { + async ({ blockNumber, network = "sei" }) => { try { const block = await services.getBlockByNumber(blockNumber, network); - + return { content: [{ type: "text", @@ -170,12 +170,12 @@ export function registerEVMTools(server: McpServer) { "get_latest_block", "Get the latest block from the EVM", { - network: z.string().optional().describe("Network name or chain ID. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.") }, - async ({ network = "ethereum" }) => { + async ({ network = "sei" }) => { try { const block = await services.getLatestBlock(network); - + return { content: [{ type: "text", @@ -195,19 +195,19 @@ export function registerEVMTools(server: McpServer) { ); // BALANCE TOOLS - - // Get ETH balance + + // Get Sei balance server.tool( "get_balance", - "Get the native token balance (ETH, MATIC, etc.) for an address", + "Get the native token balance (Sei) for an address", { address: z.string().describe("The wallet address or ENS name (e.g., '0x1234...' or 'vitalik.eth') to check the balance for"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, - async ({ address, network = "ethereum" }) => { + async ({ address, network = "sei" }) => { try { - const balance = await services.getETHBalance(address, network); - + const balance = await services.getBalance(address, network); + return { content: [{ type: "text", @@ -234,20 +234,20 @@ export function registerEVMTools(server: McpServer) { // Get ERC20 balance server.tool( "get_erc20_balance", - "Get the ERC20 token balance of an Ethereum address", + "Get the ERC20 token balance of an EVM address", { - address: z.string().describe("The Ethereum address to check"), + address: z.string().describe("The EVM address to check"), tokenAddress: z.string().describe("The ERC20 token contract address"), - network: z.string().optional().describe("Network name or chain ID. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.") }, - async ({ address, tokenAddress, network = "ethereum" }) => { + async ({ address, tokenAddress, network = "sei" }) => { try { const balance = await services.getERC20Balance( tokenAddress as Address, address as Address, network ); - + return { content: [{ type: "text", @@ -280,14 +280,14 @@ export function registerEVMTools(server: McpServer) { "get_token_balance", "Get the balance of an ERC20 token for an address", { - tokenAddress: z.string().describe("The contract address or ENS name of the ERC20 token (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' for USDC or 'uniswap.eth')"), + tokenAddress: z.string().describe("The contract address or ENS name of the ERC20 token (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')"), ownerAddress: z.string().describe("The wallet address or ENS name to check the balance for (e.g., '0x1234...' or 'vitalik.eth')"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, - async ({ tokenAddress, ownerAddress, network = "ethereum" }) => { + async ({ tokenAddress, ownerAddress, network = "sei" }) => { try { const balance = await services.getERC20Balance(tokenAddress, ownerAddress, network); - + return { content: [{ type: "text", @@ -315,19 +315,19 @@ export function registerEVMTools(server: McpServer) { ); // TRANSACTION TOOLS - + // Get transaction by hash server.tool( "get_transaction", "Get detailed information about a specific transaction by its hash. Includes sender, recipient, value, data, and more.", { txHash: z.string().describe("The transaction hash to look up (e.g., '0x1234...')"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. Defaults to Sei mainnet.") }, - async ({ txHash, network = "ethereum" }) => { + async ({ txHash, network = "sei" }) => { try { const tx = await services.getTransaction(txHash as Hash, network); - + return { content: [{ type: "text", @@ -352,12 +352,12 @@ export function registerEVMTools(server: McpServer) { "Get a transaction receipt by its hash", { txHash: z.string().describe("The transaction hash to look up"), - network: z.string().optional().describe("Network name or chain ID. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.") }, - async ({ txHash, network = "ethereum" }) => { + async ({ txHash, network = "sei" }) => { try { const receipt = await services.getTransactionReceipt(txHash as Hash, network); - + return { content: [{ type: "text", @@ -382,24 +382,24 @@ export function registerEVMTools(server: McpServer) { "Estimate the gas cost for a transaction", { to: z.string().describe("The recipient address"), - value: z.string().optional().describe("The amount of ETH to send in ether (e.g., '0.1')"), + value: z.string().optional().describe("The amount of Sei to send (e.g., '0.1')"), data: z.string().optional().describe("The transaction data as a hex string"), - network: z.string().optional().describe("Network name or chain ID. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.") }, - async ({ to, value, data, network = "ethereum" }) => { + async ({ to, value, data, network = "sei" }) => { try { const params: any = { to: to as Address }; - + if (value) { params.value = services.helpers.parseEther(value); } - + if (data) { params.data = data as `0x${string}`; } - + const gas = await services.estimateGas(params, network); - + return { content: [{ type: "text", @@ -422,21 +422,21 @@ export function registerEVMTools(server: McpServer) { ); // TRANSFER TOOLS - - // Transfer ETH + + // Transfer Sei server.tool( - "transfer_eth", - "Transfer native tokens (ETH, MATIC, etc.) to an address", + "transfer_sei", + "Transfer native tokens (Sei) to an address", { privateKey: z.string().describe("Private key of the sender account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored."), to: z.string().describe("The recipient address or ENS name (e.g., '0x1234...' or 'vitalik.eth')"), - amount: z.string().describe("Amount to send in ETH (or the native token of the network), as a string (e.g., '0.1')"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet.") + amount: z.string().describe("Amount to send in Sei, as a string (e.g., '0.1')"), + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, - async ({ privateKey, to, amount, network = "ethereum" }) => { + async ({ privateKey, to, amount, network = "sei" }) => { try { - const txHash = await services.transferETH(privateKey, to, amount, network); - + const txHash = await services.transferSei(privateKey, to, amount, network); + return { content: [{ type: "text", @@ -453,7 +453,7 @@ export function registerEVMTools(server: McpServer) { return { content: [{ type: "text", - text: `Error transferring ETH: ${error instanceof Error ? error.message : String(error)}` + text: `Error transferring Sei: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; @@ -470,23 +470,23 @@ export function registerEVMTools(server: McpServer) { tokenAddress: z.string().describe("The address of the ERC20 token contract"), toAddress: z.string().describe("The recipient address"), amount: z.string().describe("The amount of tokens to send (in token units, e.g., '10' for 10 tokens)"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, - async ({ privateKey, tokenAddress, toAddress, amount, network = "ethereum" }) => { + async ({ privateKey, tokenAddress, toAddress, amount, network = "sei" }) => { try { // Get the formattedKey with 0x prefix - const formattedKey = privateKey.startsWith('0x') - ? privateKey as `0x${string}` + const formattedKey = privateKey.startsWith('0x') + ? privateKey as `0x${string}` : `0x${privateKey}` as `0x${string}`; - + const result = await services.transferERC20( - tokenAddress as Address, - toAddress as Address, + tokenAddress as Address, + toAddress as Address, amount, formattedKey, network ); - + return { content: [{ type: "text", @@ -519,26 +519,26 @@ export function registerEVMTools(server: McpServer) { "Approve another address (like a DeFi protocol or exchange) to spend your ERC20 tokens. This is often required before interacting with DeFi protocols.", { privateKey: z.string().describe("Private key of the token owner account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored."), - tokenAddress: z.string().describe("The contract address of the ERC20 token to approve for spending (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' for USDC on Ethereum)"), + tokenAddress: z.string().describe("The contract address of the ERC20 token to approve for spending (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' for USDC)"), spenderAddress: z.string().describe("The contract address being approved to spend your tokens (e.g., a DEX or lending protocol)"), amount: z.string().describe("The amount of tokens to approve in token units, not wei (e.g., '1000' to approve spending 1000 tokens). Use a very large number for unlimited approval."), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. Defaults to Sei mainnet.") }, - async ({ privateKey, tokenAddress, spenderAddress, amount, network = "ethereum" }) => { + async ({ privateKey, tokenAddress, spenderAddress, amount, network = "sei" }) => { try { // Get the formattedKey with 0x prefix - const formattedKey = privateKey.startsWith('0x') - ? privateKey as `0x${string}` + const formattedKey = privateKey.startsWith('0x') + ? privateKey as `0x${string}` : `0x${privateKey}` as `0x${string}`; - + const result = await services.approveERC20( - tokenAddress as Address, - spenderAddress as Address, + tokenAddress as Address, + spenderAddress as Address, amount, formattedKey, network ); - + return { content: [{ type: "text", @@ -574,23 +574,23 @@ export function registerEVMTools(server: McpServer) { tokenAddress: z.string().describe("The contract address of the NFT collection (e.g., '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D' for Bored Ape Yacht Club)"), tokenId: z.string().describe("The ID of the specific NFT to transfer (e.g., '1234')"), toAddress: z.string().describe("The recipient wallet address that will receive the NFT"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Most NFTs are on Ethereum mainnet, which is the default.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. Most NFTs are on Sei mainnet, which is the default.") }, - async ({ privateKey, tokenAddress, tokenId, toAddress, network = "ethereum" }) => { + async ({ privateKey, tokenAddress, tokenId, toAddress, network = "sei" }) => { try { // Get the formattedKey with 0x prefix - const formattedKey = privateKey.startsWith('0x') - ? privateKey as `0x${string}` + const formattedKey = privateKey.startsWith('0x') + ? privateKey as `0x${string}` : `0x${privateKey}` as `0x${string}`; - + const result = await services.transferERC721( - tokenAddress as Address, - toAddress as Address, + tokenAddress as Address, + toAddress as Address, BigInt(tokenId), formattedKey, network ); - + return { content: [{ type: "text", @@ -628,24 +628,24 @@ export function registerEVMTools(server: McpServer) { tokenId: z.string().describe("The ID of the specific token to transfer (e.g., '1234')"), amount: z.string().describe("The quantity of tokens to send (e.g., '1' for a single NFT or '10' for 10 fungible tokens)"), toAddress: z.string().describe("The recipient wallet address that will receive the tokens"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. ERC1155 tokens exist across many networks. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. ERC1155 tokens exist across many networks. Defaults to Sei mainnet.") }, - async ({ privateKey, tokenAddress, tokenId, amount, toAddress, network = "ethereum" }) => { + async ({ privateKey, tokenAddress, tokenId, amount, toAddress, network = "sei" }) => { try { // Get the formattedKey with 0x prefix - const formattedKey = privateKey.startsWith('0x') - ? privateKey as `0x${string}` + const formattedKey = privateKey.startsWith('0x') + ? privateKey as `0x${string}` : `0x${privateKey}` as `0x${string}`; - + const result = await services.transferERC1155( - tokenAddress as Address, - toAddress as Address, + tokenAddress as Address, + toAddress as Address, BigInt(tokenId), amount, formattedKey, network ); - + return { content: [{ type: "text", @@ -681,9 +681,9 @@ export function registerEVMTools(server: McpServer) { tokenAddress: z.string().describe("The contract address or ENS name of the ERC20 token to transfer (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' for USDC or 'uniswap.eth')"), toAddress: z.string().describe("The recipient address or ENS name that will receive the tokens (e.g., '0x1234...' or 'vitalik.eth')"), amount: z.string().describe("Amount of tokens to send as a string (e.g., '100' for 100 tokens). This will be adjusted for the token's decimals."), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet' etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, - async ({ privateKey, tokenAddress, toAddress, amount, network = "ethereum" }) => { + async ({ privateKey, tokenAddress, toAddress, amount, network = "sei" }) => { try { const result = await services.transferERC20( tokenAddress, @@ -692,7 +692,7 @@ export function registerEVMTools(server: McpServer) { privateKey, network ); - + return { content: [{ type: "text", @@ -720,7 +720,7 @@ export function registerEVMTools(server: McpServer) { ); // CONTRACT TOOLS - + // Read contract server.tool( "read_contract", @@ -730,22 +730,22 @@ export function registerEVMTools(server: McpServer) { abi: z.array(z.any()).describe("The ABI (Application Binary Interface) of the smart contract function, as a JSON array"), functionName: z.string().describe("The name of the function to call on the contract (e.g., 'balanceOf')"), args: z.array(z.any()).optional().describe("The arguments to pass to the function, as an array (e.g., ['0x1234...'])"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. Defaults to Sei mainnet.") }, - async ({ contractAddress, abi, functionName, args = [], network = "ethereum" }) => { + async ({ contractAddress, abi, functionName, args = [], network = "sei" }) => { try { // Parse ABI if it's a string const parsedAbi = typeof abi === 'string' ? JSON.parse(abi) : abi; - + const params = { address: contractAddress as Address, abi: parsedAbi, functionName, args }; - + const result = await services.readContract(params, network); - + return { content: [{ type: "text", @@ -774,26 +774,26 @@ export function registerEVMTools(server: McpServer) { functionName: z.string().describe("The name of the function to call on the contract (e.g., 'transfer')"), args: z.array(z.any()).describe("The arguments to pass to the function, as an array (e.g., ['0x1234...', '1000000000000000000'])"), privateKey: z.string().describe("Private key of the sending account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored."), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. Defaults to Sei mainnet.") }, - async ({ contractAddress, abi, functionName, args, privateKey, network = "ethereum" }) => { + async ({ contractAddress, abi, functionName, args, privateKey, network = "sei" }) => { try { // Parse ABI if it's a string const parsedAbi = typeof abi === 'string' ? JSON.parse(abi) : abi; - + const contractParams: Record = { address: contractAddress as Address, abi: parsedAbi, functionName, args }; - + const txHash = await services.writeContract( - privateKey as Hex, - contractParams, + privateKey as Hex, + contractParams, network ); - + return { content: [{ type: "text", @@ -822,12 +822,12 @@ export function registerEVMTools(server: McpServer) { "Check if an address is a smart contract or an externally owned account (EOA)", { address: z.string().describe("The wallet or contract address or ENS name to check (e.g., '0x1234...' or 'uniswap.eth')"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, - async ({ address, network = "ethereum" }) => { + async ({ address, network = "sei" }) => { try { const isContract = await services.isContract(address, network); - + return { content: [{ type: "text", @@ -856,13 +856,13 @@ export function registerEVMTools(server: McpServer) { "get_token_info", "Get comprehensive information about an ERC20 token including name, symbol, decimals, total supply, and other metadata. Use this to analyze any token on EVM chains.", { - tokenAddress: z.string().describe("The contract address of the ERC20 token (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' for USDC on Ethereum)"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Defaults to Ethereum mainnet.") + tokenAddress: z.string().describe("The contract address of the ERC20 token (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')"), + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. Defaults to Sei mainnet.") }, - async ({ tokenAddress, network = "ethereum" }) => { + async ({ tokenAddress, network = "sei" }) => { try { const tokenInfo = await services.getERC20TokenInfo(tokenAddress as Address, network); - + return { content: [{ type: "text", @@ -892,16 +892,16 @@ export function registerEVMTools(server: McpServer) { { address: z.string().describe("The address to check balance for"), tokenAddress: z.string().describe("The ERC20 token contract address"), - network: z.string().optional().describe("Network name or chain ID. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.") }, - async ({ address, tokenAddress, network = "ethereum" }) => { + async ({ address, tokenAddress, network = "sei" }) => { try { const balance = await services.getERC20Balance( tokenAddress as Address, address as Address, network ); - + return { content: [{ type: "text", @@ -936,25 +936,25 @@ export function registerEVMTools(server: McpServer) { { tokenAddress: z.string().describe("The contract address of the NFT collection (e.g., '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D' for Bored Ape Yacht Club)"), tokenId: z.string().describe("The ID of the specific NFT token to query (e.g., '1234')"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Most NFTs are on Ethereum mainnet, which is the default.") + network: z.string().optional().describe("Network name (e.g., 'sei', ) or chain ID. Most NFTs are on Sei mainnet, which is the default.") }, - async ({ tokenAddress, tokenId, network = "ethereum" }) => { + async ({ tokenAddress, tokenId, network = "sei" }) => { try { const nftInfo = await services.getERC721TokenMetadata( - tokenAddress as Address, - BigInt(tokenId), + tokenAddress as Address, + BigInt(tokenId), network ); - + // Check ownership separately let owner = null; try { // This may fail if tokenId doesn't exist owner = await services.getPublicClient(network).readContract({ address: tokenAddress as Address, - abi: [{ - inputs: [{ type: 'uint256' }], - name: 'ownerOf', + abi: [{ + inputs: [{ type: 'uint256' }], + name: 'ownerOf', outputs: [{ type: 'address' }], stateMutability: 'view', type: 'function' @@ -965,7 +965,7 @@ export function registerEVMTools(server: McpServer) { } catch (e) { // Ownership info not available } - + return { content: [{ type: "text", @@ -998,9 +998,9 @@ export function registerEVMTools(server: McpServer) { tokenAddress: z.string().describe("The contract address or ENS name of the NFT collection (e.g., '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D' for BAYC or 'boredapeyachtclub.eth')"), tokenId: z.string().describe("The ID of the NFT to check (e.g., '1234')"), ownerAddress: z.string().describe("The wallet address or ENS name to check ownership against (e.g., '0x1234...' or 'vitalik.eth')"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet' etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, - async ({ tokenAddress, tokenId, ownerAddress, network = "ethereum" }) => { + async ({ tokenAddress, tokenId, ownerAddress, network = "sei" }) => { try { const isOwner = await services.isNFTOwner( tokenAddress, @@ -1008,7 +1008,7 @@ export function registerEVMTools(server: McpServer) { BigInt(tokenId), network ); - + return { content: [{ type: "text", @@ -1041,16 +1041,16 @@ export function registerEVMTools(server: McpServer) { { tokenAddress: z.string().describe("The contract address of the ERC1155 token collection (e.g., '0x76BE3b62873462d2142405439777e971754E8E77')"), tokenId: z.string().describe("The ID of the specific token to query metadata for (e.g., '1234')"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. ERC1155 tokens exist across many networks. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. ERC1155 tokens exist across many networks. Defaults to Sei mainnet.") }, - async ({ tokenAddress, tokenId, network = "ethereum" }) => { + async ({ tokenAddress, tokenId, network = "sei" }) => { try { const uri = await services.getERC1155TokenURI( - tokenAddress as Address, - BigInt(tokenId), + tokenAddress as Address, + BigInt(tokenId), network ); - + return { content: [{ type: "text", @@ -1081,16 +1081,16 @@ export function registerEVMTools(server: McpServer) { { tokenAddress: z.string().describe("The contract address of the NFT collection (e.g., '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D' for Bored Ape Yacht Club)"), ownerAddress: z.string().describe("The wallet address to check the NFT balance for (e.g., '0x1234...')"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. Most NFTs are on Ethereum mainnet, which is the default.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. Most NFTs are on Sei mainnet, which is the default.") }, - async ({ tokenAddress, ownerAddress, network = "ethereum" }) => { + async ({ tokenAddress, ownerAddress, network = "sei" }) => { try { const balance = await services.getERC721Balance( - tokenAddress as Address, - ownerAddress as Address, + tokenAddress as Address, + ownerAddress as Address, network ); - + return { content: [{ type: "text", @@ -1122,17 +1122,17 @@ export function registerEVMTools(server: McpServer) { tokenAddress: z.string().describe("The contract address of the ERC1155 token collection (e.g., '0x76BE3b62873462d2142405439777e971754E8E77')"), tokenId: z.string().describe("The ID of the specific token to check the balance for (e.g., '1234')"), ownerAddress: z.string().describe("The wallet address to check the token balance for (e.g., '0x1234...')"), - network: z.string().optional().describe("Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', 'polygon') or chain ID. ERC1155 tokens exist across many networks. Defaults to Ethereum mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. ERC1155 tokens exist across many networks. Defaults to Sei mainnet.") }, - async ({ tokenAddress, tokenId, ownerAddress, network = "ethereum" }) => { + async ({ tokenAddress, tokenId, ownerAddress, network = "sei" }) => { try { const balance = await services.getERC1155Balance( - tokenAddress as Address, - ownerAddress as Address, + tokenAddress as Address, + ownerAddress as Address, BigInt(tokenId), network ); - + return { content: [{ type: "text", @@ -1170,9 +1170,9 @@ export function registerEVMTools(server: McpServer) { try { // Ensure the private key has 0x prefix const formattedKey = privateKey.startsWith('0x') ? privateKey as Hex : `0x${privateKey}` as Hex; - + const address = services.getAddressFromPrivateKey(formattedKey); - + return { content: [{ type: "text", @@ -1193,4 +1193,4 @@ export function registerEVMTools(server: McpServer) { } } ); -} \ No newline at end of file +} From 4931e0de20b3c79d36e5634d5113df6109c9e25a Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Fri, 16 May 2025 15:13:33 -0700 Subject: [PATCH 02/12] update testnet address --- src/core/chains.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/chains.ts b/src/core/chains.ts index 39e6e66..0e7e5b6 100644 --- a/src/core/chains.ts +++ b/src/core/chains.ts @@ -26,7 +26,7 @@ export const networkNameMap: Record = { // Map chain IDs to RPC URLs export const rpcUrlMap: Record = { 1329: 'https://evm-rpc.sei-apis.com', - 1328: 'https://evm-rpc-atlantic-2.sei-apis.com', + 1328: 'https://evm-rpc-testnet.sei-apis.com', 713_715: 'https://evm-rpc-arctic-1.sei-apis.com' }; From 144cf5e5e0a67b1e678010105e7eb2bc69780ab7 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Fri, 16 May 2025 17:03:05 -0700 Subject: [PATCH 03/12] cut off the ENS --- README.md | 41 ++++++++-------- src/core/services/balance.ts | 84 +++++++++++++++----------------- src/core/services/contracts.ts | 12 ++--- src/core/services/ens.ts | 46 ------------------ src/core/services/index.ts | 9 ++-- src/core/services/transfer.ts | 89 +++++++++++++++------------------- src/core/services/utils.ts | 78 +++++++++++++++-------------- src/core/tools.ts | 74 ++++------------------------ 8 files changed, 158 insertions(+), 275 deletions(-) delete mode 100644 src/core/services/ens.ts diff --git a/README.md b/README.md index 298f4e1..46f6416 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,7 @@ After configuring the MCP server with `mcp.json`, you can easily use it in Curso // blockchain-example.js async function main() { try { - // Get Sei balance for an address using ENS + // Get Sei balance for an address console.log("Getting Sei balance for 0x1234..."); // When using with Cursor, you can simply ask Cursor to: @@ -284,10 +284,10 @@ claude mcp add evm-mcp-server npx @sei-protocol/sei-mcp-server claude ``` -### Example: Getting a Token Balance with ENS +### Example: Getting a Token Balance ```javascript -// Example of using the MCP client to check a token balance using ENS +// Example of using the MCP client to check a token balance const mcp = new McpClient("http://localhost:3000"); const result = await mcp.invokeTool("get-token-balance", { @@ -318,34 +318,33 @@ The server provides the following MCP tools for agents. | Tool Name | Description | Key Parameters | |-----------|-------------|----------------| -| `get-token-info` | Get ERC20 token metadata | `tokenAddress` (address/ENS), `network` | -| `get-token-balance` | Check ERC20 token balance | `tokenAddress` (address/ENS), `ownerAddress` (address/ENS), `network` | -| `transfer-token` | Transfer ERC20 tokens | `privateKey`, `tokenAddress` (address/ENS), `toAddress` (address/ENS), `amount`, `network` | -| `approve-token-spending` | Approve token allowances | `privateKey`, `tokenAddress` (address/ENS), `spenderAddress` (address/ENS), `amount`, `network` | -| `get-nft-info` | Get NFT metadata | `tokenAddress` (address/ENS), `tokenId`, `network` | -| `check-nft-ownership` | Verify NFT ownership | `tokenAddress` (address/ENS), `tokenId`, `ownerAddress` (address/ENS), `network` | -| `transfer-nft` | Transfer an NFT | `privateKey`, `tokenAddress` (address/ENS), `tokenId`, `toAddress` (address/ENS), `network` | -| `get-nft-balance` | Count NFTs owned | `tokenAddress` (address/ENS), `ownerAddress` (address/ENS), `network` | -| `get-erc1155-token-uri` | Get ERC1155 metadata | `tokenAddress` (address/ENS), `tokenId`, `network` | -| `get-erc1155-balance` | Check ERC1155 balance | `tokenAddress` (address/ENS), `tokenId`, `ownerAddress` (address/ENS), `network` | -| `transfer-erc1155` | Transfer ERC1155 tokens | `privateKey`, `tokenAddress` (address/ENS), `tokenId`, `amount`, `toAddress` (address/ENS), `network` | +| `get-token-info` | Get ERC20 token metadata | `tokenAddress` (address), `network` | +| `get-token-balance` | Check ERC20 token balance | `tokenAddress` (address), `ownerAddress` (address), `network` | +| `transfer-token` | Transfer ERC20 tokens | `privateKey`, `tokenAddress` (address), `toAddress` (address), `amount`, `network` | +| `approve-token-spending` | Approve token allowances | `privateKey`, `tokenAddress` (address), `spenderAddress` (address), `amount`, `network` | +| `get-nft-info` | Get NFT metadata | `tokenAddress` (address), `tokenId`, `network` | +| `check-nft-ownership` | Verify NFT ownership | `tokenAddress` (address), `tokenId`, `ownerAddress` (address), `network` | +| `transfer-nft` | Transfer an NFT | `privateKey`, `tokenAddress` (address), `tokenId`, `toAddress` (address), `network` | +| `get-nft-balance` | Count NFTs owned | `tokenAddress` (address), `ownerAddress` (address), `network` | +| `get-erc1155-token-uri` | Get ERC1155 metadata | `tokenAddress` (address), `tokenId`, `network` | +| `get-erc1155-balance` | Check ERC1155 balance | `tokenAddress` (address), `tokenId`, `ownerAddress` (address), `network` | +| `transfer-erc1155` | Transfer ERC1155 tokens | `privateKey`, `tokenAddress` (address), `tokenId`, `amount`, `toAddress` (address), `network` | #### Blockchain services | Tool Name | Description | Key Parameters | |-------------------|-------------|----------------| | `get-chain-info` | Get network information | `network` | -| `get-balance` | Get native token balance | `address` (address/ENS), `network` | -| `transfer-sei` | Send native tokens | `privateKey`, `to` (address/ENS), `amount`, `network` | +| `get-balance` | Get native token balance | `address` (address), `network` | +| `transfer-sei` | Send native tokens | `privateKey`, `to` (address), `amount`, `network` | | `get-transaction` | Get transaction details | `txHash`, `network` | -| `read-contract` | Read smart contract state | `contractAddress` (address/ENS), `abi`, `functionName`, `args`, `network` | -| `write-contract` | Write to smart contract | `contractAddress` (address/ENS), `abi`, `functionName`, `args`, `privateKey`, `network` | -| `is-contract` | Check if address is a contract | `address` (address/ENS), `network` | -| `resolve-ens` | Resolve ENS name to address | `ensName`, `network` | +| `read-contract` | Read smart contract state | `contractAddress` (address), `abi`, `functionName`, `args`, `network` | +| `write-contract` | Write to smart contract | `contractAddress` (address), `abi`, `functionName`, `args`, `privateKey`, `network` | +| `is-contract` | Check if address is a contract | `address` (address), `network` | ### Resources -The server exposes blockchain data through the following MCP resource URIs. All resource URIs that accept addresses also support ENS names, which are automatically resolved to addresses. +The server exposes blockchain data through the following MCP resource URIs. #### Blockchain Resources diff --git a/src/core/services/balance.ts b/src/core/services/balance.ts index 389e842..33f1670 100644 --- a/src/core/services/balance.ts +++ b/src/core/services/balance.ts @@ -2,12 +2,11 @@ import { formatEther, formatUnits, type Address, - type Abi, getContract } from 'viem'; import { getPublicClient } from './clients.js'; import { readContract } from './contracts.js'; -import { resolveAddress } from './ens.js'; +import * as services from "./index.js"; // Standard ERC20 ABI (minimal for reading) const erc20Abi = [ @@ -68,19 +67,18 @@ const erc1155Abi = [ /** * Get the Sei balance for an address - * @param addressOrEns Sei address or ENS name + * @param address Sei address * @param network Network name or chain ID * @returns Balance in wei and sei */ export async function getBalance( - addressOrEns: string, + address: string, network = 'sei' ): Promise<{ wei: bigint; ether: string }> { - // Resolve ENS name to address if needed - const address = await resolveAddress(addressOrEns, network); + const validatedAddress = services.helpers.validateAddress(address); const client = getPublicClient(network); - const balance = await client.getBalance({ address }); + const balance = await client.getBalance({ address: validatedAddress }); return { wei: balance, @@ -90,14 +88,14 @@ export async function getBalance( /** * Get the balance of an ERC20 token for an address - * @param tokenAddressOrEns Token contract address or ENS name - * @param ownerAddressOrEns Owner address or ENS name + * @param tokenAddress Token contract address + * @param ownerAddress Owner address * @param network Network name or chain ID * @returns Token balance with formatting information */ export async function getERC20Balance( - tokenAddressOrEns: string, - ownerAddressOrEns: string, + tokenAddress: string, + ownerAddress: string, network = 'sei' ): Promise<{ raw: bigint; @@ -107,20 +105,19 @@ export async function getERC20Balance( decimals: number; } }> { - // Resolve ENS names to addresses if needed - const tokenAddress = await resolveAddress(tokenAddressOrEns, network); - const ownerAddress = await resolveAddress(ownerAddressOrEns, network); + const validatedTokenAddress = services.helpers.validateAddress(tokenAddress); + const validatedOwnerAddress = services.helpers.validateAddress(ownerAddress); const publicClient = getPublicClient(network); const contract = getContract({ - address: tokenAddress, + address: validatedTokenAddress, abi: erc20Abi, client: publicClient, }); const [balance, symbol, decimals] = await Promise.all([ - contract.read.balanceOf([ownerAddress]), + contract.read.balanceOf([validatedOwnerAddress]), contract.read.symbol(), contract.read.decimals() ]); @@ -137,31 +134,30 @@ export async function getERC20Balance( /** * Check if an address owns a specific NFT - * @param tokenAddressOrEns NFT contract address or ENS name - * @param ownerAddressOrEns Owner address or ENS name + * @param tokenAddress NFT contract address + * @param ownerAddress Owner address * @param tokenId Token ID to check * @param network Network name or chain ID * @returns True if the address owns the NFT */ export async function isNFTOwner( - tokenAddressOrEns: string, - ownerAddressOrEns: string, + tokenAddress: string, + ownerAddress: string, tokenId: bigint, network = 'sei' ): Promise { - // Resolve ENS names to addresses if needed - const tokenAddress = await resolveAddress(tokenAddressOrEns, network); - const ownerAddress = await resolveAddress(ownerAddressOrEns, network); + const validatedTokenAddress = services.helpers.validateAddress(tokenAddress); + const validatedOwnerAddress = services.helpers.validateAddress(ownerAddress); try { const actualOwner = await readContract({ - address: tokenAddress, + address: validatedTokenAddress, abi: erc721Abi, functionName: 'ownerOf', args: [tokenId] }, network) as Address; - return actualOwner.toLowerCase() === ownerAddress.toLowerCase(); + return actualOwner.toLowerCase() === validatedOwnerAddress.toLowerCase(); } catch (error: any) { console.error(`Error checking NFT ownership: ${error.message}`); return false; @@ -170,50 +166,48 @@ export async function isNFTOwner( /** * Get the number of NFTs owned by an address for a specific collection - * @param tokenAddressOrEns NFT contract address or ENS name - * @param ownerAddressOrEns Owner address or ENS name + * @param tokenAddress NFT contract address + * @param ownerAddress Owner address * @param network Network name or chain ID * @returns Number of NFTs owned */ export async function getERC721Balance( - tokenAddressOrEns: string, - ownerAddressOrEns: string, + tokenAddress: string, + ownerAddress: string, network = 'sei' ): Promise { - // Resolve ENS names to addresses if needed - const tokenAddress = await resolveAddress(tokenAddressOrEns, network); - const ownerAddress = await resolveAddress(ownerAddressOrEns, network); + const validatedTokenAddress = services.helpers.validateAddress(tokenAddress); + const validatedOwnerAddress = services.helpers.validateAddress(ownerAddress); - return readContract({ - address: tokenAddress, + return await readContract({ + address: validatedTokenAddress, abi: erc721Abi, functionName: 'balanceOf', - args: [ownerAddress] + args: [validatedOwnerAddress] }, network) as Promise; } /** * Get the balance of an ERC1155 token for an address - * @param tokenAddressOrEns ERC1155 contract address or ENS name - * @param ownerAddressOrEns Owner address or ENS name + * @param tokenAddress ERC1155 contract address + * @param ownerAddress Owner address * @param tokenId Token ID to check * @param network Network name or chain ID * @returns Token balance */ export async function getERC1155Balance( - tokenAddressOrEns: string, - ownerAddressOrEns: string, + tokenAddress: string, + ownerAddress: string, tokenId: bigint, network = 'sei' ): Promise { - // Resolve ENS names to addresses if needed - const tokenAddress = await resolveAddress(tokenAddressOrEns, network); - const ownerAddress = await resolveAddress(ownerAddressOrEns, network); + const validatedTokenAddress = services.helpers.validateAddress(tokenAddress); + const validatedOwnerAddress = services.helpers.validateAddress(ownerAddress); - return readContract({ - address: tokenAddress, + return await readContract({ + address: validatedTokenAddress, abi: erc1155Abi, functionName: 'balanceOf', - args: [ownerAddress, tokenId] + args: [validatedOwnerAddress, tokenId] }, network) as Promise; } diff --git a/src/core/services/contracts.ts b/src/core/services/contracts.ts index 2e97d85..672d931 100644 --- a/src/core/services/contracts.ts +++ b/src/core/services/contracts.ts @@ -1,5 +1,4 @@ import { - type Address, type Hash, type Hex, type ReadContractParameters, @@ -7,7 +6,7 @@ import { type Log } from 'viem'; import { getPublicClient, getWalletClient } from './clients.js'; -import { resolveAddress } from './ens.js'; +import * as services from "./index.js"; /** * Read from a contract for a specific network @@ -39,15 +38,14 @@ export async function getLogs(params: GetLogsParameters, network = 'sei'): Promi /** * Check if an address is a contract - * @param addressOrEns Address or ENS name to check + * @param address Address * @param network Network name or chain ID * @returns True if the address is a contract, false if it's an EOA */ -export async function isContract(addressOrEns: string, network = 'sei'): Promise { - // Resolve ENS name to address if needed - const address = await resolveAddress(addressOrEns, network); +export async function isContract(address: string, network = 'sei'): Promise { + const validatedAddress = services.helpers.validateAddress(address); const client = getPublicClient(network); - const code = await client.getBytecode({ address }); + const code = await client.getBytecode({ address: validatedAddress }); return code !== undefined && code !== '0x'; } diff --git a/src/core/services/ens.ts b/src/core/services/ens.ts deleted file mode 100644 index 4a70bd3..0000000 --- a/src/core/services/ens.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { normalize } from 'viem/ens'; -import { getPublicClient } from './clients.js'; -import { type Address } from 'viem'; - -/** - * Resolves an ENS name to a Sei address or returns the original address if it's already valid - * @param addressOrEns An Sei address or ENS name - * @param network The network to use for ENS resolution (defaults to Sei mainnet) - * @returns The resolved Seu address - */ -export async function resolveAddress( - addressOrEns: string, - network = 'sei' -): Promise
{ - // If it's already a valid Sei 0x address (0x followed by 40 hex chars), return it - if (/^0x[a-fA-F0-9]{40}$/.test(addressOrEns)) { - return addressOrEns as Address; - } - - // If it looks like an ENS name (contains a dot), try to resolve it - if (addressOrEns.includes('.')) { - try { - // Normalize the ENS name first - const normalizedEns = normalize(addressOrEns); - - // Get the public client for the network - const publicClient = getPublicClient(network); - - // Resolve the ENS name to an address - const address = await publicClient.getEnsAddress({ - name: normalizedEns, - }); - - if (!address) { - throw new Error(`ENS name ${addressOrEns} could not be resolved to an address`); - } - - return address; - } catch (error: any) { - throw new Error(`Failed to resolve ENS name ${addressOrEns}: ${error.message}`); - } - } - - // If it's neither a valid address nor an ENS name, throw an error - throw new Error(`Invalid address or ENS name: ${addressOrEns}`); -} diff --git a/src/core/services/index.ts b/src/core/services/index.ts index f7da50a..90b40a6 100644 --- a/src/core/services/index.ts +++ b/src/core/services/index.ts @@ -6,15 +6,14 @@ export * from './blocks.js'; export * from './transactions.js'; export * from './contracts.js'; export * from './tokens.js'; -export * from './ens.js'; export { utils as helpers } from './utils.js'; // Re-export common types for convenience -export type { - Address, - Hash, +export type { + Address, + Hash, Hex, Block, TransactionReceipt, Log -} from 'viem'; \ No newline at end of file +} from 'viem'; diff --git a/src/core/services/transfer.ts b/src/core/services/transfer.ts index 10ea9d1..bd88a5b 100644 --- a/src/core/services/transfer.ts +++ b/src/core/services/transfer.ts @@ -1,17 +1,13 @@ import { parseEther, parseUnits, - formatUnits, type Address, type Hash, type Hex, - type Abi, getContract, - type Account } from 'viem'; import { getPublicClient, getWalletClient } from './clients.js'; -import { getChain } from '../chains.js'; -import { resolveAddress } from './ens.js'; +import * as services from "./index.js"; // Standard ERC20 ABI for transfers const erc20TransferAbi = [ @@ -117,19 +113,18 @@ const erc1155TransferAbi = [ /** * Transfer Sei to an address * @param privateKey Sender's private key - * @param toAddressOrEns Recipient address or ENS name + * @param toAddress Recipient address * @param amount Amount to send in Sei * @param network Network name or chain ID * @returns Transaction hash */ export async function transferSei( privateKey: string | Hex, - toAddressOrEns: string, + toAddress: string, amount: string, // in ether network = 'sei' ): Promise { - // Resolve ENS name to address if needed - const toAddress = await resolveAddress(toAddressOrEns, network); + const validatedToAddress = services.helpers.validateAddress(toAddress); // Ensure the private key has 0x prefix const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') @@ -140,7 +135,7 @@ export async function transferSei( const amountWei = parseEther(amount); return client.sendTransaction({ - to: toAddress, + to: validatedToAddress, value: amountWei, account: client.account!, chain: client.chain @@ -149,16 +144,16 @@ export async function transferSei( /** * Transfer ERC20 tokens to an address - * @param tokenAddressOrEns Token contract address or ENS name - * @param toAddressOrEns Recipient address or ENS name + * @param tokenAddress Token contract address + * @param toAddress Recipient address * @param amount Amount to send (in token units) * @param privateKey Sender's private key * @param network Network name or chain ID * @returns Transaction details */ export async function transferERC20( - tokenAddressOrEns: string, - toAddressOrEns: string, + tokenAddress: string, + toAddress: string, amount: string, privateKey: string | `0x${string}`, network: string = 'sei' @@ -173,9 +168,8 @@ export async function transferERC20( decimals: number; }; }> { - // Resolve ENS names to addresses if needed - const tokenAddress = await resolveAddress(tokenAddressOrEns, network) as Address; - const toAddress = await resolveAddress(toAddressOrEns, network) as Address; + const validatedTokenAddress = services.helpers.validateAddress(tokenAddress); + const validatedToAddress = services.helpers.validateAddress(toAddress); // Ensure the private key has 0x prefix const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') @@ -185,7 +179,7 @@ export async function transferERC20( // Get token details const publicClient = getPublicClient(network); const contract = getContract({ - address: tokenAddress, + address: validatedTokenAddress, abi: erc20TransferAbi, client: publicClient, }); @@ -202,10 +196,10 @@ export async function transferERC20( // Send the transaction const hash = await walletClient.writeContract({ - address: tokenAddress, + address: validatedTokenAddress, abi: erc20TransferAbi, functionName: 'transfer', - args: [toAddress, rawAmount], + args: [validatedToAddress, rawAmount], account: walletClient.account!, chain: walletClient.chain }); @@ -225,16 +219,16 @@ export async function transferERC20( /** * Approve ERC20 token spending - * @param tokenAddressOrEns Token contract address or ENS name - * @param spenderAddressOrEns Spender address or ENS name + * @param tokenAddress Token contract address + * @param spenderAddress Spender address * @param amount Amount to approve (in token units) * @param privateKey Owner's private key * @param network Network name or chain ID * @returns Transaction details */ export async function approveERC20( - tokenAddressOrEns: string, - spenderAddressOrEns: string, + tokenAddress: string, + spenderAddress: string, amount: string, privateKey: string | `0x${string}`, network: string = 'sei' @@ -249,9 +243,8 @@ export async function approveERC20( decimals: number; }; }> { - // Resolve ENS names to addresses if needed - const tokenAddress = await resolveAddress(tokenAddressOrEns, network) as Address; - const spenderAddress = await resolveAddress(spenderAddressOrEns, network) as Address; + const validatedTokenAddress = services.helpers.validateAddress(tokenAddress); + const validatedSpenderAddress = services.helpers.validateAddress(spenderAddress); // Ensure the private key has 0x prefix const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') @@ -261,7 +254,7 @@ export async function approveERC20( // Get token details const publicClient = getPublicClient(network); const contract = getContract({ - address: tokenAddress, + address: validatedTokenAddress, abi: erc20TransferAbi, client: publicClient, }); @@ -278,10 +271,10 @@ export async function approveERC20( // Send the transaction const hash = await walletClient.writeContract({ - address: tokenAddress, + address: validatedTokenAddress, abi: erc20TransferAbi, functionName: 'approve', - args: [spenderAddress, rawAmount], + args: [validatedSpenderAddress, rawAmount], account: walletClient.account!, chain: walletClient.chain }); @@ -301,16 +294,16 @@ export async function approveERC20( /** * Transfer an NFT (ERC721) to an address - * @param tokenAddressOrEns NFT contract address or ENS name - * @param toAddressOrEns Recipient address or ENS name + * @param tokenAddress NFT contract address + * @param toAddress Recipient address * @param tokenId Token ID to transfer * @param privateKey Owner's private key * @param network Network name or chain ID * @returns Transaction details */ export async function transferERC721( - tokenAddressOrEns: string, - toAddressOrEns: string, + tokenAddress: string, + toAddress: string, tokenId: bigint, privateKey: string | `0x${string}`, network: string = 'sei' @@ -322,9 +315,8 @@ export async function transferERC721( symbol: string; }; }> { - // Resolve ENS names to addresses if needed - const tokenAddress = await resolveAddress(tokenAddressOrEns, network) as Address; - const toAddress = await resolveAddress(toAddressOrEns, network) as Address; + const validatedTokenAddress = services.helpers.validateAddress(tokenAddress); + const validatedToAddress = services.helpers.validateAddress(toAddress); // Ensure the private key has 0x prefix const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') @@ -337,10 +329,10 @@ export async function transferERC721( // Send the transaction const hash = await walletClient.writeContract({ - address: tokenAddress, + address: validatedTokenAddress, abi: erc721TransferAbi, functionName: 'transferFrom', - args: [fromAddress, toAddress, tokenId], + args: [fromAddress, validatedToAddress, tokenId], account: walletClient.account!, chain: walletClient.chain }); @@ -348,7 +340,7 @@ export async function transferERC721( // Get token metadata const publicClient = getPublicClient(network); const contract = getContract({ - address: tokenAddress, + address: validatedTokenAddress, abi: erc721TransferAbi, client: publicClient, }); @@ -378,8 +370,8 @@ export async function transferERC721( /** * Transfer ERC1155 tokens to an address - * @param tokenAddressOrEns Token contract address or ENS name - * @param toAddressOrEns Recipient address or ENS name + * @param tokenAddress Token contract + * @param toAddress Recipient address * @param tokenId Token ID to transfer * @param amount Amount to transfer * @param privateKey Owner's private key @@ -387,8 +379,8 @@ export async function transferERC721( * @returns Transaction details */ export async function transferERC1155( - tokenAddressOrEns: string, - toAddressOrEns: string, + tokenAddress: string, + toAddress: string, tokenId: bigint, amount: string, privateKey: string | `0x${string}`, @@ -398,9 +390,8 @@ export async function transferERC1155( tokenId: string; amount: string; }> { - // Resolve ENS names to addresses if needed - const tokenAddress = await resolveAddress(tokenAddressOrEns, network) as Address; - const toAddress = await resolveAddress(toAddressOrEns, network) as Address; + const validatedTokenAddress = services.helpers.validateAddress(tokenAddress); + const validatedToAddress = services.helpers.validateAddress(toAddress); // Ensure the private key has 0x prefix const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') @@ -416,10 +407,10 @@ export async function transferERC1155( // Send the transaction const hash = await walletClient.writeContract({ - address: tokenAddress, + address: validatedTokenAddress, abi: erc1155TransferAbi, functionName: 'safeTransferFrom', - args: [fromAddress, toAddress, tokenId, amountBigInt, '0x'], + args: [fromAddress, validatedToAddress, tokenId, amountBigInt, '0x'], account: walletClient.account!, chain: walletClient.chain }); diff --git a/src/core/services/utils.ts b/src/core/services/utils.ts index f8ed8e5..56b4a71 100644 --- a/src/core/services/utils.ts +++ b/src/core/services/utils.ts @@ -1,43 +1,47 @@ -import { - parseEther, - formatEther, - type Account, - type Hash, - type Chain, - type WalletClient, - type Transport, - type HttpTransport +import { + parseEther, + formatEther, + type Address } from 'viem'; /** * Utility functions for formatting and parsing values */ export const utils = { - // Convert ether to wei - parseEther, - - // Convert wei to ether - formatEther, - - // Format a bigint to a string - formatBigInt: (value: bigint): string => value.toString(), - - // Format an object to JSON with bigint handling - formatJson: (obj: unknown): string => JSON.stringify(obj, (_, value) => - typeof value === 'bigint' ? value.toString() : value, 2), - - // Format a number with commas - formatNumber: (value: number | string): string => { - return Number(value).toLocaleString(); - }, - - // Convert a hex string to a number - hexToNumber: (hex: string): number => { - return parseInt(hex, 16); - }, - - // Convert a number to a hex string - numberToHex: (num: number): string => { - return '0x' + num.toString(16); - } -}; \ No newline at end of file + // Convert ether to wei + parseEther, + + // Convert wei to ether + formatEther, + + // Format a bigint to a string + formatBigInt: (value: bigint): string => value.toString(), + + // Format an object to JSON with bigint handling + formatJson: (obj: unknown): string => JSON.stringify(obj, (_, value) => + typeof value === 'bigint' ? value.toString() : value, 2), + + // Format a number with commas + formatNumber: (value: number | string): string => { + return Number(value).toLocaleString(); + }, + + // Convert a hex string to a number + hexToNumber: (hex: string): number => { + return parseInt(hex, 16); + }, + + // Convert a number to a hex string + numberToHex: (num: number): string => { + return '0x' + num.toString(16); + }, + + validateAddress: (address: string): Address => { + // If it's already a valid Sei 0x address (0x followed by 40 hex chars), return it + if (/^0x[a-fA-F0-9]{40}$/.test(address)) { + return address as Address; + } + + throw new Error(`Invalid address: ${address}`); + } +}; diff --git a/src/core/tools.ts b/src/core/tools.ts index 9309adf..93fe161 100644 --- a/src/core/tools.ts +++ b/src/core/tools.ts @@ -3,14 +3,10 @@ import { z } from "zod"; import { getSupportedNetworks, getRpcUrl } from "./chains.js"; import * as services from "./services/index.js"; import { type Address, type Hex, type Hash } from 'viem'; -import { normalize } from 'viem/ens'; /** * Register all EVM-related tools with the MCP server * - * All tools that accept EVM addresses also support ENS names (e.g., 'vitalik.eth'). - * ENS names are automatically resolved to addresses using the Ethereum Name Service. - * * @param server The MCP server instance */ export function registerEVMTools(server: McpServer) { @@ -52,58 +48,6 @@ export function registerEVMTools(server: McpServer) { } ); - // ENS LOOKUP TOOL - - // Resolve ENS name to address - server.tool( - "resolve_ens", - "Resolve an ENS name to an EVM address", - { - ensName: z.string().describe("ENS name to resolve (e.g., 'jay.sei')"), - network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. ENS resolution works best on Sei mainnet. Defaults to Sei mainnet.") - }, - async ({ ensName, network = "sei" }) => { - try { - // Validate that the input is an ENS name - if (!ensName.includes('.')) { - return { - content: [{ - type: "text", - text: `Error: Input "${ensName}" is not a valid ENS name. ENS names must contain a dot (e.g., 'name.sei').` - }], - isError: true - }; - } - - // Normalize the ENS name - const normalizedEns = normalize(ensName); - - // Resolve the ENS name to an address - const address = await services.resolveAddress(ensName, network); - - return { - content: [{ - type: "text", - text: JSON.stringify({ - ensName: ensName, - normalizedName: normalizedEns, - resolvedAddress: address, - network - }, null, 2) - }] - }; - } catch (error) { - return { - content: [{ - type: "text", - text: `Error resolving ENS name: ${error instanceof Error ? error.message : String(error)}` - }], - isError: true - }; - } - } - ); - // Get supported networks server.tool( "get_supported_networks", @@ -201,7 +145,7 @@ export function registerEVMTools(server: McpServer) { "get_balance", "Get the native token balance (Sei) for an address", { - address: z.string().describe("The wallet address or ENS name (e.g., '0x1234...' or 'vitalik.eth') to check the balance for"), + address: z.string().describe("The wallet address name (e.g., '0x1234...' or 'vitalik.eth') to check the balance for"), network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, async ({ address, network = "sei" }) => { @@ -280,8 +224,8 @@ export function registerEVMTools(server: McpServer) { "get_token_balance", "Get the balance of an ERC20 token for an address", { - tokenAddress: z.string().describe("The contract address or ENS name of the ERC20 token (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')"), - ownerAddress: z.string().describe("The wallet address or ENS name to check the balance for (e.g., '0x1234...' or 'vitalik.eth')"), + tokenAddress: z.string().describe("The contract address name of the ERC20 token (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')"), + ownerAddress: z.string().describe("The wallet address name to check the balance for (e.g., '0x1234...' or 'vitalik.eth')"), network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, async ({ tokenAddress, ownerAddress, network = "sei" }) => { @@ -429,7 +373,7 @@ export function registerEVMTools(server: McpServer) { "Transfer native tokens (Sei) to an address", { privateKey: z.string().describe("Private key of the sender account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored."), - to: z.string().describe("The recipient address or ENS name (e.g., '0x1234...' or 'vitalik.eth')"), + to: z.string().describe("The recipient address name (e.g., '0x1234...' or 'vitalik.eth')"), amount: z.string().describe("Amount to send in Sei, as a string (e.g., '0.1')"), network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, @@ -678,8 +622,8 @@ export function registerEVMTools(server: McpServer) { "Transfer ERC20 tokens to an address", { privateKey: z.string().describe("Private key of the sender account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored."), - tokenAddress: z.string().describe("The contract address or ENS name of the ERC20 token to transfer (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' for USDC or 'uniswap.eth')"), - toAddress: z.string().describe("The recipient address or ENS name that will receive the tokens (e.g., '0x1234...' or 'vitalik.eth')"), + tokenAddress: z.string().describe("The contract address name of the ERC20 token to transfer (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' for USDC or 'uniswap.eth')"), + toAddress: z.string().describe("The recipient address name that will receive the tokens (e.g., '0x1234...' or 'vitalik.eth')"), amount: z.string().describe("Amount of tokens to send as a string (e.g., '100' for 100 tokens). This will be adjusted for the token's decimals."), network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet' etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, @@ -821,7 +765,7 @@ export function registerEVMTools(server: McpServer) { "is_contract", "Check if an address is a smart contract or an externally owned account (EOA)", { - address: z.string().describe("The wallet or contract address or ENS name to check (e.g., '0x1234...' or 'uniswap.eth')"), + address: z.string().describe("The wallet or contract address to check (e.g., '0x1234...' or 'uniswap.eth')"), network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, async ({ address, network = "sei" }) => { @@ -995,9 +939,9 @@ export function registerEVMTools(server: McpServer) { "check_nft_ownership", "Check if an address owns a specific NFT", { - tokenAddress: z.string().describe("The contract address or ENS name of the NFT collection (e.g., '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D' for BAYC or 'boredapeyachtclub.eth')"), + tokenAddress: z.string().describe("The contract address of the NFT collection (e.g., '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D' for BAYC or 'boredapeyachtclub.eth')"), tokenId: z.string().describe("The ID of the NFT to check (e.g., '1234')"), - ownerAddress: z.string().describe("The wallet address or ENS name to check ownership against (e.g., '0x1234...' or 'vitalik.eth')"), + ownerAddress: z.string().describe("The wallet address name to check ownership against (e.g., '0x1234...' or 'vitalik.eth')"), network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet' etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, async ({ tokenAddress, tokenId, ownerAddress, network = "sei" }) => { From 5b6ef79ccfc6c765a987cd201fffc003da44a26c Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 20 May 2025 14:59:22 -0700 Subject: [PATCH 04/12] pass private key vie ENV var --- .env.example | 6 + .gitignore | 3 + README.md | 29 +- package-lock.json | 2259 ++++++++++++++++++++++++++++++++ package.json | 1 + src/core/config.ts | 45 + src/core/prompts.ts | 17 + src/core/services/contracts.ts | 15 +- src/core/services/transfer.ts | 78 +- src/core/tools.ts | 103 +- 10 files changed, 2443 insertions(+), 113 deletions(-) create mode 100644 .env.example create mode 100644 package-lock.json create mode 100644 src/core/config.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..be62ae8 --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +# Sei MCP Server Environment Variables + +# Private key for blockchain transactions (without 0x prefix) +# This is used when no private key is provided in the request +# SECURITY: Never commit your actual private key to version control +PRIVATE_KEY=your_private_key_here diff --git a/.gitignore b/.gitignore index 62ce640..b3e260e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,5 +27,8 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json mcp-context/ build/ +# Environment variables +.env + # Finder (MacOS) folder config .DS_Store diff --git a/README.md b/README.md index 46f6416..0d12a68 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ It supports a wide range of services including: All services are exposed through a consistent interface of MCP tools and resources, making it easy for AI agents to discover and use blockchain functionality. + ## ✨ Features ### Blockchain Data Access @@ -117,6 +118,21 @@ These values are hardcoded in the application. If you need to modify them, you c - For chain configuration: `src/core/chains.ts` - For server configuration: `src/server/http-server.ts` +### Environment Variables + +The server supports loading configuration from environment variables: + +- `PRIVATE_KEY`: **Required** private key for any blockchain operations that involve signing transactions (e.g., transferring tokens, interacting with smart contracts that modify state). This is the **sole method** for providing a private key. If this environment variable is not set when a transaction-signing tool is invoked, the tool will return an error message instructing the AI assistant to ask the user to set the `PRIVATE_KEY` environment variable and restart the MCP server. + +Create a `.env` file in the root directory based on the `.env.example` template: + +```bash +# .env.example +PRIVATE_KEY=your_private_key_here +``` + +> **SECURITY WARNING**: Never commit your actual private key to version control. The `.env` file is included in `.gitignore` by default. + ## 🚀 Usage ### Using npx (No Installation Required) @@ -320,15 +336,14 @@ The server provides the following MCP tools for agents. |-----------|-------------|----------------| | `get-token-info` | Get ERC20 token metadata | `tokenAddress` (address), `network` | | `get-token-balance` | Check ERC20 token balance | `tokenAddress` (address), `ownerAddress` (address), `network` | -| `transfer-token` | Transfer ERC20 tokens | `privateKey`, `tokenAddress` (address), `toAddress` (address), `amount`, `network` | -| `approve-token-spending` | Approve token allowances | `privateKey`, `tokenAddress` (address), `spenderAddress` (address), `amount`, `network` | +| `transfer-token` | Transfer ERC20 tokens | `tokenAddress` (address), `toAddress` (address), `amount`, `network` | +| `approve-token-spending` | Approve token allowances | `tokenAddress` (address), `spenderAddress` (address), `amount`, `network` | | `get-nft-info` | Get NFT metadata | `tokenAddress` (address), `tokenId`, `network` | | `check-nft-ownership` | Verify NFT ownership | `tokenAddress` (address), `tokenId`, `ownerAddress` (address), `network` | -| `transfer-nft` | Transfer an NFT | `privateKey`, `tokenAddress` (address), `tokenId`, `toAddress` (address), `network` | | `get-nft-balance` | Count NFTs owned | `tokenAddress` (address), `ownerAddress` (address), `network` | | `get-erc1155-token-uri` | Get ERC1155 metadata | `tokenAddress` (address), `tokenId`, `network` | | `get-erc1155-balance` | Check ERC1155 balance | `tokenAddress` (address), `tokenId`, `ownerAddress` (address), `network` | -| `transfer-erc1155` | Transfer ERC1155 tokens | `privateKey`, `tokenAddress` (address), `tokenId`, `amount`, `toAddress` (address), `network` | +| `transfer-erc1155` | Transfer ERC1155 tokens | `tokenAddress` (address), `tokenId`, `amount`, `toAddress` (address), `network` | #### Blockchain services @@ -336,10 +351,10 @@ The server provides the following MCP tools for agents. |-------------------|-------------|----------------| | `get-chain-info` | Get network information | `network` | | `get-balance` | Get native token balance | `address` (address), `network` | -| `transfer-sei` | Send native tokens | `privateKey`, `to` (address), `amount`, `network` | +| `transfer-sei` | Send native tokens | `to` (address), `amount`, `network` | | `get-transaction` | Get transaction details | `txHash`, `network` | -| `read-contract` | Read smart contract state | `contractAddress` (address), `abi`, `functionName`, `args`, `network` | -| `write-contract` | Write to smart contract | `contractAddress` (address), `abi`, `functionName`, `args`, `privateKey`, `network` | +| `read-contract` | Read smart contract state | `contractAddress` (address), `abi`, `functionName`, `args` (optional), `network` | +| `write-contract` | Write to smart contract | `contractAddress` (address), `abi`, `functionName`, `args` (optional), `network` | | `is-contract` | Check if address is a contract | `address` (address), `network` | ### Resources diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fb19f10 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2259 @@ +{ + "name": "@sei-protocol/sei-mcp-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@sei-protocol/sei-mcp-server", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.7.0", + "cors": "^2.8.5", + "dotenv": "^16.5.0", + "express": "^4.21.2", + "viem": "^2.23.9", + "zod": "^3.24.2" + }, + "bin": { + "sei-mcp-server": "bin/cli.js" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "conventional-changelog-cli": "^5.0.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "typescript": "^5.8.2" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz", + "integrity": "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==" + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@conventional-changelog/git-client": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-1.0.1.tgz", + "integrity": "sha512-PJEqBwAleffCMETaVm/fUgHldzBE35JFk3/9LL6NUA5EXa3qednu+UT6M7E5iBu3zIQZCULYIiZ90fBYHt6xUw==", + "dev": true, + "dependencies": { + "@types/semver": "^7.5.5", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0" + }, + "peerDependenciesMeta": { + "conventional-commits-filter": { + "optional": true + }, + "conventional-commits-parser": { + "optional": true + } + } + }, + "node_modules/@hutson/parse-repository-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-5.0.0.tgz", + "integrity": "sha512-e5+YUKENATs1JgYHMzTr2MW/NDcXGfYFAuOQU8gJgF/kEh4EqKgfGrfLI67bMD4tbhZVlkigz/9YYwWcbOFthg==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.2.tgz", + "integrity": "sha512-H9vwztj5OAqHg9GockCQC06k1natgcxWQSRpQcPJf6i5+MWBzfKkRtxGbjQf0X2ihii0ffLZCRGbYV2f2bjNCQ==", + "dependencies": { + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.3", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@noble/curves": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz", + "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", + "dependencies": { + "@noble/hashes": "1.7.2" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", + "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.5.tgz", + "integrity": "sha512-9rE6EOVeIQzt5TSu4v+K523F8u6DhBsoZWPGKlnCshhlDhy0kJzUX4V+tr2dWmzF1GdekvThABoEQBGBQI7xZw==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz", + "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", + "dependencies": { + "@noble/curves": "~1.8.1", + "@noble/hashes": "~1.7.1", + "@scure/base": "~1.2.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz", + "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", + "dependencies": { + "@noble/hashes": "~1.7.1", + "@scure/base": "~1.2.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bun": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.2.13.tgz", + "integrity": "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg==", + "dev": true, + "dependencies": { + "bun-types": "1.2.13" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.18", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.18.tgz", + "integrity": "sha512-nX3d0sxJW41CqQvfOzVG1NCTXfFDrDWIghCZncpHeWlVFd81zxB/DLhg7avFg6eHLCRX7ckBmoIIcqa++upvJA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz", + "integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.15.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.17.tgz", + "integrity": "sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw==", + "dev": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/abitype": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", + "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/add-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", + "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", + "dev": true + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bun-types": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.2.13.tgz", + "integrity": "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/conventional-changelog": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-6.0.0.tgz", + "integrity": "sha512-tuUH8H/19VjtD9Ig7l6TQRh+Z0Yt0NZ6w/cCkkyzUbGQTnUEmKfGtkC9gGfVgCfOL1Rzno5NgNF4KY8vR+Jo3w==", + "dev": true, + "dependencies": { + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-atom": "^5.0.0", + "conventional-changelog-codemirror": "^5.0.0", + "conventional-changelog-conventionalcommits": "^8.0.0", + "conventional-changelog-core": "^8.0.0", + "conventional-changelog-ember": "^5.0.0", + "conventional-changelog-eslint": "^6.0.0", + "conventional-changelog-express": "^5.0.0", + "conventional-changelog-jquery": "^6.0.0", + "conventional-changelog-jshint": "^5.0.0", + "conventional-changelog-preset-loader": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz", + "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-atom": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-5.0.0.tgz", + "integrity": "sha512-WfzCaAvSCFPkznnLgLnfacRAzjgqjLUjvf3MftfsJzQdDICqkOOpcMtdJF3wTerxSpv2IAAjX8doM3Vozqle3g==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-cli": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-cli/-/conventional-changelog-cli-5.0.0.tgz", + "integrity": "sha512-9Y8fucJe18/6ef6ZlyIlT2YQUbczvoQZZuYmDLaGvcSBP+M6h+LAvf7ON7waRxKJemcCII8Yqu5/8HEfskTxJQ==", + "dev": true, + "dependencies": { + "add-stream": "^1.0.0", + "conventional-changelog": "^6.0.0", + "meow": "^13.0.0", + "tempfile": "^5.0.0" + }, + "bin": { + "conventional-changelog": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-codemirror": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-5.0.0.tgz", + "integrity": "sha512-8gsBDI5Y3vrKUCxN6Ue8xr6occZ5nsDEc4C7jO/EovFGozx8uttCAyfhRrvoUAWi2WMm3OmYs+0mPJU7kQdYWQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-8.0.0.tgz", + "integrity": "sha512-eOvlTO6OcySPyyyk8pKz2dP4jjElYunj9hn9/s0OB+gapTO8zwS9UQWrZ1pmF2hFs3vw1xhonOLGcGjy/zgsuA==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-core": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-8.0.0.tgz", + "integrity": "sha512-EATUx5y9xewpEe10UEGNpbSHRC6cVZgO+hXQjofMqpy+gFIrcGvH3Fl6yk2VFKh7m+ffenup2N7SZJYpyD9evw==", + "dev": true, + "dependencies": { + "@hutson/parse-repository-url": "^5.0.0", + "add-stream": "^1.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-parser": "^6.0.0", + "git-raw-commits": "^5.0.0", + "git-semver-tags": "^8.0.0", + "hosted-git-info": "^7.0.0", + "normalize-package-data": "^6.0.0", + "read-package-up": "^11.0.0", + "read-pkg": "^9.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-ember": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-5.0.0.tgz", + "integrity": "sha512-RPflVfm5s4cSO33GH/Ey26oxhiC67akcxSKL8CLRT3kQX2W3dbE19sSOM56iFqUJYEwv9mD9r6k79weWe1urfg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-eslint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-6.0.0.tgz", + "integrity": "sha512-eiUyULWjzq+ybPjXwU6NNRflApDWlPEQEHvI8UAItYW/h22RKkMnOAtfCZxMmrcMO1OKUWtcf2MxKYMWe9zJuw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-5.0.0.tgz", + "integrity": "sha512-D8Q6WctPkQpvr2HNCCmwU5GkX22BVHM0r4EW8vN0230TSyS/d6VQJDAxGb84lbg0dFjpO22MwmsikKL++Oo/oQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-jquery": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-6.0.0.tgz", + "integrity": "sha512-2kxmVakyehgyrho2ZHBi90v4AHswkGzHuTaoH40bmeNqUt20yEkDOSpw8HlPBfvEQBwGtbE+5HpRwzj6ac2UfA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-jshint": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-5.0.0.tgz", + "integrity": "sha512-gGNphSb/opc76n2eWaO6ma4/Wqu3tpa2w7i9WYqI6Cs2fncDSI2/ihOfMvXveeTTeld0oFvwMVNV+IYQIk3F3g==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-preset-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-5.0.0.tgz", + "integrity": "sha512-SetDSntXLk8Jh1NOAl1Gu5uLiCNSYenB5tm0YVeZKePRIgDW9lQImromTwLa3c/Gae298tsgOM+/CYT9XAl0NA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-writer": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.0.1.tgz", + "integrity": "sha512-hlqcy3xHred2gyYg/zXSMXraY2mjAYYo0msUCpK+BGyaVJMFCKWVXPIHiaacGO2GGp13kvHWXFhYmxT4QQqW3Q==", + "dev": true, + "dependencies": { + "conventional-commits-filter": "^5.0.0", + "handlebars": "^4.7.7", + "meow": "^13.0.0", + "semver": "^7.5.2" + }, + "bin": { + "conventional-changelog-writer": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-commits-filter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", + "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-commits-parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.1.0.tgz", + "integrity": "sha512-5nxDo7TwKB5InYBl4ZC//1g9GRwB/F3TXOGR9hgUjMGfvSP4Vu5NkpNro2+1+TIEy1vwxApl5ircECr2ri5JIw==", + "dev": true, + "dependencies": { + "meow": "^13.0.0" + }, + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz", + "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/git-raw-commits": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-5.0.0.tgz", + "integrity": "sha512-I2ZXrXeOc0KrCvC7swqtIFXFN+rbjnC7b2T943tvemIOVNl+XP8YnA9UVwqFhzzLClnSA60KR/qEjLpXzs73Qg==", + "dev": true, + "dependencies": { + "@conventional-changelog/git-client": "^1.0.0", + "meow": "^13.0.0" + }, + "bin": { + "git-raw-commits": "src/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/git-semver-tags": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-8.0.0.tgz", + "integrity": "sha512-N7YRIklvPH3wYWAR2vysaqGLPRcpwQ0GKdlqTiVN5w1UmCdaeY3K8s6DMKRCh54DDdzyt/OAB6C8jgVtb7Y2Fg==", + "dev": true, + "dependencies": { + "@conventional-changelog/git-client": "^1.0.0", + "meow": "^13.0.0" + }, + "bin": { + "git-semver-tags": "src/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/index-to-position": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz", + "integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/isows": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", + "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/ox": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", + "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "^1.10.1", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "abitype": "^1.0.6", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-package-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", + "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", + "dev": true, + "dependencies": { + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/temp-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", + "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", + "dev": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/tempfile": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-5.0.0.tgz", + "integrity": "sha512-bX655WZI/F7EoTDw9JvQURqAXiPHi8o8+yFxPF2lWYyz1aHnmMRuXWqL6YB6GmeO0o4DIYWHLgGNi/X64T+X4Q==", + "dev": true, + "dependencies": { + "temp-dir": "^3.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/viem": { + "version": "2.29.2", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.29.2.tgz", + "integrity": "sha512-cukRxab90jvQ+TDD84sU3qB3UmejYqgCw4cX8SfWzvh7JPfZXI3kAMUaT5OSR2As1Mgvx1EJawccwPjGqkSSwA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "dependencies": { + "@noble/curves": "1.8.2", + "@noble/hashes": "1.7.2", + "@scure/bip32": "1.6.2", + "@scure/bip39": "1.5.4", + "abitype": "1.0.8", + "isows": "1.0.6", + "ox": "0.6.9", + "ws": "8.18.1" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/zod": { + "version": "3.24.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", + "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} diff --git a/package.json b/package.json index fbf22a8..5b6d646 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.7.0", "cors": "^2.8.5", + "dotenv": "^16.5.0", "express": "^4.21.2", "viem": "^2.23.9", "zod": "^3.24.2" diff --git a/src/core/config.ts b/src/core/config.ts new file mode 100644 index 0000000..48fb8a6 --- /dev/null +++ b/src/core/config.ts @@ -0,0 +1,45 @@ +import dotenv from 'dotenv'; +import { z } from 'zod'; +import { type Hex } from 'viem'; + +// Load environment variables from .env file +dotenv.config(); + +// Define environment variable schema +const envSchema = z.object({ + PRIVATE_KEY: z.string().optional(), +}); + +// Parse and validate environment variables +const env = envSchema.safeParse(process.env); + +// Format private key with 0x prefix if it exists +const formatPrivateKey = (key?: string): string | undefined => { + if (!key) return undefined; + + // Ensure the private key has 0x prefix + return key.startsWith('0x') ? key : `0x${key}`; +}; + +// Export validated environment variables with formatted private key +export const config = { + privateKey: env.success ? formatPrivateKey(env.data.PRIVATE_KEY) : undefined, +}; + +/** + * Get the private key from environment variable. + * Returns undefined if the PRIVATE_KEY environment variable is not set. + * @returns Private key from environment variable or undefined + */ +export function getPrivateKey(): string | undefined { + return config.privateKey; +} + +/** + * Get the private key from environment variable as a Hex type for viem. + * Returns undefined if the PRIVATE_KEY environment variable is not set. + * @returns Private key from environment variable as Hex or undefined + */ +export function getPrivateKeyAsHex(): Hex | undefined { + return config.privateKey as Hex | undefined; +} diff --git a/src/core/prompts.ts b/src/core/prompts.ts index 61b7586..d7577a7 100644 --- a/src/core/prompts.ts +++ b/src/core/prompts.ts @@ -46,6 +46,23 @@ export function registerEVMPrompts(server: McpServer) { }) ); + // Address analysis prompt + server.prompt( + "my_wallet_address", + "What is my wallet EVM address", + { + }, + () => ({ + messages: [{ + role: "user", + content: { + type: "text", + text: `Please retrieve my wallet EVM address using tools get_address_from_private_key via MCP server.` + } + }] + }) + ); + // Address analysis prompt server.prompt( "analyze_address", diff --git a/src/core/services/contracts.ts b/src/core/services/contracts.ts index 672d931..ad065d7 100644 --- a/src/core/services/contracts.ts +++ b/src/core/services/contracts.ts @@ -7,6 +7,7 @@ import { } from 'viem'; import { getPublicClient, getWalletClient } from './clients.js'; import * as services from "./index.js"; +import { getPrivateKeyAsHex } from '../config.js'; /** * Read from a contract for a specific network @@ -18,13 +19,23 @@ export async function readContract(params: ReadContractParameters, network = 'se /** * Write to a contract for a specific network + * @param params Contract parameters + * @param network Network name or chain ID + * @returns Transaction hash + * @throws Error if no private key is available */ export async function writeContract( - privateKey: Hex, params: Record, network = 'sei' ): Promise { - const client = getWalletClient(privateKey, network); + // Get private key from environment + const key = getPrivateKeyAsHex(); + + if (!key) { + throw new Error('Private key not available. Set the PRIVATE_KEY environment variable and restart the MCP server.'); + } + + const client = getWalletClient(key, network); return await client.writeContract(params as any); } diff --git a/src/core/services/transfer.ts b/src/core/services/transfer.ts index bd88a5b..d701843 100644 --- a/src/core/services/transfer.ts +++ b/src/core/services/transfer.ts @@ -8,6 +8,7 @@ import { } from 'viem'; import { getPublicClient, getWalletClient } from './clients.js'; import * as services from "./index.js"; +import { getPrivateKeyAsHex } from '../config.js'; // Standard ERC20 ABI for transfers const erc20TransferAbi = [ @@ -112,26 +113,26 @@ const erc1155TransferAbi = [ /** * Transfer Sei to an address - * @param privateKey Sender's private key * @param toAddress Recipient address * @param amount Amount to send in Sei * @param network Network name or chain ID * @returns Transaction hash + * @throws Error if no private key is available */ export async function transferSei( - privateKey: string | Hex, toAddress: string, amount: string, // in ether network = 'sei' ): Promise { const validatedToAddress = services.helpers.validateAddress(toAddress); + // Get private key from environment + const privateKey = getPrivateKeyAsHex(); - // Ensure the private key has 0x prefix - const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') - ? `0x${privateKey}` as Hex - : privateKey as Hex; + if (!privateKey) { + throw new Error('Private key not available. Set the PRIVATE_KEY environment variable and restart the MCP server.'); + } - const client = getWalletClient(formattedKey, network); + const client = getWalletClient(privateKey, network); const amountWei = parseEther(amount); return client.sendTransaction({ @@ -147,15 +148,14 @@ export async function transferSei( * @param tokenAddress Token contract address * @param toAddress Recipient address * @param amount Amount to send (in token units) - * @param privateKey Sender's private key * @param network Network name or chain ID * @returns Transaction details + * @throws Error if no private key is available */ export async function transferERC20( tokenAddress: string, toAddress: string, amount: string, - privateKey: string | `0x${string}`, network: string = 'sei' ): Promise<{ txHash: Hash; @@ -170,16 +170,18 @@ export async function transferERC20( }> { const validatedTokenAddress = services.helpers.validateAddress(tokenAddress); const validatedToAddress = services.helpers.validateAddress(toAddress); + // Get private key from environment + const privateKey = getPrivateKeyAsHex(); - // Ensure the private key has 0x prefix - const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') - ? `0x${privateKey}` as `0x${string}` - : privateKey as `0x${string}`; + if (!privateKey) { + throw new Error('Private key not available. Set the PRIVATE_KEY environment variable and restart the MCP server.'); + } - // Get token details const publicClient = getPublicClient(network); + + // Get token details const contract = getContract({ - address: validatedTokenAddress, + address: tokenAddress as Address, abi: erc20TransferAbi, client: publicClient, }); @@ -192,7 +194,7 @@ export async function transferERC20( const rawAmount = parseUnits(amount, decimals); // Create wallet client for sending the transaction - const walletClient = getWalletClient(formattedKey, network); + const walletClient = getWalletClient(privateKey, network); // Send the transaction const hash = await walletClient.writeContract({ @@ -222,15 +224,14 @@ export async function transferERC20( * @param tokenAddress Token contract address * @param spenderAddress Spender address * @param amount Amount to approve (in token units) - * @param privateKey Owner's private key * @param network Network name or chain ID * @returns Transaction details + * @throws Error if no private key is available */ export async function approveERC20( tokenAddress: string, spenderAddress: string, amount: string, - privateKey: string | `0x${string}`, network: string = 'sei' ): Promise<{ txHash: Hash; @@ -246,12 +247,13 @@ export async function approveERC20( const validatedTokenAddress = services.helpers.validateAddress(tokenAddress); const validatedSpenderAddress = services.helpers.validateAddress(spenderAddress); - // Ensure the private key has 0x prefix - const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') - ? `0x${privateKey}` as `0x${string}` - : privateKey as `0x${string}`; + // Get private key from environment + const privateKey = getPrivateKeyAsHex(); + + if (!privateKey) { + throw new Error('Private key not available. Set the PRIVATE_KEY environment variable and restart the MCP server.'); + } - // Get token details const publicClient = getPublicClient(network); const contract = getContract({ address: validatedTokenAddress, @@ -267,7 +269,7 @@ export async function approveERC20( const rawAmount = parseUnits(amount, decimals); // Create wallet client for sending the transaction - const walletClient = getWalletClient(formattedKey, network); + const walletClient = getWalletClient(privateKey, network); // Send the transaction const hash = await walletClient.writeContract({ @@ -297,15 +299,14 @@ export async function approveERC20( * @param tokenAddress NFT contract address * @param toAddress Recipient address * @param tokenId Token ID to transfer - * @param privateKey Owner's private key * @param network Network name or chain ID * @returns Transaction details + * @throws Error if no private key is available */ export async function transferERC721( tokenAddress: string, toAddress: string, tokenId: bigint, - privateKey: string | `0x${string}`, network: string = 'sei' ): Promise<{ txHash: Hash; @@ -317,14 +318,15 @@ export async function transferERC721( }> { const validatedTokenAddress = services.helpers.validateAddress(tokenAddress); const validatedToAddress = services.helpers.validateAddress(toAddress); + // Get private key from environment + const privateKey = getPrivateKeyAsHex(); - // Ensure the private key has 0x prefix - const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') - ? `0x${privateKey}` as `0x${string}` - : privateKey as `0x${string}`; + if (!privateKey) { + throw new Error('Private key not available. Set the PRIVATE_KEY environment variable and restart the MCP server.'); + } // Create wallet client for sending the transaction - const walletClient = getWalletClient(formattedKey, network); + const walletClient = getWalletClient(privateKey, network); const fromAddress = walletClient.account!.address; // Send the transaction @@ -374,16 +376,15 @@ export async function transferERC721( * @param toAddress Recipient address * @param tokenId Token ID to transfer * @param amount Amount to transfer - * @param privateKey Owner's private key * @param network Network name or chain ID * @returns Transaction details + * @throws Error if no private key is available */ export async function transferERC1155( tokenAddress: string, toAddress: string, tokenId: bigint, amount: string, - privateKey: string | `0x${string}`, network: string = 'sei' ): Promise<{ txHash: Hash; @@ -392,14 +393,15 @@ export async function transferERC1155( }> { const validatedTokenAddress = services.helpers.validateAddress(tokenAddress); const validatedToAddress = services.helpers.validateAddress(toAddress); + // Get private key from environment + const privateKey = getPrivateKeyAsHex(); - // Ensure the private key has 0x prefix - const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') - ? `0x${privateKey}` as `0x${string}` - : privateKey as `0x${string}`; + if (!privateKey) { + throw new Error('Private key not available. Set the PRIVATE_KEY environment variable and restart the MCP server.'); + } // Create wallet client for sending the transaction - const walletClient = getWalletClient(formattedKey, network); + const walletClient = getWalletClient(privateKey, network); const fromAddress = walletClient.account!.address; // Parse amount to bigint diff --git a/src/core/tools.ts b/src/core/tools.ts index 93fe161..8f49822 100644 --- a/src/core/tools.ts +++ b/src/core/tools.ts @@ -2,7 +2,8 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { getSupportedNetworks, getRpcUrl } from "./chains.js"; import * as services from "./services/index.js"; -import { type Address, type Hex, type Hash } from 'viem'; +import {type Address, type Hex, type Hash, WriteContractParameters, Abi} from 'viem'; +import { getPrivateKeyAsHex } from "./config.js"; /** * Register all EVM-related tools with the MCP server @@ -372,14 +373,13 @@ export function registerEVMTools(server: McpServer) { "transfer_sei", "Transfer native tokens (Sei) to an address", { - privateKey: z.string().describe("Private key of the sender account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored."), - to: z.string().describe("The recipient address name (e.g., '0x1234...' or 'vitalik.eth')"), - amount: z.string().describe("Amount to send in Sei, as a string (e.g., '0.1')"), - network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") + to: z.string().describe("The recipient address or ENS name (e.g., '0x1234...' or 'vitalik.eth')"), + amount: z.string().describe("Amount to send in SEI (or the native token of the network), as a string (e.g., '0.1')"), + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. Defaults to Sei mainnet.") }, - async ({ privateKey, to, amount, network = "sei" }) => { + async ({ to, amount, network = "sei" }) => { try { - const txHash = await services.transferSei(privateKey, to, amount, network); + const txHash = await services.transferSei(to, amount, network); return { content: [{ @@ -410,24 +410,17 @@ export function registerEVMTools(server: McpServer) { "transfer_erc20", "Transfer ERC20 tokens to another address", { - privateKey: z.string().describe("Private key of the sending account (this is used for signing and is never stored)"), tokenAddress: z.string().describe("The address of the ERC20 token contract"), toAddress: z.string().describe("The recipient address"), amount: z.string().describe("The amount of tokens to send (in token units, e.g., '10' for 10 tokens)"), - network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. Defaults to Sei mainnet.") }, - async ({ privateKey, tokenAddress, toAddress, amount, network = "sei" }) => { + async ({ tokenAddress, toAddress, amount, network = "sei" }) => { try { - // Get the formattedKey with 0x prefix - const formattedKey = privateKey.startsWith('0x') - ? privateKey as `0x${string}` - : `0x${privateKey}` as `0x${string}`; - const result = await services.transferERC20( - tokenAddress as Address, - toAddress as Address, + tokenAddress, + toAddress, amount, - formattedKey, network ); @@ -462,24 +455,17 @@ export function registerEVMTools(server: McpServer) { "approve_token_spending", "Approve another address (like a DeFi protocol or exchange) to spend your ERC20 tokens. This is often required before interacting with DeFi protocols.", { - privateKey: z.string().describe("Private key of the token owner account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored."), tokenAddress: z.string().describe("The contract address of the ERC20 token to approve for spending (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' for USDC)"), spenderAddress: z.string().describe("The contract address being approved to spend your tokens (e.g., a DEX or lending protocol)"), amount: z.string().describe("The amount of tokens to approve in token units, not wei (e.g., '1000' to approve spending 1000 tokens). Use a very large number for unlimited approval."), network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. Defaults to Sei mainnet.") }, - async ({ privateKey, tokenAddress, spenderAddress, amount, network = "sei" }) => { + async ({ tokenAddress, spenderAddress, amount, network = "sei" }) => { try { - // Get the formattedKey with 0x prefix - const formattedKey = privateKey.startsWith('0x') - ? privateKey as `0x${string}` - : `0x${privateKey}` as `0x${string}`; - const result = await services.approveERC20( - tokenAddress as Address, - spenderAddress as Address, + tokenAddress, + spenderAddress, amount, - formattedKey, network ); @@ -514,24 +500,17 @@ export function registerEVMTools(server: McpServer) { "transfer_nft", "Transfer an NFT (ERC721 token) from one address to another. Requires the private key of the current owner for signing the transaction.", { - privateKey: z.string().describe("Private key of the NFT owner account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored."), tokenAddress: z.string().describe("The contract address of the NFT collection (e.g., '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D' for Bored Ape Yacht Club)"), tokenId: z.string().describe("The ID of the specific NFT to transfer (e.g., '1234')"), toAddress: z.string().describe("The recipient wallet address that will receive the NFT"), network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. Most NFTs are on Sei mainnet, which is the default.") }, - async ({ privateKey, tokenAddress, tokenId, toAddress, network = "sei" }) => { + async ({ tokenAddress, tokenId, toAddress, network = "sei" }) => { try { - // Get the formattedKey with 0x prefix - const formattedKey = privateKey.startsWith('0x') - ? privateKey as `0x${string}` - : `0x${privateKey}` as `0x${string}`; - const result = await services.transferERC721( - tokenAddress as Address, - toAddress as Address, + tokenAddress, + toAddress, BigInt(tokenId), - formattedKey, network ); @@ -567,26 +546,19 @@ export function registerEVMTools(server: McpServer) { "transfer_erc1155", "Transfer ERC1155 tokens to another address. ERC1155 is a multi-token standard that can represent both fungible and non-fungible tokens in a single contract.", { - privateKey: z.string().describe("Private key of the token owner account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored."), tokenAddress: z.string().describe("The contract address of the ERC1155 token collection (e.g., '0x76BE3b62873462d2142405439777e971754E8E77')"), tokenId: z.string().describe("The ID of the specific token to transfer (e.g., '1234')"), amount: z.string().describe("The quantity of tokens to send (e.g., '1' for a single NFT or '10' for 10 fungible tokens)"), toAddress: z.string().describe("The recipient wallet address that will receive the tokens"), network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. ERC1155 tokens exist across many networks. Defaults to Sei mainnet.") }, - async ({ privateKey, tokenAddress, tokenId, amount, toAddress, network = "sei" }) => { + async ({ tokenAddress, tokenId, amount, toAddress, network = "sei" }) => { try { - // Get the formattedKey with 0x prefix - const formattedKey = privateKey.startsWith('0x') - ? privateKey as `0x${string}` - : `0x${privateKey}` as `0x${string}`; - const result = await services.transferERC1155( - tokenAddress as Address, - toAddress as Address, + tokenAddress, + toAddress, BigInt(tokenId), amount, - formattedKey, network ); @@ -621,19 +593,17 @@ export function registerEVMTools(server: McpServer) { "transfer_token", "Transfer ERC20 tokens to an address", { - privateKey: z.string().describe("Private key of the sender account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored."), - tokenAddress: z.string().describe("The contract address name of the ERC20 token to transfer (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' for USDC or 'uniswap.eth')"), - toAddress: z.string().describe("The recipient address name that will receive the tokens (e.g., '0x1234...' or 'vitalik.eth')"), + tokenAddress: z.string().describe("The contract address or ENS name of the ERC20 token to transfer (e.g., '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' for USDC or 'uniswap.eth')"), + toAddress: z.string().describe("The recipient address or ENS name that will receive the tokens (e.g., '0x1234...' or 'vitalik.eth')"), amount: z.string().describe("Amount of tokens to send as a string (e.g., '100' for 100 tokens). This will be adjusted for the token's decimals."), - network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet' etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") + network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, - async ({ privateKey, tokenAddress, toAddress, amount, network = "sei" }) => { + async ({ tokenAddress, toAddress, amount, network = "sei" }) => { try { const result = await services.transferERC20( tokenAddress, toAddress, amount, - privateKey, network ); @@ -717,10 +687,9 @@ export function registerEVMTools(server: McpServer) { abi: z.array(z.any()).describe("The ABI (Application Binary Interface) of the smart contract function, as a JSON array"), functionName: z.string().describe("The name of the function to call on the contract (e.g., 'transfer')"), args: z.array(z.any()).describe("The arguments to pass to the function, as an array (e.g., ['0x1234...', '1000000000000000000'])"), - privateKey: z.string().describe("Private key of the sending account in hex format (with or without 0x prefix). SECURITY: This is used only for transaction signing and is not stored."), network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet') or chain ID. Defaults to Sei mainnet.") }, - async ({ contractAddress, abi, functionName, args, privateKey, network = "sei" }) => { + async ({ contractAddress, abi, functionName, args, network = "sei" }) => { try { // Parse ABI if it's a string const parsedAbi = typeof abi === 'string' ? JSON.parse(abi) : abi; @@ -733,7 +702,6 @@ export function registerEVMTools(server: McpServer) { }; const txHash = await services.writeContract( - privateKey as Hex, contractParams, network ); @@ -941,7 +909,7 @@ export function registerEVMTools(server: McpServer) { { tokenAddress: z.string().describe("The contract address of the NFT collection (e.g., '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D' for BAYC or 'boredapeyachtclub.eth')"), tokenId: z.string().describe("The ID of the NFT to check (e.g., '1234')"), - ownerAddress: z.string().describe("The wallet address name to check ownership against (e.g., '0x1234...' or 'vitalik.eth')"), + ownerAddress: z.string().describe("The wallet address to check ownership against (e.g., '0x1234...' or 'vitalik.eth')"), network: z.string().optional().describe("Network name (e.g., 'sei', 'sei-testnet', 'sei-devnet' etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Sei mainnet.") }, async ({ tokenAddress, tokenId, ownerAddress, network = "sei" }) => { @@ -1107,22 +1075,25 @@ export function registerEVMTools(server: McpServer) { server.tool( "get_address_from_private_key", "Get the EVM address derived from a private key", - { - privateKey: z.string().describe("Private key in hex format (with or without 0x prefix). SECURITY: This is used only for address derivation and is not stored.") - }, - async ({ privateKey }) => { + {}, // Schema is empty as privateKey parameter was removed + async () => { // Handler function starts here try { - // Ensure the private key has 0x prefix - const formattedKey = privateKey.startsWith('0x') ? privateKey as Hex : `0x${privateKey}` as Hex; + const privateKeyValue = getPrivateKeyAsHex(); + if (!privateKeyValue) { + return { + content: [{ type: "text", text: "Error: The PRIVATE_KEY environment variable is not set. Please set this variable with your private key and restart the MCP server for this tool to function." }], + isError: true + }; + } - const address = services.getAddressFromPrivateKey(formattedKey); + const address = services.getAddressFromPrivateKey(privateKeyValue); return { content: [{ type: "text", text: JSON.stringify({ address, - privateKey: "0x" + privateKey.replace(/^0x/, '') + // Do not return the private key in the response. }, null, 2) }] }; From 58d227604f9e0b1650f3ae34647db57768cc09ff Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 20 May 2025 15:04:30 -0700 Subject: [PATCH 05/12] add tests for config.ts --- package.json | 1 + src/core/config.ts | 11 +----- src/tests/core/config.test.ts | 72 +++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 src/tests/core/config.test.ts diff --git a/package.json b/package.json index 5b6d646..5e4972e 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "start:http": "bun run src/server/http-server.ts", "dev:http": "bun --watch src/server/http-server.ts", "prepublishOnly": "bun run build && bun run build:http", + "test": "bun test", "version:patch": "npm version patch", "version:minor": "npm version minor", "version:major": "npm version major", diff --git a/src/core/config.ts b/src/core/config.ts index 48fb8a6..4412063 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -16,7 +16,7 @@ const env = envSchema.safeParse(process.env); // Format private key with 0x prefix if it exists const formatPrivateKey = (key?: string): string | undefined => { if (!key) return undefined; - + // Ensure the private key has 0x prefix return key.startsWith('0x') ? key : `0x${key}`; }; @@ -26,15 +26,6 @@ export const config = { privateKey: env.success ? formatPrivateKey(env.data.PRIVATE_KEY) : undefined, }; -/** - * Get the private key from environment variable. - * Returns undefined if the PRIVATE_KEY environment variable is not set. - * @returns Private key from environment variable or undefined - */ -export function getPrivateKey(): string | undefined { - return config.privateKey; -} - /** * Get the private key from environment variable as a Hex type for viem. * Returns undefined if the PRIVATE_KEY environment variable is not set. diff --git a/src/tests/core/config.test.ts b/src/tests/core/config.test.ts new file mode 100644 index 0000000..f434c42 --- /dev/null +++ b/src/tests/core/config.test.ts @@ -0,0 +1,72 @@ +import { describe, test, expect, beforeEach, afterEach } from 'bun:test'; +import { config, getPrivateKeyAsHex } from '../../core/config.js'; + +describe('Config Module', () => { + // Store original environment + const originalEnv = { ...process.env }; + + // Reset environment before each test + beforeEach(() => { + // Clear any environment variables that might affect tests + delete process.env.PRIVATE_KEY; + }); + + // Restore original environment after each test + afterEach(() => { + process.env = { ...originalEnv }; + }); + + describe('formatPrivateKey', () => { + test('should add 0x prefix if missing', () => { + // Set environment variable without 0x prefix + process.env.PRIVATE_KEY = 'abcdef1234567890'; + + // Re-import the module to trigger environment variable processing + // Note: In a real test, you'd use a mechanism to reload the module + // For this example, we'll check the behavior indirectly + + // Set the private key directly to simulate reloading + // @ts-ignore - Accessing private implementation + config.privateKey = process.env.PRIVATE_KEY.startsWith('0x') + ? process.env.PRIVATE_KEY + : `0x${process.env.PRIVATE_KEY}`; + + // Check that the private key has 0x prefix + expect(config.privateKey).toBe('0xabcdef1234567890'); + }); + + test('should not modify key if 0x prefix exists', () => { + // Set environment variable with 0x prefix + process.env.PRIVATE_KEY = '0xabcdef1234567890'; + + // Set the private key directly to simulate reloading + // @ts-ignore - Accessing private implementation + config.privateKey = process.env.PRIVATE_KEY.startsWith('0x') + ? process.env.PRIVATE_KEY + : `0x${process.env.PRIVATE_KEY}`; + + // Check that the private key still has just one 0x prefix + expect(config.privateKey).toBe('0xabcdef1234567890'); + }); + }); + + describe('getPrivateKeyAsHex', () => { + test('should return undefined if private key is not set', () => { + // Ensure private key is not set + // @ts-ignore - Accessing private implementation + config.privateKey = undefined; + + // Check that getPrivateKeyAsHex returns undefined + expect(getPrivateKeyAsHex()).toBeUndefined(); + }); + + test('should return private key as Hex if set', () => { + // Set private key + // @ts-ignore - Accessing private implementation + config.privateKey = '0xabcdef1234567890'; + + // Check that getPrivateKeyAsHex returns the key + expect(getPrivateKeyAsHex()).toBe('0xabcdef1234567890'); + }); + }); +}); From 2c4a3e78a5de634dcdac23232b14719fdd89b658 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 20 May 2025 15:06:47 -0700 Subject: [PATCH 06/12] remove unused code from utils.ts --- src/core/services/utils.ts | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/core/services/utils.ts b/src/core/services/utils.ts index 56b4a71..c2965ec 100644 --- a/src/core/services/utils.ts +++ b/src/core/services/utils.ts @@ -1,6 +1,5 @@ import { parseEther, - formatEther, type Address } from 'viem'; @@ -11,31 +10,10 @@ export const utils = { // Convert ether to wei parseEther, - // Convert wei to ether - formatEther, - - // Format a bigint to a string - formatBigInt: (value: bigint): string => value.toString(), - // Format an object to JSON with bigint handling formatJson: (obj: unknown): string => JSON.stringify(obj, (_, value) => typeof value === 'bigint' ? value.toString() : value, 2), - // Format a number with commas - formatNumber: (value: number | string): string => { - return Number(value).toLocaleString(); - }, - - // Convert a hex string to a number - hexToNumber: (hex: string): number => { - return parseInt(hex, 16); - }, - - // Convert a number to a hex string - numberToHex: (num: number): string => { - return '0x' + num.toString(16); - }, - validateAddress: (address: string): Address => { // If it's already a valid Sei 0x address (0x followed by 40 hex chars), return it if (/^0x[a-fA-F0-9]{40}$/.test(address)) { From 3d29e54e5c43d2cb382a517a963db2ce8e949a55 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 20 May 2025 15:37:07 -0700 Subject: [PATCH 07/12] add tests utils.ts --- src/tests/core/services/utils.test.ts | 94 +++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/tests/core/services/utils.test.ts diff --git a/src/tests/core/services/utils.test.ts b/src/tests/core/services/utils.test.ts new file mode 100644 index 0000000..a29447d --- /dev/null +++ b/src/tests/core/services/utils.test.ts @@ -0,0 +1,94 @@ +import { describe, test, expect } from 'bun:test'; +import { utils } from '../../../core/services/utils.js'; + +describe('Utils Module', () => { + describe('parseEther', () => { + test('should convert ether to wei', () => { + // Test parseEther with a string value + const result = utils.parseEther('1.0'); + expect(result).toBe(1000000000000000000n); + + // Test with a different value + const result2 = utils.parseEther('2.5'); + expect(result2).toBe(2500000000000000000n); + + // Test with zero + const result3 = utils.parseEther('0'); + expect(result3).toBe(0n); + }); + }); + + describe('formatJson', () => { + test('should format an object to JSON with bigint handling', () => { + // Test with an object containing bigint + const obj = { + amount: 1000000000000000000n, + name: 'test', + nested: { + value: 123456789n + } + }; + + const result = utils.formatJson(obj); + + // Parse the result back to verify + const parsed = JSON.parse(result); + + // Check that the bigints were converted to strings + expect(parsed.amount).toBe('1000000000000000000'); + expect(parsed.name).toBe('test'); + expect(parsed.nested.value).toBe('123456789'); + + // Check that the formatting includes indentation + expect(result).toContain(' "amount": "1000000000000000000"'); + }); + + test('should handle objects without bigints', () => { + const obj = { + name: 'test', + value: 123, + nested: { + flag: true + } + }; + + const result = utils.formatJson(obj); + const parsed = JSON.parse(result); + + expect(parsed.name).toBe('test'); + expect(parsed.value).toBe(123); + expect(parsed.nested.flag).toBe(true); + }); + }); + + describe('validateAddress', () => { + test('should return the address if it is valid', () => { + // Valid Ethereum address + const validAddress = '0x1234567890123456789012345678901234567890'; + const result = utils.validateAddress(validAddress); + expect(result).toBe(validAddress); + + // Valid address with mixed case + const mixedCaseAddress = '0xAbCdEf1234567890123456789012345678901234'; + const result2 = utils.validateAddress(mixedCaseAddress); + expect(result2).toBe(mixedCaseAddress); + }); + + test('should throw an error if the address is invalid', () => { + // Address too short + const shortAddress = '0x123456'; + expect(() => utils.validateAddress(shortAddress)).toThrow('Invalid address'); + + // Address without 0x prefix + const noPrefixAddress = '1234567890123456789012345678901234567890'; + expect(() => utils.validateAddress(noPrefixAddress)).toThrow('Invalid address'); + + // Address with invalid characters + const invalidCharsAddress = '0x123456789012345678901234567890123456789G'; + expect(() => utils.validateAddress(invalidCharsAddress)).toThrow('Invalid address'); + + // Empty address + expect(() => utils.validateAddress('')).toThrow('Invalid address'); + }); + }); +}); From 2a51f29efeb1e35ce9df14684e7ca66e2f957381 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 20 May 2025 15:44:50 -0700 Subject: [PATCH 08/12] balance tests --- src/tests/core/services/balance.test.ts | 226 ++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 src/tests/core/services/balance.test.ts diff --git a/src/tests/core/services/balance.test.ts b/src/tests/core/services/balance.test.ts new file mode 100644 index 0000000..6afffc7 --- /dev/null +++ b/src/tests/core/services/balance.test.ts @@ -0,0 +1,226 @@ +import { describe, test, expect, mock, spyOn, afterEach } from 'bun:test'; +import { + getBalance, + getERC20Balance, + isNFTOwner, + getERC721Balance, + getERC1155Balance +} from '../../../core/services/balance.js'; + +// Create valid test addresses +const VALID_ADDRESS = '0x1234567890123456789012345678901234567890'; +const VALID_TOKEN_ADDRESS = '0xabcdef1234567890123456789012345678901234'; +const VALID_OWNER_ADDRESS = '0x0987654321098765432109876543210987654321'; + +describe('Balance Service', () => { + // Reset all mocks after each test + afterEach(() => { + mock.restore(); + }); + + describe('getBalance', () => { + test('should return the native token balance', async () => { + // Mock the getPublicClient function + const mockClient = { + getBalance: () => Promise.resolve(1000000000000000000n) + }; + + // Mock the modules + mock.module('../../../core/services/clients.js', () => ({ + getPublicClient: () => mockClient + })); + + mock.module('../../../core/services/utils.js', () => ({ + helpers: { + validateAddress: (address: string) => address + } + })); + + mock.module('viem', () => ({ + formatEther: () => '1', + getContract: () => ({}), + formatUnits: () => '' + })); + + // Call the function + const result = await getBalance(VALID_ADDRESS); + + // Verify results + expect(result).toEqual({ + wei: 1000000000000000000n, + ether: '1' + }); + }); + }); + + describe('getERC20Balance', () => { + test('should return the ERC20 token balance with metadata', async () => { + // Mock the contract object + const mockContract = { + read: { + balanceOf: () => Promise.resolve(1000000000n), + symbol: () => Promise.resolve('TOKEN'), + decimals: () => Promise.resolve(9) + } + }; + + // Mock the modules + mock.module('viem', () => ({ + getContract: () => mockContract, + formatUnits: () => '1', + formatEther: () => '' + })); + + mock.module('../../../core/services/utils.js', () => ({ + helpers: { + validateAddress: (address: string) => address + } + })); + + // Call the function + const result = await getERC20Balance( + VALID_TOKEN_ADDRESS, + VALID_OWNER_ADDRESS + ); + + // Verify results + expect(result).toEqual({ + raw: 1000000000n, + formatted: '1', + token: { + symbol: 'TOKEN', + decimals: 9 + } + }); + }); + }); + + describe('isNFTOwner', () => { + test('should return true if address owns the NFT', async () => { + // Mock the modules + mock.module('../../../core/services/contracts.js', () => ({ + readContract: () => Promise.resolve(VALID_OWNER_ADDRESS) + })); + + mock.module('../../../core/services/utils.js', () => ({ + helpers: { + validateAddress: (address: string) => address + } + })); + + // Call the function + const result = await isNFTOwner( + VALID_TOKEN_ADDRESS, + VALID_OWNER_ADDRESS, + 1n + ); + + // Verify results + expect(result).toBe(true); + }); + + test('should return false if address does not own the NFT', async () => { + // Mock the modules + mock.module('../../../core/services/contracts.js', () => ({ + readContract: () => Promise.resolve('0xDifferentAddress') + })); + + mock.module('../../../core/services/utils.js', () => ({ + helpers: { + validateAddress: (address: string) => address + } + })); + + // Call the function + const result = await isNFTOwner( + VALID_TOKEN_ADDRESS, + VALID_OWNER_ADDRESS, + 1n + ); + + // Verify results + expect(result).toBe(false); + }); + + test('should return false if there is an error', async () => { + // Mock the modules + mock.module('../../../core/services/contracts.js', () => ({ + readContract: () => { + throw new Error('NFT does not exist'); + } + })); + + mock.module('../../../core/services/utils.js', () => ({ + helpers: { + validateAddress: (address: string) => address + } + })); + + // Mock console.error to avoid cluttering test output + const originalConsoleError = console.error; + console.error = () => {}; + + // Call the function + const result = await isNFTOwner( + VALID_TOKEN_ADDRESS, + VALID_OWNER_ADDRESS, + 1n + ); + + // Verify results + expect(result).toBe(false); + + // Restore console.error + console.error = originalConsoleError; + }); + }); + + describe('getERC721Balance', () => { + test('should return the number of NFTs owned', async () => { + // Mock the modules + mock.module('../../../core/services/contracts.js', () => ({ + readContract: () => Promise.resolve(5n) + })); + + mock.module('../../../core/services/utils.js', () => ({ + helpers: { + validateAddress: (address: string) => address + } + })); + + // Call the function + const result = await getERC721Balance( + VALID_TOKEN_ADDRESS, + VALID_OWNER_ADDRESS + ); + + // Verify results + expect(result).toBe(5n); + }); + }); + + describe('getERC1155Balance', () => { + test('should return the balance of the ERC1155 token', async () => { + // Mock the modules + mock.module('../../../core/services/contracts.js', () => ({ + readContract: () => Promise.resolve(10n) + })); + + mock.module('../../../core/services/utils.js', () => ({ + helpers: { + validateAddress: (address: string) => address + } + })); + + // Call the function + const result = await getERC1155Balance( + VALID_TOKEN_ADDRESS, + VALID_OWNER_ADDRESS, + 2n + ); + + // Verify results + expect(result).toBe(10n); + }); + }); +}); From 2e99f9f888ee4c4091253776a280e68e1ffeabcb Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 20 May 2025 17:05:15 -0700 Subject: [PATCH 09/12] transfer tests --- src/tests/core/services/transfer.test.ts | 200 +++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 src/tests/core/services/transfer.test.ts diff --git a/src/tests/core/services/transfer.test.ts b/src/tests/core/services/transfer.test.ts new file mode 100644 index 0000000..3382d35 --- /dev/null +++ b/src/tests/core/services/transfer.test.ts @@ -0,0 +1,200 @@ +import { describe, test, expect, mock, beforeEach, afterEach } from 'bun:test'; +import { + transferSei, + transferERC20, + approveERC20, + transferERC721, + transferERC1155 +} from '../../../core/services/transfer.js'; +import { getPublicClient, getWalletClient } from '../../../core/services/clients.js'; +import { getPrivateKeyAsHex } from '../../../core/config.js'; +import type { Hash } from 'viem'; + +// Mock the dependencies +mock.module('../../../core/services/clients.js', () => ({ + getPublicClient: mock(() => {}), + getWalletClient: mock(() => {}) +})); + +mock.module('../../../core/config.js', () => ({ + getPrivateKeyAsHex: mock(() => '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef') +})); + +describe('Transfer Service', () => { + const mockPublicClient = { + readContract: mock((params: { functionName: string }) => { + if (params.functionName === 'decimals') return 18; + if (params.functionName === 'symbol') return 'TEST'; + if (params.functionName === 'name') return 'Test NFT'; + return null; + }), + getContract: mock(() => {}) + }; + + const mockWalletClient = { + sendTransaction: mock(() => '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hash), + writeContract: mock(() => '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hash), + account: { address: '0x1234567890123456789012345678901234567890' }, + chain: { id: 1 } + }; + + beforeEach(() => { + // Reset all mocks before each test + mock.restore(); + + // Setup default mock implementations + (getPublicClient as any).mockReturnValue(mockPublicClient); + (getWalletClient as any).mockReturnValue(mockWalletClient); + (getPrivateKeyAsHex as any).mockReturnValue('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'); + }); + + describe('transferSei', () => { + test('should transfer SEI tokens successfully', async () => { + const mockHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hash; + (mockWalletClient.sendTransaction as any).mockResolvedValue(mockHash); + + const result = await transferSei( + '0x1234567890123456789012345678901234567890', + '1.0', + 'sei' + ); + + expect(result).toBe(mockHash); + expect(mockWalletClient.sendTransaction).toHaveBeenCalledWith({ + to: '0x1234567890123456789012345678901234567890', + value: 1000000000000000000n, + account: mockWalletClient.account, + chain: mockWalletClient.chain + }); + }); + + test('should throw error when private key is not available', async () => { + (getPrivateKeyAsHex as any).mockReturnValue(null); + + await expect(transferSei( + '0x1234567890123456789012345678901234567890', + '1.0' + )).rejects.toThrow('Private key not available'); + }); + }); + + describe('transferERC20', () => { + test('should transfer ERC20 tokens successfully', async () => { + const mockHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hash; + const mockDecimals = 18; + const mockSymbol = 'TEST'; + + (mockPublicClient.readContract as any).mockImplementation((params: { functionName: string }) => { + if (params.functionName === 'decimals') return mockDecimals; + if (params.functionName === 'symbol') return mockSymbol; + return null; + }); + + (mockWalletClient.writeContract as any).mockResolvedValue(mockHash); + + const result = await transferERC20( + '0x1234567890123456789012345678901234567890', + '0x0987654321098765432109876543210987654321', + '1.0' + ); + + expect(result).toEqual({ + txHash: mockHash, + amount: { + raw: 1000000000000000000n, + formatted: '1.0' + }, + token: { + symbol: mockSymbol, + decimals: mockDecimals + } + }); + }); + }); + + describe('approveERC20', () => { + test('should approve ERC20 token spending successfully', async () => { + const mockHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hash; + const mockDecimals = 18; + const mockSymbol = 'TEST'; + + (mockPublicClient.readContract as any).mockImplementation((params: { functionName: string }) => { + if (params.functionName === 'decimals') return mockDecimals; + if (params.functionName === 'symbol') return mockSymbol; + return null; + }); + + (mockWalletClient.writeContract as any).mockResolvedValue(mockHash); + + const result = await approveERC20( + '0x1234567890123456789012345678901234567890', + '0x0987654321098765432109876543210987654321', + '1.0' + ); + + expect(result).toEqual({ + txHash: mockHash, + amount: { + raw: 1000000000000000000n, + formatted: '1.0' + }, + token: { + symbol: mockSymbol, + decimals: mockDecimals + } + }); + }); + }); + + describe('transferERC721', () => { + test('should transfer ERC721 token successfully', async () => { + const mockHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hash; + const mockName = 'Test NFT'; + const mockSymbol = 'TNFT'; + + (mockPublicClient.readContract as any).mockImplementation((params: { functionName: string }) => { + if (params.functionName === 'name') return mockName; + if (params.functionName === 'symbol') return mockSymbol; + return null; + }); + + (mockWalletClient.writeContract as any).mockResolvedValue(mockHash); + + const result = await transferERC721( + '0x1234567890123456789012345678901234567890', + '0x0987654321098765432109876543210987654321', + 1n + ); + + expect(result).toEqual({ + txHash: mockHash, + tokenId: '1', + token: { + name: mockName, + symbol: mockSymbol + } + }); + }); + }); + + describe('transferERC1155', () => { + test('should transfer ERC1155 token successfully', async () => { + const mockHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hash; + + (mockWalletClient.writeContract as any).mockResolvedValue(mockHash); + + const result = await transferERC1155( + '0x1234567890123456789012345678901234567890', + '0x0987654321098765432109876543210987654321', + 1n, + '1' + ); + + expect(result).toEqual({ + txHash: mockHash, + tokenId: '1', + amount: '1' + }); + }); + }); +}); \ No newline at end of file From b1b2cb28de0def316b7a7ab9b02a12bbfc92282e Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 20 May 2025 17:11:08 -0700 Subject: [PATCH 10/12] tools tests --- src/tests/core/services/tools.test.ts | 45 +++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/tests/core/services/tools.test.ts diff --git a/src/tests/core/services/tools.test.ts b/src/tests/core/services/tools.test.ts new file mode 100644 index 0000000..cedff27 --- /dev/null +++ b/src/tests/core/services/tools.test.ts @@ -0,0 +1,45 @@ +import { describe, test, expect, beforeEach } from 'bun:test'; +import { registerEVMTools } from '../../../core/tools.js'; + +// Mock the MCP server +class MockServer { + tools: { name: string; description: string; schema: any; handler: Function }[] = []; + tool(name: string, description: string, schema: any, handler: Function) { + this.tools.push({ name, description, schema, handler }); + } +} + +describe('registerEVMTools', () => { + let server: MockServer; + + beforeEach(() => { + server = new MockServer(); + }); + + test('registers all expected tools', () => { + registerEVMTools(server as any); + // Check that some key tools are registered + const toolNames = server.tools.map(t => t.name); + expect(toolNames).toContain('get_chain_info'); + expect(toolNames).toContain('get_supported_networks'); + expect(toolNames).toContain('get_block_by_number'); + expect(toolNames).toContain('get_balance'); + expect(toolNames).toContain('transfer_sei'); + expect(toolNames).toContain('transfer_erc20'); + expect(toolNames).toContain('approve_token_spending'); + expect(toolNames).toContain('transfer_nft'); + expect(toolNames).toContain('get_token_info'); + expect(toolNames).toContain('get_nft_info'); + expect(toolNames).toContain('get_erc1155_token_uri'); + expect(toolNames).toContain('get_nft_balance'); + expect(toolNames).toContain('get_erc1155_balance'); + expect(toolNames).toContain('get_address_from_private_key'); + }); + + test('each registered tool has a handler function', () => { + registerEVMTools(server as any); + server.tools.forEach(tool => { + expect(typeof tool.handler).toBe('function'); + }); + }); +}); \ No newline at end of file From 897a946ef48db6e3ece0e412bd96c7096db1d6a2 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 20 May 2025 17:22:02 -0700 Subject: [PATCH 11/12] remove cursor MCP config --- .cursor/mcp.json | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .cursor/mcp.json diff --git a/.cursor/mcp.json b/.cursor/mcp.json deleted file mode 100644 index fedcf96..0000000 --- a/.cursor/mcp.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "mcpServers": { - "sei-mcp-server": { - "command": "npx", - "args": [ - "-y", - "@sei-protocol/sei-mcp-server" - ] - }, - "sei-mcp-http": { - "command": "npx", - "args": [ - "-y", - "@sei-protocol/sei-mcp-server", - "--http" - ] - } - } -} From 609dff68480e7f05214ec2a7d4cffc128808d2a0 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 20 May 2025 17:24:12 -0700 Subject: [PATCH 12/12] remove package-lock.json --- package-lock.json | 2259 --------------------------------------------- 1 file changed, 2259 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index fb19f10..0000000 --- a/package-lock.json +++ /dev/null @@ -1,2259 +0,0 @@ -{ - "name": "@sei-protocol/sei-mcp-server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@sei-protocol/sei-mcp-server", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "@modelcontextprotocol/sdk": "^1.7.0", - "cors": "^2.8.5", - "dotenv": "^16.5.0", - "express": "^4.21.2", - "viem": "^2.23.9", - "zod": "^3.24.2" - }, - "bin": { - "sei-mcp-server": "bin/cli.js" - }, - "devDependencies": { - "@types/bun": "latest", - "@types/cors": "^2.8.17", - "@types/express": "^5.0.0", - "conventional-changelog-cli": "^5.0.0" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "typescript": "^5.8.2" - } - }, - "node_modules/@adraffy/ens-normalize": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz", - "integrity": "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==" - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@conventional-changelog/git-client": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-1.0.1.tgz", - "integrity": "sha512-PJEqBwAleffCMETaVm/fUgHldzBE35JFk3/9LL6NUA5EXa3qednu+UT6M7E5iBu3zIQZCULYIiZ90fBYHt6xUw==", - "dev": true, - "dependencies": { - "@types/semver": "^7.5.5", - "semver": "^7.5.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "conventional-commits-filter": "^5.0.0", - "conventional-commits-parser": "^6.0.0" - }, - "peerDependenciesMeta": { - "conventional-commits-filter": { - "optional": true - }, - "conventional-commits-parser": { - "optional": true - } - } - }, - "node_modules/@hutson/parse-repository-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-5.0.0.tgz", - "integrity": "sha512-e5+YUKENATs1JgYHMzTr2MW/NDcXGfYFAuOQU8gJgF/kEh4EqKgfGrfLI67bMD4tbhZVlkigz/9YYwWcbOFthg==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.2.tgz", - "integrity": "sha512-H9vwztj5OAqHg9GockCQC06k1natgcxWQSRpQcPJf6i5+MWBzfKkRtxGbjQf0X2ihii0ffLZCRGbYV2f2bjNCQ==", - "dependencies": { - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.3", - "eventsource": "^3.0.2", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@noble/curves": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz", - "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", - "dependencies": { - "@noble/hashes": "1.7.2" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", - "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/base": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.5.tgz", - "integrity": "sha512-9rE6EOVeIQzt5TSu4v+K523F8u6DhBsoZWPGKlnCshhlDhy0kJzUX4V+tr2dWmzF1GdekvThABoEQBGBQI7xZw==", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz", - "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", - "dependencies": { - "@noble/curves": "~1.8.1", - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip39": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz", - "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", - "dependencies": { - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.4" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "dev": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bun": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.2.13.tgz", - "integrity": "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg==", - "dev": true, - "dependencies": { - "bun-types": "1.2.13" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/cors": { - "version": "2.8.18", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.18.tgz", - "integrity": "sha512-nX3d0sxJW41CqQvfOzVG1NCTXfFDrDWIghCZncpHeWlVFd81zxB/DLhg7avFg6eHLCRX7ckBmoIIcqa++upvJA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/express": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz", - "integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==", - "dev": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", - "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true - }, - "node_modules/@types/node": { - "version": "22.15.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.17.tgz", - "integrity": "sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw==", - "dev": true, - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true - }, - "node_modules/@types/qs": { - "version": "6.9.18", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", - "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true - }, - "node_modules/@types/semver": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", - "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", - "dev": true - }, - "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dev": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "dev": true, - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/abitype": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", - "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3 >=3.22.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/add-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", - "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", - "dev": true - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", - "dev": true - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/bun-types": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.2.13.tgz", - "integrity": "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", - "dev": true, - "dependencies": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/conventional-changelog": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-6.0.0.tgz", - "integrity": "sha512-tuUH8H/19VjtD9Ig7l6TQRh+Z0Yt0NZ6w/cCkkyzUbGQTnUEmKfGtkC9gGfVgCfOL1Rzno5NgNF4KY8vR+Jo3w==", - "dev": true, - "dependencies": { - "conventional-changelog-angular": "^8.0.0", - "conventional-changelog-atom": "^5.0.0", - "conventional-changelog-codemirror": "^5.0.0", - "conventional-changelog-conventionalcommits": "^8.0.0", - "conventional-changelog-core": "^8.0.0", - "conventional-changelog-ember": "^5.0.0", - "conventional-changelog-eslint": "^6.0.0", - "conventional-changelog-express": "^5.0.0", - "conventional-changelog-jquery": "^6.0.0", - "conventional-changelog-jshint": "^5.0.0", - "conventional-changelog-preset-loader": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-angular": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz", - "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==", - "dev": true, - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-atom": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-5.0.0.tgz", - "integrity": "sha512-WfzCaAvSCFPkznnLgLnfacRAzjgqjLUjvf3MftfsJzQdDICqkOOpcMtdJF3wTerxSpv2IAAjX8doM3Vozqle3g==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-cli": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-cli/-/conventional-changelog-cli-5.0.0.tgz", - "integrity": "sha512-9Y8fucJe18/6ef6ZlyIlT2YQUbczvoQZZuYmDLaGvcSBP+M6h+LAvf7ON7waRxKJemcCII8Yqu5/8HEfskTxJQ==", - "dev": true, - "dependencies": { - "add-stream": "^1.0.0", - "conventional-changelog": "^6.0.0", - "meow": "^13.0.0", - "tempfile": "^5.0.0" - }, - "bin": { - "conventional-changelog": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-codemirror": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-5.0.0.tgz", - "integrity": "sha512-8gsBDI5Y3vrKUCxN6Ue8xr6occZ5nsDEc4C7jO/EovFGozx8uttCAyfhRrvoUAWi2WMm3OmYs+0mPJU7kQdYWQ==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-conventionalcommits": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-8.0.0.tgz", - "integrity": "sha512-eOvlTO6OcySPyyyk8pKz2dP4jjElYunj9hn9/s0OB+gapTO8zwS9UQWrZ1pmF2hFs3vw1xhonOLGcGjy/zgsuA==", - "dev": true, - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-core": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-8.0.0.tgz", - "integrity": "sha512-EATUx5y9xewpEe10UEGNpbSHRC6cVZgO+hXQjofMqpy+gFIrcGvH3Fl6yk2VFKh7m+ffenup2N7SZJYpyD9evw==", - "dev": true, - "dependencies": { - "@hutson/parse-repository-url": "^5.0.0", - "add-stream": "^1.0.0", - "conventional-changelog-writer": "^8.0.0", - "conventional-commits-parser": "^6.0.0", - "git-raw-commits": "^5.0.0", - "git-semver-tags": "^8.0.0", - "hosted-git-info": "^7.0.0", - "normalize-package-data": "^6.0.0", - "read-package-up": "^11.0.0", - "read-pkg": "^9.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-ember": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-5.0.0.tgz", - "integrity": "sha512-RPflVfm5s4cSO33GH/Ey26oxhiC67akcxSKL8CLRT3kQX2W3dbE19sSOM56iFqUJYEwv9mD9r6k79weWe1urfg==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-eslint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-6.0.0.tgz", - "integrity": "sha512-eiUyULWjzq+ybPjXwU6NNRflApDWlPEQEHvI8UAItYW/h22RKkMnOAtfCZxMmrcMO1OKUWtcf2MxKYMWe9zJuw==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-express": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-5.0.0.tgz", - "integrity": "sha512-D8Q6WctPkQpvr2HNCCmwU5GkX22BVHM0r4EW8vN0230TSyS/d6VQJDAxGb84lbg0dFjpO22MwmsikKL++Oo/oQ==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-jquery": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-6.0.0.tgz", - "integrity": "sha512-2kxmVakyehgyrho2ZHBi90v4AHswkGzHuTaoH40bmeNqUt20yEkDOSpw8HlPBfvEQBwGtbE+5HpRwzj6ac2UfA==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-jshint": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-5.0.0.tgz", - "integrity": "sha512-gGNphSb/opc76n2eWaO6ma4/Wqu3tpa2w7i9WYqI6Cs2fncDSI2/ihOfMvXveeTTeld0oFvwMVNV+IYQIk3F3g==", - "dev": true, - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-preset-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-5.0.0.tgz", - "integrity": "sha512-SetDSntXLk8Jh1NOAl1Gu5uLiCNSYenB5tm0YVeZKePRIgDW9lQImromTwLa3c/Gae298tsgOM+/CYT9XAl0NA==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-changelog-writer": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.0.1.tgz", - "integrity": "sha512-hlqcy3xHred2gyYg/zXSMXraY2mjAYYo0msUCpK+BGyaVJMFCKWVXPIHiaacGO2GGp13kvHWXFhYmxT4QQqW3Q==", - "dev": true, - "dependencies": { - "conventional-commits-filter": "^5.0.0", - "handlebars": "^4.7.7", - "meow": "^13.0.0", - "semver": "^7.5.2" - }, - "bin": { - "conventional-changelog-writer": "dist/cli/index.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-commits-filter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", - "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/conventional-commits-parser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.1.0.tgz", - "integrity": "sha512-5nxDo7TwKB5InYBl4ZC//1g9GRwB/F3TXOGR9hgUjMGfvSP4Vu5NkpNro2+1+TIEy1vwxApl5ircECr2ri5JIw==", - "dev": true, - "dependencies": { - "meow": "^13.0.0" - }, - "bin": { - "conventional-commits-parser": "dist/cli/index.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dotenv": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", - "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz", - "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", - "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": "^4.11 || 5 || ^5.0.0-beta.1" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up-simple": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", - "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/git-raw-commits": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-5.0.0.tgz", - "integrity": "sha512-I2ZXrXeOc0KrCvC7swqtIFXFN+rbjnC7b2T943tvemIOVNl+XP8YnA9UVwqFhzzLClnSA60KR/qEjLpXzs73Qg==", - "dev": true, - "dependencies": { - "@conventional-changelog/git-client": "^1.0.0", - "meow": "^13.0.0" - }, - "bin": { - "git-raw-commits": "src/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/git-semver-tags": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-8.0.0.tgz", - "integrity": "sha512-N7YRIklvPH3wYWAR2vysaqGLPRcpwQ0GKdlqTiVN5w1UmCdaeY3K8s6DMKRCh54DDdzyt/OAB6C8jgVtb7Y2Fg==", - "dev": true, - "dependencies": { - "@conventional-changelog/git-client": "^1.0.0", - "meow": "^13.0.0" - }, - "bin": { - "git-semver-tags": "src/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", - "dev": true, - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/index-to-position": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz", - "integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/isows": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", - "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/normalize-package-data": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", - "dev": true, - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/ox": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", - "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "dependencies": { - "@adraffy/ens-normalize": "^1.10.1", - "@noble/curves": "^1.6.0", - "@noble/hashes": "^1.5.0", - "@scure/bip32": "^1.5.0", - "@scure/bip39": "^1.4.0", - "abitype": "^1.0.6", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/parse-json": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", - "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.26.2", - "index-to-position": "^1.1.0", - "type-fest": "^4.39.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true - }, - "node_modules/pkce-challenge": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-package-up": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", - "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", - "dev": true, - "dependencies": { - "find-up-simple": "^1.0.0", - "read-pkg": "^9.0.0", - "type-fest": "^4.6.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", - "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.3", - "normalize-package-data": "^6.0.0", - "parse-json": "^8.0.0", - "type-fest": "^4.6.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/router/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/router/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/router/node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "engines": { - "node": ">=16" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", - "dev": true - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/temp-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", - "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", - "dev": true, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/tempfile": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-5.0.0.tgz", - "integrity": "sha512-bX655WZI/F7EoTDw9JvQURqAXiPHi8o8+yFxPF2lWYyz1aHnmMRuXWqL6YB6GmeO0o4DIYWHLgGNi/X64T+X4Q==", - "dev": true, - "dependencies": { - "temp-dir": "^3.0.0" - }, - "engines": { - "node": ">=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true - }, - "node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/viem": { - "version": "2.29.2", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.29.2.tgz", - "integrity": "sha512-cukRxab90jvQ+TDD84sU3qB3UmejYqgCw4cX8SfWzvh7JPfZXI3kAMUaT5OSR2As1Mgvx1EJawccwPjGqkSSwA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "dependencies": { - "@noble/curves": "1.8.2", - "@noble/hashes": "1.7.2", - "@scure/bip32": "1.6.2", - "@scure/bip39": "1.5.4", - "abitype": "1.0.8", - "isows": "1.0.6", - "ox": "0.6.9", - "ws": "8.18.1" - }, - "peerDependencies": { - "typescript": ">=5.0.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/zod": { - "version": "3.24.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", - "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.24.5", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", - "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", - "peerDependencies": { - "zod": "^3.24.1" - } - } - } -}