┌─────────────────────────────────────────────────────────────────┐
│ LANCEPAY FRONTEND │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Badge Gallery│ │ Profile Page │ │ Verification │ │
│ │ Component │ │ Component │ │ Widget │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
└─────────┼─────────────────┼───────────────────┼──────────────────┘
│ │ │
│ Auth Token │ Auth Token │ No Auth
│ │ │
┌─────────▼─────────────────▼───────────────────▼──────────────────┐
│ API LAYER (Next.js) │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ /api/routes-d/reputation/ │ │
│ │ │ │
│ │ 🔒 GET /badges → Get all badges + eligibility │ │
│ │ 🔒 POST /badges → Claim a badge │ │
│ │ 🌐 GET /badges/verify → Public verification │ │
│ │ 🌐 GET /profile/:userId → Public badge profile │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ↓ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ BUSINESS LOGIC │ │
│ │ │ │
│ │ lib/badges.ts: │ │
│ │ • checkBadgeEligibility() → Evaluate criteria │ │
│ │ • getUserBadgeStatus() → Get user's badges │ │
│ │ • seedPredefinedBadges() → Initialize system │ │
│ │ │ │
│ │ lib/stellar.ts: │ │
│ │ • issueSoulboundBadge() → Mint on Stellar │ │
│ │ • configureBadgeIssuer() → Setup issuer │ │
│ │ • hasBadge() → Verify ownership │ │
│ └────────────────────────────────────────────────────────────┘ │
└───────────────────────────┬──────────────────┬────────────────────┘
│ │
┌───────▼──────┐ ┌──────▼──────┐
│ PostgreSQL │ │ Stellar │
│ Database │ │ Blockchain │
└──────────────┘ └─────────────┘
┌─────────┐
│ User │
│ Requests│
│ Badge │
└────┬────┘
│
▼
┌─────────────────────────────────────┐
│ 1️⃣ Authentication Check │
│ • Verify auth token │
│ • Get user from database │
└────┬────────────────────────────────┘
│ ✅ Valid
▼
┌─────────────────────────────────────┐
│ 2️⃣ Badge Validation │
│ • Badge exists? │
│ • Badge is active? │
│ • Already claimed? │
└────┬────────────────────────────────┘
│ ✅ Valid
▼
┌─────────────────────────────────────┐
│ 3️⃣ Eligibility Check │
│ • Evaluate criteria type │
│ • Query user's data │
│ • Calculate metrics │
│ • Compare vs requirements │
└────┬────────────────────────────────┘
│ ✅ Eligible
▼
┌─────────────────────────────────────┐
│ 4️⃣ Wallet Verification │
│ • User has wallet? │
│ • Wallet address valid? │
└────┬────────────────────────────────┘
│ ✅ Has Wallet
▼
┌─────────────────────────────────────┐
│ 5️⃣ Stellar Soulbound Minting │
│ • Create trustline (limit: 1) │
│ • Send 1 badge token │
│ • Record transaction hash │
└────┬────────────────────────────────┘
│ ✅ Minted
▼
┌─────────────────────────────────────┐
│ 6️⃣ Database Recording │
│ • Create UserBadge record │
│ • Store stellar TX hash │
│ • Mark as issued │
└────┬────────────────────────────────┘
│ ✅ Recorded
▼
┌─────────────────────────────────────┐
│ 7️⃣ Response to User │
│ • Return badge details │
│ • Include TX hash │
│ • Provide explorer link │
└─────────────────────────────────────┘
┌────────────────────┐
│ Badge Criteria │
│ (from JSON) │
└────────┬───────────┘
│
▼
┌────────────┐
│ Type Check │
└─┬──┬──┬──┬─┘
│ │ │ │
┌───┘ │ │ └───┐
│ │ │ │
▼ ▼ ▼ ▼
┌───────────────────────────────────────────────────────────────┐
│ │
│ REVENUE INVOICES ZERO_DISPUTES COMPLETION_RATE │
│ │
│ Query total Query paid Query invoice Calculate % │
│ transaction invoice + dispute completion │
│ amounts count count rate │
│ │
│ Compare to Compare to Check both Compare to │
│ minRevenue minInvoices criteria minRate │
│ │
└───────┬─────────────┬─────────────┬─────────────┬────────────┘
│ │ │ │
└─────────────┴─────────────┴─────────────┘
│
▼
┌─────────────────┐
│ Eligible: Y/N │
│ Reason: string │
└─────────────────┘
┌─────────────────┐
│ Badge Issuer │
│ (Platform) │
└────────┬────────┘
│
│ Issues Badge Asset
│
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ User A │ │ User B │ │ User C │
│ (Earned)│ │ (Earned)│ │(Pending)│
└─────────┘ └─────────┘ └─────────┘
│ │
│ │
▼ ▼
Can view in Can view in
Stellar wallet Stellar wallet
│ │
├─────────────────────┤
│ │
▼ ▼
❌ CANNOT TRANSFER ❌ CANNOT TRANSFER
Why?
1. Trustline limited to 1 badge
2. Issuer doesn't authorize new holders
3. Stellar network enforces restrictions
┌──────────────────┐
│ User │
│──────────────────│
│ id (PK) │◄─────┐
│ privyId │ │
│ email │ │
│ name │ │
└──────────────────┘ │
▲ │
│ │
│ │
┌────────┴──────────┐ │
│ UserBadge │ │
│────────────────────│ │
│ id (PK) │ │
│ userId (FK) ───────┼─────┘
│ badgeId (FK) ──────┼─────┐
│ stellarTxHash │ │
│ issuedAt │ │
└────────────────────┘ │
│
│
┌─────────────┘
│
▼
┌────────────────────────┐
│ BadgeDefinition │
│────────────────────────│
│ id (PK) │
│ name │
│ description │
│ criteriaJson │
│ stellarAssetCode │
│ imageUrl │
│ isActive │
└────────────────────────┘
┌────────────────────────┐
│ External Website │
│ (LinkedIn, Portfolio) │
└───────────┬────────────┘
│
│ Embed verification URL
│ ?userId=xxx&badgeId=yyy
▼
┌──────────────────────────────────┐
│ GET /badges/verify │
│ (No authentication required) │
└───────────┬──────────────────────┘
│
▼
┌──────────────────────────────────┐
│ 1. Query UserBadge in DB │
└───────────┬──────────────────────┘
│ Found
▼
┌──────────────────────────────────┐
│ 2. Get user's wallet address │
└───────────┬──────────────────────┘
│
▼
┌──────────────────────────────────┐
│ 3. Check Stellar blockchain │
│ hasBadge(wallet, assetCode) │
└───────────┬──────────────────────┘
│
▼
┌──────────────────────────────────┐
│ 4. Return verification result │
│ • verified: true/false │
│ • badge details │
│ • user info │
│ • stellar TX hash │
│ • explorer link │
└──────────────────────────────────┘
┌─────────────────────┐
│ setup-badges.sh │
│ (or manual steps) │
└──────────┬──────────┘
│
▼
┌─────────────────────────────────────┐
│ 1. npx prisma generate │
│ → Generate Prisma client │
└──────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 2. npx prisma migrate dev │
│ → Create BadgeDefinition │
│ → Create UserBadge │
│ → Add indexes │
└──────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 3. Generate Stellar keypair │
│ → Public key = issuer address │
│ → Secret key = BADGE_ISSUER_KEY │
└──────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 4. Fund issuer account │
│ → Send XLM for transaction fees │
└──────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 5. npx tsx scripts/init-badges.ts │
│ → Seed 5 predefined badges │
│ → Configure issuer flags │
└──────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ ✅ System Ready │
│ Users can now claim badges! │
└─────────────────────────────────────┘
┌──────────────┐
│ Freelancer │
│ signs up │
└──────┬───────┘
│
▼
┌──────────────────────┐
│ Completes work │
│ • Creates invoices │
│ • Gets paid │
│ • Builds reputation │
└──────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ Views badge gallery │
│ • Sees 5 available badges │
│ • Checks eligibility status │
│ • Some locked, some unlocked │
└──────┬───────────────────────┘
│
▼
┌───────────────────────────────────┐
│ Meets criteria for badge │
│ Example: 10+ paid invoices │
│ Badge shows "Eligible - Claim!" │
└──────┬────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ Clicks "Claim Badge" │
│ • Server validates eligibility │
│ • Mints soulbound token │
│ • Records in database │
└──────┬────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ Badge appears in wallet │
│ • Visible on Stellar Expert │
│ • Cannot be transferred │
│ • Permanent credential │
└──────┬────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ Shares on LinkedIn/Portfolio │
│ • Uses verification URL │
│ • Anyone can verify authenticity │
│ • Links to blockchain proof │
└───────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Security Layer 1 │
│ Authentication Required │
│ • JWT token validation (Privy) │
│ • User must be logged in │
└───────────────┬─────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Security Layer 2 │
│ Server-Side Validation │
│ • Eligibility checked against real DB │
│ • No client-side criteria bypass │
└───────────────┬─────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Security Layer 3 │
│ Database Constraints │
│ • UNIQUE (userId, badgeId) │
│ • Prevents duplicate claims │
└───────────────┬─────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Security Layer 4 │
│ Blockchain Immutability │
│ • Stellar enforces soulbound property │
│ • Transaction hash = proof │
│ • Cannot be revoked or altered │
└─────────────────────────────────────────┘
Operation | Expected Time | Optimization
─────────────────────────────|──────────────|─────────────────
GET /badges (list) | < 500ms | Index on userId
POST /badges (claim) | 3-8s | Stellar network
Eligibility calculation | < 200ms | DB query optimization
Public verification | < 300ms | Cache badge definitions
Stellar block confirmation | 5-10s | Network dependent
┌──────────────┐
│ LancePay │
└──────┬───────┘
│
├────────────► Stellar Network
│ • Badge minting
│ • On-chain storage
│ • Transaction verification
│
├────────────► PostgreSQL
│ • User data
│ • Badge definitions
│ • Earned badges
│
├────────────► Privy
│ • Authentication
│ • User management
│
└────────────► Stellar Expert
• Block explorer
• Public verification
Visual Architecture Guide On-Chain Badge System for LancePay Last Updated: January 27, 2026