Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions pr-description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# ERC721 NFT Support for Base MCP Server

## Overview
This PR adds comprehensive ERC721 (NFT) functionality to the MCP server, enabling interaction with any NFT collection on the Base chain. The implementation follows the same robust pattern as our existing ERC20 tools while adapting to the unique characteristics of non-fungible tokens.

## New Features

### 1. ERC721 Balance Check
- **Tool Name**: `erc721_balance`
- **Purpose**: Query the number of NFTs owned by an address from any ERC721 collection
- **Input Parameters**:
- `contractAddress`: The ERC721 collection's contract address
- **Output**: Returns the total number of NFTs owned by the querying address
- **Example Use Case**: Checking how many NFTs a user owns from a specific collection

### 2. ERC721 Transfer
- **Tool Name**: `erc721_transfer`
- **Purpose**: Transfer a specific NFT to another address
- **Input Parameters**:
- `contractAddress`: The ERC721 collection's contract address
- `toAddress`: The recipient's address
- `tokenId`: The specific NFT's unique identifier
- **Output**: Returns transaction hash and BaseScan URL for verification
- **Example Use Case**: Transferring a specific NFT to another wallet

## Technical Implementation

### Contract Integration
- Uses the standard ERC721 ABI from viem
- Supports all ERC721-compliant contracts on Base chain
- Implements proper address validation for both contract and recipient addresses

### Security Features
- Input validation using Zod schemas
- Address format verification
- Transaction simulation before execution
- Proper error handling for invalid inputs

### Chain Support
- Currently implemented for Base chain
- Uses BaseScan for transaction verification
- Ready for potential multi-chain expansion

## Testing
- All new functionality has been tested with:
- Valid contract addresses
- Invalid contract addresses
- Valid/invalid recipient addresses
- Various token IDs

## Usage Examples

### Checking NFT Balance
```typescript
const result = await erc721BalanceHandler(wallet, {
contractAddress: "0x..." // NFT collection address
});
// Returns: "5" (if user owns 5 NFTs from this collection)
```

### Transferring an NFT
```typescript
const result = await erc721TransferHandler(wallet, {
contractAddress: "0x...", // NFT collection address
toAddress: "0x...", // Recipient address
tokenId: "123" // Specific NFT to transfer
});
// Returns: { hash: "0x...", url: "https://basescan.org/..." }
```

## Future Enhancements
1. Add support for NFT metadata queries
2. Implement batch transfer functionality
3. Add support for ERC721Enumerable extensions
4. Add approval management tools
5. Support for other chains beyond Base

## Breaking Changes
None. This is a purely additive feature that doesn't affect existing functionality.

## Dependencies
- viem: For blockchain interaction
- zod: For input validation
- Base chain configuration

## Related Issues
Closes #XXX (if there's a related issue)

## Checklist
- [x] Added new ERC721 tools
- [x] Implemented proper error handling
- [x] Added input validation
- [x] Added transaction simulation
- [x] Added BaseScan integration
- [x] Updated documentation
61 changes: 61 additions & 0 deletions src/tools/erc721/handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
erc721Abi,
isAddress,
type PublicActions,
type WalletClient,
} from 'viem';
import { base } from 'viem/chains';
import type { z } from 'zod';
import { constructBaseScanUrl } from '../utils/index.js';
import { Erc721BalanceSchema, Erc721TransferSchema } from './schemas.js';

export async function erc721BalanceHandler(
wallet: WalletClient & PublicActions,
args: z.infer<typeof Erc721BalanceSchema>,
): Promise<string> {
const { contractAddress } = args;

if (!isAddress(contractAddress, { strict: false })) {
throw new Error(`Invalid contract address: ${contractAddress}`);
}

const balance = await wallet.readContract({
address: contractAddress,
abi: erc721Abi,
functionName: 'balanceOf',
args: [wallet.account?.address ?? '0x'],
});

return balance.toString();
}

export async function erc721TransferHandler(
wallet: WalletClient & PublicActions,
args: z.infer<typeof Erc721TransferSchema>,
): Promise<string> {
const { contractAddress, toAddress, tokenId } = args;

if (!isAddress(contractAddress, { strict: false })) {
throw new Error(`Invalid contract address: ${contractAddress}`);
}

if (!isAddress(toAddress, { strict: false })) {
throw new Error(`Invalid to address: ${toAddress}`);
}

const tx = await wallet.simulateContract({
address: contractAddress,
abi: erc721Abi,
functionName: 'transferFrom',
args: [wallet.account?.address ?? '0x', toAddress, BigInt(tokenId)],
account: wallet.account,
chain: wallet.chain,
});

const txHash = await wallet.writeContract(tx.request);

return JSON.stringify({
hash: txHash,
url: constructBaseScanUrl(wallet.chain ?? base, txHash),
});
}
17 changes: 17 additions & 0 deletions src/tools/erc721/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { generateTool } from '../../utils.js';
import { erc721BalanceHandler, erc721TransferHandler } from './handlers.js';
import { Erc721BalanceSchema, Erc721TransferSchema } from './schemas.js';

export const erc721BalanceTool = generateTool({
name: 'erc721_balance',
description: 'Get the balance of NFTs owned by an address',
inputSchema: Erc721BalanceSchema,
toolHandler: erc721BalanceHandler,
});

export const erc721TransferTool = generateTool({
name: 'erc721_transfer',
description: 'Transfer an NFT to another address',
inputSchema: Erc721TransferSchema,
toolHandler: erc721TransferHandler,
});
15 changes: 15 additions & 0 deletions src/tools/erc721/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { z } from 'zod';

export const Erc721BalanceSchema = z.object({
contractAddress: z
.string()
.describe('The contract address for which to get the NFT balance'),
});

export const Erc721TransferSchema = z.object({
contractAddress: z
.string()
.describe('The address of the NFT contract'),
toAddress: z.string().describe('The address of the recipient'),
tokenId: z.string().describe('The ID of the NFT to transfer'),
});
3 changes: 3 additions & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { callContractTool } from './contracts/index.js';
import { erc20BalanceTool, erc20TransferTool } from './erc20/index.js';
import { erc721BalanceTool, erc721TransferTool } from './erc721/index.js';
import { getMorphoVaultsTool } from './morpho/index.js';
import { getOnrampAssetsTool, onrampTool } from './onramp/index.js';
import { buyOpenRouterCreditsTool } from './open-router/index.js';
Expand All @@ -12,6 +13,8 @@ export const baseMcpTools: ToolWithHandler[] = [
onrampTool,
erc20BalanceTool,
erc20TransferTool,
erc721BalanceTool,
erc721TransferTool,
buyOpenRouterCreditsTool,
];

Expand Down