import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import * as dotenv from 'dotenv';
import { HyperionClient } from './hyperion-client.js';
import { WalletManager } from './wallet-manager.js';
import {
HyperionConfig,
} from './types.js';
// DAT operations use ethers.js directly with the pre-deployed LazAI testnet contract
// Load environment variables
dotenv.config();
// Configuration schema for Smithery
export const configSchema = z.object({
privateKey: z.string().optional().describe("Your funded private key for Hyperion testnet"),
rpcUrl: z.string().default('https://hyperion-testnet.metis.io').describe("Hyperion RPC URL"),
chainId: z.number().default(133717).describe("Hyperion Chain ID"),
networkName: z.string().default('Hyperion Testnet').describe("Network name"),
explorerUrl: z.string().default('https://explorer.hyperion-testnet.metis.io').describe("Block explorer URL"),
currencySymbol: z.string().default('tMETIS').describe("Currency symbol"),
debug: z.boolean().default(false).describe("Enable debug logging"),
});
export default function createStatelessServer({
config,
}: {
config: z.infer<typeof configSchema>;
}) {
const server = new McpServer({
name: "Hyperion Blockchain MCP Server",
version: "1.1.0",
});
// Initialize configuration with defaults
const hyperionConfig: HyperionConfig = {
rpcUrl: config?.rpcUrl || 'https://hyperion-testnet.metis.io',
chainId: config?.chainId || 133717,
networkName: config?.networkName || 'Hyperion Testnet',
explorerUrl: config?.explorerUrl || 'https://explorer.hyperion-testnet.metis.io',
currencySymbol: config?.currencySymbol || 'tMETIS',
};
// Initialize clients - wrapped in try/catch for resilience
let hyperionClient: HyperionClient;
try {
hyperionClient = new HyperionClient(hyperionConfig);
} catch (error) {
console.error('Failed to initialize HyperionClient:', error);
hyperionClient = new HyperionClient(hyperionConfig);
}
const walletManager = new WalletManager();
// Import wallet from config if privateKey is provided
if (config?.privateKey) {
try {
walletManager.importWallet(config.privateKey, undefined, 'Smithery Wallet');
} catch (error) {
console.error('Failed to import wallet from config:', error);
}
}
// Create Wallet Tool
server.tool(
"create_wallet",
"Create a new Hyperion wallet with a generated mnemonic phrase",
{
name: z.string().optional().describe("Optional name for the wallet"),
},
async ({ name }) => {
try {
const walletInfo = walletManager.createWallet(name);
return {
content: [
{
type: "text",
text: `Wallet created successfully!\n\nAddress: ${walletInfo.address}\nMnemonic: ${walletInfo.mnemonic}\n\n⚠️ IMPORTANT: Save your mnemonic phrase securely. It's the only way to recover your wallet!`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error creating wallet: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Import Wallet Tool
server.tool(
"import_wallet",
"Import an existing wallet using private key or mnemonic phrase",
{
privateKey: z.string().optional().describe("Private key to import (alternative to mnemonic)"),
mnemonic: z.string().optional().describe("Mnemonic phrase to import (alternative to private key)"),
name: z.string().optional().describe("Optional name for the wallet"),
},
async ({ privateKey, mnemonic, name }) => {
try {
const walletInfo = walletManager.importWallet(privateKey, mnemonic, name);
return {
content: [
{
type: "text",
text: `Wallet imported successfully!\n\nAddress: ${walletInfo.address}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error importing wallet: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// List Wallets Tool
server.tool(
"list_wallets",
"List all available wallets",
{},
async () => {
try {
const wallets = walletManager.listWallets();
const currentAddress = walletManager.getCurrentAddress();
let response = `Available Wallets (${wallets.length}):\n\n`;
for (const wallet of wallets) {
const isCurrent = wallet.address.toLowerCase() === currentAddress.toLowerCase();
response += `${isCurrent ? '→ ' : ' '}${wallet.address}${isCurrent ? ' (current)' : ''}\n`;
}
return {
content: [
{
type: "text",
text: response,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error listing wallets: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Get Balance Tool
server.tool(
"get_balance",
"Get the balance of a wallet address (native tokens or ERC20 tokens)",
{
address: z.string().describe("Wallet address to check balance for"),
tokenAddress: z.string().optional().describe("Optional ERC20 token contract address"),
},
async ({ address, tokenAddress }) => {
try {
if (tokenAddress) {
const tokenBalance = await hyperionClient.getTokenBalance(address, tokenAddress);
return {
content: [
{
type: "text",
text: `Token Balance:\n\nAddress: ${address}\nToken: ${tokenBalance.name} (${tokenBalance.symbol})\nBalance: ${tokenBalance.balance} ${tokenBalance.symbol}`,
},
],
};
} else {
const balance = await hyperionClient.getBalance(address);
return {
content: [
{
type: "text",
text: `Native Balance:\n\nAddress: ${address}\nBalance: ${balance} ${hyperionClient.getCurrencySymbol()}`,
},
],
};
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting balance: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Get Native Balance Tool (dedicated for tMETIS)
server.tool(
"get_native_balance",
"Get the native tMETIS balance of a wallet address on Hyperion testnet",
{
address: z.string().describe("Wallet address to check native balance for"),
},
async ({ address }) => {
try {
const balance = await hyperionClient.getBalance(address);
const networkInfo = await hyperionClient.getNetworkInfo();
return {
content: [
{
type: "text",
text: `Native tMETIS Balance:\n\nAddress: ${address}\nBalance: ${balance} ${hyperionClient.getCurrencySymbol()}\nNetwork: ${networkInfo.networkName} (Chain ID: ${networkInfo.chainId})\nBlock: ${networkInfo.blockNumber}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting native balance: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Send Transaction Tool
server.tool(
"send_transaction",
"Send native tokens or ERC20 tokens to another address",
{
to: z.string().describe("Recipient address"),
amount: z.union([z.string(), z.number()]).describe("Amount to send (in token units, not wei)"),
tokenAddress: z.string().optional().describe("Optional ERC20 token contract address (for token transfers)"),
gasLimit: z.string().optional().describe("Optional gas limit"),
gasPrice: z.string().optional().describe("Optional gas price"),
},
async ({ to, amount, tokenAddress, gasLimit, gasPrice }) => {
try {
// Check if wallet is configured
let wallet;
try {
wallet = walletManager.getCurrentWallet();
} catch (error) {
return {
content: [
{
type: "text",
text: `❌ No wallet configured for transactions!\n\n` +
`To send transactions, you need to configure your private key:\n\n` +
`🔧 **Via Smithery Interface (Recommended):**\n` +
`1. Click "Save & Connect" below\n` +
`2. Enter your funded private key for Hyperion testnet (64 hex chars without 0x prefix)\n` +
`3. Example format: abc123def456...xyz (your actual private key here)\n\n` +
`🔧 **Alternative Methods:**\n` +
`• Use 'import_wallet' tool to add your private key\n` +
`• Set HYPERION_PRIVATE_KEYS environment variable\n\n` +
`Original error: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
// Convert amount to string if it's a number
const amountStr = typeof amount === 'number' ? amount.toString() : amount;
let result;
if (tokenAddress) {
result = await hyperionClient.sendTokenTransaction(
wallet,
tokenAddress,
to,
amountStr,
gasLimit,
gasPrice
);
} else {
result = await hyperionClient.sendTransaction(
wallet,
to,
amountStr,
gasLimit,
gasPrice
);
}
return {
content: [
{
type: "text",
text: `Transaction sent successfully!\n\nHash: ${result.hash}\nFrom: ${result.from}\nTo: ${result.to}\nAmount: ${result.value}\nStatus: ${result.status}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error sending transaction: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Get Network Info Tool
server.tool(
"get_network_info",
"Get current network information and status",
{},
async () => {
try {
const networkInfo = await hyperionClient.getNetworkInfo();
return {
content: [
{
type: "text",
text: `Network Information:\n\nChain ID: ${networkInfo.chainId}\nNetwork: ${networkInfo.networkName}\nLatest Block: ${networkInfo.blockNumber}\nGas Price: ${networkInfo.gasPrice}\nConnected: ${networkInfo.isConnected}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting network info: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Get Transaction Tool
server.tool(
"get_transaction",
"Get details of a transaction by hash",
{
hash: z.string().describe("Transaction hash"),
},
async ({ hash }) => {
try {
const transaction = await hyperionClient.getTransaction(hash);
return {
content: [
{
type: "text",
text: `Transaction Details:\n\nHash: ${transaction.hash}\nFrom: ${transaction.from}\nTo: ${transaction.to}\nValue: ${transaction.value}\nGas Used: ${transaction.gasUsed}\nStatus: ${transaction.status}\nBlock: ${transaction.blockNumber}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting transaction: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Get Block Tool
server.tool(
"get_block",
"Get block information by number or hash",
{
blockNumber: z.number().optional().describe("Block number (alternative to blockHash)"),
blockHash: z.string().optional().describe("Block hash (alternative to blockNumber)"),
},
async ({ blockNumber, blockHash }) => {
try {
const block = await hyperionClient.getBlock(blockNumber, blockHash);
return {
content: [
{
type: "text",
text: `Block Information:\n\nNumber: ${block.number}\nHash: ${block.hash}\nTimestamp: ${new Date(block.timestamp * 1000).toISOString()}\nTransaction Count: ${block.transactionCount}\nGas Used: ${block.gasUsed}\nGas Limit: ${block.gasLimit}\nMiner: ${block.miner}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting block: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Estimate Gas Tool
server.tool(
"estimate_gas",
"Estimate gas cost for a transaction",
{
to: z.string().describe("Recipient address"),
value: z.string().optional().describe("Optional value to send (in ether)"),
data: z.string().optional().describe("Optional transaction data"),
},
async ({ to, value, data }) => {
try {
const gasEstimate = await hyperionClient.estimateGas(to, value, data);
return {
content: [
{
type: "text",
text: `Gas Estimation:\n\nEstimated Gas: ${gasEstimate.gasLimit}\nGas Price: ${gasEstimate.gasPrice}\nEstimated Cost: ${gasEstimate.estimatedCost} ${hyperionClient.getCurrencySymbol()}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error estimating gas: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Set Current Wallet Tool
server.tool(
"set_current_wallet",
"Set the current active wallet for transactions",
{
address: z.string().describe("Wallet address to set as current"),
},
async ({ address }) => {
try {
walletManager.setCurrentWallet(address);
const currentAddress = walletManager.getCurrentAddress();
return {
content: [
{
type: "text",
text: `Current wallet set successfully!\n\nActive Wallet: ${currentAddress}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error setting current wallet: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Get Current Wallet Tool
server.tool(
"get_current_wallet",
"Get the current active wallet information",
{},
async () => {
try {
const currentAddress = walletManager.getCurrentAddress();
const wallet = walletManager.getCurrentWallet();
return {
content: [
{
type: "text",
text: `Current Wallet:\n\nAddress: ${currentAddress}\nWallet Type: ${wallet.constructor.name}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting current wallet: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Deploy ERC20 Token Tool
server.tool(
"deploy_erc20_token",
"Deploy a new ERC20 token contract",
{
name: z.string().describe("Token name (e.g., 'My Token')"),
symbol: z.string().describe("Token symbol (e.g., 'MTK')"),
decimals: z.number().optional().describe("Token decimals (default: 18)"),
initialSupply: z.string().optional().describe("Initial token supply (default: 0)"),
mintable: z.boolean().optional().describe("Whether token should be mintable (default: true)"),
gasLimit: z.string().optional().describe("Gas limit for deployment"),
gasPrice: z.string().optional().describe("Gas price for deployment"),
},
async ({ name, symbol, decimals, initialSupply, mintable, gasLimit, gasPrice }) => {
try {
// Check if wallet is configured
let wallet;
try {
wallet = walletManager.getCurrentWallet();
} catch (error) {
return {
content: [
{
type: "text",
text: `❌ No wallet configured for ERC20 deployment!\n\n` +
`To deploy ERC20 tokens, you need to configure your private key:\n\n` +
`🔧 **Via Smithery Interface (Recommended):**\n` +
`1. Click "Save & Connect" below\n` +
`2. Enter your funded private key for Hyperion testnet (64 hex chars without 0x prefix)\n` +
`3. Example format: abc123def456...xyz (your actual private key here)\n\n` +
`🔧 **Alternative Methods:**\n` +
`• Use 'import_wallet' tool to add your private key\n` +
`• Set HYPERION_PRIVATE_KEYS environment variable\n\n` +
`Original error: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
const result = await hyperionClient.deployERC20Token(
wallet,
name,
symbol,
decimals || 18,
initialSupply || "0",
mintable ?? true,
gasLimit,
gasPrice
);
const explorerUrl = hyperionClient.getExplorerUrl();
return {
content: [
{
type: "text",
text: `ERC20 Token Deployed Successfully!\n\n` +
`Contract Address: ${result.contractAddress}\n` +
`Transaction Hash: ${result.transactionHash}\n` +
`Token Name: ${result.name}\n` +
`Token Symbol: ${result.symbol}\n` +
`Decimals: ${result.decimals}\n` +
`Initial Supply: ${result.initialSupply}\n` +
`Deployer: ${result.deployer}\n` +
`Gas Used: ${result.gasUsed || 'N/A'}\n` +
`Block Number: ${result.blockNumber || 'Pending'}\n\n` +
`Transaction Explorer: ${explorerUrl}/tx/${result.transactionHash}\n` +
`Contract Explorer: ${explorerUrl}/address/${result.contractAddress}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error deploying ERC20 token: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Deploy ERC721 Token Tool
server.tool(
"deploy_erc721_token",
"Deploy a new ERC721 token contract (NFT)",
{
name: z.string().describe("Token name (e.g., 'My NFT')"),
symbol: z.string().describe("Token symbol (e.g., 'NFT')"),
mintable: z.boolean().optional().describe("Whether the token should be mintable (default: false)"),
gasLimit: z.string().optional().describe("Gas limit for deployment"),
gasPrice: z.string().optional().describe("Gas price for deployment"),
},
async ({ name, symbol, mintable, gasLimit, gasPrice }) => {
try {
let wallet;
try {
wallet = walletManager.getCurrentWallet();
} catch (error) {
return {
content: [
{
type: "text",
text: `❌ No wallet configured for ERC721 deployment!\n\nTo deploy ERC721 tokens, you need to configure your private key.\n\nOriginal error: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
const result = await hyperionClient.deployERC721Token(
wallet,
name,
symbol,
mintable || false,
gasLimit,
gasPrice
);
const explorerUrl = hyperionClient.getExplorerUrl();
return {
content: [
{
type: "text",
text: `ERC721 Token Deployed Successfully!\n\n` +
`Contract Address: ${result.contractAddress}\n` +
`Transaction Hash: ${result.transactionHash}\n` +
`Token Name: ${result.name}\n` +
`Token Symbol: ${result.symbol}\n` +
`Deployer: ${result.deployer}\n` +
`Gas Used: ${result.gasUsed || 'N/A'}\n` +
`Block Number: ${result.blockNumber || 'Pending'}\n\n` +
`Transaction Explorer: ${explorerUrl}/tx/${result.transactionHash}\n` +
`Contract Explorer: ${explorerUrl}/address/${result.contractAddress}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error deploying ERC721 token: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Get Token Info Tool
server.tool(
"get_token_info",
"Get comprehensive ERC20 token information including name, symbol, decimals, supply, and owner",
{
tokenAddress: z.string().describe("ERC20 token contract address"),
},
async ({ tokenAddress }) => {
try {
const result = await hyperionClient.getTokenInfo(tokenAddress);
const explorerUrl = hyperionClient.getExplorerUrl();
return {
content: [
{
type: "text",
text: `ERC20 Token Information:\n\n` +
`Contract Address: ${result.address}\n` +
`Name: ${result.name}\n` +
`Symbol: ${result.symbol}\n` +
`Decimals: ${result.decimals}\n` +
`Total Supply: ${result.totalSupply}\n` +
`Owner: ${result.owner || 'N/A'}\n\n` +
`Contract Explorer: ${explorerUrl}/address/${result.address}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting token info: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Mint Tokens Tool
server.tool(
"mint_tokens",
"Mint additional tokens for mintable ERC20 contracts (owner only)",
{
tokenAddress: z.string().describe("ERC20 token contract address"),
to: z.string().describe("Address to mint tokens to"),
amount: z.union([z.string(), z.number()]).describe("Amount of tokens to mint"),
gasLimit: z.string().optional().describe("Gas limit for minting"),
gasPrice: z.string().optional().describe("Gas price for minting"),
},
async ({ tokenAddress, to, amount, gasLimit, gasPrice }) => {
try {
// Check if wallet is configured
let wallet;
try {
wallet = walletManager.getCurrentWallet();
} catch (error) {
return {
content: [
{
type: "text",
text: `❌ No wallet configured for token minting!\n\n` +
`To mint tokens, you need to configure your private key:\n\n` +
`🔧 **Via Smithery Interface (Recommended):**\n` +
`1. Click "Save & Connect" below\n` +
`2. Enter your funded private key for Hyperion testnet (64 hex chars without 0x prefix)\n` +
`3. Example format: abc123def456...xyz (your actual private key here)\n\n` +
`🔧 **Alternative Methods:**\n` +
`• Use 'import_wallet' tool to add your private key\n` +
`• Set HYPERION_PRIVATE_KEYS environment variable\n\n` +
`Original error: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
// Convert amount to string if it's a number
const amountStr = typeof amount === 'number' ? amount.toString() : amount;
const result = await hyperionClient.mintTokens(
wallet,
tokenAddress,
to,
amountStr,
gasLimit,
gasPrice
);
const explorerUrl = hyperionClient.getExplorerUrl();
return {
content: [
{
type: "text",
text: `Tokens Minted Successfully!\n\n` +
`Transaction Hash: ${result.hash}\n` +
`Token Contract: ${tokenAddress}\n` +
`Recipient: ${to}\n` +
`Amount Minted: ${amount}\n` +
`From: ${result.from}\n` +
`Gas Used: ${result.gasUsed || 'N/A'}\n` +
`Block Number: ${result.blockNumber || 'Pending'}\n\n` +
`Transaction Explorer: ${explorerUrl}/tx/${result.hash}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error minting tokens: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Get NFT Info Tool
server.tool(
"get_nft_info",
"Get comprehensive ERC721 NFT information including collection details and specific token info",
{
tokenAddress: z.string().describe("ERC721 NFT contract address"),
tokenId: z.string().optional().describe("Optional token ID to get specific token info"),
},
async ({ tokenAddress, tokenId }) => {
try {
const result = await hyperionClient.getNFTInfo(tokenAddress, tokenId);
const explorerUrl = hyperionClient.getExplorerUrl();
return {
content: [
{
type: "text",
text: `ERC721 NFT Information:\n\n` +
`Contract Address: ${result.address}\n` +
`Collection Name: ${result.name}\n` +
`Collection Symbol: ${result.symbol}\n` +
`Total Supply: ${result.totalSupply}\n` +
`${result.tokenId ? `Token ID: ${result.tokenId}\n` : ''}` +
`${result.tokenURI ? `Token URI: ${result.tokenURI}\n` : ''}` +
`${result.owner ? `Token Owner: ${result.owner}\n` : ''}\n` +
`Contract Explorer: ${explorerUrl}/address/${result.address}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting NFT info: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Mint NFT Tool
server.tool(
"mint_nft",
"Mint a new NFT for mintable ERC721 contracts (owner only)",
{
tokenAddress: z.string().describe("ERC721 NFT contract address"),
to: z.string().describe("Address to mint NFT to"),
tokenId: z.string().describe("Unique token ID for the new NFT"),
tokenURI: z.string().optional().describe("Metadata URI for the NFT (optional)"),
gasLimit: z.string().optional().describe("Gas limit for minting"),
gasPrice: z.string().optional().describe("Gas price for minting"),
},
async ({ tokenAddress, to, tokenId, tokenURI, gasLimit, gasPrice }) => {
try {
// Check if wallet is configured
let wallet;
try {
wallet = walletManager.getCurrentWallet();
} catch (error) {
return {
content: [
{
type: "text",
text: `❌ No wallet configured for NFT minting!\n\n` +
`To mint NFTs, you need to configure your private key:\n\n` +
`🔧 **Via Smithery Interface (Recommended):**\n` +
`1. Click "Save & Connect" below\n` +
`2. Enter your funded private key for Hyperion testnet (64 hex chars without 0x prefix)\n` +
`3. Example format: abc123def456...xyz (your actual private key here)\n\n` +
`🔧 **Alternative Methods:**\n` +
`• Use 'import_wallet' tool to add your private key\n` +
`• Set HYPERION_PRIVATE_KEYS environment variable\n\n` +
`Original error: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
const result = await hyperionClient.mintNFT(
wallet,
tokenAddress,
to,
tokenId,
tokenURI || '',
gasLimit,
gasPrice
);
const explorerUrl = hyperionClient.getExplorerUrl();
return {
content: [
{
type: "text",
text: `NFT Minted Successfully!\n\n` +
`Transaction Hash: ${result.transactionHash}\n` +
`NFT Contract: ${tokenAddress}\n` +
`Recipient: ${result.to}\n` +
`Token ID: ${result.tokenId}\n` +
`${result.tokenURI ? `Token URI: ${result.tokenURI}\n` : ''}` +
`Gas Used: ${result.gasUsed || 'N/A'}\n` +
`Block Number: ${result.blockNumber || 'Pending'}\n\n` +
`Transaction Explorer: ${explorerUrl}/tx/${result.transactionHash}\n` +
`Contract Explorer: ${explorerUrl}/address/${tokenAddress}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error minting NFT: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// DAT Contract Info Tool - Uses pre-deployed Alith SDK contract
const TESTNET_DAT_CONTRACT = '0xD59CDFFEb65aCc539994e41D0B40efF61bE37118';
server.tool(
"get_dat_contract",
"Get the pre-deployed DAT (Data Anchoring Token) contract address on the LazAI testnet. This contract is managed by the Alith SDK.",
{},
async () => {
return {
content: [
{
type: "text",
text: `DAT Contract Information\n\n` +
`Contract Address: ${TESTNET_DAT_CONTRACT}\n` +
`Network: LazAI Testnet (Chain ID: 133718)\n` +
`RPC: https://testnet.lazai.network\n\n` +
`This is the official pre-deployed DAT contract managed by the Alith SDK.\n` +
`Use 'mint_dat' to mint new Data Anchoring Tokens.\n` +
`Use 'get_dat_balance' to check your DAT balance.\n\n` +
`Explorer: https://explorer.lazai.network/address/${TESTNET_DAT_CONTRACT}`,
},
],
};
}
);
// Mint DAT Tool using ethers.js with LazAI testnet
// Register File with LazAI Tool
server.tool(
"register_dat_file",
"Register a file URL with LazAI to get a fileId. This is the first step in the DAT minting workflow. After registering, you can request proof and then claim the DAT reward.",
{
url: z.string().describe("The URL of the file to register (IPFS or HTTP URL)"),
},
async ({ url }) => {
try {
const wallet = walletManager.getCurrentWallet();
if (!wallet) {
return {
content: [
{
type: "text",
text: "Error: No wallet available. Please create or import a wallet first.",
},
],
};
}
// Import Alith SDK Client
const { Client } = await import('alith/lazai');
const client = new Client(undefined, undefined, wallet.privateKey);
// Check if file is already registered
let fileId = await client.getFileIdByUrl(url);
let alreadyRegistered = false;
if (fileId && fileId > BigInt(0)) {
alreadyRegistered = true;
} else {
// Register the file
fileId = await client.addFile(url);
}
return {
content: [
{
type: "text",
text: `File ${alreadyRegistered ? 'Already Registered' : 'Registered Successfully'}!\n\n` +
`File ID: ${fileId.toString()}\n` +
`URL: ${url}\n` +
`Wallet: ${wallet.address}\n\n` +
`Next Steps:\n` +
`1. Request proof using: request_dat_proof with fileId=${fileId.toString()}\n` +
`2. After proof is verified, claim reward using: claim_dat_reward with fileId=${fileId.toString()}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error registering file: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Request DAT Proof Tool
server.tool(
"request_dat_proof",
"Request proof verification for a registered file from LazAI verified computing nodes. This is required before claiming a DAT reward.",
{
fileId: z.string().describe("The file ID returned from register_dat_file"),
value: z.string().optional().describe("Optional value to send with the request (in wei)"),
},
async ({ fileId, value }) => {
try {
const wallet = walletManager.getCurrentWallet();
if (!wallet) {
return {
content: [
{
type: "text",
text: "Error: No wallet available. Please create or import a wallet first.",
},
],
};
}
// Import Alith SDK Client
const { Client } = await import('alith/lazai');
const client = new Client(undefined, undefined, wallet.privateKey);
// Get the node fee required for proof request
let proofValue = value ? BigInt(value) : BigInt(0);
if (!value) {
try {
const nodeFee = await client.nodeFee();
proofValue = BigInt(nodeFee.toString());
} catch {
// If we can't get node fee, use 0 and let it fail with a better error
}
}
// Request proof from verified computing nodes
const result = await client.requestProof(
BigInt(fileId),
proofValue
);
// Get job IDs for this file
const jobIds = await client.fileJobIds(BigInt(fileId));
const latestJobId = jobIds.length > 0 ? jobIds[jobIds.length - 1] : null;
return {
content: [
{
type: "text",
text: `Proof Request Submitted Successfully!\n\n` +
`Transaction Hash: ${result.transactionHash}\n` +
`File ID: ${fileId}\n` +
`Latest Job ID: ${latestJobId ? latestJobId.toString() : 'N/A'}\n` +
`Node Fee Paid: ${proofValue.toString()} wei\n` +
`Wallet: ${wallet.address}\n\n` +
`Next Steps:\n` +
`1. Wait for the proof to be verified by computing nodes\n` +
`2. Check file info to see if proofs > 0\n` +
`3. Then claim reward using: claim_dat_reward with fileId=${fileId}\n\n` +
`Explorer: https://explorer.lazai.network/tx/${result.transactionHash}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error requesting proof: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Claim DAT Reward Tool
server.tool(
"claim_dat_reward",
"Claim a DAT (Data Anchoring Token) reward for a registered file. This mints the DAT token after the file has been verified by computing nodes (proofs > 0).",
{
fileId: z.string().describe("The file ID returned from register_dat_file"),
proofIndex: z.string().optional().describe("The proof index to use (default: 1)"),
},
async ({ fileId, proofIndex }) => {
try {
const wallet = walletManager.getCurrentWallet();
if (!wallet) {
return {
content: [
{
type: "text",
text: "Error: No wallet available. Please create or import a wallet first.",
},
],
};
}
// Import Alith SDK Client
const { Client } = await import('alith/lazai');
const client = new Client(undefined, undefined, wallet.privateKey);
// Request reward (this mints the DAT)
const result = await client.requestReward(
BigInt(fileId),
BigInt(proofIndex || '1')
);
return {
content: [
{
type: "text",
text: `DAT Reward Claimed Successfully!\n\n` +
`Transaction Hash: ${result.transactionHash}\n` +
`File ID: ${fileId}\n` +
`Wallet: ${wallet.address}\n` +
`DAT Contract: ${TESTNET_DAT_CONTRACT}\n\n` +
`Explorer: https://explorer.lazai.network/tx/${result.transactionHash}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error claiming DAT reward: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Get File Info Tool
server.tool(
"get_dat_file_info",
"Get information about a registered file on LazAI by its file ID.",
{
fileId: z.string().describe("The file ID to look up"),
},
async ({ fileId }) => {
try {
const wallet = walletManager.getCurrentWallet();
if (!wallet) {
return {
content: [
{
type: "text",
text: "Error: No wallet available. Please create or import a wallet first.",
},
],
};
}
// Import Alith SDK Client
const { Client } = await import('alith/lazai');
const client = new Client(undefined, undefined, wallet.privateKey);
// Get file info
const fileInfo = await client.getFile(BigInt(fileId));
return {
content: [
{
type: "text",
text: `File Information:\n\n` +
`File ID: ${fileId}\n` +
`URL: ${fileInfo.url || 'N/A'}\n` +
`Owner: ${fileInfo.ownerAddress || 'N/A'}\n` +
`Added At: ${fileInfo.addedAtBlock ? `Block ${fileInfo.addedAtBlock}` : 'N/A'}\n` +
`Proofs: ${fileInfo.proofsCount || '0'}\n` +
`Rewards: ${fileInfo.rewardsCount || '0'}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting file info: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Legacy mint_dat tool - now uses the proper workflow
server.tool(
"mint_dat",
"Mint a Data Anchoring Token (DAT) on the LazAI testnet. This is a convenience tool that combines register_dat_file and claim_dat_reward. For more control, use those tools separately.",
{
url: z.string().describe("The URL of the file to register and mint DAT for (IPFS or HTTP URL)"),
},
async ({ url }) => {
try {
const wallet = walletManager.getCurrentWallet();
if (!wallet) {
return {
content: [
{
type: "text",
text: "Error: No wallet available. Please create or import a wallet first.",
},
],
};
}
// Import Alith SDK Client
const { Client } = await import('alith/lazai');
const client = new Client(undefined, undefined, wallet.privateKey);
// Step 1: Check if file is already registered, if not register it
let fileId = await client.getFileIdByUrl(url);
let wasRegistered = false;
if (!fileId || fileId === BigInt(0)) {
fileId = await client.addFile(url);
wasRegistered = true;
}
// Step 2: Request reward (this mints the DAT)
const result = await client.requestReward(fileId, BigInt(1));
return {
content: [
{
type: "text",
text: `DAT Minted Successfully!\n\n` +
`Transaction Hash: ${result.transactionHash}\n` +
`File ID: ${fileId.toString()}\n` +
`URL: ${url}\n` +
`File Was Registered: ${wasRegistered ? 'Yes (new)' : 'No (already existed)'}\n` +
`Wallet: ${wallet.address}\n` +
`DAT Contract: ${TESTNET_DAT_CONTRACT}\n\n` +
`Explorer: https://explorer.lazai.network/tx/${result.transactionHash}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error minting DAT: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Get DAT Balance Tool using ethers.js
server.tool(
"get_dat_balance",
"Get the balance of a specific DAT token for an account on LazAI testnet",
{
account: z.string().describe("Account address to check balance for"),
tokenId: z.string().describe("Token ID of the DAT to query"),
},
async ({ account, tokenId }) => {
try {
// Connect to LazAI testnet
const lazaiProvider = new (await import('ethers')).JsonRpcProvider('https://testnet.lazai.network', 133718);
const ethers = await import('ethers');
const datABI = ['function balanceOf(address account, uint256 id) view returns (uint256)'];
const datContract = new ethers.Contract(TESTNET_DAT_CONTRACT, datABI, lazaiProvider);
const balance = await datContract.balanceOf(account, tokenId);
return {
content: [
{
type: "text",
text: `DAT Balance\n\n` +
`Account: ${account}\n` +
`Token ID: ${tokenId}\n` +
`Balance: ${balance.toString()}\n` +
`Contract: ${TESTNET_DAT_CONTRACT}\n` +
`Network: LazAI Testnet`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting DAT balance: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
// Get DAT URI Tool using ethers.js
server.tool(
"get_dat_uri",
"Get the metadata URI for a specific DAT token on LazAI testnet",
{
tokenId: z.string().describe("Token ID of the DAT to query"),
},
async ({ tokenId }) => {
try {
// Connect to LazAI testnet
const lazaiProvider = new (await import('ethers')).JsonRpcProvider('https://testnet.lazai.network', 133718);
const ethers = await import('ethers');
const datABI = ['function uri(uint256 tokenId) view returns (string)'];
const datContract = new ethers.Contract(TESTNET_DAT_CONTRACT, datABI, lazaiProvider);
const uri = await datContract.uri(tokenId);
return {
content: [
{
type: "text",
text: `DAT Metadata URI\n\n` +
`Token ID: ${tokenId}\n` +
`URI: ${uri}\n` +
`Contract: ${TESTNET_DAT_CONTRACT}\n` +
`Network: LazAI Testnet`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error getting DAT URI: ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
}
);
return server.server;
}
// For Smithery deployment - create and start the server
// Check if this file is being run directly (works in both ESM and CommonJS)
const isDirectExecution = process.argv[1] && (
process.argv[1].endsWith('smithery-server.js') ||
process.argv[1].endsWith('smithery-server.ts') ||
process.argv[1].includes('smithery-server')
);
if (isDirectExecution) {
const defaultConfig = {
rpcUrl: process.env.HYPERION_RPC_URL || 'https://hyperion-testnet.metis.io',
chainId: parseInt(process.env.HYPERION_CHAIN_ID || '133717'),
networkName: process.env.HYPERION_NETWORK_NAME || 'Hyperion Testnet',
explorerUrl: process.env.HYPERION_EXPLORER_URL || 'https://explorer.hyperion-testnet.metis.io',
currencySymbol: process.env.HYPERION_CURRENCY_SYMBOL || 'tMETIS',
debug: process.env.DEBUG === 'true' || false,
};
const server = createStatelessServer({ config: defaultConfig });
// Start the server with stdio transport
const transport = new StdioServerTransport();
server.connect(transport).catch((error) => {
console.error("Failed to start Hyperion MCP Server:", error);
process.exit(1);
});
}