From f1e4fadeddaa829aa82fc3fd380d9a1adc1f50be Mon Sep 17 00:00:00 2001 From: cryptobeijing Date: Fri, 21 Mar 2025 15:14:41 -0400 Subject: [PATCH 1/2] feat: add ERC721 tools for NFT balance and transfer functionality --- src/tools/erc721/handlers.ts | 61 ++++++++++++++++++++++++++++++++++++ src/tools/erc721/index.ts | 17 ++++++++++ src/tools/erc721/schemas.ts | 15 +++++++++ src/tools/index.ts | 3 ++ 4 files changed, 96 insertions(+) create mode 100644 src/tools/erc721/handlers.ts create mode 100644 src/tools/erc721/index.ts create mode 100644 src/tools/erc721/schemas.ts diff --git a/src/tools/erc721/handlers.ts b/src/tools/erc721/handlers.ts new file mode 100644 index 0000000..89788e7 --- /dev/null +++ b/src/tools/erc721/handlers.ts @@ -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, +): Promise { + 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, +): Promise { + 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), + }); +} \ No newline at end of file diff --git a/src/tools/erc721/index.ts b/src/tools/erc721/index.ts new file mode 100644 index 0000000..144247f --- /dev/null +++ b/src/tools/erc721/index.ts @@ -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, +}); \ No newline at end of file diff --git a/src/tools/erc721/schemas.ts b/src/tools/erc721/schemas.ts new file mode 100644 index 0000000..b5c70c6 --- /dev/null +++ b/src/tools/erc721/schemas.ts @@ -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'), +}); \ No newline at end of file diff --git a/src/tools/index.ts b/src/tools/index.ts index b254d32..cabf12c 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -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'; @@ -12,6 +13,8 @@ export const baseMcpTools: ToolWithHandler[] = [ onrampTool, erc20BalanceTool, erc20TransferTool, + erc721BalanceTool, + erc721TransferTool, buyOpenRouterCreditsTool, ]; From 40c3d0d8b73d62df09510c1114ed4691791e5924 Mon Sep 17 00:00:00 2001 From: cryptobeijing Date: Fri, 21 Mar 2025 15:20:43 -0400 Subject: [PATCH 2/2] docs: add detailed PR description for ERC721 tools --- pr-description.md | 95 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 pr-description.md diff --git a/pr-description.md b/pr-description.md new file mode 100644 index 0000000..332f6e7 --- /dev/null +++ b/pr-description.md @@ -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 \ No newline at end of file