Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.vibesflow.ai/llms.txt

Use this file to discover all available pages before exploring further.

Vibe Market

The Vibe Market is the discovery and playback interface for all stored vibestreams. It integrates with FilCDN for content delivery and implements subscription-based access control for Metis users.

Architecture Overview

FilCDN Integration

Context Provider

The Vibe Market relies on the FilCDN context for vibestream data:
// FilCDN context usage in VibeMarket.tsx
const { 
  vibestreams, 
  loading, 
  error, 
  isConnected,
  networkType,
  currentAddress,
  refreshVibestreams 
} = useFilCDN();

Data Loading

// Load vibestreams when connected
useEffect(() => {
  if (isConnected || vibestreams.length === 0) {
    refreshVibestreams();
  }
}, [isConnected]);

URL Construction

FilCDN URLs are constructed with provider wallet addresses:
// FilCDN URL construction in context/filcdn.tsx
const constructFilCDNUrl = (cid, providerAddress) => {
  const address = providerAddress || 
    process.env.EXPO_PUBLIC_FILCDN_PROVIDER_ADDRESS;
  
  // Use calibration network
  return `https://${address}.calibration.filcdn.io/${cid}`;
};

// Chunk transformation with FilCDN support
const transformedData = data.map((vibestream) => ({
  ...vibestream,
  chunks_detail: vibestream.chunks_detail?.map((chunk) => {
    if (!chunk.cid) return chunk;
    
    const filcdnUrl = constructFilCDNUrl(chunk.cid);
    
    return {
      ...chunk,
      url: `${backendUrl}/api/proxy/${chunk.cid}`, // Backend fallback
      filcdn_url: filcdnUrl, // Direct FilCDN URL
    };
  }) || []
}));

Subscription System

Network-Based Access Control

// Subscription status checking in VibeMarket.tsx
useEffect(() => {
  const checkSubscriptionStatus = async () => {
    if (!connected || !account) {
      setSubscriptionStatus({
        isSubscribed: false,
        isLoading: false,
        requiresSubscription: false
      });
      return;
    }

    const networkInfo = getNetworkInfo();
    
    // Only check subscription for Metis Hyperion network
    if (networkInfo?.type === 'metis-hyperion') {
      setSubscriptionStatus(prev => ({ 
        ...prev, 
        isLoading: true, 
        requiresSubscription: true 
      }));
      
      try {
        const isSubscribed = await isUserSubscribed();
        setSubscriptionStatus({
          isSubscribed,
          isLoading: false,
          requiresSubscription: true
        });
      } catch (error) {
        console.error('❌ Failed to check subscription status:', error);
        setSubscriptionStatus({
          isSubscribed: false,
          isLoading: false,
          requiresSubscription: true
        });
      }
    } else {
      // For non-Metis networks, no subscription required
      setSubscriptionStatus({
        isSubscribed: true, // Allow access
        isLoading: false,
        requiresSubscription: false
      });
    }
  };

  checkSubscriptionStatus();
}, [connected, account, isUserSubscribed, getNetworkInfo]);

Subscription Gate UI

// Subscription gate for Metis users
{subscriptionStatus.requiresSubscription && !subscriptionStatus.isSubscribed && (
  <GlitchContainer intensity="medium" style={styles.subscriptionGate}>
    <FontAwesome5 name="lock" size={32} color={COLORS.accent} />
    <Text style={styles.gateTitle}>PREMIUM ACCESS REQUIRED</Text>
    <Text style={styles.gateSubtext}>
      Access all DJ sets ever created on Metis Hyperion for just 10 tMETIS per month!
    </Text>
    <TouchableOpacity 
      style={styles.subscribeButton}
      onPress={() => setSubscribeModalVisible(true)}
    >
      <Text style={styles.subscribeButtonText}>SUBSCRIBE NOW</Text>
    </TouchableOpacity>
  </GlitchContainer>
)}

Preview System

Audio Streaming Service

// Preview playback using AudioStreamer service
const togglePreview = useCallback(async (stream) => {
  try {
    if (player.currentRTA === stream.rta_id && player.isPlaying) {
      // Stop current preview
      if (player.stopPreview) {
        player.stopPreview();
      }
      setPlayer({
        isPlaying: false,
        currentRTA: null,
        stopPreview: null
      });
      return;
    }

    // Stop any existing preview
    if (player.stopPreview) {
      player.stopPreview();
    }

    // Get first chunk for preview
    const firstChunk = stream.chunks_detail?.[0];
    if (!firstChunk) {
      Alert.alert('No Audio', 'No chunks available for preview');
      return;
    }

    console.log('🎵 Starting preview for:', stream.rta_id);

    if (Platform.OS === 'web' && audioStreamer.current) {
      // Use FilCDN URL if available, fallback to proxy
      const audioUrl = firstChunk.filcdn_url || firstChunk.url;
      
      // Start preview using AudioStreamer service
      const stopPreview = await audioStreamer.current.playPreview(audioUrl, 0.6);

      setPlayer({
        isPlaying: true,
        currentRTA: stream.rta_id,
        stopPreview
      });

      // Auto-stop after 30 seconds
      setTimeout(() => {
        if (playerRef.current.currentRTA === stream.rta_id && playerRef.current.isPlaying) {
          stopPreview();
          setPlayer(prev => ({
            ...prev,
            isPlaying: false,
            currentRTA: null,
            stopPreview: null
          }));
        }
      }, 30000);
    }
  } catch (error) {
    console.error('❌ Preview toggle failed:', error);
    Alert.alert('Error', 'Failed to toggle preview');
  }
}, [player]);

Preview Controls

// Preview button in vibestream cards
<TouchableOpacity 
  style={styles.previewButton}
  onPress={(e) => {
    e.stopPropagation();
    togglePreview(stream);
  }}
>
  <FontAwesome5 
    name={isPlaying ? "stop" : "play"} 
    size={12} 
    color={COLORS.background} 
    style={!isPlaying ? { marginLeft: 1 } : {}}
  />
</TouchableOpacity>

Profile Integration

Creator Profile Loading

// Profile preloading in VibeMarket.tsx
useEffect(() => {
  const loadCreatorProfiles = async () => {
    if (vibestreams.length === 0) return;
    
    const uniqueCreators = [...new Set(vibestreams.map(stream => stream.creator))];
    
    // Use ProfileLoader's efficient preloading
    await profileLoader.current.preloadProfiles(uniqueCreators);
  };
  
  loadCreatorProfiles();
}, [vibestreams]);

Display Name Resolution

// Creator display name helper using ProfileLoader cache
const getDisplayName = useCallback((creator) => {
  // ProfileLoader handles caching internally
  const cached = profileLoader.current.getDisplayName(creator);
  if (cached) return cached;
  
  // Fallback formatting
  if (creator.startsWith('0x')) {
    return `${creator.slice(0, 5)}...${creator.slice(-6)}`;
  }
  return creator;
}, []);

Data Processing Utilities

Network Detection

// Network detection from RTA ID
const getNetworkFromRtaId = (rtaId) => {
  const upperRtaId = rtaId.toUpperCase();
  
  if (upperRtaId.startsWith('METIS_') || upperRtaId.includes('METIS')) {
    return 'metis';
  }
  return 'near';
};

Title Extraction

// Clean title extraction from RTA ID
const getVibestreamTitle = (stream) => {
  const rtaId = stream.rta_id;
  
  if (rtaId.toUpperCase().startsWith('METIS_VIBE_')) {
    return rtaId.substring(11);
  } else if (rtaId.toUpperCase().startsWith('METIS_')) {
    return rtaId.substring(6);
  } else if (rtaId.toUpperCase().startsWith('RTA_ID_')) {
    return rtaId.substring(7);
  } else if (rtaId.toUpperCase().startsWith('RTA_')) {
    return rtaId.substring(4);
  }
  
  return rtaId.toUpperCase();
};

Date Formatting

// Timestamp formatting with fallbacks
const formatDate = (timestamp) => {
  let date;
  
  if (timestamp > 1e12) {
    date = new Date(timestamp);
  } else if (timestamp > 1e9) {
    date = new Date(timestamp * 1000);
  } else {
    date = new Date();
  }
  
  const options = {
    month: 'short',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
    hour12: false
  };
  
  return date.toLocaleDateString('en-US', options).toUpperCase();
};

Filtering System

Filter State

// Filter state management
const [filters, setFilters] = useState({
  network: 'all',
  creator: '',
  date: 'all'
});

Filter Implementation

// Comprehensive filtering logic
const filteredVibestreams = vibestreams.filter(stream => {
  // Network filter
  if (filters.network !== 'all') {
    const streamNetwork = getNetworkFromRtaId(stream.rta_id);
    if (streamNetwork !== filters.network) return false;
  }

  // Creator filter
  if (filters.creator && !stream.creator.toLowerCase().includes(filters.creator.toLowerCase())) {
    return false;
  }

  // Date filter
  if (filters.date !== 'all') {
    const timestamp = stream.upload_timestamp;
    let streamDate;
    
    if (timestamp > 1e12) {
      streamDate = new Date(timestamp);
    } else if (timestamp > 1e9) {
      streamDate = new Date(timestamp * 1000);
    } else {
      return true;
    }
    
    const now = new Date();
    const diffMs = now.getTime() - streamDate.getTime();
    const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));

    switch (filters.date) {
      case 'today':
        if (diffDays > 0) return false;
        break;
      case 'week':
        if (diffDays > 7) return false;
        break;
      case 'month':
        if (diffDays > 30) return false;
        break;
    }
  }

  return true;
});

Filter UI

// Filter chips interface
<View style={styles.filtersContainer}>
  <ScrollView horizontal showsHorizontalScrollIndicator={false}>
    {/* Network filter */}
    <TouchableOpacity 
      style={[styles.filterChip, filters.network !== 'all' && styles.activeFilterChip]}
      onPress={() => setFilters(prev => ({
        ...prev, 
        network: prev.network === 'all' ? 'metis' : prev.network === 'metis' ? 'near' : 'all'
      }))}
    >
      <Text style={[styles.filterText, filters.network !== 'all' && styles.activeFilterText]}>
        {filters.network === 'all' ? 'ALL NETWORKS' : filters.network.toUpperCase()}
      </Text>
    </TouchableOpacity>

    {/* Date filter */}
    <TouchableOpacity 
      style={[styles.filterChip, filters.date !== 'all' && styles.activeFilterChip]}
      onPress={() => setFilters(prev => ({
        ...prev,
        date: prev.date === 'all' ? 'today' : prev.date === 'today' ? 'week' : prev.date === 'week' ? 'month' : 'all'
      }))}
    >
      <Text style={[styles.filterText, filters.date !== 'all' && styles.activeFilterText]}>
        {filters.date === 'all' ? 'ALL TIME' : filters.date.toUpperCase()}
      </Text>
    </TouchableOpacity>
  </ScrollView>
</View>

Card Rendering

Vibestream Card

// Minimal vibestream card renderer
const renderVibestreamCard = (stream, index) => {
  const isPlaying = player.currentRTA === stream.rta_id && player.isPlaying;
  const network = getNetworkFromRtaId(stream.rta_id);
  const title = getVibestreamTitle(stream);
  const displayName = getDisplayName(stream.creator);
  
  return (
    <GlitchContainer key={stream.rta_id} intensity="low" style={styles.cardContainer}>
      <TouchableOpacity 
        style={styles.card}
        onPress={() => onOpenPlayback?.(stream.rta_id)}
      >
        {/* Network indicator */}
        <View style={[styles.networkIndicator, { 
          backgroundColor: network === 'metis' ? COLORS.accent : COLORS.secondary 
        }]} />
        
        {/* Main content */}
        <View style={styles.cardContent}>
          <View style={styles.cardHeader}>
            <GlitchText text={title} style={styles.cardTitle} />
            <TouchableOpacity 
              style={styles.previewButton}
              onPress={(e) => {
                e.stopPropagation();
                togglePreview(stream);
              }}
            >
              <FontAwesome5 
                name={isPlaying ? "stop" : "play"} 
                size={12} 
                color={COLORS.background} 
              />
            </TouchableOpacity>
          </View>
          
          <Text style={styles.creatorName}>{displayName}</Text>
          
          <View style={styles.cardMeta}>
            <Text style={styles.metaText}>{stream.chunks} CHUNKS</Text>
            <Text style={styles.metaText}>{stream.rta_duration}</Text>
            <Text style={styles.metaText}>{formatDate(stream.upload_timestamp)}</Text>
          </View>
        </View>
      </TouchableOpacity>
    </GlitchContainer>
  );
};

Error States

Connection Warning

// Connection warning display
{!isConnected && (
  <GlitchContainer intensity="medium" style={styles.warningContainer}>
    <FontAwesome5 name="exclamation-triangle" size={24} color={COLORS.accent} />
    <Text style={styles.warningText}>WALLET NOT CONNECTED</Text>
    <Text style={styles.warningSubtext}>
      Connect your wallet to access all features
    </Text>
  </GlitchContainer>
)}

Empty States

// Empty state when no vibestreams found
{filteredVibestreams.length === 0 ? (
  <View style={styles.emptyState}>
    <FontAwesome5 name="satellite-dish" size={32} color={COLORS.textTertiary} />
    <Text style={styles.emptyText}>NO SIGNALS DETECTED</Text>
    <Text style={styles.emptySubtext}>Adjust filters or check back later</Text>
  </View>
) : (
  <View style={styles.cardsList}>
    {filteredVibestreams.map((stream, index) => renderVibestreamCard(stream, index))}
  </View>
)}

Performance Optimizations

Cleanup Management

// Cleanup audio when component unmounts
useEffect(() => {
  return () => {
    if (player.stopPreview) {
      player.stopPreview();
    }
    audioStreamer.current?.dispose();
  };
}, [player.stopPreview]);

Subscription Success Handling

// Handle subscription success callback
const handleSubscriptionSuccess = useCallback(() => {
  setSubscriptionStatus({
    isSubscribed: true,
    isLoading: false,
    requiresSubscription: true
  });
  setSubscribeModalVisible(false);
}, []);

Next Steps

User Profiles

Learn about profile management

Playback System

Understand continuous playback