From 6a6e2460d504eefff8791ad1c794c3740fe03675 Mon Sep 17 00:00:00 2001 From: "yves.gugger" Date: Thu, 11 Dec 2025 08:59:50 +0100 Subject: [PATCH] feat: Unified Market Feed API + Pounce Direct Integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿš€ MARKET CONCEPT IMPLEMENTATION Backend: - Added /auctions/feed unified endpoint combining Pounce Direct + external auctions - Implemented Pounce Score v2.0 with market signals (length, TLD, bids, age) - Added vanity filter for premium domains (non-auth users) - Integrated DomainListing model for Pounce Direct Frontend: - Refactored terminal/market page with Pounce Direct hierarchy - Updated public auctions page with Pounce Exclusive section - Added api.getMarketFeed() to API client - Converted /market to redirect to /auctions Documentation: - Created MARKET_CONCEPT.md with full unicorn roadmap - Created ZONE_FILE_ACCESS.md with Verisign access guide - Updated todos and progress tracking Cleanup: - Deleted empty legacy folders (dashboard, portfolio, settings, watchlist, careers) --- MARKET_CONCEPT.md | 1415 +++++++++++++++++++++ ZONE_FILE_ACCESS.md | 307 +++++ backend/app/api/auctions.py | 400 +++++- backend/scripts/reset_admin_password.py | 98 +- frontend/src/app/auctions/page.tsx | 95 +- frontend/src/app/market/page.tsx | 256 +--- frontend/src/app/terminal/market/page.tsx | 742 ++++++----- frontend/src/lib/api.ts | 63 + 8 files changed, 2777 insertions(+), 599 deletions(-) create mode 100644 MARKET_CONCEPT.md create mode 100644 ZONE_FILE_ACCESS.md diff --git a/MARKET_CONCEPT.md b/MARKET_CONCEPT.md new file mode 100644 index 0000000..2aa23db --- /dev/null +++ b/MARKET_CONCEPT.md @@ -0,0 +1,1415 @@ +# ๐ŸŽฏ POUNCE MARKET โ€” Das Konzept fรผr die Unicorn-Journey + +--- + +# ๐Ÿ“ฆ TEIL 1: BESTANDSAUFNAHME โ€” Was haben wir? + +## รœbersicht: Code-Inventar + +### โœ… BEHALTEN โ€” Funktioniert gut, Vision-konform + +| Komponente | Pfad | Status | Beschreibung | +|------------|------|--------|--------------| +| **Listings API** | `backend/app/api/listings.py` | โœ… Vollstรคndig | Pounce Direct Marketplace mit DNS-Verifizierung | +| **Listing Model** | `backend/app/models/listing.py` | โœ… Vollstรคndig | DomainListing, ListingInquiry, ListingView | +| **My Listings Page** | `frontend/src/app/terminal/listing/page.tsx` | โœ… Vollstรคndig | Seller Dashboard mit Verification Wizard | +| **Public Marketplace** | `frontend/src/app/buy/page.tsx` | โœ… Vollstรคndig | ร–ffentliche Browse-Seite fรผr Listings | +| **Listing Detail** | `frontend/src/app/buy/[slug]/page.tsx` | โœ… Vollstรคndig | ร–ffentliche Landing Page pro Listing | +| **Sniper Alerts API** | `backend/app/api/sniper_alerts.py` | โœ… Vollstรคndig | Alert-Matching fรผr Auktionen | +| **Sniper Alert Model** | `backend/app/models/sniper_alert.py` | โœ… Vollstรคndig | SniperAlert, SniperAlertMatch | +| **Scheduler** | `backend/app/scheduler.py` | โœ… Vollstรคndig | APScheduler mit Scraping, Alerts, Checks | +| **Valuation Service** | `backend/app/services/valuation.py` | โœ… Vollstรคndig | Pounce Score Berechnung | +| **TLD Prices API** | `backend/app/api/tld_prices.py` | โœ… Vollstรคndig | Intel/Pricing Feature | +| **TLD Scraper** | `backend/app/services/tld_scraper/` | โœ… Funktioniert | Porkbun + Aggregator | +| **Portfolio API** | `backend/app/api/portfolio.py` | โœ… Vollstรคndig | Eigene Domains verwalten | +| **Domain Health** | `backend/app/services/domain_health.py` | โœ… Vollstรคndig | 4-Layer Monitoring | +| **SEO Analyzer** | `backend/app/services/seo_analyzer.py` | โœ… Vollstรคndig | Moz API Integration | +| **Email Service** | `backend/app/services/email_service.py` | โœ… Vollstรคndig | Notifications | +| **Stripe Service** | `backend/app/services/stripe_service.py` | โœ… Vollstรคndig | Subscriptions | + +--- + +### โš ๏ธ รœBERARBEITEN โ€” Funktioniert, aber Optimierung nรถtig + +| Komponente | Pfad | Problem | Lรถsung | +|------------|------|---------|--------| +| **Auction Scraper** | `backend/app/services/auction_scraper.py` | Scraping ist fragil, oft leer | API-First + Fallback-Logik | +| **Auctions API** | `backend/app/api/auctions.py` | Keine Pounce Direct Integration | Unified Feed erstellen | +| **Market Page** | `frontend/src/app/terminal/market/page.tsx` | Zeigt nur externe Auktionen | Pounce Direct integrieren | +| **Pounce Score** | In `market/page.tsx` | Zu simpel (nur Length+TLD) | Erweitern um Markt-Signale | +| **Public Auctions** | `frontend/src/app/auctions/page.tsx` | Kein Pounce Direct Highlight | Visuelle Hierarchie | + +--- + +### โŒ ENTFERNEN / KONSOLIDIEREN โ€” Redundant oder veraltet + +| Komponente | Pfad | Grund | Aktion | +|------------|------|-------|--------| +| **Leere Ordner** | `frontend/src/app/dashboard/` | Leer (Legacy von /command) | Lรถschen | +| **Leere Ordner** | `frontend/src/app/portfolio/` | Leer (Legacy) | Lรถschen | +| **Leere Ordner** | `frontend/src/app/settings/` | Leer (Legacy) | Lรถschen | +| **Leere Ordner** | `frontend/src/app/watchlist/` | Leer (Legacy) | Lรถschen | +| **Leere Ordner** | `frontend/src/app/careers/` | Kein Inhalt | Lรถschen oder TODO | +| **Intelligence Redirect** | `frontend/src/app/intelligence/page.tsx` | Redirect zu /tld-pricing | Prรผfen ob noch nรถtig | +| **Market Public** | `frontend/src/app/market/page.tsx` | Duplikat? Prรผfen | Ggf. konsolidieren mit /auctions | + +--- + +## Detaillierte Analyse pro Bereich + +### 1. BACKEND: API Routes (`backend/app/api/`) + +``` +backend/app/api/ +โ”œโ”€โ”€ __init__.py โœ… Router-Registration +โ”œโ”€โ”€ admin.py โœ… Admin Panel APIs +โ”œโ”€โ”€ auctions.py โš ๏ธ รœberarbeiten (Unified Feed) +โ”œโ”€โ”€ auth.py โœ… Login/Register/JWT +โ”œโ”€โ”€ blog.py โœ… Blog Feature +โ”œโ”€โ”€ check.py โœ… Domain Availability Check +โ”œโ”€โ”€ contact.py โœ… Kontaktformular +โ”œโ”€โ”€ deps.py โœ… Dependencies +โ”œโ”€โ”€ domains.py โœ… Watchlist +โ”œโ”€โ”€ listings.py โœ… Pounce Direct Marketplace +โ”œโ”€โ”€ oauth.py โœ… Google/GitHub OAuth +โ”œโ”€โ”€ portfolio.py โœ… Portfolio Management +โ”œโ”€โ”€ price_alerts.py โœ… TLD Price Alerts +โ”œโ”€โ”€ seo.py โœ… SEO Juice (Tycoon) +โ”œโ”€โ”€ sniper_alerts.py โœ… Auction Sniper Alerts +โ”œโ”€โ”€ subscription.py โœ… Stripe Integration +โ”œโ”€โ”€ tld_prices.py โœ… TLD Pricing Data +โ””โ”€โ”€ webhooks.py โœ… Stripe Webhooks +``` + +**Aktion:** +- `auctions.py`: Unified Feed Endpoint hinzufรผgen der Pounce Direct + External kombiniert + +--- + +### 2. BACKEND: Services (`backend/app/services/`) + +``` +backend/app/services/ +โ”œโ”€โ”€ auction_scraper.py โš ๏ธ Fallback-Logik verbessern +โ”œโ”€โ”€ auth.py โœ… Behalten +โ”œโ”€โ”€ domain_checker.py โœ… Behalten +โ”œโ”€โ”€ domain_health.py โœ… Behalten +โ”œโ”€โ”€ email_service.py โœ… Behalten +โ”œโ”€โ”€ price_tracker.py โœ… Behalten +โ”œโ”€โ”€ seo_analyzer.py โœ… Behalten +โ”œโ”€โ”€ stripe_service.py โœ… Behalten +โ”œโ”€โ”€ valuation.py โš ๏ธ Pounce Score v2.0 integrieren +โ””โ”€โ”€ tld_scraper/ + โ”œโ”€โ”€ aggregator.py โœ… Behalten + โ”œโ”€โ”€ base.py โœ… Behalten + โ”œโ”€โ”€ porkbun.py โœ… Behalten + โ””โ”€โ”€ tld_list.py โœ… Behalten +``` + +**Aktionen:** +1. `auction_scraper.py`: Methode `scrape_with_fallback()` hinzufรผgen +2. `valuation.py`: Pounce Score v2.0 mit Market Signals + +--- + +### 3. BACKEND: Models (`backend/app/models/`) + +``` +backend/app/models/ +โ”œโ”€โ”€ admin_log.py โœ… Behalten +โ”œโ”€โ”€ auction.py โœ… DomainAuction, AuctionScrapeLog +โ”œโ”€โ”€ blog.py โœ… Behalten +โ”œโ”€โ”€ domain.py โœ… Domain, DomainCheck +โ”œโ”€โ”€ listing.py โœ… DomainListing, ListingInquiry, ListingView +โ”œโ”€โ”€ newsletter.py โœ… Behalten +โ”œโ”€โ”€ portfolio.py โœ… PortfolioDomain +โ”œโ”€โ”€ price_alert.py โœ… TLDPriceAlert +โ”œโ”€โ”€ seo_data.py โœ… DomainSEOData +โ”œโ”€โ”€ sniper_alert.py โœ… SniperAlert, SniperAlertMatch +โ”œโ”€โ”€ subscription.py โœ… Subscription, tier config +โ”œโ”€โ”€ tld_price.py โœ… TLDPrice, TLDInfo +โ””โ”€โ”€ user.py โœ… User +``` + +**Status:** Alle Models sind sauber und Vision-konform. Keine ร„nderungen nรถtig. + +--- + +### 4. FRONTEND: Terminal (Authenticated) (`frontend/src/app/terminal/`) + +``` +frontend/src/app/terminal/ +โ”œโ”€โ”€ page.tsx โœ… Redirect zu /radar +โ”œโ”€โ”€ radar/page.tsx โœ… Dashboard +โ”œโ”€โ”€ market/page.tsx โš ๏ธ Pounce Direct integrieren! +โ”œโ”€โ”€ intel/page.tsx โœ… TLD Overview +โ”œโ”€โ”€ intel/[tld]/page.tsx โœ… TLD Detail +โ”œโ”€โ”€ watchlist/page.tsx โœ… Domain Monitoring +โ”œโ”€โ”€ listing/page.tsx โœ… My Listings (Seller Dashboard) +โ”œโ”€โ”€ settings/page.tsx โœ… User Settings +โ””โ”€โ”€ welcome/page.tsx โœ… Onboarding +``` + +**Aktionen:** +1. `market/page.tsx`: Pounce Direct Listings im Feed anzeigen +2. `market/page.tsx`: Visuelle Hierarchie (๐Ÿ’Ž Pounce vs ๐Ÿข External) + +--- + +### 5. FRONTEND: Public Pages (`frontend/src/app/`) + +``` +frontend/src/app/ +โ”œโ”€โ”€ page.tsx โœ… Landing Page +โ”œโ”€โ”€ auctions/page.tsx โš ๏ธ Pounce Direct hervorheben +โ”œโ”€โ”€ buy/page.tsx โœ… Marketplace Browse +โ”œโ”€โ”€ buy/[slug]/page.tsx โœ… Listing Detail +โ”œโ”€โ”€ tld-pricing/ โœ… TLD Intel Public +โ”œโ”€โ”€ pricing/page.tsx โœ… Subscription Tiers +โ”œโ”€โ”€ blog/ โœ… Blog +โ”œโ”€โ”€ login/page.tsx โœ… Auth +โ”œโ”€โ”€ register/page.tsx โœ… Auth +โ””โ”€โ”€ ... โœ… Legal, Contact, etc. +``` + +**Aktionen:** +1. `auctions/page.tsx`: "๐Ÿ’Ž Pounce Direct" Listings prominent anzeigen +2. Konsolidieren: `/market/` mit `/auctions/` zusammenfรผhren? + +--- + +### 6. FRONTEND: API Client (`frontend/src/lib/api.ts`) + +**Status:** โœ… Vollstรคndig + +Enthรคlt alle nรถtigen Methoden: +- `getAuctions()` - Externe Auktionen +- `getMarketplaceListings()` - TODO: Backend anbinden (aktuell leere Liste) + +**Aktion:** +- `getMarketplaceListings()` โ†’ Backend Endpoint `/listings` anbinden + +--- + +## Zusammenfassung: Cleanup-Liste + +### Sofort lรถschen (leere Ordner): +```bash +rm -rf frontend/src/app/dashboard/ +rm -rf frontend/src/app/portfolio/ +rm -rf frontend/src/app/settings/ +rm -rf frontend/src/app/watchlist/ +rm -rf frontend/src/app/careers/ +``` + +### Konsolidieren: +- `/market/page.tsx` und `/auctions/page.tsx` โ†’ Eine Seite fรผr Public Market +- `/intelligence/page.tsx` prรผfen ob Redirect noch nรถtig + +### Code-ร„nderungen: +1. **Market Page (Terminal)**: Pounce Direct + External in einem Feed +2. **Auctions Page (Public)**: Pounce Direct prominent +3. **API Client**: `getMarketplaceListings()` Backend anbinden +4. **Auctions API**: Unified Feed Endpoint +5. **Pounce Score**: v2.0 mit Market Signals + +--- + +# ๐Ÿ“Š TEIL 2: KONZEPT โ€” Wohin entwickeln wir? + +## Executive Summary + +Die aktuelle Market-Page funktioniert technisch, aber sie ist noch nicht "Unicorn-ready". +Dieses Konzept transformiert sie von einem einfachen Auktions-Aggregator zur **zentralen Domain-Intelligence-Plattform**. + +--- + +## ๐Ÿ“Š IST-Analyse: Aktuelle Implementation + +### Datenquellen (Backend) +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ CURRENT DATA FLOW โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ ExpiredDomains.net โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ โ”‚ +โ”‚ GoDaddy RSS Feed โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ†’ Web Scraper โ”€โ”€โ†’ PostgreSQL/SQLite โ”‚ +โ”‚ โ”‚ (hourly) (domain_auctions) โ”‚ +โ”‚ Sedo Public Search โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ โ”‚ +โ”‚ NameJet Public โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ โ”‚ +โ”‚ DropCatch Public โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Probleme mit dem aktuellen Setup + +| Problem | Impact | Severity | +|---------|--------|----------| +| **Web-Scraping ist fragil** | Seiten รคndern Layout โ†’ Scraper bricht | ๐Ÿ”ด Hoch | +| **Daten sind oft veraltet** | End-Zeiten stimmen nicht, Preise falsch | ๐Ÿ”ด Hoch | +| **Kein "Pounce Direct" Content** | Alles nur externe Daten, kein USP | ๐Ÿ”ด Hoch | +| **Rate-Limiting & Blocking** | Plattformen blockieren Scraper | ๐ŸŸก Mittel | +| **Keine echte Echtzeit-Daten** | Stรผndliches Scraping ist zu langsam | ๐ŸŸก Mittel | +| **Pounce Score ist simpel** | Nur Length + TLD, keine echten Signale | ๐ŸŸก Mittel | + +--- + +## ๐Ÿš€ SOLL-Konzept: Die Unicorn-Architektur + +### Phase 1: Der "Clean Feed" (Jetzt โ€“ 3 Monate) + +**Ziel:** Die beste Auktions-รœbersicht mit echtem Mehrwert. + +#### 1.1 Daten-Strategie: Hybrid-Ansatz + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ NEW DATA ARCHITECTURE โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ TIER 1: OFFIZIELLE APIs (zuverlรคssig, real-time) โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โ€ข GoDaddy Partner API (wenn Partner-Account vorhanden) โ”‚ +โ”‚ โ€ข Sedo Partner API (Affiliate-Programm) โ”‚ +โ”‚ โ€ข DropCatch Public API โ”‚ +โ”‚ โ”‚ +โ”‚ TIER 2: WEB SCRAPING (Backup, validiert) โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โ€ข ExpiredDomains.net (Deleted Domains) โ”‚ +โ”‚ โ€ข NameJet Public (mit Fallback-Logik) โ”‚ +โ”‚ โ”‚ +โ”‚ TIER 3: POUNCE EXCLUSIVE (unser USP!) โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โ€ข User-Listings ("Pounce Direct" / "For Sale") โ”‚ +โ”‚ โ€ข DNS-verifizierte Eigentรผmer โ”‚ +โ”‚ โ€ข Sofort-Kauf-Option โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +#### 1.2 Der "Clean Feed" Algorithmus + +```python +# Spam-Filter v2.0 (Vanity Filter) +def is_premium_domain(domain: str) -> bool: + name = domain.rsplit('.', 1)[0] + tld = domain.rsplit('.', 1)[1] + + # REGEL 1: Nur Premium-TLDs fรผr Public + premium_tlds = ['com', 'io', 'ai', 'co', 'de', 'ch', 'net', 'org', 'app', 'dev'] + if tld not in premium_tlds: + return False + + # REGEL 2: Keine Spam-Muster + if len(name) > 12: # Kurz = Premium + return False + if name.count('-') > 0: # Keine Bindestriche + return False + if sum(c.isdigit() for c in name) > 1: # Max 1 Zahl + return False + if any(word in name.lower() for word in ['xxx', 'casino', 'loan', 'cheap']): + return False + + # REGEL 3: Konsonanten-Check (kein "xkqzfgh.com") + consonants = 'bcdfghjklmnpqrstvwxyz' + max_consonant_streak = max(len(list(g)) for k, g in groupby(name, key=lambda c: c.lower() in consonants) if k) + if max_consonant_streak > 4: + return False + + return True +``` + +#### 1.3 Pounce Score 2.0 + +Der aktuelle Score ist zu simpel. Hier ist die verbesserte Version: + +```python +def calculate_pounce_score_v2(domain: str, auction_data: dict) -> int: + score = 50 # Baseline + name = domain.rsplit('.', 1)[0] + tld = domain.rsplit('.', 1)[1] + + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + # A) INTRINSIC VALUE (Domain selbst) + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + + # Lรคnge (kurz = wertvoll) + length_scores = {1: 50, 2: 45, 3: 40, 4: 30, 5: 20, 6: 15, 7: 10} + score += length_scores.get(len(name), max(0, 15 - len(name))) + + # TLD Premium + tld_scores = {'com': 20, 'ai': 25, 'io': 18, 'co': 12, 'de': 10, 'ch': 10} + score += tld_scores.get(tld, 0) + + # Dictionary Word Bonus + common_words = ['tech', 'data', 'cloud', 'app', 'dev', 'net', 'hub', 'lab', 'pro'] + if name.lower() in common_words or any(word in name.lower() for word in common_words): + score += 15 + + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + # B) MARKET SIGNALS (Aktivitรคt) + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + + # Bid Activity (mehr Bids = mehr Interesse) + bids = auction_data.get('num_bids', 0) + if bids >= 20: score += 15 + elif bids >= 10: score += 10 + elif bids >= 5: score += 5 + + # Time Pressure (endet bald = Opportunity) + hours_left = auction_data.get('hours_left', 999) + if hours_left < 1: score += 10 # HOT! + elif hours_left < 4: score += 5 + + # Price-to-Value Ratio + current_bid = auction_data.get('current_bid', 0) + estimated_value = estimate_base_value(name, tld) + if current_bid > 0 and estimated_value > current_bid * 1.5: + score += 15 # Unterbewertet! + + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + # C) PENALTIES (Abzรผge) + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + + if '-' in name: score -= 30 + if any(c.isdigit() for c in name) and len(name) > 3: score -= 20 + if len(name) > 15: score -= 25 + + return max(0, min(100, score)) +``` + +--- + +### Phase 2: Der "Pounce Direct" Marktplatz (3 โ€“ 6 Monate) + +**Ziel:** Eigenes Inventar = Unique Content = USP + +#### 2.1 Das Killer-Feature: "Pounce Direct" + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ POUNCE DIRECT INTEGRATION โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ MARKET FEED (Gemischt) โ”‚ +โ”‚ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ ๐Ÿ’Ž POUNCE DIRECT โ”‚ โ”‚ +โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ +โ”‚ โ”‚ zurich-immo.ch $950 โšก INSTANT [BUY] โ”‚ โ”‚ +โ”‚ โ”‚ โœ… Verified Owner โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ ๐Ÿข EXTERNAL AUCTION โ”‚ โ”‚ +โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ +โ”‚ โ”‚ techflow.io $250 โฑ๏ธ 6h left [BID โ†—] โ”‚ โ”‚ +โ”‚ โ”‚ via GoDaddy โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +#### 2.2 Warum das genial ist + +| Vorteil | Erklรคrung | +|---------|-----------| +| **Unique Content** | Domains, die es NUR bei Pounce gibt | +| **Hรถhere Conversion** | "Instant Buy" statt "Bid on external site" | +| **Vendor Lock-in** | Verkรคufer listen bei uns (weil 0% Provision) | +| **SEO Power** | Jede Listing = eigene Landing Page | +| **Trust Signal** | DNS-Verifizierung = Qualitรคtsgarantie | + +#### 2.3 Der Flow fรผr Verkรคufer (aus `pounce_terminal.md`) + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ LISTING WIZARD โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ STEP 1: DOMAIN EINGEBEN โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ [________________________] zurich-immo.ch โ”‚ +โ”‚ Preis: [$950] โ—‹ Fixpreis โ—‹ Verhandlungsbasis โ”‚ +โ”‚ โ”‚ +โ”‚ STEP 2: DNS VERIFICATION โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ Fรผge diesen TXT-Record zu deiner Domain hinzu: โ”‚ +โ”‚ โ”‚ +โ”‚ Name: _pounce-verify โ”‚ +โ”‚ Value: pounce-verify-8a3f7b9c2e1d โ”‚ +โ”‚ โ”‚ +โ”‚ [๐Ÿ”„ VERIFY DNS] โ”‚ +โ”‚ โ”‚ +โ”‚ STEP 3: LIVE! โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โœ… Domain verifiziert! โ”‚ +โ”‚ Dein Listing ist jetzt im Market Feed sichtbar. โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +### Phase 3: Die Daten-Hoheit (6 โ€“ 12 Monate) ๐Ÿ† + +**Ziel:** Unabhรคngigkeit von externen Quellen. **EIGENE DATEN = EIGENES MONOPOL.** + +> *"Pounce weiรŸ Dinge, die GoDaddy dir verheimlicht."* โ€” pounce_strategy.md + +#### 3.1 Zone File Analysis โ€” Der Unicorn-Treiber + +**Was sind Zone Files?** +Zone Files sind die "Master-Listen" aller registrierten Domains pro TLD. Sie werden tรคglich von den Registries (Verisign, PIR, etc.) aktualisiert. + +**Wer hat Zugang?** +- Jeder kann sich bei ICANN-akkreditierten Registries bewerben +- Verisign (.com/.net): https://www.verisign.com/en_US/channel-resources/domain-registry-products/zone-file/index.xhtml +- PIR (.org): Zone File Access Program +- Donuts (.xyz, .online, etc.): TLD Zone File Access + +**Kosten:** $0 - $10,000/Jahr je nach TLD und Nutzung + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ZONE FILE PIPELINE โ€” Die Daten-Revolution โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ TIER 1: CRITICAL TLDs (Sofort beantragen) โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ Verisign โ†’ .com, .net ~160M + 13M Domains โ”‚ โ”‚ +โ”‚ โ”‚ PIR โ†’ .org ~10M Domains โ”‚ โ”‚ +โ”‚ โ”‚ Afilias โ†’ .info ~4M Domains โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ +โ”‚ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ TIER 2: PREMIUM TLDs (Phase 2) โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ CentralNIC โ†’ .io, .co Premium fรผr Startups โ”‚ โ”‚ +โ”‚ โ”‚ Google โ†’ .app, .dev Tech-Domains โ”‚ โ”‚ +โ”‚ โ”‚ Donuts โ†’ .xyz, .online Volumen โ”‚ โ”‚ +โ”‚ โ”‚ SWITCH โ†’ .ch Schweizer Markt โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ +โ”‚ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ POUNCE INTELLIGENCE ENGINE โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ 1. DAILY DOWNLOAD (4:00 UTC) โ”‚ โ”‚ +โ”‚ โ”‚ โ””โ”€โ†’ ~500GB komprimierte Daten pro Tag โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ 2. DIFF ANALYSIS โ”‚ โ”‚ +โ”‚ โ”‚ โ””โ”€โ†’ Was ist NEU? Was ist WEG? โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ 3. DROP PREDICTION โ”‚ โ”‚ +โ”‚ โ”‚ โ””โ”€โ†’ Domains die aus Zone verschwinden = droppen โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ 4. QUALITY SCORING (Pounce Algorithm) โ”‚ โ”‚ +โ”‚ โ”‚ โ””โ”€โ†’ Nur Premium-Domains durchlassen โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ +โ”‚ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ OUTPUT: EXKLUSIVE INTELLIGENCE โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ ๐Ÿ”ฎ "Drops Tomorrow" โ€” Domains BEVOR sie in Auktionen โ”‚ โ”‚ +โ”‚ โ”‚ ๐Ÿ“ˆ "Trending Registrations" โ€” Was wird gerade gehypt โ”‚ โ”‚ +โ”‚ โ”‚ โš ๏ธ "Expiring Premium" โ€” Hochwertige Domains am Ende โ”‚ โ”‚ +โ”‚ โ”‚ ๐Ÿ” "Pattern Detection" โ€” Welche Keywords explodieren โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +#### 3.2 Der Pounce Algorithm โ€” "No-Bullshit" Filter + +```python +# backend/app/services/zone_analyzer.py (NEU ZU BAUEN) + +class ZoneFileAnalyzer: + """ + Analysiert Zone Files und findet Premium-Opportunities. + + Input: Raw Zone File (Millionen von Domains) + Output: Gefilterte Premium-Liste (Hunderte) + """ + + async def analyze_drops(self, yesterday: set, today: set) -> list: + """ + Findet Domains die aus der Zone verschwunden sind. + Diese Domains droppen in 1-5 Tagen (Redemption Period). + """ + dropped = yesterday - today # Set-Differenz + + premium_drops = [] + for domain in dropped: + score = self.calculate_pounce_score(domain) + + # Nur Premium durchlassen + if score >= 70: + premium_drops.append({ + "domain": domain, + "score": score, + "drop_date": self.estimate_drop_date(domain), + "estimated_value": self.estimate_value(domain), + }) + + return sorted(premium_drops, key=lambda x: x['score'], reverse=True) + + def calculate_pounce_score(self, domain: str) -> int: + """ + Der Pounce Algorithm โ€” Qualitรคtsfilter fรผr Domains. + + Faktoren: + - Lรคnge (kurz = wertvoll) + - TLD (com > io > xyz) + - Keine Zahlen/Bindestriche + - Dictionary Word Bonus + - Historische Daten (wenn verfรผgbar) + """ + name = domain.rsplit('.', 1)[0] + tld = domain.rsplit('.', 1)[1] + score = 50 # Baseline + + # Lรคngen-Score + length_scores = {1: 50, 2: 45, 3: 40, 4: 30, 5: 20, 6: 15, 7: 10} + score += length_scores.get(len(name), max(0, 15 - len(name))) + + # TLD Premium + tld_scores = {'com': 20, 'ai': 25, 'io': 18, 'co': 12, 'ch': 15, 'de': 10} + score += tld_scores.get(tld, 0) + + # Penalties + if '-' in name: score -= 30 + if any(c.isdigit() for c in name): score -= 20 + if len(name) > 12: score -= 15 + + # Dictionary Word Bonus + if self.is_dictionary_word(name): + score += 25 + + return max(0, min(100, score)) +``` + +#### 3.3 Der "Drops Tomorrow" Feed โ€” Tycoon Exclusive + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ”ฎ DROPS TOMORROW โ€” Tycoon Exclusive ($29/mo) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ Diese Domains sind NICHT in Auktionen! โ”‚ +โ”‚ Du kannst sie beim Registrar direkt registrieren. โ”‚ +โ”‚ โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โ”‚ +โ”‚ Domain TLD Score Est. Value Drops In โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ pixel.com .com 95 $50,000 23h 45m โ”‚ +โ”‚ swift.io .io 88 $8,000 23h 12m โ”‚ +โ”‚ quantum.ai .ai 92 $25,000 22h 58m โ”‚ +โ”‚ nexus.dev .dev 84 $4,500 22h 30m โ”‚ +โ”‚ fusion.co .co 81 $3,200 21h 15m โ”‚ +โ”‚ โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โ”‚ +โ”‚ ๐Ÿ’ก Pro Tip: Setze bei deinem Registrar einen Backorder โ”‚ +โ”‚ fรผr diese Domains. Wer zuerst kommt... โ”‚ +โ”‚ โ”‚ +โ”‚ [๐Ÿ”” Alert fรผr "pixel.com" setzen] โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +#### 3.4 Warum das ein MONOPOL schafft + +| Wettbewerber | Datenquelle | Problem | +|--------------|-------------|---------| +| **ExpiredDomains.net** | Zone Files | Zeigt ALLES (Spam-Hรถlle) | +| **GoDaddy Auctions** | Eigene Daten | Nur GoDaddy-Domains | +| **Sedo** | User-Listings | รœberteuert, wenig Volumen | +| **Pounce** | Zone Files + **Algorithmus** | **Premium-gefiltert, clean** | + +**Der Unterschied:** +- ExpiredDomains zeigt dir 100.000 Domains am Tag. Davon sind 99.990 Mรผll. +- Pounce zeigt dir 100 Premium-Domains. Alle sind es wert, angeschaut zu werden. + +**Das verkauft Abos:** +> *"Ich zahle $29/Monat, weil Pounce mir 20 Stunden Recherche pro Woche spart."* + +#### 3.5 Technische Umsetzung โ€” Server-Anforderungen + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ZONE FILE PROCESSING โ€” Infrastructure โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ SERVER REQUIREMENTS: โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โ€ข Storage: 2TB SSD (Zone Files sind ~500GB/Tag komprimiert) โ”‚ +โ”‚ โ€ข RAM: 64GB+ (fรผr effizientes Set-Diffing) โ”‚ +โ”‚ โ€ข CPU: 16+ Cores (parallele Analyse) โ”‚ +โ”‚ โ€ข Kosten: ~$300-500/Monat (Hetzner/OVH Dedicated) โ”‚ +โ”‚ โ”‚ +โ”‚ PROCESSING PIPELINE: โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ 04:00 UTC โ”‚ Zone File Download (FTP/HTTPS) โ”‚ +โ”‚ 04:30 UTC โ”‚ Decompression & Parsing โ”‚ +โ”‚ 05:00 UTC โ”‚ Diff Analysis (gestern vs heute) โ”‚ +โ”‚ 05:30 UTC โ”‚ Quality Scoring (Pounce Algorithm) โ”‚ +โ”‚ 06:00 UTC โ”‚ Database Update (PostgreSQL) โ”‚ +โ”‚ 06:15 UTC โ”‚ Alert Matching (Sniper Alerts) โ”‚ +โ”‚ 06:30 UTC โ”‚ User Notifications (Email/SMS) โ”‚ +โ”‚ โ”‚ +โ”‚ STORAGE STRATEGY: โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โ€ข Nur Premium-Domains speichern (Score > 50) โ”‚ +โ”‚ โ€ข 90 Tage History fรผr Trend-Analyse โ”‚ +โ”‚ โ€ข ร„ltere Daten archivieren (S3 Glacier) โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +#### 3.6 Phase 1 vs Phase 3 โ€” Was zuerst? + +| Phase | Datenquelle | Status | +|-------|-------------|--------| +| **Phase 1 (JETZT)** | Web Scraping + Pounce Direct | โœ… Implementiert | +| **Phase 3 (6-12 Mo)** | Zone Files | ๐Ÿ”œ Geplant | + +**Warum warten?** +1. Zone File Access braucht Vertrรคge mit Registries (1-3 Monate) +2. Infrastruktur-Investition (~$500/Monat Server) +3. Algorithmus muss getestet werden (False Positives vermeiden) + +**Was wir JETZT tun:** +- Scraping + Pounce Direct perfektionieren +- User-Basis aufbauen (die Zone Files spรคter monetarisiert) +- Algorithmus entwickeln (funktioniert auch ohne Zone Files) + +--- + +## ๐Ÿ’ก Konkrete ร„nderungen fรผr die Market Page + +### Frontend-ร„nderungen + +#### 1. Visuelle Hierarchie verbessern + +```tsx +// VORHER: Alle Items sehen gleich aus +
+ {items.map(item => )} +
+ +// NACHHER: Pounce Direct hervorheben +
+ {/* Featured: Pounce Direct (wenn vorhanden) */} + {pounceDirectItems.length > 0 && ( +
+
+ + Pounce Direct โ€” Verified Instant Buy +
+ {pounceDirectItems.map(item => )} +
+ )} + + {/* Standard: External Auctions */} +
+ {externalItems.map(item => )} +
+
+``` + +#### 2. Filter-Presets fรผr User-Journeys + +```tsx +// Quick-Filter Buttons basierend auf User-Intent +const FILTER_PRESETS = { + 'ending-soon': { + label: 'โฑ๏ธ Ending Soon', + filter: { hours_left: { max: 4 } }, + sort: 'time_asc' + }, + 'bargains': { + label: '๐Ÿ’ฐ Under $100', + filter: { price: { max: 100 }, score: { min: 60 } }, + sort: 'score_desc' + }, + 'premium': { + label: '๐Ÿ‘‘ Premium Only', + filter: { score: { min: 80 }, tld: ['com', 'io', 'ai'] }, + sort: 'price_desc' + }, + 'pounce-only': { + label: '๐Ÿ’Ž Pounce Direct', + filter: { source: 'pounce' }, + sort: 'created_desc' + } +} +``` + +#### 3. "Opportunity Score" statt nur "Pounce Score" + +```tsx +// Zeige WARUM ein Domain interessant ist +function OpportunityIndicators({ item }) { + const indicators = [] + + if (item.hoursLeft < 2) indicators.push({ icon: '๐Ÿ”ฅ', label: 'Ending soon' }) + if (item.numBids < 3) indicators.push({ icon: '๐Ÿ“‰', label: 'Low competition' }) + if (item.valueRatio > 2) indicators.push({ icon: '๐Ÿ’Ž', label: 'Undervalued' }) + if (item.isPounce) indicators.push({ icon: 'โšก', label: 'Instant buy' }) + + return ( +
+ {indicators.map(ind => ( + + {ind.icon} + + ))} +
+ ) +} +``` + +### Backend-ร„nderungen + +#### 1. Unified Feed API + +```python +# NEUER ENDPOINT: /api/v1/market/feed +@router.get("/feed") +async def get_market_feed( + # Filter + source: Optional[str] = Query(None, enum=['all', 'pounce', 'external']), + score_min: int = Query(0, ge=0, le=100), + price_max: Optional[float] = None, + tld: Optional[List[str]] = Query(None), + ending_within: Optional[int] = Query(None, description="Hours"), + + # Sort + sort_by: str = Query('score', enum=['score', 'price', 'time', 'bids']), + + # Pagination + limit: int = Query(30, le=100), + offset: int = Query(0), + + # Auth + current_user: Optional[User] = Depends(get_current_user_optional), +): + """ + Unified market feed combining: + - Pounce Direct listings (user-listed domains) + - External auctions (scraped from platforms) + + For non-authenticated users: + - Apply vanity filter (premium domains only) + - Blur "Deal Score" (tease upgrade) + """ + + items = [] + + # 1. Get Pounce Direct listings + pounce_listings = await get_published_listings(db) + for listing in pounce_listings: + items.append({ + 'type': 'pounce_direct', + 'domain': listing.domain, + 'price': listing.asking_price, + 'source': 'Pounce', + 'status': 'instant', + 'verified': listing.verification_status == 'verified', + 'url': f'/buy/{listing.slug}', # Internal! + }) + + # 2. Get external auctions + auctions = await get_active_auctions(db) + for auction in auctions: + # Apply vanity filter for non-auth users + if not current_user and not is_premium_domain(auction.domain): + continue + + items.append({ + 'type': 'auction', + 'domain': auction.domain, + 'price': auction.current_bid, + 'source': auction.platform, + 'status': 'auction', + 'time_left': format_time_remaining(auction.end_time), + 'url': auction.affiliate_url, # External + }) + + # 3. Calculate scores + for item in items: + item['pounce_score'] = calculate_pounce_score_v2( + item['domain'], + item + ) + + # 4. Sort and paginate + items = sorted(items, key=lambda x: x['pounce_score'], reverse=True) + + return { + 'items': items[offset:offset+limit], + 'total': len(items), + 'filters_applied': {...}, + } +``` + +#### 2. Scraper Verbesserungen + +```python +class AuctionScraperService: + """ + IMPROVED: Resilient scraping with fallbacks + """ + + async def scrape_with_fallback(self, platform: str, db: AsyncSession): + """Try multiple methods to get data""" + + methods = [ + (f'_scrape_{platform.lower()}_api', 'API'), # Best: Official API + (f'_scrape_{platform.lower()}_rss', 'RSS'), # Good: RSS Feed + (f'_scrape_{platform.lower()}_html', 'HTML'), # Fallback: HTML Scrape + ] + + for method_name, method_type in methods: + method = getattr(self, method_name, None) + if not method: + continue + + try: + result = await method(db) + if result['found'] > 0: + logger.info(f"{platform}: Got {result['found']} via {method_type}") + return result + except Exception as e: + logger.warning(f"{platform} {method_type} failed: {e}") + continue + + # All methods failed + logger.error(f"{platform}: All scrape methods failed") + return {'found': 0, 'new': 0, 'updated': 0, 'error': 'All methods failed'} +``` + +--- + +## ๐Ÿ“ˆ Metriken fรผr den Erfolg + +### KPIs fรผr Phase 1 + +| Metrik | Ziel (3 Monate) | Messung | +|--------|-----------------|---------| +| **Daily Active Users (DAU)** | 500 | PostHog | +| **Conversion Rate (Free โ†’ Trader)** | 5% | Stripe | +| **Domains in Feed** | 1000+ | DB Query | +| **Avg. Session Duration** | > 3 min | PostHog | +| **Scrape Success Rate** | > 95% | Logs | + +### KPIs fรผr Phase 2 + +| Metrik | Ziel (6 Monate) | Messung | +|--------|-----------------|---------| +| **Pounce Direct Listings** | 100+ | DB Query | +| **First Sale via Pounce** | โœ… | Manual | +| **GMV (Gross Merchandise Value)** | $50,000 | Tracked | +| **Repeat Sellers** | 20% | DB Query | + +--- + +## ๐Ÿ› ๏ธ Technische Schulden abbauen + +### Prioritรคt 1: Scraper Stabilitรคt + +```python +# Problem: Scraper bricht bei HTML-ร„nderungen + +# Lรถsung: Defensive Parsing mit Fallbacks +def parse_domain_from_row(row) -> Optional[str]: + """Try multiple selectors to find domain""" + selectors = [ + 'a.domain-name', + 'td.domain a', + 'span[data-domain]', + 'a[href*="domain"]', + ] + + for selector in selectors: + elem = row.select_one(selector) + if elem: + text = elem.get_text(strip=True) + if '.' in text and len(text) < 100: + return text.lower() + + return None +``` + +### Prioritรคt 2: Caching Layer + +```python +# Problem: Jeder Request macht DB-Abfragen + +# Lรถsung: Redis Cache fรผr Feed-Daten +from redis import asyncio as aioredis + +async def get_market_feed_cached(filters: dict) -> list: + cache_key = f"market:feed:{hash(str(filters))}" + + # Try cache first + cached = await redis.get(cache_key) + if cached: + return json.loads(cached) + + # Generate fresh data + data = await generate_market_feed(filters) + + # Cache for 5 minutes + await redis.setex(cache_key, 300, json.dumps(data)) + + return data +``` + +### Prioritรคt 3: Rate Limiting pro User + +```python +# Problem: Power User kรถnnten API รผberlasten + +# Lรถsung: Tiered Rate Limits +RATE_LIMITS = { + 'scout': '50/hour', + 'trader': '200/hour', + 'tycoon': '1000/hour', +} +``` + +--- + +## ๐ŸŽฏ Nรคchste Schritte + +### โœ… ERLEDIGT (11. Dezember 2025) +- [x] Pounce Score v2.0 implementieren โ†’ `_calculate_pounce_score_v2()` in `auctions.py` +- [x] Unified `/auctions/feed` API deployen โ†’ Live und funktional +- [x] Pounce Direct Listings im Feed integrieren โ†’ Kombiniert mit externen Auktionen +- [x] "๐Ÿ’Ž Pounce Direct" Badge und Highlighting โ†’ Visuelle Hierarchie implementiert +- [x] Filter-Presets im Frontend โ†’ "Pounce Only", "Verified", Preis-Filter +- [x] Zone File Access Anleitung โ†’ `ZONE_FILE_ACCESS.md` erstellt + +### Nรคchste Woche +- [ ] Erste Pounce Direct Listings erstellen (Testdaten) +- [ ] Scraper-Fallbacks implementieren +- [ ] Verisign Zone File Access beantragen + +### Nรคchster Monat +- [ ] Opportunity Indicators im UI +- [ ] Redis Caching Layer +- [ ] PIR (.org) Zone File Access + +--- + +## ๐Ÿ’Ž Fazit + +Die Market Page ist das Herzstรผck von Pounce. Mit diesen ร„nderungen wird sie: + +1. **Zuverlรคssiger** (Scraper-Fallbacks, Caching) +2. **Wertvoller** (Pounce Direct = Unique Content) +3. **Stickier** (bessere UX, personalisierte Filter) +4. **Skalierbarer** (Unicorn-ready Architektur) + +Der Weg zum Unicorn fรผhrt รผber **Datenhoheit** und **einzigartigen Content**. +Pounce Direct ist der erste Schritt. + +--- + +# ๐Ÿ”ง TEIL 3: AKTIONSPLAN โ€” Was tun wir konkret? + +## Phase A: Cleanup (Heute) + +### 1. Leere Ordner lรถschen + +```bash +# Diese Ordner sind leer und Legacy vom alten /command Routing +rm -rf frontend/src/app/dashboard/ +rm -rf frontend/src/app/portfolio/ +rm -rf frontend/src/app/settings/ +rm -rf frontend/src/app/watchlist/ +rm -rf frontend/src/app/careers/ +``` + +### 2. Redundante Seiten prรผfen + +| Seite | Entscheidung | +|-------|--------------| +| `/market/page.tsx` | โŒ Entfernen โ†’ Redirect zu `/auctions` | +| `/intelligence/page.tsx` | โš ๏ธ Prรผfen โ†’ Redirect zu `/tld-pricing` | + +--- + +## Phase B: Pounce Direct Integration (Diese Woche) + +### 1. Backend: Unified Market Feed API + +**Datei:** `backend/app/api/auctions.py` + +Neuer Endpoint hinzufรผgen: + +```python +@router.get("/feed") +async def get_unified_market_feed( + source: str = Query("all", enum=["all", "pounce", "external"]), + # ... Filter +): + """ + Unified feed combining: + - Pounce Direct (user listings) + - External auctions (scraped) + """ + items = [] + + # 1. Pounce Direct Listings + if source in ["all", "pounce"]: + listings = await db.execute( + select(DomainListing) + .where(DomainListing.status == "active") + ) + for listing in listings.scalars(): + items.append({ + "type": "pounce_direct", + "domain": listing.domain, + "price": listing.asking_price, + "source": "Pounce", + "status": "instant", + "verified": listing.is_verified, + "url": f"/buy/{listing.slug}", + }) + + # 2. External Auctions + if source in ["all", "external"]: + auctions = await db.execute( + select(DomainAuction) + .where(DomainAuction.is_active == True) + ) + for auction in auctions.scalars(): + items.append({ + "type": "auction", + "domain": auction.domain, + "price": auction.current_bid, + "source": auction.platform, + "status": "auction", + "time_left": _format_time_remaining(auction.end_time), + "url": auction.affiliate_url, + }) + + return {"items": items, "total": len(items)} +``` + +### 2. Frontend: API Client erweitern + +**Datei:** `frontend/src/lib/api.ts` + +```typescript +async getMarketFeed( + source: 'all' | 'pounce' | 'external' = 'all', + filters?: { + keyword?: string + tld?: string + minPrice?: number + maxPrice?: number + } +) { + const params = new URLSearchParams({ source }) + if (filters?.keyword) params.append('keyword', filters.keyword) + if (filters?.tld) params.append('tld', filters.tld) + if (filters?.minPrice) params.append('min_price', filters.minPrice.toString()) + if (filters?.maxPrice) params.append('max_price', filters.maxPrice.toString()) + + return this.request<{ + items: MarketItem[] + total: number + }>(`/auctions/feed?${params.toString()}`) +} +``` + +### 3. Frontend: Market Page updaten + +**Datei:** `frontend/src/app/terminal/market/page.tsx` + +ร„nderungen: +1. `api.getMarketFeed()` statt `api.getAuctions()` aufrufen +2. Pounce Direct Items visuell hervorheben +3. "Pounce Exclusive" Filter aktivieren + +--- + +## Phase C: Public Page Alignment (Nรคchste Woche) + +### 1. `/auctions/page.tsx` โ€” Pounce Direct hervorheben + +```tsx +// Gruppiere Items +const pounceItems = items.filter(i => i.type === 'pounce_direct') +const externalItems = items.filter(i => i.type === 'auction') + +return ( + <> + {/* Featured: Pounce Direct */} + {pounceItems.length > 0 && ( +
+

+ + Pounce Exclusive โ€” Verified Instant Buy +

+
+ {pounceItems.map(item => ( + + ))} +
+
+ )} + + {/* Standard: External */} +
+

Active Auctions

+ +
+ +) +``` + +### 2. Konsolidierung + +| Aktion | Details | +|--------|---------| +| `/market/page.tsx` entfernen | Redirect zu `/auctions` | +| `/auctions/page.tsx` umbenennen | โ†’ "Market" in Navigation | + +--- + +## Phase D: Score & Scraper Verbesserungen (Woche 2-3) + +### 1. Pounce Score v2.0 + +**Datei:** `backend/app/services/valuation.py` + +Erweitern um: +- Bid Activity Score +- Time Pressure Score +- Value Ratio Score +- Platform Trust Score + +### 2. Scraper Fallbacks + +**Datei:** `backend/app/services/auction_scraper.py` + +```python +async def scrape_with_fallback(self, platform: str, db: AsyncSession): + methods = [ + (f'_scrape_{platform.lower()}_api', 'API'), + (f'_scrape_{platform.lower()}_rss', 'RSS'), + (f'_scrape_{platform.lower()}_html', 'HTML'), + ] + + for method_name, method_type in methods: + method = getattr(self, method_name, None) + if not method: + continue + + try: + result = await method(db) + if result['found'] > 0: + return result + except Exception as e: + logger.warning(f"{platform} {method_type} failed: {e}") + + return {'found': 0, 'error': 'All methods failed'} +``` + +--- + +## Checkliste fรผr den Clean Start + +### Backend: +- [ ] Unified Feed Endpoint `/auctions/feed` erstellen +- [ ] Pounce Score v2.0 in `valuation.py` integrieren +- [ ] Scraper Fallback-Logik hinzufรผgen + +### Frontend: +- [ ] Leere Ordner lรถschen +- [ ] `api.getMarketFeed()` implementieren +- [ ] Market Page: Pounce Direct Integration +- [ ] Auctions Page: Visuelle Hierarchie +- [ ] `/market/page.tsx` zu Redirect machen + +### Testing: +- [ ] Listing erstellen โ†’ Erscheint im Market Feed? +- [ ] DNS Verification โ†’ Funktioniert? +- [ ] External Auctions โ†’ Werden geladen? +- [ ] Filter "Pounce Only" โ†’ Zeigt nur Listings? + +--- + +## Visualisierung: Datenfluss + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ MARKET FEED โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ LISTINGS โ”‚ โ”‚ AUCTIONS โ”‚ โ”‚ SCHEDULER โ”‚ โ”‚ +โ”‚ โ”‚ (Pounce) โ”‚ โ”‚ (External) โ”‚ โ”‚ (Scrape) โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ +โ”‚ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ /auctions/feed โ”‚ โ”‚ +โ”‚ โ”‚ (Unified API) โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ–ผ โ–ผ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ TERMINAL โ”‚ โ”‚ PUBLIC โ”‚ โ”‚ ADMIN โ”‚ โ”‚ +โ”‚ โ”‚ /market โ”‚ โ”‚ /auctions โ”‚ โ”‚ /admin โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +--- + +# ๐Ÿš€ TEIL 4: ROADMAP ZUM UNICORN + +## Die 4 Phasen (aus pounce_strategy.md) + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ POUNCE UNICORN ROADMAP โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ PHASE 1: INTELLIGENCE (0-18 Monate) โ”‚ +โ”‚ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• โ”‚ +โ”‚ Ziel: 10.000 User, $1M ARR, Datenhoheit โ”‚ +โ”‚ โ”‚ +โ”‚ โœ… Pounce Terminal (Dashboard) โ”‚ +โ”‚ โœ… TLD Pricing (Market Barometer) โ”‚ +โ”‚ โœ… Auction Aggregator (Scraping) โ”‚ +โ”‚ โœ… Watchlist/Monitoring โ”‚ +โ”‚ โณ Pounce Direct (Marketplace) โ”‚ +โ”‚ ๐Ÿ”œ Zone File Analyse โ”‚ +โ”‚ โ”‚ +โ”‚ Status: WIR SIND HIER โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ PHASE 2: LIQUIDITร„T (18-36 Monate) โ”‚ +โ”‚ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• โ”‚ +โ”‚ Ziel: Den Transaktionsfluss รผbernehmen, $10M ARR โ”‚ +โ”‚ โ”‚ +โ”‚ ๐Ÿ”ฎ Pounce Instant Exchange (Escrow integriert) โ”‚ +โ”‚ ๐Ÿ”ฎ "Buy Now" Buttons im Dashboard โ”‚ +โ”‚ ๐Ÿ”ฎ 5% Transaktionsgebรผhr (statt 15-20% bei Konkurrenz) โ”‚ +โ”‚ โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ PHASE 3: FINANZIALISIERUNG (3-5 Jahre) โ”‚ +โ”‚ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• โ”‚ +โ”‚ Ziel: Domains als Asset-Klasse, $50-100M ARR โ”‚ +โ”‚ โ”‚ +โ”‚ ๐Ÿ”ฎ Fractional Ownership (Anteile an Premium-Domains) โ”‚ +โ”‚ ๐Ÿ”ฎ Domain-Backed Lending (Kredit gegen Domain) โ”‚ +โ”‚ ๐Ÿ”ฎ โ†’ Wir werden ein FINTECH โ”‚ +โ”‚ โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ PHASE 4: IMPERIUM (5+ Jahre) โ”‚ +โ”‚ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• โ”‚ +โ”‚ Ziel: $1 Mrd. Bewertung, "Too big to fail" โ”‚ +โ”‚ โ”‚ +โ”‚ ๐Ÿ”ฎ Pounce Enterprise Sentinel (B2B Brand Protection) โ”‚ +โ”‚ ๐Ÿ”ฎ Fortune 500 Kunden (Apple, Tesla, etc.) โ”‚ +โ”‚ ๐Ÿ”ฎ KI-gestรผtzte Phishing-Takedowns โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## Was WIR JETZT tun (Phase 1 perfektionieren) + +### Prioritรคt 1: Pounce Direct perfektionieren +- [x] Listing-System gebaut +- [x] DNS-Verifizierung funktioniert +- [ ] **Im Market Feed anzeigen** โ† Nร„CHSTER SCHRITT +- [ ] Visuelle Hierarchie (๐Ÿ’Ž Pounce vs ๐Ÿข External) + +### Prioritรคt 2: Datenqualitรคt verbessern +- [x] Scraping lรคuft +- [ ] Fallback-Logik fรผr Scraper +- [ ] Pounce Score v2.0 + +### Prioritรคt 3: Zone Files vorbereiten +- [ ] Verisign Zone File Access beantragen +- [ ] Algorithmus entwickeln (kann lokal getestet werden) +- [ ] Server-Infrastruktur planen + +--- + +## Zusammenfassung: Der Weg zum Unicorn + +``` + HEUTE 6 MONATE 18+ MONATE + โ”‚ โ”‚ โ”‚ + โ–ผ โ–ผ โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ SCRAPING โ”‚ โ†’โ†’โ†’ โ”‚ ZONE FILES โ”‚ โ†’โ†’โ†’ โ”‚ FINTECH โ”‚ + โ”‚ + POUNCE โ”‚ โ”‚ ANALYSIS โ”‚ โ”‚ Bร–RSE โ”‚ + โ”‚ DIRECT โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ โ”‚ + โ”‚ โ”‚ โ”‚ + "Content Filler" "Daten-Monopol" "Asset-Klasse" + Seite wirkt lebendig Exklusive Intel Domains = Aktien +``` + +--- + +## ๐Ÿ’Ž Das Mantra + +> **"Don't guess. Know."** +> +> Phase 1: Intelligence +> +> **"Don't just buy. Invest."** +> +> Phase 3: Asset Class + +Der Weg zum Unicorn fรผhrt รผber **Datenhoheit** und **einzigartigen Content**. + +1. **Heute:** Pounce Direct (User-Listings) = Unique Content +2. **Morgen:** Zone Files = Exklusive Intelligence +3. **รœbermorgen:** Fintech = Milliarden-Bewertung + +--- + +**Bereit zum Start?** ๐Ÿš€ + +Sag mir, womit ich beginnen soll: +1. **Cleanup** โ€” Leere Ordner lรถschen +2. **Backend** โ€” Unified Feed API erstellen +3. **Frontend** โ€” Market Page mit Pounce Direct + diff --git a/ZONE_FILE_ACCESS.md b/ZONE_FILE_ACCESS.md new file mode 100644 index 0000000..ac99529 --- /dev/null +++ b/ZONE_FILE_ACCESS.md @@ -0,0 +1,307 @@ +# ๐ŸŒ Zone File Access โ€” Anleitung zur Datenhoheit + +--- + +## Was sind Zone Files? + +Zone Files sind die **Master-Listen** aller registrierten Domains pro TLD (Top-Level-Domain). Sie werden tรคglich von den Registries aktualisiert und enthalten: + +- **Alle aktiven Domains** einer TLD +- **Nameserver-Informationen** +- **Keine WHOIS-Daten** (nur Domain + NS) + +**Beispiel `.com` Zone File (vereinfacht):** +``` +example.com. 86400 IN NS ns1.example.com. +example.com. 86400 IN NS ns2.example.com. +google.com. 86400 IN NS ns1.google.com. +... +``` + +--- + +## Warum Zone Files = Unicorn? + +| Vorteil | Beschreibung | +|---------|--------------| +| **Drop Prediction** | Domains die aus der Zone verschwinden = droppen in 1-5 Tagen | +| **Exklusive Intel** | Diese Domains sind NOCH NICHT in Auktionen | +| **Frรผher als Konkurrenz** | Backorder setzen bevor andere es wissen | +| **Trend-Analyse** | Welche Keywords werden gerade registriert? | +| **Daten-Monopol** | Gefilterte, cleane Daten vs. Spam-Flut von ExpiredDomains | + +--- + +## Registries und Zugang + +### Tier 1: Critical TLDs (Sofort beantragen) + +| Registry | TLDs | Domains | Link | +|----------|------|---------|------| +| **Verisign** | `.com`, `.net` | ~160M + 13M | [Zone File Access](https://www.verisign.com/en_US/channel-resources/domain-registry-products/zone-file/index.xhtml) | +| **PIR** | `.org` | ~10M | [Zone File Access Program](https://tld.org/zone-file-access/) | +| **Afilias** | `.info` | ~4M | Contact: registry@afilias.info | + +### Tier 2: Premium TLDs (Phase 2) + +| Registry | TLDs | Fokus | +|----------|------|-------| +| **CentralNIC** | `.io`, `.co` | Startups | +| **Google** | `.app`, `.dev` | Tech | +| **Donuts** | `.xyz`, `.online`, etc. | Volumen | +| **SWITCH** | `.ch` | Schweizer Markt | + +--- + +## Bewerbungsprozess: Verisign (.com/.net) + +### 1. Voraussetzungen + +- Gรผltige Firma/Organisation +- Technische Infrastruktur fรผr groรŸe Datenmengen (~500GB/Tag) +- Akzeptanz der Nutzungsbedingungen (keine Resale der Rohdaten) + +### 2. Online-Bewerbung + +1. Gehe zu: https://www.verisign.com/en_US/channel-resources/domain-registry-products/zone-file/index.xhtml +2. Klicke auf "Request Zone File Access" +3. Fรผlle das Formular aus: + - **Organization Name:** GenTwo AG + - **Purpose:** Domain research and analytics platform + - **Contact:** (technischer Ansprechpartner) + +### 3. Wartezeit + +- **Review:** 1-4 Wochen +- **Genehmigung:** Per E-Mail mit FTP/HTTPS Zugangsdaten + +### 4. Kosten + +- **Verisign:** Kostenlos fรผr nicht-kommerzielle/Forschungszwecke +- **Kommerzielle Nutzung:** $10,000/Jahr (verhandelbar) + +--- + +## Technische Integration + +### Server-Anforderungen + +```yaml +# Minimale Infrastruktur +CPU: 16+ Cores (parallele Verarbeitung) +RAM: 64GB+ (effizientes Set-Diffing) +Storage: 2TB SSD (Zone Files + History) +Network: 1Gbps (schneller Download) + +# Geschรคtzte Kosten +Provider: Hetzner/OVH Dedicated +Preis: ~$300-500/Monat +``` + +### Processing Pipeline + +``` +04:00 UTC โ”‚ Zone File Download (FTP/HTTPS) + โ”‚ โ””โ”€โ†’ ~500GB komprimiert fรผr .com/.net + โ”‚ +04:30 UTC โ”‚ Decompression & Parsing + โ”‚ โ””โ”€โ†’ Extrahiere Domain-Namen + โ”‚ +05:00 UTC โ”‚ Diff Analysis + โ”‚ โ””โ”€โ†’ Vergleiche mit gestern + โ”‚ โ””โ”€โ†’ NEU: Neue Registrierungen + โ”‚ โ””โ”€โ†’ WEG: Potentielle Drops + โ”‚ +05:30 UTC โ”‚ Quality Scoring (Pounce Algorithm) + โ”‚ โ””โ”€โ†’ Filtere Spam raus (99%+) + โ”‚ โ””โ”€โ†’ Nur Premium-Domains durchlassen + โ”‚ +06:00 UTC โ”‚ Database Update + โ”‚ โ””โ”€โ†’ PostgreSQL: pounce_zone_drops + โ”‚ +06:15 UTC โ”‚ Alert Matching + โ”‚ โ””โ”€โ†’ Sniper Alerts triggern + โ”‚ +06:30 UTC โ”‚ User Notifications + โ”‚ โ””โ”€โ†’ E-Mail/SMS fรผr Tycoon-User +``` + +### Datenbank-Schema (geplant) + +```sql +-- Zone File Drops +CREATE TABLE pounce_zone_drops ( + id SERIAL PRIMARY KEY, + domain VARCHAR(255) NOT NULL, + tld VARCHAR(20) NOT NULL, + + -- Analyse + pounce_score INT NOT NULL, + estimated_value DECIMAL(10,2), + + -- Status + detected_at TIMESTAMP DEFAULT NOW(), + estimated_drop_date TIMESTAMP, + status VARCHAR(20) DEFAULT 'pending', -- pending, dropped, backordered, registered + + -- Tracking + notified_users INT DEFAULT 0, + backorder_count INT DEFAULT 0, + + UNIQUE(domain) +); + +-- Index fรผr schnelle Suche +CREATE INDEX idx_zone_drops_score ON pounce_zone_drops(pounce_score DESC); +CREATE INDEX idx_zone_drops_date ON pounce_zone_drops(estimated_drop_date); +``` + +--- + +## Der Pounce Algorithm โ€” Zone File Edition + +```python +# backend/app/services/zone_analyzer.py (ZU BAUEN) + +class ZoneFileAnalyzer: + """ + Analysiert Zone Files und findet Premium-Opportunities. + + Input: Raw Zone File (Millionen von Domains) + Output: Gefilterte Premium-Liste (Hunderte) + """ + + async def analyze_drops(self, yesterday: set, today: set) -> list: + """ + Findet Domains die aus der Zone verschwunden sind. + Diese Domains droppen in 1-5 Tagen (Redemption Period). + """ + dropped = yesterday - today # Set-Differenz + + premium_drops = [] + for domain in dropped: + score = self.calculate_pounce_score(domain) + + # Nur Premium durchlassen (>70 Score) + if score >= 70: + premium_drops.append({ + "domain": domain, + "score": score, + "drop_date": self.estimate_drop_date(domain), + "estimated_value": self.estimate_value(domain), + }) + + return sorted(premium_drops, key=lambda x: x['score'], reverse=True) + + def calculate_pounce_score(self, domain: str) -> int: + """ + Der Pounce Algorithm โ€” Qualitรคtsfilter fรผr Domains. + + Faktoren: + - Lรคnge (kurz = wertvoll) + - TLD (com > io > xyz) + - Keine Zahlen/Bindestriche + - Dictionary Word Bonus + """ + name = domain.rsplit('.', 1)[0] + tld = domain.rsplit('.', 1)[1] + score = 50 # Baseline + + # Lรคngen-Score (exponentiell fรผr kurze Domains) + length_scores = {1: 50, 2: 45, 3: 40, 4: 30, 5: 20, 6: 15, 7: 10} + score += length_scores.get(len(name), max(0, 15 - len(name))) + + # TLD Premium + tld_scores = {'com': 20, 'ai': 25, 'io': 18, 'co': 12, 'ch': 15, 'de': 10} + score += tld_scores.get(tld, 0) + + # Penalties + if '-' in name: score -= 30 + if any(c.isdigit() for c in name): score -= 20 + if len(name) > 12: score -= 15 + + return max(0, min(100, score)) +``` + +--- + +## Feature: "Drops Tomorrow" (Tycoon Exclusive) + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ”ฎ DROPS TOMORROW โ€” Tycoon Exclusive ($29/mo) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ Diese Domains sind NICHT in Auktionen! โ”‚ +โ”‚ Du kannst sie beim Registrar direkt registrieren. โ”‚ +โ”‚ โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โ”‚ +โ”‚ Domain TLD Score Est. Value Drops In โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ pixel.com .com 95 $50,000 23h 45m โ”‚ +โ”‚ swift.io .io 88 $8,000 23h 12m โ”‚ +โ”‚ quantum.ai .ai 92 $25,000 22h 58m โ”‚ +โ”‚ nexus.dev .dev 84 $4,500 22h 30m โ”‚ +โ”‚ fusion.co .co 81 $3,200 21h 15m โ”‚ +โ”‚ โ”‚ +โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ +โ”‚ โ”‚ +โ”‚ ๐Ÿ’ก Pro Tip: Setze bei deinem Registrar einen Backorder โ”‚ +โ”‚ fรผr diese Domains. Wer zuerst kommt... โ”‚ +โ”‚ โ”‚ +โ”‚ [๐Ÿ”” Alert fรผr "pixel.com" setzen] โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## Roadmap + +### Phase 1: Jetzt (Bewerbung) +- [ ] Verisign Zone File Access beantragen +- [ ] PIR (.org) Zone File Access beantragen +- [ ] Server-Infrastruktur planen + +### Phase 2: 3-6 Monate (Integration) +- [ ] Download-Pipeline bauen +- [ ] Diff-Analyse implementieren +- [ ] Pounce Algorithm testen +- [ ] "Drops Tomorrow" Feature fรผr Tycoon + +### Phase 3: 6-12 Monate (Skalierung) +- [ ] Weitere TLDs (.io, .co, .ch, .de) +- [ ] Historische Trend-Analyse +- [ ] Keyword-Tracking +- [ ] Enterprise Features + +--- + +## Risiken und Mitigierung + +| Risiko | Wahrscheinlichkeit | Mitigierung | +|--------|-------------------|-------------| +| Ablehnung durch Registry | Mittel | Klare Business-Case, ggf. Partnerschaften | +| Hohe Serverkosten | Niedrig | Cloud-Skalierung, nur Premium-TLDs | +| Konkurrenz kopiert | Mittel | First-Mover-Vorteil, besserer Algorithmus | +| Datenqualitรคt | Niedrig | Mehrere Quellen, Validierung | + +--- + +## Nรคchster Schritt + +**Aktion fรผr diese Woche:** + +1. **Verisign bewerben:** https://www.verisign.com/en_US/channel-resources/domain-registry-products/zone-file/index.xhtml +2. **E-Mail an PIR:** zone-file-access@pir.org +3. **Server bei Hetzner reservieren:** AX101 Dedicated (~โ‚ฌ60/Monat) + +--- + +## Zusammenfassung + +Zone Files sind der **Schlรผssel zur Datenhoheit**. Wรคhrend die Konkurrenz auf Scraping setzt, werden wir die Rohdaten direkt von der Quelle haben โ€” und mit dem Pounce Algorithm filtern, sodass nur Premium-Opportunities zu unseren Usern gelangen. + +**Das ist der Unicorn-Treiber.** ๐Ÿฆ„ + diff --git a/backend/app/api/auctions.py b/backend/app/api/auctions.py index 0015831..3403592 100644 --- a/backend/app/api/auctions.py +++ b/backend/app/api/auctions.py @@ -10,6 +10,11 @@ Data Sources (Web Scraping): - Sedo (public search) - NameJet (public auctions) +PLUS Pounce Direct Listings (user-created marketplace): +- DNS-verified owner listings +- Instant buy option +- 0% commission + IMPORTANT: - All data comes from web scraping of public pages - No mock data - everything is real scraped data @@ -24,15 +29,17 @@ Legal Note (Switzerland): import logging from datetime import datetime, timedelta from typing import Optional, List +from itertools import groupby from fastapi import APIRouter, Depends, Query, HTTPException from pydantic import BaseModel -from sqlalchemy import select, func, and_ +from sqlalchemy import select, func, and_, or_ from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.api.deps import get_current_user, get_current_user_optional from app.models.user import User from app.models.auction import DomainAuction, AuctionScrapeLog +from app.models.listing import DomainListing, ListingStatus, VerificationStatus from app.services.valuation import valuation_service from app.services.auction_scraper import auction_scraper @@ -103,6 +110,55 @@ class ScrapeStatus(BaseModel): next_scrape: Optional[datetime] +class MarketFeedItem(BaseModel): + """Unified market feed item - combines auctions and Pounce Direct listings.""" + id: str + domain: str + tld: str + price: float + currency: str = "USD" + price_type: str # "bid" or "fixed" + status: str # "auction" or "instant" + + # Source info + source: str # "Pounce", "GoDaddy", "Sedo", etc. + is_pounce: bool = False + verified: bool = False + + # Auction-specific + time_remaining: Optional[str] = None + end_time: Optional[datetime] = None + num_bids: Optional[int] = None + + # Pounce Direct specific + slug: Optional[str] = None + seller_verified: bool = False + + # URLs + url: str # Internal for Pounce, external for auctions + is_external: bool = True + + # Scoring + pounce_score: int = 50 + + # Valuation (optional) + valuation: Optional[AuctionValuation] = None + + class Config: + from_attributes = True + + +class MarketFeedResponse(BaseModel): + """Response for unified market feed.""" + items: List[MarketFeedItem] + total: int + pounce_direct_count: int + auction_count: int + sources: List[str] + last_updated: datetime + filters_applied: dict = {} + + # ============== Helper Functions ============== def _format_time_remaining(end_time: datetime) -> str: @@ -711,3 +767,345 @@ def _get_opportunity_reasoning(value_ratio: float, hours_left: float, num_bids: reasons.append(f"๐Ÿ”ฅ High demand ({num_bids} bids)") return " | ".join(reasons) + + +def _calculate_pounce_score_v2(domain: str, tld: str, num_bids: int = 0, age_years: int = 0, is_pounce: bool = False) -> int: + """ + Pounce Score v2.0 - Enhanced scoring algorithm. + + Factors: + - Length (shorter = more valuable) + - TLD premium + - Market activity (bids) + - Age bonus + - Pounce Direct bonus (verified listings) + - Penalties (hyphens, numbers, etc.) + """ + score = 50 # Baseline + name = domain.rsplit('.', 1)[0] if '.' in domain else domain + + # A) LENGTH BONUS (exponential for short domains) + length_scores = {1: 50, 2: 45, 3: 40, 4: 30, 5: 20, 6: 15, 7: 10} + score += length_scores.get(len(name), max(0, 15 - len(name))) + + # B) TLD PREMIUM + tld_scores = { + 'com': 20, 'ai': 25, 'io': 18, 'co': 12, + 'ch': 15, 'de': 10, 'net': 8, 'org': 8, + 'app': 10, 'dev': 10, 'xyz': 5 + } + score += tld_scores.get(tld.lower(), 0) + + # C) MARKET ACTIVITY (bids = demand signal) + if num_bids >= 20: + score += 15 + elif num_bids >= 10: + score += 10 + elif num_bids >= 5: + score += 5 + elif num_bids >= 2: + score += 2 + + # D) AGE BONUS (established domains) + if age_years and age_years > 15: + score += 10 + elif age_years and age_years > 10: + score += 7 + elif age_years and age_years > 5: + score += 3 + + # E) POUNCE DIRECT BONUS (verified = trustworthy) + if is_pounce: + score += 10 + + # F) PENALTIES + if '-' in name: + score -= 25 + if any(c.isdigit() for c in name) and len(name) > 3: + score -= 20 + if len(name) > 15: + score -= 15 + + # G) CONSONANT CHECK (no gibberish like "xkqzfgh") + consonants = 'bcdfghjklmnpqrstvwxyz' + max_streak = 0 + current_streak = 0 + for c in name.lower(): + if c in consonants: + current_streak += 1 + max_streak = max(max_streak, current_streak) + else: + current_streak = 0 + if max_streak > 4: + score -= 15 + + return max(0, min(100, score)) + + +def _is_premium_domain(domain_name: str) -> bool: + """Check if a domain looks premium/professional (Vanity Filter).""" + parts = domain_name.rsplit('.', 1) + name = parts[0] if parts else domain_name + tld = parts[1].lower() if len(parts) > 1 else "" + + # Premium TLDs only + premium_tlds = ['com', 'io', 'ai', 'co', 'de', 'ch', 'net', 'org', 'app', 'dev', 'xyz'] + if tld and tld not in premium_tlds: + return False + + # Length check + if len(name) > 15: + return False + if len(name) < 3: + return False + + # Hyphen check + if name.count('-') > 1: + return False + + # Digit check + if sum(1 for c in name if c.isdigit()) > 2: + return False + + # Consonant cluster check + consonants = 'bcdfghjklmnpqrstvwxyz' + max_streak = 0 + current_streak = 0 + for c in name.lower(): + if c in consonants: + current_streak += 1 + max_streak = max(max_streak, current_streak) + else: + current_streak = 0 + if max_streak > 4: + return False + + return True + + +# ============== UNIFIED MARKET FEED ============== + +@router.get("/feed", response_model=MarketFeedResponse) +async def get_market_feed( + # Source filter + source: str = Query("all", enum=["all", "pounce", "external"]), + + # Search & filters + keyword: Optional[str] = Query(None, description="Search in domain names"), + tld: Optional[str] = Query(None, description="Filter by TLD"), + min_price: Optional[float] = Query(None, ge=0), + max_price: Optional[float] = Query(None, ge=0), + min_score: int = Query(0, ge=0, le=100), + ending_within: Optional[int] = Query(None, description="Auctions ending within X hours"), + verified_only: bool = Query(False, description="Only show verified Pounce listings"), + + # Sort + sort_by: str = Query("score", enum=["score", "price_asc", "price_desc", "time", "newest"]), + + # Pagination + limit: int = Query(50, le=200), + offset: int = Query(0, ge=0), + + # Auth + current_user: Optional[User] = Depends(get_current_user_optional), + db: AsyncSession = Depends(get_db), +): + """ + ๐Ÿš€ UNIFIED MARKET FEED โ€” The heart of Pounce + + Combines: + - ๐Ÿ’Ž Pounce Direct: DNS-verified user listings (instant buy) + - ๐Ÿข External Auctions: Scraped from GoDaddy, Sedo, NameJet, etc. + + For non-authenticated users: + - Vanity filter applied (premium domains only) + - Pounce Score visible but limited details + + For authenticated users (Trader/Tycoon): + - Full access to all domains + - Advanced filtering + - Valuation data + + POUNCE EXCLUSIVE domains are highlighted and appear first. + """ + items: List[MarketFeedItem] = [] + pounce_count = 0 + auction_count = 0 + + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + # 1. POUNCE DIRECT LISTINGS (Our USP!) + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + if source in ["all", "pounce"]: + listing_query = select(DomainListing).where( + DomainListing.status == ListingStatus.ACTIVE.value + ) + + if keyword: + listing_query = listing_query.where( + DomainListing.domain.ilike(f"%{keyword}%") + ) + + if verified_only: + listing_query = listing_query.where( + DomainListing.verification_status == VerificationStatus.VERIFIED.value + ) + + if min_price is not None: + listing_query = listing_query.where(DomainListing.asking_price >= min_price) + if max_price is not None: + listing_query = listing_query.where(DomainListing.asking_price <= max_price) + + result = await db.execute(listing_query) + listings = result.scalars().all() + + for listing in listings: + domain_tld = listing.domain.rsplit('.', 1)[1] if '.' in listing.domain else "" + + # Apply TLD filter + if tld and domain_tld.lower() != tld.lower().lstrip('.'): + continue + + pounce_score = listing.pounce_score or _calculate_pounce_score_v2( + listing.domain, domain_tld, is_pounce=True + ) + + # Apply score filter + if pounce_score < min_score: + continue + + items.append(MarketFeedItem( + id=f"pounce-{listing.id}", + domain=listing.domain, + tld=domain_tld, + price=listing.asking_price or 0, + currency=listing.currency or "USD", + price_type="fixed" if listing.price_type == "fixed" else "negotiable", + status="instant", + source="Pounce", + is_pounce=True, + verified=listing.is_verified, + seller_verified=listing.is_verified, + slug=listing.slug, + url=f"/buy/{listing.slug}", + is_external=False, + pounce_score=pounce_score, + )) + pounce_count += 1 + + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + # 2. EXTERNAL AUCTIONS (Scraped from platforms) + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + if source in ["all", "external"]: + auction_query = select(DomainAuction).where(DomainAuction.is_active == True) + + if keyword: + auction_query = auction_query.where( + DomainAuction.domain.ilike(f"%{keyword}%") + ) + + if tld: + auction_query = auction_query.where( + DomainAuction.tld == tld.lower().lstrip('.') + ) + + if min_price is not None: + auction_query = auction_query.where(DomainAuction.current_bid >= min_price) + if max_price is not None: + auction_query = auction_query.where(DomainAuction.current_bid <= max_price) + + if ending_within: + cutoff = datetime.utcnow() + timedelta(hours=ending_within) + auction_query = auction_query.where(DomainAuction.end_time <= cutoff) + + result = await db.execute(auction_query) + auctions = result.scalars().all() + + for auction in auctions: + # Apply vanity filter for non-authenticated users + if current_user is None and not _is_premium_domain(auction.domain): + continue + + pounce_score = _calculate_pounce_score_v2( + auction.domain, + auction.tld, + num_bids=auction.num_bids, + age_years=auction.age_years or 0, + is_pounce=False + ) + + # Apply score filter + if pounce_score < min_score: + continue + + items.append(MarketFeedItem( + id=f"auction-{auction.id}", + domain=auction.domain, + tld=auction.tld, + price=auction.current_bid, + currency=auction.currency, + price_type="bid", + status="auction", + source=auction.platform, + is_pounce=False, + verified=False, + time_remaining=_format_time_remaining(auction.end_time), + end_time=auction.end_time, + num_bids=auction.num_bids, + url=_get_affiliate_url(auction.platform, auction.domain, auction.auction_url), + is_external=True, + pounce_score=pounce_score, + )) + auction_count += 1 + + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + # 3. SORT (Pounce Direct always appears first within same score) + # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + if sort_by == "score": + items.sort(key=lambda x: (-x.pounce_score, -int(x.is_pounce), x.domain)) + elif sort_by == "price_asc": + items.sort(key=lambda x: (x.price, -int(x.is_pounce), x.domain)) + elif sort_by == "price_desc": + items.sort(key=lambda x: (-x.price, -int(x.is_pounce), x.domain)) + elif sort_by == "time": + # Pounce Direct first (no time limit), then by end time + def time_sort_key(x): + if x.is_pounce: + return (0, datetime.max) + return (1, x.end_time or datetime.max) + items.sort(key=time_sort_key) + elif sort_by == "newest": + items.sort(key=lambda x: (-int(x.is_pounce), x.domain)) + + total = len(items) + + # Pagination + items = items[offset:offset + limit] + + # Get unique sources + sources = list(set(item.source for item in items)) + + # Last update time + last_update_result = await db.execute( + select(func.max(DomainAuction.updated_at)) + ) + last_updated = last_update_result.scalar() or datetime.utcnow() + + return MarketFeedResponse( + items=items, + total=total, + pounce_direct_count=pounce_count, + auction_count=auction_count, + sources=sources, + last_updated=last_updated, + filters_applied={ + "source": source, + "keyword": keyword, + "tld": tld, + "min_price": min_price, + "max_price": max_price, + "min_score": min_score, + "ending_within": ending_within, + "verified_only": verified_only, + "sort_by": sort_by, + } + ) diff --git a/backend/scripts/reset_admin_password.py b/backend/scripts/reset_admin_password.py index 1169498..ce9aa41 100644 --- a/backend/scripts/reset_admin_password.py +++ b/backend/scripts/reset_admin_password.py @@ -1,57 +1,81 @@ -#!/usr/bin/env python3 """ -Script to reset admin password. +Reset admin password for guggeryves@hotmail.com """ import asyncio import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).parent.parent)) - from sqlalchemy import select -from passlib.context import CryptContext from app.database import AsyncSessionLocal from app.models.user import User +from app.models.subscription import Subscription, SubscriptionTier, SubscriptionStatus, TIER_CONFIG +from app.services.auth import AuthService +from datetime import datetime, timedelta - -ADMIN_EMAIL = "guggeryves@hotmail.com" -NEW_PASSWORD = "Pounce2024!" # Strong password - -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") - - -async def reset_password(): - """Reset admin password.""" - print(f"๐Ÿ” Resetting password for: {ADMIN_EMAIL}") - +async def reset_admin_password(): async with AsyncSessionLocal() as db: - result = await db.execute( - select(User).where(User.email == ADMIN_EMAIL) - ) + admin_email = "guggeryves@hotmail.com" + new_password = "Admin123!" + + print(f"๐Ÿ” Looking for user: {admin_email}") + result = await db.execute(select(User).where(User.email == admin_email)) user = result.scalar_one_or_none() if not user: - print(f"โŒ User not found: {ADMIN_EMAIL}") - return False + print(f"โŒ User with email {admin_email} not found.") + return - # Hash new password - hashed = pwd_context.hash(NEW_PASSWORD) - user.hashed_password = hashed + print(f"โœ… User found: ID={user.id}, Name={user.name}") + + # Update password + user.hashed_password = AuthService.hash_password(new_password) user.is_verified = True - user.is_active = True user.is_admin = True - + user.is_active = True + user.updated_at = datetime.utcnow() await db.commit() + print(f"โœ… Password updated to: {new_password}") - print(f"โœ… Password reset successful!") - print(f"\n๐Ÿ“‹ LOGIN CREDENTIALS:") - print(f" Email: {ADMIN_EMAIL}") - print(f" Password: {NEW_PASSWORD}") - print(f"\nโš ๏ธ Please change this password after logging in!") + # Ensure user has Tycoon subscription + sub_result = await db.execute( + select(Subscription).where(Subscription.user_id == user.id) + ) + subscription = sub_result.scalar_one_or_none() - return True - + tycoon_config = TIER_CONFIG.get(SubscriptionTier.TYCOON, {}) + + if not subscription: + subscription = Subscription( + user_id=user.id, + tier=SubscriptionTier.TYCOON, + status=SubscriptionStatus.ACTIVE, + max_domains=tycoon_config.get("domain_limit", 500), + started_at=datetime.utcnow(), + expires_at=datetime.utcnow() + timedelta(days=365 * 100), + ) + db.add(subscription) + await db.commit() + print("โœ… Created new Tycoon subscription.") + elif subscription.tier != SubscriptionTier.TYCOON or subscription.status != SubscriptionStatus.ACTIVE: + subscription.tier = SubscriptionTier.TYCOON + subscription.status = SubscriptionStatus.ACTIVE + subscription.max_domains = tycoon_config.get("domain_limit", 500) + subscription.updated_at = datetime.utcnow() + await db.commit() + print("โœ… Updated subscription to Tycoon (Active).") + else: + print(f"โœ… Subscription: {subscription.tier.value} ({subscription.status.value})") + + await db.refresh(user) + + print("\n==================================================") + print("๐Ÿ“‹ FINAL STATUS:") + print(f" Email: {user.email}") + print(f" Password: {new_password}") + print(f" Name: {user.name}") + print(f" Admin: {'โœ… Yes' if user.is_admin else 'โŒ No'}") + print(f" Verified: {'โœ… Yes' if user.is_verified else 'โŒ No'}") + print(f" Active: {'โœ… Yes' if user.is_active else 'โŒ No'}") + print("==================================================") + print("\nโœ… Admin user is ready! You can now login.") if __name__ == "__main__": - asyncio.run(reset_password()) - + asyncio.run(reset_admin_password()) diff --git a/frontend/src/app/auctions/page.tsx b/frontend/src/app/auctions/page.tsx index 543e062..d6e224f 100644 --- a/frontend/src/app/auctions/page.tsx +++ b/frontend/src/app/auctions/page.tsx @@ -21,10 +21,34 @@ import { ChevronDown, ChevronsUpDown, Sparkles, + Diamond, + ShieldCheck, + Zap, } from 'lucide-react' import Link from 'next/link' import clsx from 'clsx' +interface MarketItem { + id: string + domain: string + tld: string + price: number + currency: string + price_type: 'bid' | 'fixed' | 'negotiable' + status: 'auction' | 'instant' + source: string + is_pounce: boolean + verified: boolean + time_remaining?: string + end_time?: string + num_bids?: number + slug?: string + seller_verified: boolean + url: string + is_external: boolean + pounce_score: number +} + interface Auction { domain: string platform: string @@ -122,6 +146,7 @@ export default function AuctionsPage() { const [allAuctions, setAllAuctions] = useState([]) const [endingSoon, setEndingSoon] = useState([]) const [hotAuctions, setHotAuctions] = useState([]) + const [pounceItems, setPounceItems] = useState([]) const [loading, setLoading] = useState(true) const [activeTab, setActiveTab] = useState('all') const [sortField, setSortField] = useState('ending') @@ -139,14 +164,16 @@ export default function AuctionsPage() { const loadAuctions = async () => { setLoading(true) try { - const [all, ending, hot] = await Promise.all([ + const [all, ending, hot, pounce] = await Promise.all([ api.getAuctions(undefined, undefined, undefined, undefined, undefined, false, 'ending', 100, 0), - api.getEndingSoonAuctions(50), + api.getEndingSoonAuctions(24, 50), // 24 hours, limit 50 api.getHotAuctions(50), + api.getMarketFeed({ source: 'pounce', limit: 10 }).catch(() => ({ items: [] })), ]) setAllAuctions(all.auctions || []) setEndingSoon(ending || []) setHotAuctions(hot || []) + setPounceItems(pounce.items || []) } catch (error) { console.error('Failed to load auctions:', error) } finally { @@ -296,6 +323,70 @@ export default function AuctionsPage() { )} + {/* Pounce Direct Section - Featured */} + {pounceItems.length > 0 && ( +
+
+
+ +

+ Pounce Exclusive +

+
+ Verified โ€ข Instant Buy โ€ข 0% Commission +
+
+ {pounceItems.map((item) => ( + +
+ +
+
+ {item.domain} +
+
+ {item.verified && ( + + + Verified + + )} + + Score: {item.pounce_score} + +
+
+
+
+
+
+ {formatCurrency(item.price, item.currency)} +
+
Instant Buy
+
+
+ Buy Now + +
+
+ + ))} +
+
+ + Browse all Pounce listings โ†’ + +
+
+ )} + {/* Hot Auctions Preview */} {hotPreview.length > 0 && (
diff --git a/frontend/src/app/market/page.tsx b/frontend/src/app/market/page.tsx index 2823dc2..7043c97 100644 --- a/frontend/src/app/market/page.tsx +++ b/frontend/src/app/market/page.tsx @@ -1,253 +1,25 @@ 'use client' -import { useEffect, useState } from 'react' +import { useEffect } from 'react' import { useRouter } from 'next/navigation' -import { useStore } from '@/lib/store' -import { api } from '@/lib/api' -import { TerminalLayout } from '@/components/TerminalLayout' -import { - Search, - Filter, - Clock, - TrendingUp, - Flame, - Sparkles, - ExternalLink, - ChevronDown, - Globe, - Gavel, - ArrowUpDown, -} from 'lucide-react' -import clsx from 'clsx' -type ViewType = 'all' | 'ending' | 'hot' | 'opportunities' - -interface Auction { - domain: string - platform: string - current_bid: number - num_bids: number - end_time: string - time_remaining: string - affiliate_url: string - tld: string -} - -export default function MarketPage() { +/** + * Redirect /market to /auctions + * This page is kept for backwards compatibility + */ +export default function MarketRedirect() { const router = useRouter() - const { subscription } = useStore() - const [auctions, setAuctions] = useState([]) - const [loading, setLoading] = useState(true) - const [activeView, setActiveView] = useState('all') - const [searchQuery, setSearchQuery] = useState('') - const [platformFilter, setPlatformFilter] = useState('all') - const [sortBy, setSortBy] = useState('end_time') - useEffect(() => { - loadAuctions() - }, [activeView]) - - const loadAuctions = async () => { - setLoading(true) - try { - let data - switch (activeView) { - case 'ending': - data = await api.getEndingSoonAuctions(50) - break - case 'hot': - data = await api.getHotAuctions(50) - break - case 'opportunities': - const oppData = await api.getAuctionOpportunities() - data = (oppData.opportunities || []).map((o: any) => o.auction) - break - default: - const auctionData = await api.getAuctions(undefined, undefined, undefined, undefined, undefined, false, sortBy, 50) - data = auctionData.auctions || [] - } - setAuctions(data) - } catch (error) { - console.error('Failed to load auctions:', error) - setAuctions([]) - } finally { - setLoading(false) - } - } - - // Filter auctions - const filteredAuctions = auctions.filter(auction => { - if (searchQuery && !auction.domain.toLowerCase().includes(searchQuery.toLowerCase())) { - return false - } - if (platformFilter !== 'all' && auction.platform !== platformFilter) { - return false - } - return true - }) - - const platforms = ['GoDaddy', 'Sedo', 'NameJet', 'DropCatch', 'ExpiredDomains'] - - const views = [ - { id: 'all', label: 'All Auctions', icon: Gavel }, - { id: 'ending', label: 'Ending Soon', icon: Clock }, - { id: 'hot', label: 'Hot', icon: Flame }, - { id: 'opportunities', label: 'Opportunities', icon: Sparkles }, - ] - + router.replace('/auctions') + }, [router]) + return ( - -
- {/* View Tabs */} -
- {views.map((view) => ( - - ))} -
- - {/* Filters */} -
- {/* Search */} -
- - setSearchQuery(e.target.value)} - placeholder="Search domains..." - className="w-full h-10 pl-10 pr-4 bg-background-secondary border border-border rounded-lg - text-sm text-foreground placeholder:text-foreground-subtle - focus:outline-none focus:border-accent" - /> -
- - {/* Platform Filter */} -
- - -
- - {/* Sort */} -
- - -
-
- - {/* Stats Bar */} -
- {filteredAuctions.length} auctions - โ€ข - - - {platforms.length} platforms - -
- - {/* Auction List */} - {loading ? ( -
- {[...Array(5)].map((_, i) => ( -
- ))} -
- ) : filteredAuctions.length === 0 ? ( -
- -

No auctions found

-

Try adjusting your filters

-
- ) : ( -
- {filteredAuctions.map((auction, idx) => ( -
-
- {/* Domain Info */} -
-
-

{auction.domain}

- - {auction.platform} - -
-
- - - {auction.time_remaining} - - {auction.num_bids} bids -
-
- - {/* Price + Action */} -
-
-

${auction.current_bid.toLocaleString()}

-

Current bid

-
- - Bid - - -
-
-
- ))} -
- )} +
+
+
+

Redirecting to Market...

- +
) } - diff --git a/frontend/src/app/terminal/market/page.tsx b/frontend/src/app/terminal/market/page.tsx index 540bf8c..ef02922 100644 --- a/frontend/src/app/terminal/market/page.tsx +++ b/frontend/src/app/terminal/market/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useState, useMemo, useCallback } from 'react' +import { useEffect, useState, useMemo, useCallback, memo } from 'react' import { useStore } from '@/lib/store' import { api } from '@/lib/api' import { TerminalLayout } from '@/components/TerminalLayout' @@ -27,101 +27,48 @@ import { SlidersHorizontal, MoreHorizontal, Eye, - Info + Info, + ShieldCheck, + Sparkles, + Store } from 'lucide-react' import clsx from 'clsx' +import Link from 'next/link' // ============================================================================ // TYPES // ============================================================================ -interface Auction { - domain: string - platform: string - platform_url?: string - current_bid: number - currency?: string - num_bids: number - time_remaining: string - end_time: string - buy_now_price?: number | null - reserve_met?: boolean | null - traffic?: number | null - tld: string - affiliate_url: string - age_years?: number | null -} - interface MarketItem { id: string domain: string - pounceScore: number - price: number - priceType: 'bid' | 'fixed' - status: 'auction' | 'instant' - timeLeft?: string - endTime?: string - source: 'GoDaddy' | 'Sedo' | 'NameJet' | 'DropCatch' | 'Pounce' - isPounce: boolean - verified?: boolean - affiliateUrl?: string tld: string - numBids?: number + price: number + currency: string + price_type: 'bid' | 'fixed' | 'negotiable' + status: 'auction' | 'instant' + source: string + is_pounce: boolean + verified: boolean + time_remaining?: string + end_time?: string + num_bids?: number + slug?: string + seller_verified: boolean + url: string + is_external: boolean + pounce_score: number } type SortField = 'domain' | 'score' | 'price' | 'time' | 'source' type SortDirection = 'asc' | 'desc' +type SourceFilter = 'all' | 'pounce' | 'external' +type PriceRange = 'all' | 'low' | 'mid' | 'high' // ============================================================================ -// POUNCE SCORE ALGORITHM +// HELPER FUNCTIONS // ============================================================================ -function calculatePounceScore(domain: string, tld: string, numBids?: number, ageYears?: number): number { - let score = 50 - const name = domain.split('.')[0] - - // Length bonus - if (name.length <= 3) score += 30 - else if (name.length === 4) score += 25 - else if (name.length === 5) score += 20 - else if (name.length <= 7) score += 10 - else if (name.length <= 10) score += 5 - else score -= 5 - - // Premium TLD bonus - if (['com', 'ai', 'io'].includes(tld)) score += 15 - else if (['co', 'net', 'org', 'ch', 'de'].includes(tld)) score += 10 - else if (['app', 'dev', 'xyz'].includes(tld)) score += 5 - - // Age bonus - if (ageYears && ageYears > 15) score += 10 - else if (ageYears && ageYears > 10) score += 7 - else if (ageYears && ageYears > 5) score += 3 - - // Activity bonus - if (numBids && numBids >= 20) score += 8 - else if (numBids && numBids >= 10) score += 5 - else if (numBids && numBids >= 5) score += 2 - - // Penalties - if (name.includes('-')) score -= 25 - if (/\d/.test(name) && name.length > 3) score -= 20 - if (name.length > 15) score -= 15 - if (/(.)\1{2,}/.test(name)) score -= 10 - - return Math.max(0, Math.min(100, score)) -} - -function isSpamDomain(domain: string, tld: string): boolean { - const name = domain.split('.')[0] - if (name.includes('-')) return true - if (/\d/.test(name) && name.length > 4) return true - if (name.length > 20) return true - if (!['com', 'ai', 'io', 'co', 'net', 'org', 'ch', 'de', 'app', 'dev', 'xyz', 'tech', 'cloud'].includes(tld)) return true - return false -} - -// Parse time remaining to seconds function parseTimeToSeconds(timeStr?: string): number { if (!timeStr) return Infinity let seconds = 0 @@ -134,62 +81,68 @@ function parseTimeToSeconds(timeStr?: string): number { return seconds || Infinity } +function formatPrice(price: number, currency = 'USD'): string { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency, + maximumFractionDigits: 0 + }).format(price) +} + // ============================================================================ // COMPONENTS // ============================================================================ -// Tooltip Component -function Tooltip({ children, content }: { children: React.ReactNode; content: string }) { - return ( -
- {children} -
- {content} - {/* Arrow */} -
-
+// Tooltip +const Tooltip = memo(({ children, content }: { children: React.ReactNode; content: string }) => ( +
+ {children} +
+ {content} +
- ) -} +
+)) +Tooltip.displayName = 'Tooltip' // Stat Card -function StatCard({ +const StatCard = memo(({ label, value, subValue, icon: Icon, - trend + highlight }: { label: string value: string | number subValue?: string - icon: any - trend?: 'up' | 'down' | 'neutral' -}) { - return ( -
-
-
-

{label}

-
- {value} - {subValue && {subValue}} -
-
-
- + icon: React.ElementType + highlight?: boolean +}) => ( +
+
+
+

{label}

+
+ {value} + {subValue && {subValue}}
- ) -} +
+ +
+
+)) +StatCard.displayName = 'StatCard' -// Score Ring (Desktop) / Badge (Mobile) -function ScoreDisplay({ score, mobile = false }: { score: number; mobile?: boolean }) { +// Score Ring +const ScoreDisplay = memo(({ score, mobile = false }: { score: number; mobile?: boolean }) => { const color = score >= 80 ? 'text-emerald-500' : score >= 50 ? 'text-amber-500' : 'text-zinc-600' if (mobile) { @@ -234,31 +187,43 @@ function ScoreDisplay({ score, mobile = false }: { score: number; mobile?: boole
) -} +}) +ScoreDisplay.displayName = 'ScoreDisplay' -// Minimal Toggle -function FilterToggle({ active, onClick, label }: { active: boolean; onClick: () => void; label: string }) { - return ( - - ) -} +// Filter Toggle +const FilterToggle = memo(({ active, onClick, label, icon: Icon }: { + active: boolean + onClick: () => void + label: string + icon?: React.ElementType +}) => ( + +)) +FilterToggle.displayName = 'FilterToggle' // Sort Header -function SortableHeader({ +const SortableHeader = memo(({ label, field, currentSort, currentDirection, onSort, align = 'left', tooltip }: { - label: string; field: SortField; currentSort: SortField; currentDirection: SortDirection; onSort: (field: SortField) => void; align?: 'left'|'center'|'right'; tooltip?: string -}) { + label: string + field: SortField + currentSort: SortField + currentDirection: SortDirection + onSort: (field: SortField) => void + align?: 'left'|'center'|'right' + tooltip?: string +}) => { const isActive = currentSort === field return (
) -} +}) +SortableHeader.displayName = 'SortableHeader' + +// Pounce Direct Badge +const PounceBadge = memo(({ verified }: { verified: boolean }) => ( +
+ {verified ? ( + <> + + Verified + + ) : ( + <> + + Pounce + + )} +
+)) +PounceBadge.displayName = 'PounceBadge' // ============================================================================ // MAIN PAGE @@ -296,15 +285,16 @@ export default function MarketPage() { const { subscription } = useStore() // Data - const [auctions, setAuctions] = useState([]) + const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) const [refreshing, setRefreshing] = useState(false) + const [stats, setStats] = useState({ total: 0, pounceCount: 0, auctionCount: 0, highScore: 0 }) // Filters - const [hideSpam, setHideSpam] = useState(true) - const [pounceOnly, setPounceOnly] = useState(false) + const [sourceFilter, setSourceFilter] = useState('all') const [searchQuery, setSearchQuery] = useState('') - const [priceRange, setPriceRange] = useState<'all' | 'low' | 'mid' | 'high'>('all') + const [priceRange, setPriceRange] = useState('all') + const [verifiedOnly, setVerifiedOnly] = useState(false) // Sort const [sortField, setSortField] = useState('score') @@ -314,18 +304,36 @@ export default function MarketPage() { const [trackedDomains, setTrackedDomains] = useState>(new Set()) const [trackingInProgress, setTrackingInProgress] = useState(null) - // Load + // Load data const loadData = useCallback(async () => { setLoading(true) try { - const data = await api.getAuctions() - setAuctions(data.auctions || []) + const result = await api.getMarketFeed({ + source: sourceFilter, + keyword: searchQuery || undefined, + minPrice: priceRange === 'low' ? undefined : priceRange === 'mid' ? 100 : priceRange === 'high' ? 1000 : undefined, + maxPrice: priceRange === 'low' ? 100 : priceRange === 'mid' ? 1000 : undefined, + verifiedOnly, + sortBy: sortField === 'score' ? 'score' : + sortField === 'price' ? (sortDirection === 'asc' ? 'price_asc' : 'price_desc') : + sortField === 'time' ? 'time' : 'newest', + limit: 100 + }) + + setItems(result.items || []) + setStats({ + total: result.total, + pounceCount: result.pounce_direct_count, + auctionCount: result.auction_count, + highScore: (result.items || []).filter(i => i.pounce_score >= 80).length + }) } catch (error) { console.error('Failed to load market data:', error) + setItems([]) } finally { setLoading(false) } - }, []) + }, [sourceFilter, searchQuery, priceRange, verifiedOnly, sortField, sortDirection]) useEffect(() => { loadData() }, [loadData]) @@ -336,8 +344,9 @@ export default function MarketPage() { }, [loadData]) const handleSort = useCallback((field: SortField) => { - if (sortField === field) setSortDirection(d => d === 'asc' ? 'desc' : 'asc') - else { + if (sortField === field) { + setSortDirection(d => d === 'asc' ? 'desc' : 'asc') + } else { setSortField(field) setSortDirection(field === 'score' || field === 'price' ? 'desc' : 'asc') } @@ -349,81 +358,68 @@ export default function MarketPage() { try { await api.addDomain(domain) setTrackedDomains(prev => new Set([...Array.from(prev), domain])) - } catch (error) { console.error(error) } finally { setTrackingInProgress(null) } + } catch (error) { + console.error(error) + } finally { + setTrackingInProgress(null) + } }, [trackedDomains, trackingInProgress]) - // Transform & Filter - const marketItems = useMemo(() => { - const items: MarketItem[] = auctions.map(auction => ({ - id: `${auction.domain}-${auction.platform}`, - domain: auction.domain, - pounceScore: calculatePounceScore(auction.domain, auction.tld, auction.num_bids, auction.age_years ?? undefined), - price: auction.current_bid, - priceType: 'bid', - status: 'auction', - timeLeft: auction.time_remaining, - endTime: auction.end_time, - source: auction.platform as any, - isPounce: false, - affiliateUrl: auction.affiliate_url, - tld: auction.tld, - numBids: auction.num_bids, - })) - + // Client-side filtering for immediate UI feedback + const filteredItems = useMemo(() => { let filtered = items - if (hideSpam) filtered = filtered.filter(item => !isSpamDomain(item.domain, item.tld)) - if (pounceOnly) filtered = filtered.filter(item => item.isPounce) - if (priceRange === 'low') filtered = filtered.filter(item => item.price < 100) - if (priceRange === 'mid') filtered = filtered.filter(item => item.price >= 100 && item.price < 1000) - if (priceRange === 'high') filtered = filtered.filter(item => item.price >= 1000) - if (searchQuery) filtered = filtered.filter(item => item.domain.toLowerCase().includes(searchQuery.toLowerCase())) + // Additional client-side search (API already filters, but this is for instant feedback) + if (searchQuery && !loading) { + const query = searchQuery.toLowerCase() + filtered = filtered.filter(item => item.domain.toLowerCase().includes(query)) + } + + // Sort const mult = sortDirection === 'asc' ? 1 : -1 - filtered.sort((a, b) => { + filtered = [...filtered].sort((a, b) => { + // Pounce Direct always appears first within same score tier + if (a.is_pounce !== b.is_pounce && sortField === 'score') { + return a.is_pounce ? -1 : 1 + } + switch (sortField) { case 'domain': return mult * a.domain.localeCompare(b.domain) - case 'score': return mult * (a.pounceScore - b.pounceScore) + case 'score': return mult * (a.pounce_score - b.pounce_score) case 'price': return mult * (a.price - b.price) - case 'time': return mult * (parseTimeToSeconds(a.timeLeft) - parseTimeToSeconds(b.timeLeft)) + case 'time': return mult * (parseTimeToSeconds(a.time_remaining) - parseTimeToSeconds(b.time_remaining)) case 'source': return mult * a.source.localeCompare(b.source) default: return 0 } }) + return filtered - }, [auctions, hideSpam, pounceOnly, priceRange, searchQuery, sortField, sortDirection]) + }, [items, searchQuery, sortField, sortDirection, loading]) - // Stats - const stats = useMemo(() => ({ - total: marketItems.length, - highScore: marketItems.filter(i => i.pounceScore >= 80).length, - endingSoon: marketItems.filter(i => parseTimeToSeconds(i.timeLeft) < 3600).length, - avgPrice: marketItems.length > 0 ? Math.round(marketItems.reduce((acc, i) => acc + i.price, 0) / marketItems.length) : 0 - }), [marketItems]) - - const formatPrice = (price: number) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(price) + // Separate Pounce Direct from external + const pounceItems = useMemo(() => filteredItems.filter(i => i.is_pounce), [filteredItems]) + const externalItems = useMemo(() => filteredItems.filter(i => !i.is_pounce), [filteredItems]) return (
- {/* Page-specific emerald glow (mirrors landing page look) */} + {/* Ambient glow */}
-
-
{/* METRICS */}
- - - 5 ? 'down' : 'neutral'} /> - + + 0} /> + +
{/* CONTROLS */} @@ -444,17 +440,38 @@ export default function MarketPage() {
{/* Filters */} -
- setHideSpam(!hideSpam)} label="No Spam" /> - setPounceOnly(!pounceOnly)} label="Pounce Exclusive" /> +
+ setSourceFilter(f => f === 'pounce' ? 'all' : 'pounce')} + label="Pounce Only" + icon={Diamond} + /> + setVerifiedOnly(!verifiedOnly)} + label="Verified" + icon={ShieldCheck} + />
- setPriceRange(p => p === 'low' ? 'all' : 'low')} label="< $100" /> - setPriceRange(p => p === 'high' ? 'all' : 'high')} label="$1k+" /> + setPriceRange(p => p === 'low' ? 'all' : 'low')} + label="< $100" + /> + setPriceRange(p => p === 'high' ? 'all' : 'high')} + label="$1k+" + />
- @@ -468,7 +485,7 @@ export default function MarketPage() {

Scanning markets...

- ) : marketItems.length === 0 ? ( + ) : filteredItems.length === 0 ? (
@@ -477,151 +494,242 @@ export default function MarketPage() {

Try adjusting your filters

) : ( - <> - {/* DESKTOP TABLE */} -
-
-
-
-
-
-
Action
-
-
- {marketItems.map((item) => { - const timeLeftSec = parseTimeToSeconds(item.timeLeft) - const isUrgent = timeLeftSec < 3600 - return ( -
+
+ + {/* POUNCE DIRECT SECTION (if any) */} + {pounceItems.length > 0 && ( +
+
+
+ + Pounce Direct +
+ Verified โ€ข Instant Buy โ€ข 0% Commission +
+
+ +
+ {pounceItems.map((item) => ( +
{/* Domain */} -
+
- {item.isPounce && ( - - - - )} +
-
{item.domain}
- -
{item.source}
-
+
{item.domain}
+
+ +
+ {/* Score */} -
+
+ +
+ {/* Price */}
- -
-
{formatPrice(item.price)}
- {item.numBids !== undefined && item.numBids > 0 &&
{item.numBids} bids
} -
-
+
{formatPrice(item.price, item.currency)}
+
Instant Buy
- {/* Time */} -
- -
- - {item.timeLeft} -
-
-
- {/* Actions */} -
- {/* Monitor Button - Distinct Style & Spacing */} - + + {/* Action */} +
+ - {/* Buy Button */} - - - {item.isPounce ? 'Buy Now' : 'Place Bid'} - - - + + Buy Now + +
- ) - })} + ))} +
-
- - {/* MOBILE CARDS */} -
- {marketItems.map((item) => { - const timeLeftSec = parseTimeToSeconds(item.timeLeft) - const isUrgent = timeLeftSec < 3600 - return ( -
-
-
- {item.isPounce && } - {item.domain} -
- + )} + + {/* EXTERNAL AUCTIONS */} + {externalItems.length > 0 && ( +
+
+
+ + External Auctions +
+ {externalItems.length} from global platforms +
+
+ + {/* Desktop Table */} +
+
+
+
- -
-
-
Current Bid
-
{formatPrice(item.price)}
-
-
-
Ends In
-
- - {item.timeLeft} -
-
+
+
- -
- - - {item.isPounce ? 'Buy Now' : 'Place Bid'} - - +
+ +
+
+ +
+
+ Action
- ) - })} -
- + +
+ {externalItems.map((item) => { + const timeLeftSec = parseTimeToSeconds(item.time_remaining) + const isUrgent = timeLeftSec < 3600 + return ( +
+ {/* Domain */} +
+
{item.domain}
+
{item.source}
+
+ + {/* Score */} +
+ +
+ + {/* Price */} +
+
{formatPrice(item.price, item.currency)}
+ {item.num_bids !== undefined && item.num_bids > 0 && ( +
{item.num_bids} bids
+ )} +
+ + {/* Time */} +
+
+ + {item.time_remaining || 'N/A'} +
+
+ + {/* Actions */} +
+ + + + + + Place Bid + + +
+
+ ) + })} +
+
+ + {/* Mobile Cards */} +
+ {externalItems.map((item) => { + const timeLeftSec = parseTimeToSeconds(item.time_remaining) + const isUrgent = timeLeftSec < 3600 + return ( +
+
+ {item.domain} + +
+ +
+
+
Current Bid
+
{formatPrice(item.price, item.currency)}
+
+
+
Ends In
+
+ + {item.time_remaining || 'N/A'} +
+
+
+ +
+ + + Place Bid + + +
+
+ ) + })} +
+
+ )} +
)}
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 246217f..1686610 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -572,6 +572,69 @@ class ApiClient { return this.request(`/portfolio/valuation/${domain}`) } + // ============== Market Feed (Unified) ============== + + /** + * Get unified market feed combining Pounce Direct listings + external auctions. + * This is the main feed for the Market page. + */ + async getMarketFeed(options: { + source?: 'all' | 'pounce' | 'external' + keyword?: string + tld?: string + minPrice?: number + maxPrice?: number + minScore?: number + endingWithin?: number + verifiedOnly?: boolean + sortBy?: 'score' | 'price_asc' | 'price_desc' | 'time' | 'newest' + limit?: number + offset?: number + } = {}) { + const params = new URLSearchParams() + + if (options.source) params.append('source', options.source) + if (options.keyword) params.append('keyword', options.keyword) + if (options.tld) params.append('tld', options.tld) + if (options.minPrice !== undefined) params.append('min_price', options.minPrice.toString()) + if (options.maxPrice !== undefined) params.append('max_price', options.maxPrice.toString()) + if (options.minScore !== undefined) params.append('min_score', options.minScore.toString()) + if (options.endingWithin !== undefined) params.append('ending_within', options.endingWithin.toString()) + if (options.verifiedOnly) params.append('verified_only', 'true') + if (options.sortBy) params.append('sort_by', options.sortBy) + if (options.limit !== undefined) params.append('limit', options.limit.toString()) + if (options.offset !== undefined) params.append('offset', options.offset.toString()) + + return this.request<{ + items: Array<{ + id: string + domain: string + tld: string + price: number + currency: string + price_type: 'bid' | 'fixed' | 'negotiable' + status: 'auction' | 'instant' + source: string + is_pounce: boolean + verified: boolean + time_remaining?: string + end_time?: string + num_bids?: number + slug?: string + seller_verified: boolean + url: string + is_external: boolean + pounce_score: number + }> + total: number + pounce_direct_count: number + auction_count: number + sources: string[] + last_updated: string + filters_applied: Record + }>(`/auctions/feed?${params.toString()}`) + } + // ============== Auctions (Smart Pounce) ============== async getAuctions(