Skip to main content
Glama

Hashkey MCP Server

Official
by HashkeyHSK
utils.ts•5.93 kB
import { erc721Abi as viem_erc721Abi } from 'viem'; import type { PublicActions } from 'viem'; import { erc1155Abi } from '../../lib/contracts/erc1155.js'; import type { FetchNftsParams, FormattedNft, NftData, TransferNftParams, } from './types.js'; // Extend viem's ERC721 ABI with supportsInterface function const erc721Abi = [ ...viem_erc721Abi, { inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], name: 'supportsInterface', outputs: [{ internalType: 'bool', name: '', type: 'bool' }], stateMutability: 'view', type: 'function', }, ] as const; /** * Format NFT data from Alchemy API into a more usable format * @param nftData The raw NFT data from Alchemy API * @returns Formatted NFT data */ export function formatNftData({ nftData, }: { nftData: Record<string, unknown>; }): Array<FormattedNft> { if (!nftData || !nftData.ownedNfts || !Array.isArray(nftData.ownedNfts)) { return []; } const ownedNfts = nftData.ownedNfts as Array<NftData>; return ownedNfts.map((nft) => { return { contractAddress: nft.contract?.address || '', tokenId: nft.tokenId || nft.id?.tokenId || '', title: nft.title || nft.name || 'Unnamed NFT', description: nft.description || '', tokenType: nft.tokenType || 'UNKNOWN', imageUrl: nft.media?.[0]?.gateway || nft.media?.[0]?.raw || nft.image || '', metadata: nft.metadata || {}, }; }); } /** * Detect if a contract is ERC721 or ERC1155 by checking if it supports the respective interface ID * @param wallet Viem wallet client with public actions * @param contractAddress The contract address to check * @returns The detected NFT standard or "UNKNOWN" */ export async function detectNftStandard( wallet: PublicActions, contractAddress: `0x${string}`, ): Promise<'ERC721' | 'ERC1155' | 'UNKNOWN'> { try { // ERC721 interface ID: 0x80ac58cd const isErc721 = await wallet.readContract({ address: contractAddress, abi: erc721Abi, functionName: 'supportsInterface', args: ['0x80ac58cd'], }); if (isErc721) { return 'ERC721'; } // ERC1155 interface ID: 0xd9b67a26 const isErc1155 = await wallet.readContract({ address: contractAddress, abi: erc1155Abi, functionName: 'supportsInterface', args: ['0xd9b67a26'], }); if (isErc1155) { return 'ERC1155'; } return 'UNKNOWN'; } catch (error) { console.error( `Error detecting NFT standard for ${contractAddress}:`, error, ); return 'UNKNOWN'; } } /** * Helper function to fetch NFTs from Alchemy API * @param ownerAddress The address to fetch NFTs for * @param limit Maximum number of NFTs to fetch * @returns The NFT data from Alchemy API */ export async function fetchNftsFromAlchemy({ ownerAddress, limit = 50, }: FetchNftsParams): Promise<Record<string, unknown>> { // Access environment variables safely const apiKey = typeof process !== 'undefined' ? process.env.ALCHEMY_API_KEY : undefined; if (!apiKey) { throw new Error('ALCHEMY_API_KEY is not set in environment variables'); } try { const baseUrl = 'https://base-mainnet.g.alchemy.com/nft/v3'; const url = `${baseUrl}/${apiKey}/getNFTsForOwner?owner=${ownerAddress}&withMetadata=true&pageSize=${limit}`; const response = await fetch(url); if (!response.ok) { const errorText = await response.text(); console.error(`Alchemy API error (${response.status}): ${errorText}`); throw new Error( `Alchemy API error: ${response.status} ${response.statusText}`, ); } const data = await response.json(); return data; } catch (error) { console.error(`Error fetching NFTs from Alchemy:`, error); throw error; } } /** * Transfer an NFT from one address to another * @param wallet Wallet client with public actions * @param contractAddress Address of the NFT contract * @param tokenId ID of the token to transfer * @param toAddress Address to transfer the NFT to * @param amount Amount of tokens to transfer (for ERC1155) * @returns Transaction hash */ export async function transferNft({ wallet, contractAddress, tokenId, toAddress, amount = '1', }: TransferNftParams): Promise<`0x${string}`> { try { // Detect the NFT standard const nftStandard = await detectNftStandard(wallet, contractAddress); if (nftStandard === 'UNKNOWN') { throw new Error( `Contract at ${contractAddress} does not implement a supported NFT standard`, ); } // Get the wallet address const [fromAddress] = await wallet.getAddresses(); // Convert values to the correct format const tokenIdBigInt = BigInt(tokenId); const amountBigInt = BigInt(amount); let hash: `0x${string}`; if (nftStandard === 'ERC721') { // Transfer ERC721 NFT hash = await wallet.writeContract({ address: contractAddress, abi: erc721Abi, functionName: 'safeTransferFrom', args: [fromAddress, toAddress, tokenIdBigInt], chain: null, account: fromAddress, }); } else { // Transfer ERC1155 NFT hash = await wallet.writeContract({ address: contractAddress, abi: erc1155Abi, functionName: 'safeTransferFrom', args: [fromAddress, toAddress, tokenIdBigInt, amountBigInt, '0x'], chain: null, account: fromAddress, }); } // Ensure the hash is in the correct format if (!hash.startsWith('0x')) { throw new Error(`Invalid transaction hash format: ${hash}`); } return hash; } catch (error) { console.error( `Error transferring NFT ${tokenId} from contract ${contractAddress}:`, error, ); throw new Error( `Failed to transfer NFT: ${error instanceof Error ? error.message : String(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/HashkeyHSK/hsk-mcp'

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