- New left-palette tools: Hand (pan), Shape (drag to draw a primitive), Text (click to place). Removed the redundant sidebar add-buttons. - Pencil strokes now keep a constant on-screen thickness (pixel-space viewBox) instead of scaling with stroke length/aspect. - Restructured tabs: "Disc Designer" -> "Designer", which now hosts a Layout panel with Custom Art (default) + Disc Design; Custom Art moved off the Template tab. Underlying appMode/save-format unchanged. - Renamed the app from CaseWrap Studio to Artstr Studio (brand text only; Nostr tags and localStorage keys kept for compatibility); smaller one-line logo wordmark. - README updated for the new tabs, tools, and keyboard shortcuts. Co-Authored-By: Claude Opus 4.7 <[email protected]>
7.2 KiB
NIP-58 Badges Feature Plan
Overview
Award and display Nostr badges on creator profiles in the community feed. Badges like "Top Creator", "100 Zaps Received", and "Verified Designer" appear on template cards and profile views. Because badges are a Nostr standard (NIP-58), they automatically sync with and appear in other Nostr clients (Damus, Primal, Snort) — no proprietary profile system needed.
Complexity
Low–Moderate — Estimated 3–5 hours
- Badge display (reading + rendering): ~2 hours
- Badge awarding (publishing badge events): ~2 hours
- Admin/auto-award logic: ~1 hour
NIP-58 Primer
Three event kinds make up the badge system:
| Kind | Name | Purpose |
|---|---|---|
| 30009 | Badge Definition | Defines a badge: name, description, image (addressable, created by issuer) |
| 8 | Badge Award | Issues a badge to specific pubkeys |
| 30008 | Profile Badges | User's curated list of badges they want displayed on their profile |
Badge Definition (kind 30009)
{
"kind": 30009,
"pubkey": "<app-issuer-pubkey>",
"tags": [
["d", "top-creator"],
["name", "Top Creator"],
["description", "Awarded to creators with 10+ published templates"],
["image", "https://casewrap.app/badges/top-creator.png", "512x512"],
["thumb", "https://casewrap.app/badges/top-creator-64.png", "64x64"]
]
}
Badge Award (kind 8)
{
"kind": 8,
"pubkey": "<app-issuer-pubkey>",
"tags": [
["a", "30009:<issuer-pubkey>:top-creator"],
["p", "<recipient-pubkey>", "<relay-hint>"]
]
}
Profile Badges (kind 30008)
Users opt-in to displaying specific badges. The app reads this to know which badges to show on a profile:
{
"kind": 30008,
"pubkey": "<user-pubkey>",
"tags": [
["d", "profile_badges"],
["a", "30009:<issuer-pubkey>:top-creator"],
["e", "<award-event-id>"]
]
}
Badge Catalog (Proposed)
| Badge ID | Name | Criteria | Auto-awarded? |
|---|---|---|---|
first-publish |
First Publish | Published first template | Yes |
top-creator |
Top Creator | 10+ published templates | Yes |
prolific |
Prolific | 50+ published templates | Yes |
zap-magnet |
Zap Magnet | Received 100+ zaps | Yes (via kind 9735 scan) |
early-adopter |
Early Adopter | Joined before a cutoff date | Manual |
verified-designer |
Verified Designer | Manually awarded by app admins | Manual |
App issues badges from a dedicated Artstr app keypair (not tied to any user's NIP-07 key). The app keypair's pubkey and private key are stored in the app's admin config.
Key Components
1. Badge Display on Template Cards and Profile Views
Fetch the creator's kind 30008 profile badges event, cross-reference with known badge definitions, and render badge thumbnails:
async function fetchProfileBadges(pubkey) {
// Query relays for kind 30008 authored by pubkey
// Returns array of badge definition a-tags the user has accepted
}
async function resolveDisplayBadges(pubkey) {
const profileBadges = await fetchProfileBadges(pubkey);
// Filter to only Artstr-issued badges (by app issuer pubkey)
// Return array of { id, name, thumbUrl } for known badges
}
Render as small badge icons (32×32) in the author row of template cards:
[email protected] 🏆 🎨 ⚡ · 2 days ago
Hovering a badge shows a tooltip with the badge name and description.
2. Badge Cache
Badge definitions rarely change — cache fetched definitions in sessionStorage:
const BADGE_CACHE_KEY = 'casewrap-badge-cache';
function cacheBadgeDefinition(def) { /* store in sessionStorage */ }
function getCachedBadge(dTag) { /* retrieve from sessionStorage */ }
3. Auto-Award Logic (client-side trigger)
When a user publishes a template, check if they've crossed a badge threshold and award automatically. Award logic runs after publish:
async function checkAndAwardBadges(pubkey) {
const count = await fetchPublishedTemplateCount(pubkey);
if (count === 1) await awardBadge(pubkey, 'first-publish');
if (count === 10) await awardBadge(pubkey, 'top-creator');
if (count === 50) await awardBadge(pubkey, 'prolific');
}
Note: Auto-awarding requires the app's issuer private key to sign the kind 8 event. For a fully static app this means either:
- Keeping the issuer key client-side (acceptable for low-stakes community badges), or
- Using a small serverless function as the award endpoint (one POST, no database)
4. Badge Award Function
async function awardBadge(recipientPubkey, badgeId) {
const aTag = `30009:${APP_ISSUER_PUBKEY}:${badgeId}`;
const event = {
kind: 8,
pubkey: APP_ISSUER_PUBKEY,
tags: [
['a', aTag],
['p', recipientPubkey],
],
content: '',
created_at: Math.floor(Date.now() / 1000),
};
// Sign with APP_ISSUER_PRIVKEY (noble-secp256k1), publish to relays
await publishToRelays(signEvent(event, APP_ISSUER_PRIVKEY));
}
5. Badge Accept UI (Optional)
After receiving a badge, the user can add it to their kind 30008 profile badges so it shows across all Nostr clients. Show a prompt: "You earned Top Creator! Add it to your profile?" Clicking yes publishes/updates their kind 30008 event via NIP-07.
UI Changes
- Author row on template feed cards: small badge icon row (max 3, +N overflow)
- Template detail panel: full badge list with names and descriptions on hover
- Profile section in settings: "Your Badges" list showing earned + pending badges
- Publish success toast: "🏆 You earned a new badge!" when a threshold is crossed
Performance Considerations
- Fetch kind 30008 lazily — only when a card is hovered or detail panel opens
- Cache badge definitions in
sessionStorage— definitions rarely change - Batch profile badge fetches with other kind 0 / kind 3 queries at feed load time
- Show badges from cache immediately; refresh in background
Limitations
- Badge display depends on the creator having accepted badges into their kind 30008 event (users who never do this won't show badges even if awarded)
- Auto-award requires the app's issuer key to be available; keep it budget-appropriate (not a treasury key) since it lives client-side
- No revocation mechanism in NIP-58 — badges cannot be taken back once awarded
Future Enhancements
- User-to-user badge awards ("Community Pick" badge that any user can nominate for)
- Badge showcase page listing all Artstr badges and current recipients
- Integration with zap receipts to auto-award "Zap Magnet" by scanning kind 9735 events
Testing Checklist
- Badge thumbnails render in author row on template cards
- Hovering badge shows tooltip with name and description
- Badge cache prevents redundant relay queries on repeated views
first-publishbadge is awarded on first successful template publishtop-creatorbadge is awarded when published count reaches 10- Kind 8 award event contains correct
aandptags - "Add to profile" prompt publishes/updates kind 30008 via NIP-07
- Badge display respects user's kind 30008 accepted list (not all awarded badges shown)
- No badge icons appear for creators with no accepted badges
- Settings "Your Badges" panel shows earned badges correctly