Skip to main content
Glama
stacks_account.ts13.2 kB
import { Tool } from "fastmcp"; import { z } from "zod"; import { recordTelemetry } from "../../../utils/telemetry.js"; import { StacksApiService } from "../../../services/StacksApiService.js"; // ============================================================================ // STACKS ACCOUNT MANAGEMENT TOOLS // ============================================================================ // Schema for account information queries const AccountInfoScheme = z.object({ address: z.string().describe("Stacks address (e.g., SP1H1733V5MZ3SZ9XRW9FKYGEZT0JDGEB8Y634C7R)"), includeTokens: z.boolean().optional().describe("Include token balances in response"), includeTransactions: z.boolean().optional().describe("Include recent transactions in response"), }); const BalanceCheckScheme = z.object({ address: z.string().describe("Stacks address to check balance for"), token: z.string().optional().describe("Specific token contract to check (e.g., SP1H1733V5MZ3SZ9XRW9FKYGEZT0JDGEB8Y634C7R.token-name)"), }); const TransactionHistoryScheme = z.object({ address: z.string().describe("Stacks address to get transaction history for"), limit: z.number().optional().describe("Number of transactions to retrieve (default: 20, max: 50)"), offset: z.number().optional().describe("Number of transactions to skip for pagination"), }); const AddressValidationScheme = z.object({ address: z.string().describe("Address to validate"), network: z.enum(["mainnet", "testnet"]).optional().describe("Network to validate against"), }); // Get comprehensive account information export const getStacksAccountInfoTool: Tool<undefined, typeof AccountInfoScheme> = { name: "get_stacks_account_info", description: "Get comprehensive information about a Stacks account including STX balance, nonce, tokens, and recent activity.", parameters: AccountInfoScheme, execute: async (args, context) => { try { await recordTelemetry({ action: "get_stacks_account_info" }, context); const apiService = new StacksApiService(); const network = (process.env.STACKS_NETWORK as "mainnet" | "testnet" | "devnet") || "mainnet"; const accountInfo = await apiService.getAccountInfo(args.address, network); let response = `# Stacks Account Information ## Address: ${args.address} ### STX Balance - **Total STX**: ${(parseInt(accountInfo.balance) / 1000000).toLocaleString()} STX - **Available STX**: ${(parseInt(accountInfo.balance) / 1000000).toLocaleString()} STX - **Locked STX**: ${(parseInt(accountInfo.locked) / 1000000).toLocaleString()} STX ### Account Details - **Account Nonce**: ${accountInfo.nonce} - **Account Status**: Active ### Network Information - **Network**: ${process.env.STACKS_NETWORK || 'mainnet'} - **API Endpoint**: ${process.env.STACKS_API_URL || 'https://api.stacks.co'}`; // Add token information if requested if (args.includeTokens) { try { const balanceData = await apiService.getAccountBalance(args.address, network); if (balanceData && balanceData.fungible_tokens) { response += `\n\n### Token Balances\n`; Object.entries(balanceData.fungible_tokens).forEach(([tokenId, tokenData]: [string, any]) => { response += `- **${tokenId}**: ${tokenData.balance}\n`; }); } else { response += `\n\n### Token Balances\nNo token balances found.`; } } catch (error) { response += `\n\n### Token Balances\n⚠️ Could not retrieve token balances: ${error}`; } } // Add transaction history if requested if (args.includeTransactions) { try { const txData = await apiService.getAccountTransactions(args.address, network, 10); const transactions = txData.results || []; if (transactions && transactions.length > 0) { response += `\n\n### Recent Transactions (Last 10)\n`; transactions.forEach((tx: any, index: number) => { response += `${index + 1}. **${tx.tx_type}** - ${tx.tx_status} (Block: ${tx.block_height || 'Pending'})\n`; response += ` - TX ID: ${tx.tx_id}\n`; response += ` - Fee: ${(parseInt(tx.fee_rate || '0') / 1000000).toFixed(6)} STX\n\n`; }); } else { response += `\n\n### Recent Transactions\nNo recent transactions found.`; } } catch (error) { response += `\n\n### Recent Transactions\n⚠️ Could not retrieve transactions: ${error}`; } } response += `\n\n## Useful Tools - Use \`check_stx_balance\` for simple STX balance checks - Use \`get_transaction_history\` for detailed transaction analysis - Use \`validate_stacks_address\` to verify address format - Use \`get_sip010_balance\` for specific token balance checks`; return response; } catch (error) { return `❌ Failed to get account information: ${error}`; } }, }; // Check STX balance for an address export const checkSTXBalanceTool: Tool<undefined, typeof BalanceCheckScheme> = { name: "check_stx_balance", description: "Check the STX balance for a Stacks address. Simple and fast balance lookup.", parameters: BalanceCheckScheme, execute: async (args, context) => { try { await recordTelemetry({ action: "check_stx_balance" }, context); const apiService = new StacksApiService(); const network = (process.env.STACKS_NETWORK as "mainnet" | "testnet" | "devnet") || "mainnet"; if (args.token) { // Check specific token balance const [contractAddress, contractName] = args.token.split('.'); const balance = await apiService.getFungibleTokenBalance(args.token, args.address, network); return `# Token Balance **Address**: ${args.address} **Token**: ${args.token} **Balance**: ${balance} base units ## Token Details - **Contract**: ${args.token} Use \`get_sip010_info\` to get more details about this token contract.`; } else { // Check STX balance const accountInfo = await apiService.getAccountInfo(args.address, network); const stxBalance = parseInt(accountInfo.balance) / 1000000; const lockedSTX = parseInt(accountInfo.locked) / 1000000; const totalSTX = stxBalance + lockedSTX; return `# STX Balance **Address**: ${args.address} **Available STX**: ${stxBalance.toLocaleString()} STX **Locked STX**: ${lockedSTX.toLocaleString()} STX **Total STX**: ${totalSTX.toLocaleString()} STX **Account Nonce**: ${accountInfo.nonce} ${lockedSTX > 0 ? `\n⚠️ **Note**: ${lockedSTX.toLocaleString()} STX is locked (likely in Stacking)` : ''} Use \`get_stacks_account_info\` with \`includeTokens: true\` to see all token balances.`; } } catch (error) { return `❌ Failed to check balance: ${error}`; } }, }; // Get transaction history for an address export const getTransactionHistoryTool: Tool<undefined, typeof TransactionHistoryScheme> = { name: "get_transaction_history", description: "Get detailed transaction history for a Stacks address with pagination support.", parameters: TransactionHistoryScheme, execute: async (args, context) => { try { await recordTelemetry({ action: "get_transaction_history" }, context); const apiService = new StacksApiService(); const network = (process.env.STACKS_NETWORK as "mainnet" | "testnet" | "devnet") || "mainnet"; const limit = Math.min(args.limit || 20, 50); // Cap at 50 const offset = args.offset || 0; const txData = await apiService.getAccountTransactions(args.address, network, limit, offset); const transactions = txData.results || []; if (!transactions || transactions.length === 0) { return `# Transaction History **Address**: ${args.address} **Result**: No transactions found. The address may be new or have no transaction activity yet.`; } let response = `# Transaction History **Address**: ${args.address} **Showing**: ${transactions.length} transactions (offset: ${offset}) `; transactions.forEach((tx: any, index: number) => { const txNumber = offset + index + 1; const fee = (parseInt(tx.fee_rate || '0') / 1000000).toFixed(6); response += `## ${txNumber}. ${tx.tx_type.toUpperCase()} Transaction **Status**: ${tx.tx_status} **TX ID**: \`${tx.tx_id}\` **Block Height**: ${tx.block_height || 'Pending'} **Fee**: ${fee} STX **Nonce**: ${tx.nonce} `; // Add type-specific details if (tx.tx_type === 'token_transfer') { const amount = (parseInt(tx.token_transfer?.amount || '0') / 1000000).toFixed(6); response += `**Amount**: ${amount} STX **From**: ${tx.sender_address} **To**: ${tx.token_transfer?.recipient_address} **Memo**: ${tx.token_transfer?.memo || 'None'} `; } else if (tx.tx_type === 'contract_call') { response += `**Contract**: ${tx.contract_call?.contract_id} **Function**: ${tx.contract_call?.function_name} **Sender**: ${tx.sender_address} `; } else if (tx.tx_type === 'smart_contract') { response += `**Contract**: ${tx.smart_contract?.contract_id} **Source Code**: ${tx.smart_contract?.source_code ? 'Available' : 'Not available'} `; } }); response += `\n## Pagination - **Current Offset**: ${offset} - **Results Per Page**: ${limit} - **Next Page**: Use offset ${offset + limit} to get more transactions Use \`get_stacks_account_info\` for account overview or specific transaction tools for detailed analysis.`; return response; } catch (error) { return `❌ Failed to get transaction history: ${error}`; } }, }; // Validate Stacks address format export const validateStacksAddressTool: Tool<undefined, typeof AddressValidationScheme> = { name: "validate_stacks_address", description: "Validate a Stacks address format and check if it's correctly formatted for the specified network.", parameters: AddressValidationScheme, execute: async (args, context) => { try { await recordTelemetry({ action: "validate_stacks_address" }, context); const address = args.address.trim(); const network = args.network || 'mainnet'; // Basic format validation const isValidFormat = /^S[TPMNX][A-Z0-9]{36,41}$/.test(address); if (!isValidFormat) { return `# Address Validation Result **Address**: ${address} **Valid Format**: ❌ No **Network**: ${network} ## Issues Detected - Address does not match Stacks address format - Expected format: S[TPMNX][A-Z0-9]{36-41} ## Valid Examples - **Mainnet**: SP1H1733V5MZ3SZ9XRW9FKYGEZT0JDGEB8Y634C7R - **Testnet**: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM ## Next Steps - Check address spelling and format - Ensure you're using the correct network prefix (SP for mainnet, ST for testnet)`; } // Network-specific validation const prefix = address.substring(0, 2); const expectedMainnetPrefix = 'SP'; const expectedTestnetPrefix = 'ST'; let networkMatch = true; let actualNetwork = 'unknown'; if (prefix === expectedMainnetPrefix) { actualNetwork = 'mainnet'; networkMatch = (network === 'mainnet'); } else if (prefix === expectedTestnetPrefix) { actualNetwork = 'testnet'; networkMatch = (network === 'testnet'); } else { networkMatch = false; } // Try to get additional info if address is valid let additionalInfo = ''; if (isValidFormat && networkMatch) { try { const apiService = new StacksApiService(); const validNetwork = network as "mainnet" | "testnet" | "devnet"; const accountInfo = await apiService.getAccountInfo(address, validNetwork); const balance = (parseInt(accountInfo.balance) / 1000000).toLocaleString(); additionalInfo = `\n## Account Status - **STX Balance**: ${balance} STX - **Account Nonce**: ${accountInfo.nonce} - **Status**: Account exists and is active`; } catch (error) { additionalInfo = `\n## Account Status - **Status**: Valid format but account may not exist or be inactive - **Note**: ${error}`; } } return `# Address Validation Result **Address**: ${address} **Valid Format**: ${isValidFormat ? '✅ Yes' : '❌ No'} **Expected Network**: ${network} **Actual Network**: ${actualNetwork} **Network Match**: ${networkMatch ? '✅ Yes' : '❌ No'} ## Address Details - **Prefix**: ${prefix} - **Length**: ${address.length} characters - **Format**: ${isValidFormat ? 'Correct Stacks address format' : 'Invalid format'} ${!networkMatch && isValidFormat ? `\n⚠️ **Network Mismatch**: Address is for ${actualNetwork} but you specified ${network}` : ''} ${additionalInfo} ## Supported Networks - **Mainnet**: Addresses start with 'SP' - **Testnet**: Addresses start with 'ST'`; } catch (error) { return `❌ Failed to validate address: ${error}`; } }, };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/exponentlabshq/stacks-clarity-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server