/** * Modern Marketplace Service for Web3.js v2 (2025) */ import type { Address } from '@solana/addresses'; import type { Rpc, SolanaRpcApi } from '@solana/rpc'; import type { Commitment } from '@solana/rpc-types'; import type { KeyPairSigner } from '@solana/signers'; // Import real instruction builders import { getCreateServiceListingInstructionAsync, type ServiceListingDataArgs } from '../generated-v2/instructions/createServiceListing'; import { getPurchaseServiceInstructionAsync, type ServicePurchaseDataArgs } from '../generated-v2/instructions/purchaseService'; import { getCreateJobPostingInstructionAsync, type JobPostingDataArgs } from '../generated-v2/instructions/createJobPosting'; import { sendAndConfirmTransactionFactory } from '../utils/transaction-sender'; /** * Marketplace listing */ export interface IMarketplaceListing { id: Address; seller: Address; agentId: Address; price: bigint; status: 'active' | 'sold' | 'cancelled'; listedAt: number; } /** * Sale transaction */ export interface ISaleTransaction { listingId: Address; buyer: Address; seller: Address; price: bigint; timestamp: number; signature: string; } /** * Advanced search filters for marketplace */ export interface IMarketplaceSearchFilters { // Price filtering minPrice?: bigint; maxPrice?: bigint; priceSort?: 'asc' | 'desc'; // Category and type filtering categories?: string[]; serviceTypes?: string[]; tags?: string[]; // Agent capabilities filtering capabilities?: number[]; minimumReputation?: number; // Time-based filtering listedAfter?: number; listedBefore?: number; sortBy?: 'price' | 'reputation' | 'created' | 'popularity'; // Seller filtering sellers?: Address[]; excludeSellers?: Address[]; // Availability and status status?: Array<'active' | 'sold' | 'cancelled'>; availableNow?: boolean; // Text search searchQuery?: string; searchFields?: Array<'title' | 'description' | 'tags'>; } /** * Search result with metadata */ export interface IMarketplaceSearchResult { listings: IMarketplaceListing[]; totalCount: number; hasMore: boolean; searchMetadata: { query: string; filters: IMarketplaceSearchFilters; executionTime: number; relevanceScores?: number[]; }; } /** * Marketplace analytics and insights */ export interface IMarketplaceAnalytics { totalListings: number; activeListings: number; totalSales: number; totalVolume: bigint; averagePrice: bigint; topCategories: Array<{ category: string; count: number }>; priceDistribution: Array<{ range: string; count: number }>; sellerStats: Array<{ seller: Address; listings: number; sales: number; volume: bigint }>; } /** * Modern Marketplace Service */ export class MarketplaceService { constructor( private readonly rpc: Rpc, private readonly _programId: Address, private readonly commitment: Commitment = 'confirmed' ) {} /** * Create a service listing using real instruction builder */ async createServiceListing( creator: KeyPairSigner, serviceListing: Address, agent: Address, listingData: ServiceListingDataArgs ): Promise<{ listingId: Address; signature: string; }> { try { console.log(`🏪 Creating service listing: ${listingData.title}`); // Use real createServiceListing instruction const instruction = await getCreateServiceListingInstructionAsync({ serviceListing, agent, creator: creator.address, listingData, }); const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({ rpc: this.rpc }); const signature = await sendAndConfirmTransaction([instruction], { signers: [creator], }); return { listingId: serviceListing, signature }; } catch (error) { throw new Error(`Service listing creation failed: ${String(error)}`); } } /** * Purchase a service using real instruction builder */ async purchaseService( buyer: KeyPairSigner, servicePurchase: Address, serviceListing: Address, purchaseData: ServicePurchaseDataArgs ): Promise { try { console.log(`💰 Purchasing service for listing: ${serviceListing}`); // Use real purchaseService instruction const instruction = await getPurchaseServiceInstructionAsync({ servicePurchase, serviceListing, buyer: buyer.address, purchaseData, }); const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({ rpc: this.rpc }); const signature = await sendAndConfirmTransaction([instruction], { signers: [buyer], }); return signature; } catch (error) { throw new Error(`Service purchase failed: ${String(error)}`); } } /** * Create a job posting using real instruction builder */ async createJobPosting( employer: KeyPairSigner, jobPosting: Address, jobData: JobPostingDataArgs ): Promise<{ jobId: Address; signature: string; }> { try { console.log(`📋 Creating job posting: ${jobData.title}`); // Use real createJobPosting instruction const instruction = await getCreateJobPostingInstructionAsync({ jobPosting, employer: employer.address, jobData, }); const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({ rpc: this.rpc }); const signature = await sendAndConfirmTransaction([instruction], { signers: [employer], }); return { jobId: jobPosting, signature }; } catch (error) { throw new Error(`Job posting creation failed: ${String(error)}`); } } /** * List an agent for sale (legacy method for compatibility) */ async listAgent( seller: KeyPairSigner, agentId: Address, price: bigint ): Promise<{ listingId: Address; signature: string; }> { try { console.log(`🏪 Listing agent for ${price} tokens`); // Create service listing for the agent const serviceListing = `listing_${Date.now()}` as Address; const listingData: ServiceListingDataArgs = { title: `Agent ${agentId}`, description: 'AI Agent for sale', price, tokenMint: '11111111111111111111111111111111' as Address, // SOL serviceType: 'agent', paymentToken: '11111111111111111111111111111111' as Address, // SOL estimatedDelivery: BigInt(0), // Immediate tags: ['agent', 'ai'], }; return await this.createServiceListing(seller, serviceListing, agentId, listingData); } catch (error) { throw new Error(`Agent listing failed: ${String(error)}`); } } /** * Purchase an agent from marketplace (legacy method for compatibility) */ async purchaseAgent( buyer: KeyPairSigner, listingId: Address ): Promise { try { console.log(`💰 Purchasing agent`); const servicePurchase = `purchase_${Date.now()}` as Address; const purchaseData: ServicePurchaseDataArgs = { listingId: BigInt(1), quantity: 1, requirements: [], customInstructions: 'Agent purchase', deadline: BigInt(Date.now() + 86400000), // 1 day }; return await this.purchaseService(buyer, servicePurchase, listingId, purchaseData); } catch (error) { throw new Error(`Agent purchase failed: ${String(error)}`); } } /** * Cancel a listing - Real implementation with proper error handling */ async cancelListing( seller: KeyPairSigner, listingId: Address ): Promise { try { console.log('❌ Cancelling marketplace listing'); // Note: The smart contract doesn't currently have a cancelListing instruction // This functionality would require extending the smart contract // For now, we verify the listing exists and provide a proper response const listingInfo = await this.rpc .getAccountInfo(listingId, { commitment: this.commitment }) .send(); if (!listingInfo.value) { throw new Error(`Listing ${listingId} does not exist`); } // In practice, this would need a new instruction in the smart contract console.log('⚠️ Cancel listing instruction not available in current smart contract'); throw new Error('Cancel listing functionality requires smart contract update'); } catch (error) { throw new Error(`Listing cancellation failed: ${String(error)}`); } } /** * Get marketplace listing */ async getListing(listingId: Address): Promise { try { const accountInfo = await this.rpc .getAccountInfo(listingId, { commitment: this.commitment, encoding: 'base64', }) .send(); if (!accountInfo.value) { return null; } // Parse account data - simplified implementation // Note: This would use the ListingAccount parser from generated-v2/accounts return { id: listingId, seller: `parsed_seller_${Date.now()}` as Address, agentId: `parsed_agent_${Date.now()}` as Address, price: BigInt(500000000), status: 'active' as const, listedAt: Date.now() - 3600000, }; } catch (error) { console.error('Failed to get listing:', error); return null; } } /** * Get all active listings */ async getActiveListings(_limit: number = 50): Promise { try { console.log('📝 Getting active marketplace listings'); // TODO: Implement real listing account enumeration await new Promise(resolve => setTimeout(resolve, 1000)); // Mock listings for now return [ { id: `listing_1` as Address, seller: `seller_1` as Address, agentId: `agent_1` as Address, price: BigInt(1000000000), status: 'active', listedAt: Date.now() - 7200000, }, { id: `listing_2` as Address, seller: `seller_2` as Address, agentId: `agent_2` as Address, price: BigInt(2000000000), status: 'active', listedAt: Date.now() - 3600000, }, ]; } catch (error) { throw new Error(`Failed to get active listings: ${String(error)}`); } } /** * Get sales history */ async getSalesHistory(_limit: number = 50): Promise { try { console.log('📊 Getting marketplace sales history'); // TODO: Implement real sales history from transaction logs await new Promise(resolve => setTimeout(resolve, 800)); // Mock sales history for now return [ { listingId: `listing_sold_1` as Address, buyer: `buyer_1` as Address, seller: `seller_1` as Address, price: BigInt(1500000000), timestamp: Date.now() - 86400000, // 1 day ago signature: `sig_sale_1`, }, { listingId: `listing_sold_2` as Address, buyer: `buyer_2` as Address, seller: `seller_2` as Address, price: BigInt(800000000), timestamp: Date.now() - 172800000, // 2 days ago signature: `sig_sale_2`, }, ]; } catch (error) { throw new Error(`Failed to get sales history: ${String(error)}`); } } /** * Get sales statistics */ async getSalesStats(): Promise<{ totalSales: number; totalVolume: bigint; averagePrice: bigint; }> { try { console.log('📈 Getting marketplace statistics'); const salesHistory = await this.getSalesHistory(1000); const totalSales = salesHistory.length; const totalVolume = salesHistory.reduce((sum, sale) => sum + sale.price, BigInt(0)); const averagePrice = totalSales > 0 ? totalVolume / BigInt(totalSales) : BigInt(0); return { totalSales, totalVolume, averagePrice, }; } catch (error) { throw new Error(`Failed to get sales statistics: ${String(error)}`); } } /** * Advanced marketplace search with comprehensive filtering */ async searchMarketplace( filters: IMarketplaceSearchFilters = {}, limit: number = 50, offset: number = 0 ): Promise { const startTime = Date.now(); try { console.log('🔍 Performing advanced marketplace search with filters:', filters); // Get all listings first (in a real implementation, this would use efficient indexing) const allListings = await this.getActiveListings(1000); // Apply filters let filteredListings = this.applyFilters(allListings, filters); // Apply sorting filteredListings = this.applySorting(filteredListings, filters); // Apply text search if provided if (filters.searchQuery) { filteredListings = this.applyTextSearch(filteredListings, filters); } // Apply pagination const totalCount = filteredListings.length; const paginatedListings = filteredListings.slice(offset, offset + limit); const executionTime = Date.now() - startTime; return { listings: paginatedListings, totalCount, hasMore: offset + limit < totalCount, searchMetadata: { query: filters.searchQuery || '', filters, executionTime, relevanceScores: filters.searchQuery ? this.calculateRelevanceScores(paginatedListings, filters.searchQuery) : undefined, }, }; } catch (error) { throw new Error(`Marketplace search failed: ${String(error)}`); } } /** * Get marketplace listings by category */ async getListingsByCategory( category: string, limit: number = 50 ): Promise { try { console.log(`📂 Getting listings for category: ${category}`); const filters: IMarketplaceSearchFilters = { categories: [category], status: ['active'], sortBy: 'created', }; const result = await this.searchMarketplace(filters, limit); return result.listings; } catch (error) { throw new Error(`Failed to get listings by category: ${String(error)}`); } } /** * Get trending listings based on popularity and recent activity */ async getTrendingListings(limit: number = 20): Promise { try { console.log('🔥 Getting trending marketplace listings'); const filters: IMarketplaceSearchFilters = { status: ['active'], sortBy: 'popularity', listedAfter: Date.now() - (7 * 24 * 60 * 60 * 1000), // Last 7 days }; const result = await this.searchMarketplace(filters, limit); return result.listings; } catch (error) { throw new Error(`Failed to get trending listings: ${String(error)}`); } } /** * Get recommended listings for a user based on their history and preferences */ async getRecommendedListings( userAddress: Address, preferences: { categories?: string[]; priceRange?: { min: bigint; max: bigint }; capabilities?: number[]; } = {}, limit: number = 20 ): Promise { try { console.log(`🎯 Getting personalized recommendations for user: ${userAddress}`); // Build recommendation filters based on user preferences const filters: IMarketplaceSearchFilters = { status: ['active'], categories: preferences.categories, capabilities: preferences.capabilities, minPrice: preferences.priceRange?.min, maxPrice: preferences.priceRange?.max, sortBy: 'reputation', excludeSellers: [userAddress], // Don't recommend user's own listings }; const result = await this.searchMarketplace(filters, limit); // In a real implementation, this would use machine learning for better recommendations return result.listings; } catch (error) { throw new Error(`Failed to get recommended listings: ${String(error)}`); } } /** * Get comprehensive marketplace analytics */ async getMarketplaceAnalytics(): Promise { try { console.log('📊 Generating comprehensive marketplace analytics'); const [allListings, salesHistory] = await Promise.all([ this.getActiveListings(1000), this.getSalesHistory(1000), ]); const activeListings = allListings.filter(l => l.status === 'active').length; const totalSales = salesHistory.length; const totalVolume = salesHistory.reduce((sum, sale) => sum + sale.price, BigInt(0)); const averagePrice = totalSales > 0 ? totalVolume / BigInt(totalSales) : BigInt(0); // Calculate category distribution const categoryMap = new Map(); allListings.forEach(listing => { // In a real implementation, listings would have category data const category = this.extractCategoryFromListing(listing); categoryMap.set(category, (categoryMap.get(category) || 0) + 1); }); const topCategories = Array.from(categoryMap.entries()) .map(([category, count]) => ({ category, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); // Calculate price distribution const priceRanges = [ { range: '0-0.1 SOL', min: 0n, max: 100000000n }, { range: '0.1-1 SOL', min: 100000000n, max: 1000000000n }, { range: '1-10 SOL', min: 1000000000n, max: 10000000000n }, { range: '10+ SOL', min: 10000000000n, max: BigInt(Number.MAX_SAFE_INTEGER) }, ]; const priceDistribution = priceRanges.map(({ range, min, max }) => ({ range, count: allListings.filter(l => l.price >= min && l.price < max).length, })); // Calculate seller statistics const sellerMap = new Map(); allListings.forEach(listing => { const seller = listing.seller; const current = sellerMap.get(seller) || { listings: 0, sales: 0, volume: 0n }; current.listings++; sellerMap.set(seller, current); }); salesHistory.forEach(sale => { const seller = sale.seller; const current = sellerMap.get(seller) || { listings: 0, sales: 0, volume: 0n }; current.sales++; current.volume += sale.price; sellerMap.set(seller, current); }); const sellerStats = Array.from(sellerMap.entries()) .map(([seller, stats]) => ({ seller: seller as Address, ...stats })) .sort((a, b) => Number(b.volume - a.volume)) .slice(0, 20); return { totalListings: allListings.length, activeListings, totalSales, totalVolume, averagePrice, topCategories, priceDistribution, sellerStats, }; } catch (error) { throw new Error(`Failed to get marketplace analytics: ${String(error)}`); } } /** * Apply filters to listings array */ private applyFilters( listings: IMarketplaceListing[], filters: IMarketplaceSearchFilters ): IMarketplaceListing[] { return listings.filter(listing => { // Price filtering if (filters.minPrice && listing.price < filters.minPrice) return false; if (filters.maxPrice && listing.price > filters.maxPrice) return false; // Status filtering if (filters.status && !filters.status.includes(listing.status)) return false; // Time-based filtering if (filters.listedAfter && listing.listedAt < filters.listedAfter) return false; if (filters.listedBefore && listing.listedAt > filters.listedBefore) return false; // Seller filtering if (filters.sellers && !filters.sellers.includes(listing.seller)) return false; if (filters.excludeSellers && filters.excludeSellers.includes(listing.seller)) return false; // In a real implementation, we would check categories, tags, capabilities, etc. // For now, we'll simulate some filtering based on listing ID patterns if (filters.categories && filters.categories.length > 0) { const hasMatchingCategory = filters.categories.some(category => listing.id.includes(category.toLowerCase()) ); if (!hasMatchingCategory) return false; } return true; }); } /** * Apply sorting to listings array */ private applySorting( listings: IMarketplaceListing[], filters: IMarketplaceSearchFilters ): IMarketplaceListing[] { const sortedListings = [...listings]; switch (filters.sortBy) { case 'price': sortedListings.sort((a, b) => { const comparison = Number(a.price - b.price); return filters.priceSort === 'desc' ? -comparison : comparison; }); break; case 'created': sortedListings.sort((a, b) => b.listedAt - a.listedAt); break; case 'popularity': // Simulate popularity sorting based on a combination of factors sortedListings.sort((a, b) => { const aScore = this.calculatePopularityScore(a); const bScore = this.calculatePopularityScore(b); return bScore - aScore; }); break; case 'reputation': // Simulate reputation-based sorting sortedListings.sort((a, b) => { const aReputation = this.getSellerReputation(a.seller); const bReputation = this.getSellerReputation(b.seller); return bReputation - aReputation; }); break; default: // Default to created date sortedListings.sort((a, b) => b.listedAt - a.listedAt); } return sortedListings; } /** * Apply text search to listings */ private applyTextSearch( listings: IMarketplaceListing[], filters: IMarketplaceSearchFilters ): IMarketplaceListing[] { if (!filters.searchQuery) return listings; const searchTerms = filters.searchQuery.toLowerCase().split(' '); const searchFields = filters.searchFields || ['title', 'description', 'tags']; return listings.filter(listing => { // In a real implementation, we would search actual listing data // For now, simulate search based on listing ID and seller const searchableContent = [ listing.id, listing.seller, listing.agentId, ].join(' ').toLowerCase(); return searchTerms.every(term => searchableContent.includes(term)); }); } /** * Calculate relevance scores for search results */ private calculateRelevanceScores( listings: IMarketplaceListing[], searchQuery: string ): number[] { // Simulate relevance scoring return listings.map(listing => { const searchableContent = [listing.id, listing.seller, listing.agentId].join(' ').toLowerCase(); const queryTerms = searchQuery.toLowerCase().split(' '); let score = 0; queryTerms.forEach(term => { if (searchableContent.includes(term)) { score += term.length / searchableContent.length; } }); return Math.min(score, 1.0); }); } /** * Calculate popularity score for a listing */ private calculatePopularityScore(listing: IMarketplaceListing): number { // Simulate popularity calculation based on various factors const timeFactor = Math.max(0, 1 - (Date.now() - listing.listedAt) / (30 * 24 * 60 * 60 * 1000)); const priceFactor = Math.min(1, Number(listing.price) / 1000000000); // Normalize to SOL return timeFactor * 0.7 + priceFactor * 0.3; } /** * Get seller reputation (simulated) */ private getSellerReputation(seller: Address): number { // Simulate reputation based on seller address hash const hash = seller.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0); return (hash % 100) + 1; // 1-100 reputation score } /** * Extract category from listing (simulated) */ private extractCategoryFromListing(listing: IMarketplaceListing): string { // Simulate category extraction const categories = ['AI Agents', 'Data Analysis', 'Content Creation', 'Trading Bots', 'NFT Tools']; const hash = listing.id.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0); return categories[hash % categories.length]; } }