Already installed in your project:
@stellar/stellar-sdk@prisma/client
cd /home/jojo/Documents/Blockchain\ project/Lancepay/LancePay
npx prisma migrate dev --name add_badge_systemnpx prisma generateGenerate a new Stellar keypair for badge issuance:
node -e "const stellar = require('@stellar/stellar-sdk'); const pair = stellar.Keypair.random(); console.log('Public Key:', pair.publicKey()); console.log('Secret Key:', pair.secret());"IMPORTANT: Save both keys securely!
For Testnet:
- Visit: https://laboratory.stellar.org/#account-creator?network=test
- Paste your public key and click "Get test network lumens"
For Mainnet:
- Send at least 2 XLM to the issuer address
Add to .env:
# Badge Issuer (Stellar keypair for minting badges)
BADGE_ISSUER_SECRET_KEY=SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX# Seed predefined badges and configure issuer
npx tsx scripts/init-badges.tsThis will create 5 badges:
- β¨ Top 1% Earner (TOP1PCT)
- π Zero Dispute Champion (NODISPUTE)
- β Verified Professional (VERIPRO)
- β Rising Star (RISESTAR)
- π€ Trusted Freelancer (TRUSTED)
# Run test suite
npx tsx scripts/test-badges.tsOr test via API:
# Get badges (requires auth)
curl -X GET http://localhost:3000/api/routes-d/reputation/badges \
-H "Authorization: Bearer YOUR_AUTH_TOKEN"
# Claim a badge
curl -X POST http://localhost:3000/api/routes-d/reputation/badges \
-H "Authorization: Bearer YOUR_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"badgeId": "BADGE_ID"}'
# Verify badge (public, no auth)
curl http://localhost:3000/api/routes-d/reputation/badges/verify?userId=USER_ID&badgeId=BADGE_IDprisma/schema.prisma- Added BadgeDefinition and UserBadge modelsprisma/migrations/20260127_add_badge_system/migration.sql- Migration SQL
lib/badges.ts- Badge criteria evaluation and eligibility checkinglib/stellar.ts- Added soulbound token functions:issueSoulboundBadge()- Mint and send non-transferable badgesconfigureBadgeIssuer()- Set up issuer account flagshasBadge()- Verify badge ownership on-chain
badges/route.ts- GET badges, POST claim badgebadges/verify/route.ts- Public badge verificationprofile/[userId]/route.ts- Public badge profile
scripts/init-badges.ts- Initialize badge systemscripts/test-badges.ts- Test badge functionality
docs/BADGE_SYSTEM.md- Complete API documentation
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/api/routes-d/reputation/badges |
GET | β | Get all badges with eligibility |
/api/routes-d/reputation/badges |
POST | β | Claim a badge |
/api/routes-d/reputation/badges/verify |
GET | β | Verify badge ownership (public) |
/api/routes-d/reputation/profile/[userId] |
GET | β | Get user's badge profile (public) |
- Database migration successful
- Badge definitions seeded
- Issuer account configured
- User can view badges with eligibility
- Eligible user can claim badge
- Stellar transaction recorded
- Badge visible in wallet on Stellar Expert
- Ineligible user gets 403 Forbidden
- Duplicate claim returns 409 Conflict
- Badge cannot be transferred (soulbound)
- Public verification works
- Public profile displays badges
- Never commit
BADGE_ISSUER_SECRET_KEYto version control - Store badge issuer keys in a secure secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.)
- Badge issuance is irreversible - verify criteria carefully
- Consider rate limiting on badge endpoints
- Monitor badge minting for abuse
Modify /lib/badges.ts to customize:
{
name: "Your Badge",
stellarAssetCode: "YOURBADGE",
criteriaJson: {
type: "revenue", // or "invoices", "zero_disputes", "completion_rate"
minRevenue: 50000, // $50k
}
}-- Recent badge claims
SELECT u.email, bd.name, ub."issuedAt", ub."stellarTxHash"
FROM "UserBadge" ub
JOIN "User" u ON ub."userId" = u.id
JOIN "BadgeDefinition" bd ON ub."badgeId" = bd.id
ORDER BY ub."issuedAt" DESC
LIMIT 20;
-- Most popular badges
SELECT bd.name, COUNT(*) as holders
FROM "UserBadge" ub
JOIN "BadgeDefinition" bd ON ub."badgeId" = bd.id
GROUP BY bd.name
ORDER BY holders DESC;
-- Users eligible for badges they haven't claimed
-- (requires custom logic based on criteria)Example React component:
import { useState, useEffect } from 'react';
export function BadgeGallery() {
const [badges, setBadges] = useState([]);
useEffect(() => {
fetch('/api/routes-d/reputation/badges', {
headers: { Authorization: `Bearer ${authToken}` }
})
.then(res => res.json())
.then(data => setBadges(data.badges));
}, []);
const claimBadge = async (badgeId) => {
const res = await fetch('/api/routes-d/reputation/badges', {
method: 'POST',
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ badgeId })
});
if (res.ok) {
alert('Badge claimed! Check your Stellar wallet.');
// Refresh badges
}
};
return (
<div className="badge-grid">
{badges.map(badge => (
<div key={badge.id} className="badge-card">
<img src={badge.imageUrl} alt={badge.name} />
<h3>{badge.name}</h3>
<p>{badge.description}</p>
{badge.earned ? (
<span className="earned">β Earned</span>
) : badge.eligible ? (
<button onClick={() => claimBadge(badge.id)}>
Claim Badge
</button>
) : (
<p className="ineligible">{badge.reason}</p>
)}
</div>
))}
</div>
);
}- Stellar Laboratory: https://laboratory.stellar.org
- Stellar Expert (Testnet): https://stellar.expert/explorer/testnet
- Stellar Expert (Mainnet): https://stellar.expert/explorer/public
- Full Documentation: See
docs/BADGE_SYSTEM.md
Migration fails:
npx prisma migrate reset
npx prisma migrate devBadge minting fails:
- Check issuer has XLM balance
- Verify secret key is correct
- Ensure user has a wallet
Badge not in wallet:
- Check transaction on Stellar Expert
- Verify asset code matches
- Confirm network (testnet vs mainnet)
For issues or questions:
- Check
docs/BADGE_SYSTEM.md - Review error logs
- Test on Stellar testnet first
- Verify environment variables
Next Steps: Update badge image URLs to IPFS for permanent storage