cardano support

This commit is contained in:
Alex Cheng
2025-03-13 13:01:50 -05:00
parent 7135d5af5a
commit 9b86ce4a74
9 changed files with 933 additions and 6 deletions

View File

@@ -21,6 +21,12 @@ XRP_PRIVATE_KEY=your-xrp-private-key # Private key in hex format
XRP_MNEMONIC=your-xrp-mnemonic # Alternative: 12-word recovery phrase XRP_MNEMONIC=your-xrp-mnemonic # Alternative: 12-word recovery phrase
XRP_ADDRESS=your-xrp-address # Optional: Used to verify key derivation XRP_ADDRESS=your-xrp-address # Optional: Used to verify key derivation
# Cardano Configuration
BLOCKFROST_API_KEY=your-blockfrost-api-key # Register at https://blockfrost.io/
CARDANO_NETWORK=mainnet # or 'testnet', 'preview', 'preprod'
CARDANO_MNEMONIC=word1 word2 word3 ... word15 # Must be exactly 15 or 24 words
CARDANO_ACCOUNT_INDEX=0 # Wallet account index (default: 0)
# UTXO Chain API Keys (optional) # UTXO Chain API Keys (optional)
BLOCKCYPHER_API_KEY=your-blockcypher-api-key BLOCKCYPHER_API_KEY=your-blockcypher-api-key
SOCHAIN_API_KEY=your-sochain-api-key SOCHAIN_API_KEY=your-sochain-api-key
@@ -34,6 +40,7 @@ ENABLE_DOGECOIN_TOOLS=true
ENABLE_BITCOINCASH_TOOLS=true ENABLE_BITCOINCASH_TOOLS=true
ENABLE_THORCHAIN_TOOLS=true ENABLE_THORCHAIN_TOOLS=true
ENABLE_RIPPLE_TOOLS=true ENABLE_RIPPLE_TOOLS=true
ENABLE_CARDANO_TOOLS=true # Enable/disable Cardano tools
# CoinGecko API Key # CoinGecko API Key
COINGECKO_API_KEY=CG-Z9ajuvwG1cheNJSByUgmvTgg #replace with your-coingecko-api-key if you hit rate limits COINGECKO_API_KEY=CG-Z9ajuvwG1cheNJSByUgmvTgg #replace with your-coingecko-api-key if you hit rate limits

View File

@@ -1,6 +1,6 @@
# Web3 MCP # Web3 MCP
A Model-Context-Protocol server for interacting with multiple blockchains including Solana, Ethereum, THORChain, XRP Ledger, and UTXO chains. This server provides simple RPC endpoints for common blockchain operations, allowing secure interactions with various blockchains through environment variables. A Model-Context-Protocol server for interacting with multiple blockchains including Solana, Ethereum, THORChain, XRP Ledger, Cardano, and UTXO chains. This server provides simple RPC endpoints for common blockchain operations, allowing secure interactions with various blockchains through environment variables.
<a href="https://glama.ai/mcp/servers/an8x6gmzdn"><img width="380" height="200" src="https://glama.ai/mcp/servers/an8x6gmzdn/badge" alt="Web3 Server MCP server" /></a> <a href="https://glama.ai/mcp/servers/an8x6gmzdn"><img width="380" height="200" src="https://glama.ai/mcp/servers/an8x6gmzdn/badge" alt="Web3 Server MCP server" /></a>
@@ -21,6 +21,15 @@ Ethereum & EVM Chain Operations:
- Send ERC-20 tokens (using private key from .env) - Send ERC-20 tokens (using private key from .env)
- Approve ERC-20 token spending (using private key from .env) - Approve ERC-20 token spending (using private key from .env)
Cardano Operations:
- Get network information and statistics
- Check address balances and transaction history
- View UTxOs for an address
- Explore stake pools and delegation information
- Get details about native assets
- View detailed transaction information
- Get statistics about the current epoch
THORChain Operations: THORChain Operations:
- Check RUNE balances - Check RUNE balances
- Get detailed pool information - Get detailed pool information
@@ -90,6 +99,7 @@ cp .env.example .env
# Tool Registration Controls # Tool Registration Controls
ENABLE_SOLANA_TOOLS=true # Enable/disable Solana tools ENABLE_SOLANA_TOOLS=true # Enable/disable Solana tools
ENABLE_ETHEREUM_TOOLS=true # Enable/disable Ethereum and EVM chain tools ENABLE_ETHEREUM_TOOLS=true # Enable/disable Ethereum and EVM chain tools
ENABLE_CARDANO_TOOLS=true # Enable/disable Cardano tools
# UTXO Chain Tools # UTXO Chain Tools
ENABLE_BITCOIN_TOOLS=true # Enable/disable Bitcoin tools ENABLE_BITCOIN_TOOLS=true # Enable/disable Bitcoin tools
@@ -109,6 +119,12 @@ XRP_PRIVATE_KEY=your-xrp-private-key-in-hex
XRP_MNEMONIC=your-xrp-mnemonic-recovery-phrase XRP_MNEMONIC=your-xrp-mnemonic-recovery-phrase
# Optional - used to verify the derived address # Optional - used to verify the derived address
XRP_ADDRESS=your-xrp-account-address XRP_ADDRESS=your-xrp-account-address
# Cardano Configuration
BLOCKFROST_API_KEY=your-blockfrost-api-key # Get a real API key from https://blockfrost.io/
CARDANO_NETWORK=mainnet # or 'testnet', 'preview', 'preprod'
CARDANO_MNEMONIC=your-cardano-mnemonic-phrase # Required for transaction signing
CARDANO_ACCOUNT_INDEX=0 # Optional - defaults to 0
``` ```
### Optional Configuration ### Optional Configuration
@@ -173,6 +189,7 @@ The Web3 MCP server allows you to control which blockchain tools are registered
- `ENABLE_BITCOINCASH_TOOLS`: Enable/disable Bitcoin Cash tools - `ENABLE_BITCOINCASH_TOOLS`: Enable/disable Bitcoin Cash tools
- `ENABLE_THORCHAIN_TOOLS`: Enable/disable THORChain tools - `ENABLE_THORCHAIN_TOOLS`: Enable/disable THORChain tools
- `ENABLE_RIPPLE_TOOLS`: Enable/disable XRP Ledger tools - `ENABLE_RIPPLE_TOOLS`: Enable/disable XRP Ledger tools
- `ENABLE_CARDANO_TOOLS`: Enable/disable Cardano tools
Set these variables to `true` or `false` in your `.env` file to control which tools are available to the server. This allows you to: Set these variables to `true` or `false` in your `.env` file to control which tools are available to the server. This allows you to:
@@ -199,6 +216,12 @@ Ask Claude (or your MCP client of choice):
- "What's the current gas price on Arbitrum?" - "What's the current gas price on Arbitrum?"
- "Send 100 USDC to 0x556437c4d22ceaeeebf82006b85bdcc0ae67d933 on Polygon" - "Send 100 USDC to 0x556437c4d22ceaeeebf82006b85bdcc0ae67d933 on Polygon"
### Cardano Operations (when ENABLE_CARDANO_TOOLS=true)
- Get the balance of any Cardano
- View your wallet information
- Send ADA to another address
- Send native tokens to another address
### Bitcoin Operations (when ENABLE_BITCOIN_TOOLS=true) ### Bitcoin Operations (when ENABLE_BITCOIN_TOOLS=true)
- "What's the BTC balance of 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?" - "What's the BTC balance of 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?"
- "Show me the transaction history for bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh" - "Show me the transaction history for bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
@@ -249,6 +272,15 @@ You can configure custom RPC endpoints in your .env file for better reliability
### Network Selection ### Network Selection
For EVM operations, you can specify the network by name (ethereum, base, arbitrum, optimism, bsc, polygon, avalanche, berachain). The tool will automatically use the appropriate RPC endpoint and network configuration. For EVM operations, you can specify the network by name (ethereum, base, arbitrum, optimism, bsc, polygon, avalanche, berachain). The tool will automatically use the appropriate RPC endpoint and network configuration.
### Cardano Configuration
The tool uses the Blockfrost API and Lucid library to interact with the Cardano blockchain.
- `BLOCKFROST_API_KEY`: Required - Your Blockfrost API key (register at https://blockfrost.io/)
- `CARDANO_NETWORK`: Optional - The Cardano network to use (mainnet, testnet, preview, preprod). Defaults to 'mainnet'
- `CARDANO_MNEMONIC`: Required for transactions - Your Cardano wallet's mnemonic phrase (15 or 24 words)
- `CARDANO_ACCOUNT_INDEX`: Optional - The account index to use (defaults to 0)
The wallet derived from your mnemonic will be used to sign and send transactions.
### THORChain Configuration ### THORChain Configuration
The tool uses Nine Realms public endpoints by default, but you can configure a custom THORChain node URL in the .env file for better reliability and rate limits. The tool uses Nine Realms public endpoints by default, but you can configure a custom THORChain node URL in the .env file for better reliability and rate limits.

213
docs/CARDANO.md Normal file
View File

@@ -0,0 +1,213 @@
# Cardano Support for Web3-MCP
This document covers the Cardano blockchain integration for web3-mcp.
## Overview
The Cardano integration in web3-mcp allows you to interact with the Cardano blockchain through the Blockfrost API, providing access to network data, address information, transaction details, stake pools, and more.
## Prerequisites
To use the Cardano integration, you need a Blockfrost API key. You can get one by signing up at [blockfrost.io](https://blockfrost.io/).
## Configuration
Update your `.env` file with the following settings:
```
# Cardano Configuration
BLOCKFROST_API_KEY=your-blockfrost-api-key-here
CARDANO_NETWORK=mainnet # or 'testnet', 'preview', 'preprod'
ENABLE_CARDANO_TOOLS=true
```
## Available Tools
### Network Information
#### 1. Get Cardano Network Information
```javascript
const response = await server.call("getCardanoNetworkInfo", {});
```
Returns current information about the Cardano network including:
- Latest block details (height, hash, slot, epoch)
- Network parameters
- Timing information
#### 2. Get Cardano Network Statistics
```javascript
const response = await server.call("getCardanoNetworkStats", {});
```
Returns statistics about the Cardano network including:
- Number of blocks
- Number of transactions
- Number of native tokens
- Number of addresses with ADA
- Number of delegated stakes
#### 3. Get Current Epoch Information
```javascript
const response = await server.call("getCardanoEpochInfo", {});
```
Returns information about the current epoch including:
- Epoch number
- Start and end time
- Block and transaction counts
- Protocol parameters
### Address Operations
#### 1. Get Address Information
```javascript
const response = await server.call("getCardanoAddressInfo", {
address: "addr1qyx...",
});
```
Returns information about a Cardano address including:
- Balance in ADA
- UTxO count
- Address type (script or key-based)
- Stake address (if delegated)
#### 2. Get Address Transactions
```javascript
const response = await server.call("getCardanoAddressTransactions", {
address: "addr1qyx...",
count: 10, // Optional, default is 10
page: 1 // Optional, default is 1
});
```
Returns a list of transactions for the specified address, including:
- Transaction hash
- Block and slot
- Output amounts
- Fees
- Date and time
#### 3. Get Address UTxOs
```javascript
const response = await server.call("getCardanoAddressUtxos", {
address: "addr1qyx...",
});
```
Returns a list of unspent transaction outputs (UTxOs) for the specified address.
### Staking Operations
#### 1. Get Stake Address Information
```javascript
const response = await server.call("getCardanoStakeAddressInfo", {
stakeAddress: "stake1ux...",
});
```
Returns information about a Cardano stake address including:
- Total balance delegated
- Rewards balance
- Withdrawal history
- Delegation information
- Recent rewards
#### 2. Get Pool Information
```javascript
const response = await server.call("getCardanoPoolInfo", {
poolId: "pool1...",
});
```
Returns detailed information about a specific stake pool:
- Pool metadata (name, ticker, description)
- Performance metrics
- Stake and delegator information
- Reward details
#### 3. List Top Stake Pools
```javascript
const response = await server.call("listCardanoTopPools", {
count: 10, // Optional, default is 10
});
```
Returns a list of the top stake pools by live stake, including key metrics for each pool.
### Asset Operations
#### 1. Get Asset Information
```javascript
const response = await server.call("getCardanoAssetInfo", {
assetId: "asset1...",
});
```
Returns detailed information about a Cardano native asset:
- Asset policy and name
- Total supply
- Mint/burn history
- Metadata
- Top addresses holding the asset
### Transaction Operations
#### 1. Get Transaction Information
```javascript
const response = await server.call("getCardanoTransactionInfo", {
txHash: "5c7...",
});
```
Returns detailed information about a Cardano transaction:
- Basic transaction details (block, slot, fees)
- Inputs and outputs
- Metadata
- Validity interval
## Error Handling
All tools return a standardized error format if they fail:
```javascript
{
content: [
{
type: "text",
text: "Failed to [operation]: [error message]"
}
]
}
```
## Advanced Usage
For more advanced Cardano operations that go beyond querying blockchain data, such as:
1. Creating and submitting transactions
2. Minting native tokens
3. Working with smart contracts
You may need to extend the functionality by implementing additional tools that use:
- [Cardano Serialization Library](https://github.com/Emurgo/cardano-serialization-lib)
- [Mesh.js](https://meshjs.dev/) for client-side operations
## References
- [Blockfrost Documentation](https://docs.blockfrost.io/)
- [Cardano Developer Portal](https://developers.cardano.org/)
- [Mesh.js Documentation](https://meshjs.dev/)

View File

@@ -20,11 +20,12 @@
"rpc", "rpc",
"blockchain", "blockchain",
"xrp", "xrp",
"ripple" "ripple",
"cardano"
], ],
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"description": "Blockchain RPC Model-Context-Protocol Server for Solana, EVM chains, UTXO chains, Ripple (XRP) and more", "description": "Blockchain RPC Model-Context-Protocol Server for Solana, EVM chains, UTXO chains, Ripple (XRP), Cardano and more",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/akc2267/web3-mcp.git" "url": "https://github.com/akc2267/web3-mcp.git"
@@ -66,12 +67,14 @@
"@xchainjs/xchain-thorchain-query": "^1.0.13", "@xchainjs/xchain-thorchain-query": "^1.0.13",
"@xchainjs/xchain-util": "^1.0.8", "@xchainjs/xchain-util": "^1.0.8",
"bchaddrjs": "^0.5.2", "bchaddrjs": "^0.5.2",
"bip39": "^3.1.0",
"bitcoinjs-lib": "^6.1.5", "bitcoinjs-lib": "^6.1.5",
"bs58": "^5.0.0", "bs58": "^5.0.0",
"cross-fetch": "^4.0.0", "cross-fetch": "^4.0.0",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"ethers": "^6.11.1", "ethers": "^6.11.1",
"fetch-retry": "^5.0.6", "fetch-retry": "^5.0.6",
"lucid-cardano": "^0.10.11",
"ripple-keypairs": "^1.3.1", "ripple-keypairs": "^1.3.1",
"wif": "^4.0.0", "wif": "^4.0.0",
"xrpl": "^2.14.0", "xrpl": "^2.14.0",
@@ -81,4 +84,4 @@
"@types/node": "^22.13.1", "@types/node": "^22.13.1",
"typescript": "^5.7.3" "typescript": "^5.7.3"
} }
} }

657
src/chains/UTXO/cardano.ts Normal file
View File

@@ -0,0 +1,657 @@
import { z } from "zod";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import fetch from 'cross-fetch';
import { Lucid, Blockfrost, fromText } from 'lucid-cardano';
import * as dotenv from 'dotenv';
import { resolve } from 'path';
import { fileURLToPath } from 'url';
import { existsSync } from 'fs';
// Type definitions
export interface CardanoToken {
unit: string;
name: string;
quantity: string;
}
export interface CardanoWalletInfo {
address: string;
utxoCount: number;
ada: string;
tokens: CardanoToken[];
}
export interface CardanoAdaTransactionResult {
txHash: string;
senderAddress: string;
recipientAddress: string;
amount: number;
links: {
explorer: string;
};
}
export interface CardanoTokenTransactionResult {
txHash: string;
senderAddress: string;
recipientAddress: string;
token: {
policyId: string;
name: string;
amount: string;
};
ada: string;
links: {
explorer: string;
};
}
// Direct .env loading for debugging
const __filename = fileURLToPath(import.meta.url);
const projectRoot = resolve(__filename, '../../../..');
const envPath = resolve(projectRoot, '.env');
// Load env file directly
if (existsSync(envPath)) {
const result = dotenv.config({ path: envPath });
if (result.error) {
console.error('Error loading .env file:', result.error);
}
}
// Configuration from environment variables
const BLOCKFROST_API_KEY = process.env.BLOCKFROST_API_KEY || '';
const CARDANO_NETWORK = process.env.CARDANO_NETWORK || 'mainnet';
const BLOCKFROST_BASE_URL = `https://cardano-${CARDANO_NETWORK}.blockfrost.io/api/v0`;
const CARDANO_MNEMONIC = process.env.CARDANO_MNEMONIC || '';
const CARDANO_ACCOUNT_INDEX = parseInt(process.env.CARDANO_ACCOUNT_INDEX || '0');
// Helper function to make Blockfrost API requests
async function blockfrostRequest<T>(endpoint: string): Promise<T> {
const url = `${BLOCKFROST_BASE_URL}${endpoint}`;
try {
const headers = {
'project_id': BLOCKFROST_API_KEY
};
const response = await fetch(url, {
method: 'GET',
headers: headers
});
if (!response.ok) {
let errorText = await response.text();
throw new Error(`API error ${response.status}: ${errorText}`);
}
return await response.json() as T;
} catch (error) {
throw error;
}
}
// Helper function to format ADA amounts
function lovelaceToAda(lovelace: string | number): string {
return (parseInt(String(lovelace)) / 1000000).toFixed(6);
}
// Helper function to format asset name
function formatAssetName(name: string): string {
try {
// If the name is in hex, try to convert it to ASCII
if (/^[0-9a-fA-F]+$/.test(name)) {
return Buffer.from(name, 'hex').toString('utf8');
}
return name;
} catch (e) {
return name; // Return original if conversion fails
}
}
// Initialize Lucid instance
async function initLucid() {
// Map network name to Lucid network name
let network: 'Mainnet' | 'Preprod' | 'Preview';
if (CARDANO_NETWORK === 'mainnet') {
network = 'Mainnet';
} else if (['testnet', 'preprod'].includes(CARDANO_NETWORK)) {
network = 'Preprod';
} else if (CARDANO_NETWORK === 'preview') {
network = 'Preview';
} else {
network = 'Mainnet';
}
try {
// Check for required configurations
if (!BLOCKFROST_API_KEY) {
throw new Error('BLOCKFROST_API_KEY is required in .env file');
}
if (!CARDANO_MNEMONIC) {
throw new Error('CARDANO_MNEMONIC is required in .env file');
}
// Create Lucid instance
const provider = new Blockfrost(
`https://cardano-${CARDANO_NETWORK}.blockfrost.io/api/v0`,
BLOCKFROST_API_KEY
);
const lucid = await Lucid.new(provider, network);
// Load wallet from mnemonic
try {
// Trim mnemonic and check for valid words
const trimmedMnemonic = CARDANO_MNEMONIC.trim();
const words = trimmedMnemonic.split(/\s+/);
// Check if word count is valid (should be 15 or 24 for Cardano)
if (words.length !== 15 && words.length !== 24) {
throw new Error(`Invalid mnemonic: Expected 15 or 24 words, got ${words.length}`);
}
// Select wallet from seed
lucid.selectWalletFromSeed(trimmedMnemonic, { accountIndex: CARDANO_ACCOUNT_INDEX });
// Verify wallet was loaded correctly by checking address
const address = await lucid.wallet.address();
if (!address) {
throw new Error('Failed to derive address from mnemonic');
}
return lucid;
} catch (error) {
throw new Error(`Failed to load wallet: ${error instanceof Error ? error.message : String(error)}`);
}
} catch (error) {
throw new Error(`Failed to initialize Lucid: ${error instanceof Error ? error.message : String(error)}`);
}
}
// Get wallet info
export async function getWalletInfo(): Promise<CardanoWalletInfo> {
try {
// Initialize Lucid with wallet
const lucid = await initLucid();
// Get wallet address
const address = await lucid.wallet.address();
// Get UTXOs
const utxos = await lucid.wallet.getUtxos();
// Calculate balance
let adaBalance = '0';
let tokenBalances: CardanoToken[] = [];
if (utxos.length > 0) {
// Combine all UTXOs to get total balance
const value = utxos.reduce(
(acc, utxo) => acc.add(utxo.assets),
// @ts-ignore: Lucid types are not fully compatible
lucid.newValue()
).assets;
// Extract ADA balance
adaBalance = value.lovelace ? lovelaceToAda(value.lovelace) : '0';
// Extract token balances
for (const [unit, quantity] of Object.entries(value)) {
if (unit === 'lovelace') continue;
try {
// Try to get a readable name for the token
// @ts-ignore: Lucid types are not fully compatible
const { policyId, assetName } = lucid.utils.fromUnit(unit);
const displayName = assetName ?
// @ts-ignore: Lucid types are not fully compatible
(lucid.utils.toText(assetName) || formatAssetName(assetName)) :
`${policyId.substring(0, 8)}...`;
tokenBalances.push({
unit,
name: displayName,
quantity: (quantity as bigint).toString()
});
} catch (e) {
tokenBalances.push({
unit,
name: unit,
quantity: (quantity as bigint).toString()
});
}
}
}
return {
address,
utxoCount: utxos.length,
ada: adaBalance,
tokens: tokenBalances
};
} catch (error) {
throw error;
}
}
// Send ADA transaction
export async function sendAda(
recipientAddress: string,
amountAda: number,
metadata: any = null
): Promise<CardanoAdaTransactionResult> {
try {
// Validate input
if (!recipientAddress) {
throw new Error('Recipient address is required');
}
if (typeof amountAda !== 'number' || amountAda <= 0) {
throw new Error('Amount must be a positive number');
}
// Initialize Lucid with wallet
const lucid = await initLucid();
// Get sender address for return value
const senderAddress = await lucid.wallet.address();
// Validate recipient address
try {
// @ts-ignore: Lucid types are not fully compatible
lucid.utils.getAddressDetails(recipientAddress);
} catch (error) {
throw new Error(`Invalid recipient address: ${error instanceof Error ? error.message : String(error)}`);
}
// Convert ADA to lovelace
const lovelaceAmount = BigInt(Math.floor(amountAda * 1000000));
// Build transaction
// @ts-ignore: Lucid types are not fully compatible
let tx = lucid.newTx()
.payToAddress(recipientAddress, { lovelace: lovelaceAmount });
// Add metadata if provided
if (metadata) {
const parsedMetadata = typeof metadata === 'string' ? JSON.parse(metadata) : metadata;
// @ts-ignore: Lucid types are not fully compatible
tx = tx.attachMetadata(674, parsedMetadata);
}
// Complete the transaction (adds inputs and change output)
// @ts-ignore: Lucid types are not fully compatible
tx = await tx.complete();
// Sign the transaction
// @ts-ignore: Lucid types are not fully compatible
const signedTx = await tx.sign().complete();
// Submit the transaction
const txHash = await signedTx.submit();
return {
txHash,
senderAddress,
recipientAddress,
amount: amountAda,
links: {
explorer: `https://cardanoscan.io/transaction/${txHash}`
}
};
} catch (error) {
throw error;
}
}
// Send tokens transaction
export async function sendTokens(
recipientAddress: string,
policyId: string,
assetName: string,
amount: string,
adaAmount: number | null = null
): Promise<CardanoTokenTransactionResult> {
try {
// Validate input
if (!recipientAddress) {
throw new Error('Recipient address is required');
}
if (!policyId) {
throw new Error('Policy ID is required');
}
if (!amount || isNaN(Number(amount)) || Number(amount) <= 0) {
throw new Error('Amount must be a positive number');
}
// Initialize Lucid with wallet
const lucid = await initLucid();
// Get sender address for return value
const senderAddress = await lucid.wallet.address();
// Validate recipient address
try {
// @ts-ignore: Lucid types are not fully compatible
lucid.utils.getAddressDetails(recipientAddress);
} catch (error) {
throw new Error(`Invalid recipient address: ${error instanceof Error ? error.message : String(error)}`);
}
// Convert asset name to hex if it's in readable format
let assetNameHex = assetName;
if (assetName && !/^[0-9a-fA-F]+$/.test(assetName)) {
assetNameHex = fromText(assetName);
}
// Create the unit identifier
const unit = `${policyId}${assetNameHex}`;
// Create the assets object
const assets = { [unit]: BigInt(amount) };
// Calculate minimum required ADA
// @ts-ignore: Lucid types are not fully compatible
const minLovelace = await lucid.utils.minAdaRequired({
outputs: [{ address: recipientAddress, assets }]
});
// Use provided ADA amount or minimum required
const outputLovelace = adaAmount
? BigInt(Math.floor(adaAmount * 1000000))
: minLovelace;
// Check if we're sending at least the minimum required
if (outputLovelace < minLovelace) {
throw new Error(`Minimum ${lovelaceToAda(minLovelace)} ADA required to send these tokens`);
}
// Build transaction
// @ts-ignore: Lucid types are not fully compatible
let tx = lucid.newTx()
.payToAddress(recipientAddress, {
lovelace: outputLovelace,
...assets
});
// Complete the transaction (adds inputs and change output)
// @ts-ignore: Lucid types are not fully compatible
tx = await tx.complete();
// Sign the transaction
// @ts-ignore: Lucid types are not fully compatible
const signedTx = await tx.sign().complete();
// Submit the transaction
const txHash = await signedTx.submit();
// Format asset name for display
const displayAssetName = assetName ? formatAssetName(assetNameHex) : '';
return {
txHash,
senderAddress,
recipientAddress,
token: {
policyId,
name: displayAssetName,
amount
},
ada: lovelaceToAda(outputLovelace),
links: {
explorer: `https://cardanoscan.io/transaction/${txHash}`
}
};
} catch (error) {
throw error;
}
}
// Interface for Cardano amount
interface CardanoAmount {
unit: string;
quantity: string;
}
// Interface for address info
interface AddressInfo {
address: string;
amount: string;
stake_address: string | null;
type: string;
script: boolean;
}
export function registerCardanoTools(server: McpServer) {
// Get Address Balance
server.tool(
"getCardanoAddressBalance",
"Get balance and token holdings for a Cardano address",
{
address: z.string().describe("Cardano address to check"),
},
async ({ address }) => {
try {
// Get address info
const addressInfo = await blockfrostRequest<AddressInfo>(`/addresses/${address}`);
// Get address UTXOs to get token info
interface CardanoUtxo {
tx_hash: string;
tx_index: number;
output_index: number;
amount: CardanoAmount[];
block: string;
}
const utxos = await blockfrostRequest<CardanoUtxo[]>(`/addresses/${address}/utxos`);
// Extract all tokens from UTXOs
const tokenMap = new Map<string, bigint>();
utxos.forEach(utxo => {
utxo.amount.forEach(asset => {
const currentAmount = tokenMap.get(asset.unit) || BigInt(0);
tokenMap.set(asset.unit, currentAmount + BigInt(asset.quantity));
});
});
// Get ADA balance
const adaBalance = tokenMap.get('lovelace') || BigInt(addressInfo.amount);
// Remove lovelace from token map for separate display
tokenMap.delete('lovelace');
// Convert token map to array for sorting and formatting
const tokens = Array.from(tokenMap.entries()).map(([unit, quantity]) => {
// Split into policy ID and asset name
const policyId = unit.slice(0, 56);
const assetName = unit.slice(56);
const formattedName = formatAssetName(assetName);
return {
unit,
policyId,
assetName: formattedName,
quantity: quantity.toString()
};
});
// Sort tokens by quantity (descending)
tokens.sort((a, b) => (BigInt(b.quantity) - BigInt(a.quantity)) > 0n ? 1 : -1);
// Format token list for display
const tokenList = tokens.length > 0
? tokens.map(token =>
`${token.quantity} ${token.assetName || token.unit} (Policy: ${token.policyId.substring(0, 8)}...)`
).join('\n')
: 'No tokens found';
// Build response
return {
content: [
{
type: "text",
text: `Cardano Address Balance for ${address}:
ADA Balance: ${lovelaceToAda(adaBalance.toString())} ADA
Stake Address: ${addressInfo.stake_address || 'Not staked'}
Address Type: ${addressInfo.script ? 'Script' : 'Key-based'}
${tokens.length > 0 ? `Token Holdings (${tokens.length}):\n${tokenList}` : 'No token holdings found'}`,
},
],
}
} catch (err) {
const error = err as Error
return {
content: [{
type: "text",
text: `Failed to retrieve Cardano address balance: ${error.message}`
}],
}
}
}
)
// Get Cardano Wallet Info
server.tool(
"getCardanoWalletInfo",
"Get the current wallet information, including balance and tokens",
{},
async () => {
try {
// Get wallet info
const walletInfo = await getWalletInfo();
// Format token list
const tokenList = walletInfo.tokens.length > 0
? walletInfo.tokens.map((token: CardanoToken) => `${token.quantity} ${token.name}`).join('\n')
: 'No tokens found';
return {
content: [
{
type: "text",
text: `# Cardano Wallet Information
Address: ${walletInfo.address}
ADA Balance: ${walletInfo.ada} ADA
UTXO Count: ${walletInfo.utxoCount}
${walletInfo.tokens.length > 0 ? `## Token Holdings (${walletInfo.tokens.length}):\n${tokenList}` : 'No token holdings found'}`
},
],
};
} catch (err) {
const error = err as Error;
return {
content: [
{
type: "text",
text: `Failed to get wallet information: ${error.message}\n\n**Troubleshooting Tips:**\n1. Make sure you have a valid 15 or 24-word Cardano mnemonic in your .env file\n2. Verify your Blockfrost API key is correct and has sufficient access rights\n3. Check the console logs for detailed error information`
},
],
};
}
}
)
// Send ADA Transaction
server.tool(
"sendCardanoAda",
"Send ADA from your wallet to a recipient address",
{
recipientAddress: z.string().describe("Recipient Cardano address"),
amount: z.number().min(1).describe("Amount of ADA to send"),
metadata: z.optional(z.string()).describe("Optional transaction metadata in JSON format")
},
async ({ recipientAddress, amount, metadata }) => {
try {
// Call sendAda
const result = await sendAda(recipientAddress, amount, metadata);
return {
content: [
{
type: "text",
text: `# ADA Transaction Successful
Transaction Hash: ${result.txHash}
From: ${result.senderAddress}
To: ${result.recipientAddress}
Amount: ${result.amount} ADA
[View on Explorer](${result.links.explorer})`
},
],
};
} catch (err) {
const error = err as Error;
return {
content: [
{
type: "text",
text: `Failed to send ADA: ${error.message}\n\n**Troubleshooting Tips:**\n1. Make sure you have a valid 15 or 24-word Cardano mnemonic in your .env file\n2. Verify your Blockfrost API key is correct and has sufficient access rights\n3. Check that your wallet has sufficient balance\n4. Verify the recipient address is correct\n5. Check the console logs for detailed error information`
},
],
};
}
}
)
// Send Tokens Transaction
server.tool(
"sendCardanoTokens",
"Send Cardano native tokens from your wallet to a recipient address",
{
recipientAddress: z.string().describe("Recipient Cardano address"),
policyId: z.string().describe("Token policy ID"),
assetName: z.string().describe("Asset name (can be empty for policy-only tokens)"),
amount: z.string().describe("Amount of tokens to send"),
adaAmount: z.optional(z.number()).describe("Optional amount of ADA to send with tokens (will use minimum required if not specified)")
},
async ({ recipientAddress, policyId, assetName, amount, adaAmount }) => {
try {
// Call sendTokens
const result = await sendTokens(recipientAddress, policyId, assetName, amount, adaAmount);
return {
content: [
{
type: "text",
text: `# Token Transaction Successful
Transaction Hash: ${result.txHash}
From: ${result.senderAddress}
To: ${result.recipientAddress}
Token Details:
- Policy ID: ${result.token.policyId}
- Asset Name: ${result.token.name || '(none)'}
- Amount: ${result.token.amount}
Included ADA: ${result.ada} ADA
[View on Explorer](${result.links.explorer})`
},
],
};
} catch (err) {
const error = err as Error;
return {
content: [
{
type: "text",
text: `Failed to send tokens: ${error.message}\n\n**Troubleshooting Tips:**\n1. Make sure you have a valid 15 or 24-word Cardano mnemonic in your .env file\n2. Verify your Blockfrost API key is correct and has sufficient access rights\n3. Check that your wallet has sufficient ADA balance for the transaction\n4. Verify the policy ID and asset name are correct\n5. Verify the recipient address is correct\n6. Check the console logs for detailed error information`
},
],
};
}
}
)
}

View File

@@ -1,4 +1,8 @@
export { registerBitcoinTools } from './bitcoin.js'; export { registerBitcoinTools } from './bitcoin.js';
export { registerLitecoinTools } from './litecoin.js'; export { registerLitecoinTools } from './litecoin.js';
export { registerDogecoinTools } from './dogecoin.js'; export { registerDogecoinTools } from './dogecoin.js';
export { registerBitcoinCashTools } from './bitcoincash.js'; export { registerBitcoinCashTools } from './bitcoincash.js';
export { registerCardanoTools, getWalletInfo, sendAda, sendTokens } from './cardano.js';
// Export Cardano types
export type { CardanoWalletInfo, CardanoToken, CardanoAdaTransactionResult, CardanoTokenTransactionResult } from './cardano.js';

View File

@@ -3,6 +3,7 @@ import { registerBitcoinTools } from "./UTXO/bitcoin"
import { registerLitecoinTools } from "./UTXO/litecoin" import { registerLitecoinTools } from "./UTXO/litecoin"
import { registerDogecoinTools } from "./UTXO/dogecoin" import { registerDogecoinTools } from "./UTXO/dogecoin"
import { registerBitcoinCashTools } from "./UTXO/bitcoincash" import { registerBitcoinCashTools } from "./UTXO/bitcoincash"
import { registerCardanoTools } from "./UTXO/cardano"
import { registerThorchainTools } from "./thorchain/thorchain" import { registerThorchainTools } from "./thorchain/thorchain"
import { registerRippleTools } from "./ripple/ripple" import { registerRippleTools } from "./ripple/ripple"
@@ -12,6 +13,7 @@ export function registerUtxoTools(server: McpServer) {
registerLitecoinTools(server) registerLitecoinTools(server)
registerDogecoinTools(server) registerDogecoinTools(server)
registerBitcoinCashTools(server) registerBitcoinCashTools(server)
registerCardanoTools(server)
} }
// Export all tools registration function // Export all tools registration function

View File

@@ -29,6 +29,7 @@ import {
registerLitecoinTools, registerLitecoinTools,
registerDogecoinTools, registerDogecoinTools,
registerBitcoinCashTools, registerBitcoinCashTools,
registerCardanoTools,
} from "./chains/UTXO/index.js"; } from "./chains/UTXO/index.js";
import { registerThorchainTools } from "./chains/thorchain/thorchain.js"; import { registerThorchainTools } from "./chains/thorchain/thorchain.js";
import { registerRippleTools } from "./chains/ripple/ripple.js"; import { registerRippleTools } from "./chains/ripple/ripple.js";
@@ -78,6 +79,11 @@ if (isEnabled('ENABLE_BITCOINCASH_TOOLS')) {
registerBitcoinCashTools(server); registerBitcoinCashTools(server);
} }
if (isEnabled('ENABLE_CARDANO_TOOLS')) {
console.error('Registering Cardano tools...');
registerCardanoTools(server);
}
if (isEnabled('ENABLE_THORCHAIN_TOOLS')) { if (isEnabled('ENABLE_THORCHAIN_TOOLS')) {
console.error('Registering THORChain tools...'); console.error('Registering THORChain tools...');
registerThorchainTools(server); registerThorchainTools(server);

View File

@@ -8,7 +8,10 @@
"strict": true, "strict": true,
"skipLibCheck": true, "skipLibCheck": true,
"outDir": "build", "outDir": "build",
"rootDir": "src" "rootDir": "src",
"allowJs": true,
"checkJs": false,
"resolveJsonModule": true
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "build"] "exclude": ["node_modules", "build"]