SkateSpots/NIP.md
shakespeare.diy f10aa7971c Step 3: Crews — map-anchored collective feature (kind 35006)
- New SkateCrew kind 35006 + type/parser + useCrews/useCrew hooks
- /crews directory, /create-crew, /edit-crew, /crew/:pubkey/:identifier pages
- Reuses the collective engine: NIP-32 membership attestation (confirmed/pending),
  CollectiveMembers + ConfirmMembershipCard, ClinkPayButton, SpotPicker
- Activity v1: home spots + crew member feed (no events, per plan)
- Map: teal/cyan Crews layer at home turf with crew markers + popups;
  wired through SpotMap props, layer toggles (mobile popover + filters sheet),
  and mobileLayerCount badge
- Nav: Crews added to navItemKeys (desktop header, mobile bottom bar, sidebar)
  with en/es i18n keys
- NIP.md: documents kind 35006, tag taxonomy, and shared membership mechanism

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 13:59:50 +07:00

54 KiB
Raw Blame History

SkateSpots Protocol

This document defines the Nostr event kinds used by the SkateSpots application for managing skateboarding locations, shops, and related content.

Skate Shops (kind 30017)

Skate shops use the NIP-15 marketplace "stall" kind (30017) extended with skateboarding-specific tags. This enables interoperability with existing Nostr marketplace clients.

Event Format

{
  "kind": 30017,
  "content": "<shop description>",
  "tags": [
    ["d", "<unique-identifier>"],
    ["name", "<shop name>"],
    ["g", "<geohash>"],
    ["location", "<shop address>"],
    ["t", "skateshop"],
    ["phone", "<phone number>"],
    ["email", "<email address>"],
    ["website", "<website URL>"],
    ["instagram", "<instagram handle>"],
    ["hours", "<hours of operation>"],
    ["imeta", "url <image-url>", "m image/jpeg"],
    ["alt", "Skate shop: <name>"]
  ]
}

Required Tags

Tag Description
d Unique identifier for the shop
name Shop name
g Geohash encoding the shop's coordinates
t Must include skateshop to identify as a skate shop
location Physical shop address

Optional Tags

Tag Description
phone Contact phone number
email Contact email address
website Shop website URL
instagram Instagram handle
hours Hours of operation (freeform text)
noffer Optional CLINK Offer code (noffer1…) for self-custodial payments — see the CLINK Payments section
imeta Image metadata per NIP-92
lat Latitude (decimal)
lon Longitude (decimal)

Shop Ownership Verification

Shops can be verified using NIP-05 domain verification. A shop is considered verified when:

  1. The shop has a website tag with a domain (e.g., supremenewyork.com)
  2. The shop owner's NIP-05 identifier matches the shop's domain (e.g., owner@supremenewyork.com)

This proves the shop owner controls the domain, establishing ownership.

Example

{
  "kind": 30017,
  "pubkey": "abc123...",
  "created_at": 1703001600,
  "content": "Los Angeles' premier skateboard shop since 1994. Full selection of decks, trucks, wheels, and streetwear.",
  "tags": [
    ["d", "supreme-la"],
    ["name", "Supreme Los Angeles"],
    ["g", "9q5ctr"],
    ["location", "411 N Fairfax Ave, Los Angeles, CA 90036"],
    ["t", "skateshop"],
    ["phone", "+1-323-655-6205"],
    ["website", "https://www.supremenewyork.com"],
    ["instagram", "@supremenewyork"],
    ["hours", "Mon-Sat 11AM-7PM, Sun 12PM-6PM"],
    ["imeta", "url https://example.com/shop-front.jpg", "m image/jpeg"],
    ["lat", "34.0737"],
    ["lon", "-118.3614"],
    ["alt", "Skate shop: Supreme Los Angeles"]
  ],
  "id": "...",
  "sig": "..."
}

Querying Shops

{
  "kinds": [30017],
  "#t": ["skateshop"]
}

Skate Spot (kind 30818)

A skate spot is an addressable event that represents a skateboarding location anywhere in the world. Users can create, share, and discover skate spots.

Event Format

{
  "kind": 30818,
  "content": "<detailed description of the skate spot>",
  "tags": [
    ["d", "<unique-identifier>"],
    ["title", "<name of the skate spot>"],
    ["g", "<geohash>"],
    ["location", "<human-readable address>"],
    ["category", "<spot-category>"],
    ["t", "skatespot"],
    ["t", "<feature-tag>"],
    ["t", "<surface-tag>"],
    ["t", "<vibe-tag>"],
    ["imeta", "url <image-url>", "m image/jpeg"],
    ["alt", "Skate spot: <title>"]
  ]
}

Required Tags

Tag Description
d Unique identifier for the spot (UUID or slug)
title Human-readable name of the skate spot
g Geohash encoding the spot's geographic coordinates (minimum 6 characters for ~1km precision)
category Primary spot category (see Spot Categories below)
t Must include skatespot for discoverability
alt Human-readable description for clients that don't understand this kind (NIP-31)

Optional Tags

Tag Description
location Human-readable address or location description
imeta Image metadata per NIP-92
lat Latitude (decimal)
lon Longitude (decimal)
access Access type: public, private, locals
contact Contact info for the spot
t Freeform hashtags for features, surfaces, vibes (max 20)

Spot Categories

The category tag defines the primary type of spot. Only ONE category per spot:

Value Description
street Street spots (stairs, rails, ledges, etc.)
park Skateparks (public or private)
diy DIY/homemade spots
plaza Public plazas with skate-friendly architecture

Freeform Tags (t tags)

In addition to the required skatespot tag, spots can have up to 20 freeform tags for detailed categorization. Tags are stored lowercase without # prefix.

Feature Tags (obstacles/terrain)

ledge, rail, handrail, flatrail, kinked-rail, rainbow-rail, stairs, gap, bank, quarter, halfpipe, bowl, pool, miniramp, vert, hubba, manual-pad, flatground, curb, bump, pyramid, euro-gap, kicker, hip, spine, cradle, snake-run, pump-track, wallride, drop, loading-dock

Surface Tags (materials)

concrete, marble, granite, brick, metal, wood, asphalt, tile, smooth, rough, crusty, slippery, painted

Size Tags

small, medium, large, massive, mellow, steep, low, high, long, short

Stair Counts

2-stair, 3-stair, 4-stair, 5-stair, 6-stair, 7-stair, 8-stair, 9-stair, 10-stair, 10-plus-stair

Vibe/Condition Tags

chill, famous, classic, hidden-gem, locals-only, beginner-friendly, intermediate, advanced, pro-spot, night-spot, covered, lit, shaded, security, bust-factor, skate-stopped, well-maintained, run-down

Location Context Tags

downtown, industrial, residential, campus, waterfront, rooftop, underground, parking-garage, school, business-district

Tag Normalization Rules

  1. Lowercase: All tags stored in lowercase (Ledge → ledge)
  2. No spaces: Use hyphens for multi-word tags (night spot → night-spot)
  3. No special characters: Alphanumeric and hyphens only
  4. No # prefix: Tags stored without hash (ledge not #ledge)
  5. Max 20 tags: Prevent spam/over-tagging

Example

{
  "kind": 30818,
  "pubkey": "abc123...",
  "created_at": 1703001600,
  "content": "Classic LA spot with a perfect 10-stair and hubba ledge. Usually busy on weekends. Security is chill during the day but kicks out after 6pm.",
  "tags": [
    ["d", "hollywood-high"],
    ["title", "Hollywood High School"],
    ["g", "9q5ctr"],
    ["location", "1521 N Highland Ave, Los Angeles, CA"],
    ["category", "street"],
    ["access", "public"],
    ["t", "skatespot"],
    ["t", "stairs"],
    ["t", "10-stair"],
    ["t", "hubba"],
    ["t", "ledge"],
    ["t", "marble"],
    ["t", "famous"],
    ["t", "classic"],
    ["t", "pro-spot"],
    ["t", "security"],
    ["imeta", "url https://example.com/hollywood-high.jpg", "m image/jpeg"],
    ["lat", "34.1017"],
    ["lon", "-118.3385"],
    ["alt", "Skate spot: Hollywood High School"]
  ],
  "id": "...",
  "sig": "..."
}

Querying Skate Spots

All Spots

{
  "kinds": [30818],
  "#t": ["skatespot"]
}

Spots by Feature Tag

{
  "kinds": [30818],
  "#t": ["skatespot", "ledge"]
}

Spots by Multiple Tags

{
  "kinds": [30818],
  "#t": ["skatespot", "marble", "ledge"]
}
{
  "kinds": [30818],
  "#g": ["9q5"]
}

Note: Relays may or may not support geohash prefix queries. Clients should fall back to client-side filtering if needed.

Client-Side Category Filtering

Since category is not a single-letter tag, filter by category client-side after fetching spots:

const streetSpots = spots.filter(spot => 
  spot.tags.find(([k, v]) => k === 'category' && v === 'street')
);

Skate Events

Skateboarding events use the standard NIP-52 time-based calendar event (kind 31923) with the hashtag #skateboarding:

{
  "kind": 31923,
  "content": "<event description>",
  "tags": [
    ["d", "<unique-id>"],
    ["title", "<event name>"],
    ["start", "<unix-timestamp>"],
    ["end", "<unix-timestamp>"],
    ["g", "<geohash>"],
    ["location", "<venue address>"],
    ["t", "skateboarding"],
    ["t", "<event-type>"]
  ]
}

Event Types

Common t tags for skate events:

  • contest - Skateboarding competitions
  • demo - Pro demos
  • session - Group sessions
  • workshop - Lessons or workshops
  • premiere - Video premieres
  • tradeshow - Industry events
  • gameskate - Game of S.K.A.T.E. competitions

Music & Culture Events

Skateboarding and music culture are deeply connected. These event types support concerts, jams, and cultural gatherings:

  • concert - Live music performances at skate venues
  • jam - Skate jams with music, art, and community vibes
  • festival - Multi-day skateboarding festivals (e.g., Tampa Pro, Dew Tour)
  • popup - Pop-up events, brand activations, and temporary installations

Events can have multiple type tags. For example, a video premiere with a live band would have both premiere and concert tags.

Social Interaction

Comments and reactions to skate spots and events use standard Nostr kinds:

  • Reactions (kind 7): React to spots or events with emoji
  • Comments (kind 1111): Comment on spots or events per NIP-22
  • Zaps (kind 9735): Send sats to spot creators or event organizers

Spot Collections (kind 30003)

Spot collections use NIP-51 "Bookmark sets" (kind 30003) to allow users to organize skate spots into personalized lists. This enables interoperability with other Nostr bookmark clients.

Event Format

{
  "kind": 30003,
  "content": "<collection description>",
  "tags": [
    ["d", "<unique-identifier>"],
    ["title", "<collection name>"],
    ["t", "skatespots"],
    ["description", "<collection description>"],
    ["image", "<cover image URL>"],
    ["a", "30818:<pubkey>:<spot-d-tag>"],
    ["a", "30818:<pubkey>:<spot-d-tag>"],
    ["alt", "Spot collection: <title>"]
  ]
}

Required Tags

Tag Description
d Unique identifier for the collection
title Collection name
t Must include skatespots to identify as a spot collection

Optional Tags

Tag Description
description Description of the collection
image Cover image URL
a Reference to a skate spot (format: 30818:pubkey:d-tag)
alt Human-readable description for clients that don't understand this kind

Example

{
  "kind": 30003,
  "pubkey": "abc123...",
  "created_at": 1703001600,
  "content": "My favorite spots from my Barcelona trip in summer 2024",
  "tags": [
    ["d", "barcelona-trip-2024-1703001600"],
    ["title", "Barcelona Trip 2024"],
    ["t", "skatespots"],
    ["description", "My favorite spots from my Barcelona trip in summer 2024"],
    ["image", "https://example.com/barcelona-cover.jpg"],
    ["a", "30818:abc123:macba"],
    ["a", "30818:def456:parallel-plaza"],
    ["a", "30818:ghi789:sants-station"],
    ["alt", "Spot collection: Barcelona Trip 2024"]
  ],
  "id": "...",
  "sig": "..."
}

Querying Collections

{
  "kinds": [30003],
  "authors": ["<user-pubkey>"],
  "#t": ["skatespots"]
}

Gear Listings (kind 30402)

Gear listings use the NIP-99 Classified Listings format (kind 30402) for peer-to-peer skateboarding gear sales. This enables skaters to buy and sell used equipment directly at skate spots.

Event Format

{
  "kind": 30402,
  "content": "<detailed description>",
  "tags": [
    ["d", "<unique-identifier>"],
    ["title", "<item name>"],
    ["t", "skateboarding"],
    ["t", "<category>"],
    ["price", "<amount>", "<currency>"],
    ["price_type", "<fixed|free|trade|offer>"],
    ["condition", "<new|like_new|good|well_used>"],
    ["listing_type", "<selling|wtb>"],
    ["size", "<size-value>"],
    ["g", "<geohash>"],
    ["location", "<pickup location>"],
    ["a", "30818:<pubkey>:<spot-d-tag>"],
    ["available", "<when seller is available>"],
    ["expiration", "<unix-timestamp>"],
    ["status", "active"],
    ["published_at", "<unix-timestamp>"],
    ["image", "<image-url>"],
    ["alt", "Gear listing: <title>"]
  ]
}

Required Tags

Tag Description
d Unique identifier for the listing
title Item name
t Must include skateboarding for discoverability
t Category tag (see categories below)
image At least one product photo
price_type How the item is being sold: fixed, free, trade, offer
listing_type Whether selling or buying: selling or wtb (Want to Buy)

Optional Tags

Tag Description
price Price amount and currency (e.g., ["price", "50", "USD"]) - required if price_type is fixed
condition Item condition: new, like_new, good, well_used
size Item size (varies by category)
g Geohash for pickup location
location Human-readable pickup location
a Reference to a skate spot (format: 30818:pubkey:d-tag)
available When the seller is typically available
expiration Unix timestamp when listing expires (default: 30 days)
status Listing status: active, sold, pending
summary Short one-liner about the item

Categories

The following t tags are used for categorizing gear:

  • deck - Skateboard decks
  • trucks - Trucks
  • wheels - Wheels
  • bearings - Bearings
  • shoes - Skate shoes
  • clothing - Clothing and apparel
  • protective - Protective gear (helmets, pads, etc.)
  • stickers - Stickers
  • wax - Skate wax
  • tools - Skate tools
  • griptape - Griptape
  • rubbrick - Rub bricks

Condition Values

Value Label Description
new 🆕 New Never used, still in packaging
like_new 👍 Like New Barely used, excellent condition
good ✅ Good Normal wear, functions perfectly
well_used ⚠️ Well Used Shows wear but still works

Price Types

Value Label Description
fixed Fixed Price Set price in fiat or sats
free Free Giving away
trade Trade Looking to swap
offer Make Offer Price negotiable

Size Options by Category

Decks:

  • 7.5", 7.75", 8.0", 8.125", 8.25", 8.38", 8.5", 8.75", 9.0"+

Trucks:

  • Sizes: 129, 139, 144, 149, 159, etc.
  • Heights: low, mid, high, inverted

Shoes:

  • US sizes: 6, 6.5, 7, 7.5, ... 14
  • EU sizes: 38, 39, 40, ... 48

Clothing:

  • XS, S, M, L, XL, XXL

Wheels:

  • Diameter: 50mm, 52mm, 54mm, 56mm, 58mm, 60mm
  • Hardness: 78a, 87a, 92a, 97a, 99a, 101a

Listing Type

Value Description
selling User is selling gear
wtb Want to Buy - User is looking for specific gear

Expiration

Listings should include an expiration tag with a Unix timestamp. Default expiration periods:

  • 7 days
  • 14 days
  • 30 days (default)

Expired listings should be filtered out by clients.

Spot Association

Gear listings can be tied to specific skate spots using an a tag referencing a spot event. This allows:

  • Listings to appear on spot detail pages
  • Easy meetup coordination at familiar locations
  • Geographic filtering of gear listings

Important: Gear listings should NOT appear at shop locations (conflict of interest). Only show gear at regular skate spots or dual shop+spot locations.

NIP-05 Verification

For trust and spam prevention, sellers should have a verified NIP-05 identifier. Clients may:

  • Require NIP-05 to create listings
  • Display verification badges on listings
  • Allow filtering by verified sellers

Example: Selling a Deck

{
  "kind": 30402,
  "pubkey": "abc123...",
  "created_at": 1703001600,
  "content": "Got this deck for Christmas but prefer a smaller size. Happy to let you try it before buying! Some minor scratches from one session but graphics are clean.",
  "tags": [
    ["d", "listing-1703001600"],
    ["title", "Palace Deck 8.25\""],
    ["t", "skateboarding"],
    ["t", "deck"],
    ["price", "40", "USD"],
    ["price_type", "fixed"],
    ["condition", "like_new"],
    ["listing_type", "selling"],
    ["size", "8.25\""],
    ["g", "9q5ctr"],
    ["location", "Venice Beach Skatepark"],
    ["a", "30818:def456:venice-beach-park"],
    ["available", "Weekends 10am-2pm"],
    ["expiration", "1705593600"],
    ["status", "active"],
    ["published_at", "1703001600"],
    ["image", "https://example.com/deck-front.jpg"],
    ["image", "https://example.com/deck-back.jpg"],
    ["alt", "Gear listing: Palace Deck 8.25\""]
  ],
  "id": "...",
  "sig": "..."
}

Example: Want to Buy (WTB)

{
  "kind": 30402,
  "pubkey": "xyz789...",
  "created_at": 1703001600,
  "content": "Looking for a solid 8.0\" deck, any brand. Prefer minimal graphics or blank. Can pay cash or trade wheels.",
  "tags": [
    ["d", "listing-1703001700"],
    ["title", "Looking for 8.0\" Deck"],
    ["t", "skateboarding"],
    ["t", "deck"],
    ["price_type", "offer"],
    ["listing_type", "wtb"],
    ["size", "8.0\""],
    ["g", "9q5ctr"],
    ["location", "Near Downtown Rails"],
    ["expiration", "1705593600"],
    ["status", "active"],
    ["published_at", "1703001600"],
    ["alt", "WTB: Looking for 8.0\" Deck"]
  ],
  "id": "...",
  "sig": "..."
}

Querying Gear Listings

All Active Listings:

{
  "kinds": [30402],
  "#t": ["skateboarding"]
}

Listings by Category:

{
  "kinds": [30402],
  "#t": ["skateboarding", "deck"]
}

Listings at a Spot:

{
  "kinds": [30402],
  "#a": ["30818:pubkey:spot-id"],
  "#t": ["skateboarding"]
}

Listings Near Location (using geohash prefix):

{
  "kinds": [30402],
  "#g": ["9q5"],
  "#t": ["skateboarding"]
}

Community Contributions

Community members can contribute to spots without being the owner. Contributions are stored as separate events that reference the original spot.

Photo Contributions (kind 1063)

Community photos use NIP-94 File Metadata (kind 1063) with an a tag referencing the spot.

{
  "kind": 1063,
  "content": "Great ledge for slappies!",
  "tags": [
    ["url", "https://example.com/photo.jpg"],
    ["m", "image/jpeg"],
    ["a", "30818:<spot-pubkey>:<spot-d-tag>"],
    ["alt", "Community photo for Hollywood High"]
  ]
}

Tag Suggestions (kind 1111)

Tag suggestions use NIP-22 Comments (kind 1111) with a special marker tag.

{
  "kind": 1111,
  "content": "Suggested tags for Hollywood High: #marble #ledge #10-stair",
  "tags": [
    ["a", "30818:<spot-pubkey>:<spot-d-tag>"],
    ["K", "30818"],
    ["t", "skatespot-tags"],
    ["t", "marble"],
    ["t", "ledge"],
    ["t", "10-stair"]
  ]
}

The skatespot-tags marker distinguishes tag suggestions from regular comments.

Querying Contributions

Community Photos:

{
  "kinds": [1063],
  "#a": ["30818:pubkey:spot-id"]
}

Tag Suggestions:

{
  "kinds": [1111],
  "#a": ["30818:pubkey:spot-id"],
  "#t": ["skatespot-tags"]
}

Spot Ownership Transfer

Spot ownership can be transferred in two ways:

Voluntary Transfer

The owner publishes a transfer request as a kind 1111 comment:

{
  "kind": 1111,
  "content": "Ownership transfer request for spot \"Hollywood High\"",
  "tags": [
    ["a", "30818:<owner-pubkey>:<spot-d-tag>"],
    ["K", "30818"],
    ["t", "spot-transfer"],
    ["p", "<new-owner-pubkey>", "", "new-owner"]
  ]
}

Abandoned Spot Claim

If a spot hasn't been updated for 6+ months, any user can claim it by:

  1. Re-publishing the spot under their own pubkey
  2. Adding claim-from and claim-reason tags
{
  "kind": 30818,
  "content": "<original description>",
  "tags": [
    ["d", "<same-d-tag>"],
    ["title", "<same-title>"],
    // ... other original tags ...
    ["claim-from", "<original-owner-pubkey>"],
    ["claim-reason", "abandoned"]
  ]
}

Note: Since Nostr is decentralized, the original spot remains. Clients should prefer the most recently updated version or allow users to choose.

Skate Tours (kind 30823)

Skate tours allow experienced skaters to organize and host various types of skateboarding adventures. Tours range from local hidden spot tours to multi-country expeditions and KOTR-style World Destruction competitions.

Tour Categories

Category Emoji Description Typical Duration
local 🏠 Show hidden spots in your city 2-8 hours
national 🚐 Multi-city road trips across the country 2-14 days
international ✈️ Cross-border adventures across multiple countries 1-8 weeks (up to 2 months)
destruction 💀 World Destruction Tour - KOTR-style global team competition 1-12 months

Event Format (Base)

{
  "kind": 30823,
  "content": "<tour description - what skaters will experience>",
  "tags": [
    ["d", "<unique-identifier>"],
    ["title", "<tour name>"],
    ["t", "skate-tour"],
    ["tour-category", "<local|national|international|destruction>"],
    ["t", "<spot-type>"],
    ["g", "<geohash>"],
    ["location", "<city/region>"],
    ["meeting-point", "<public meeting location name>"],
    ["meeting-point-coords", "<lat>", "<lon>"],
    ["duration", "<number>", "<hours|days|weeks>"],
    ["skill-level", "<beginner|intermediate|advanced|all>"],
    ["max-group", "<max participants>"],
    ["price", "<sats>"],
    ["available", "<availability description>"],
    ["languages", "<lang1>", "<lang2>"],
    ["shop", "<partner-shop-naddr>"],
    ["shop-discount", "<percentage>"],
    ["zap-split", "skatespots@minibits.cash", "10"],
    ["image", "<tour-image-url>"],
    ["alt", "Skate Tour: <title> in <location>"]
  ]
}

Required Tags

Tag Description
d Unique identifier for the tour
title Tour name
t Must include skate-tour to identify as a tour event
tour-category Tour type: local, national, international, or destruction
location City or region where tour operates/starts

Optional Tags (All Categories)

Tag Description
g Geohash for location (low precision - 4 chars recommended)
meeting-point Public meeting location name
meeting-point-coords Latitude and longitude of meeting point
duration Tour length with unit: ["duration", "3", "hours"] or ["duration", "5", "days"]
skill-level Recommended skill level: beginner, intermediate, advanced, or all
max-group Maximum number of participants
price Price per person in satoshis (host decides)
available When host is available (e.g., "Weekends", "By appointment")
languages Languages the host speaks
shop Reference to partner skate shop (naddr)
shop-discount Percentage discount at partner shop for tour participants
zap-split Platform fee recipient and percentage
image Tour or host photo

Spot Type Tags

Tours can include multiple spot type tags to describe what kind of terrain will be covered:

  • street - Street spots
  • park - Skateparks
  • diy - DIY spots
  • transition - Transition/bowl spots
  • hidden - Hidden gems

National & International Tour Tags

Tag Description
cities List of cities: ["cities", "LA", "SF", "Portland", "Seattle"]
countries List of countries: ["countries", "Spain", "France", "Germany"]
transport Transport information/recommendations
accommodation Accommodation information/recommendations

World Destruction Tour Tags

World Destruction Tours are KOTR-style global team competitions where organizers define EVERYTHING - challenges, rules, prizes. The platform only provides infrastructure.

Key Characteristics:

  • Duration: 1-12 months
  • Skill level: Always advanced (hardcoded)
  • Spot types: ALL types included automatically
  • Challenges: SECRET until competition start date
Tag Description
cities List of competition cities: ["cities", "Berlin", "Barcelona", "Tokyo"]
countries List of countries: ["countries", "Germany", "Spain", "Japan"]
organizer-contact Organizer contact details (Nostr npub, email, Telegram, etc.)
transport Transport information/recommendations
start-date Unix timestamp when competition starts (challenges revealed on this date)
end-date Unix timestamp when competition ends
entry-fee Entry fee per team in satoshis (organizer decides, can be 0)
prize-pool Total prize pool in satoshis
prize-dist Prize distribution: ["prize-dist", "1", "50", "2", "30", "3", "20"] (place, percentage pairs)
max-teams Maximum number of teams allowed
min-team-size Minimum skaters per team
max-team-size Maximum skaters per team
challenge Challenge definition (multiple allowed): ["challenge", "id", "title", "description", "points", "revealDate"]
clip-sources Accepted clip sources: ["clip-sources", "blossom", "youtube", "instagram", "any"]

Challenge Tag Format

["challenge", "<id>", "<title>", "<description>", "<points>", "<revealDate>"]
  • id: Unique challenge identifier
  • title: Challenge name (e.g., "Kickflip a 10 stair")
  • description: Optional detailed description
  • points: Points awarded for completion
  • revealDate: Unix timestamp when challenge becomes visible (defaults to tour start date - ALL challenges are SECRET until this date)

Revenue Model

Hosts set their own prices. Platform commission varies by tour type via zap-split tag.

Tour Type Platform Fee Host Keeps
Local 10% 90%
National 7% 93%
International 5% 95%
World Destruction 5% 95%

Important: For World Destruction Tours, the 5% fee applies only to entry fees, NOT to sponsor money or prize pools.

Example zap-split tags:

  • Local: ["zap-split", "skatespots@minibits.cash", "10"]
  • National: ["zap-split", "skatespots@minibits.cash", "7"]
  • International/Destruction: ["zap-split", "skatespots@minibits.cash", "5"]

Sponsor Tags

Tours can have sponsors. Sponsors are stored as tags and displayed on tour pages.

["sponsor", "<tier>", "<name>", "<logo-url>", "<website-url>", "<challengeId?>"]
Field Description
tier Sponsor level: title, gold, silver, challenge, partner
name Sponsor name (required)
logo-url URL to sponsor logo image (optional)
website-url Sponsor website URL (optional)
challengeId For challenge sponsors only - which challenge they sponsor

Sponsor Tiers:

  • title - Title/naming sponsor (e.g., "The Vans World Destruction Tour")
  • gold - Major sponsor with prominent placement
  • silver - Secondary sponsor
  • challenge - Sponsors a specific challenge
  • partner - General partner/supporter

Example:

["sponsor", "title", "Vans", "https://vans.com/logo.png", "https://vans.com"],
["sponsor", "gold", "Monster Energy", "https://monster.com/logo.png", "https://monster.com"],
["sponsor", "challenge", "Red Bull", "https://redbull.com/logo.png", "https://redbull.com", "ch3"]

Privacy Model

Critical: Hidden spots are NEVER published to Nostr. They are shared in-person during the tour only.

  • Meeting point is a known public location
  • City-level geohash provides general location for discovery
  • Actual hidden spots are revealed only during the tour

Video/Clip Submissions

For destruction tours, organizers can accept clip submissions as proof of challenge completion:

  1. Blossom uploads - For clips under 50MB
  2. External links - YouTube, Instagram, or any URL

Organizers review submissions and award points manually. The platform does NOT host video - it only stores references.

Example: Local Tour

{
  "kind": 30823,
  "pubkey": "abc123...",
  "created_at": 1703001600,
  "content": "Join me for a 3-hour street spot tour of Barcelona's Gothic Quarter.",
  "tags": [
    ["d", "barcelona-gothic-hidden-tour"],
    ["title", "Barcelona Gothic Quarter - Hidden Gems Tour"],
    ["t", "skate-tour"],
    ["tour-category", "local"],
    ["t", "street"],
    ["t", "hidden"],
    ["g", "sp3e"],
    ["location", "Barcelona, Spain"],
    ["meeting-point", "MACBA Museum Steps"],
    ["meeting-point-coords", "41.3833", "2.1670"],
    ["duration", "3", "hours"],
    ["skill-level", "intermediate"],
    ["max-group", "4"],
    ["price", "25000"],
    ["languages", "en", "es", "ca"],
    ["zap-split", "skatespots@minibits.cash", "10"],
    ["alt", "Skate Tour: Barcelona Gothic Quarter in Barcelona, Spain"]
  ]
}

Example: National Tour

{
  "kind": 30823,
  "pubkey": "def456...",
  "created_at": 1703001600,
  "content": "5-day California coast tour hitting the best spots from LA to SF. Van included, bring your own board!",
  "tags": [
    ["d", "california-coast-tour-2024"],
    ["title", "California Coast Tour 2024"],
    ["t", "skate-tour"],
    ["tour-category", "national"],
    ["t", "street"],
    ["t", "park"],
    ["g", "9q5"],
    ["location", "California, USA"],
    ["cities", "Los Angeles", "Santa Barbara", "San Luis Obispo", "Santa Cruz", "San Francisco"],
    ["meeting-point", "Venice Beach Skatepark"],
    ["meeting-point-coords", "33.9850", "-118.4695"],
    ["duration", "5", "days"],
    ["skill-level", "intermediate"],
    ["max-group", "8"],
    ["price", "500000"],
    ["transport", "15-passenger van, driver included"],
    ["accommodation", "Budget hostels along the route - not included in price"],
    ["languages", "en", "es"],
    ["zap-split", "skatespots@minibits.cash", "10"],
    ["alt", "Skate Tour: California Coast Tour 2024"]
  ]
}

Example: World Destruction Tour

{
  "kind": 30823,
  "pubkey": "ghi789...",
  "created_at": 1703001600,
  "content": "EUROPEAN WORLD DESTRUCTION TOUR 2024. 4 teams battle across Europe's gnarliest spots for 3 months. Complete challenges, film clips, earn points. Winner takes all!",
  "tags": [
    ["d", "europe-destruction-2024"],
    ["title", "European World Destruction Tour 2024"],
    ["t", "skate-tour"],
    ["tour-category", "destruction"],
    ["t", "street"],
    ["t", "park"],
    ["t", "diy"],
    ["t", "transition"],
    ["t", "hidden"],
    ["cities", "Berlin", "Barcelona", "Paris", "London", "Amsterdam"],
    ["countries", "Germany", "Spain", "France", "UK", "Netherlands"],
    ["organizer-contact", "DM on Nostr: npub1... or email: destruction@skatespots.org"],
    ["transport", "Teams arrange own transport between cities"],
    ["duration", "12", "weeks"],
    ["skill-level", "advanced"],
    ["start-date", "1720000000"],
    ["end-date", "1727776000"],
    ["entry-fee", "100000"],
    ["prize-pool", "5000000"],
    ["prize-dist", "1", "50", "2", "30", "3", "20"],
    ["max-teams", "8"],
    ["min-team-size", "4"],
    ["max-team-size", "6"],
    ["challenge", "ch1", "First kickflip at Warschauer", "Land a kickflip on the famous Warschauer banks", "100", "1720000000"],
    ["challenge", "ch2", "Get a local to join", "Convince a local skater to join your session", "200", "1720000000"],
    ["challenge", "ch3", "Fountain trick", "Land a trick into the Alexanderplatz fountain", "500", "1720000000"],
    ["challenge", "ch4", "3-country video part", "Film a video part spanning at least 3 countries", "1000", "1720000000"],
    ["challenge", "ch5", "MACBA line", "Film a full line at MACBA Barcelona", "777", "1720000000"],
    ["clip-sources", "blossom", "youtube", "instagram", "any"],
    ["languages", "en", "de", "es", "fr"],
    ["zap-split", "skatespots@minibits.cash", "10"],
    ["alt", "World Destruction Tour: European World Destruction Tour 2024"]
  ]
}

Note: All challenges have the same revealDate (1720000000) which is the tour start date. Teams cannot see challenge details until this date.

Querying Tours

All Tours:

{
  "kinds": [30823],
  "#t": ["skate-tour"]
}

Tours by Category:

{
  "kinds": [30823],
  "#t": ["skate-tour"]
}

Note: Filter by tour-category tag client-side.

Tours in a City (by geohash prefix):

{
  "kinds": [30823],
  "#t": ["skate-tour"],
  "#g": ["sp3"]
}

Tours by a Host:

{
  "kinds": [30823],
  "authors": ["<host-pubkey>"],
  "#t": ["skate-tour"]
}

Food Recommendations

Food spots near skate locations are handled client-side by querying external APIs (like OpenStreetMap/Overpass) using the geohash coordinates from skate spots.

Private Stash (kind 30001)

The Stash feature uses NIP-51 Follow Sets (kind 30001) to store a user's private gear inventory. The content is NIP-44 encrypted to self, making it private by default with selective disclosure options.

Event Format

{
  "kind": 30001,
  "content": "<NIP-44 encrypted JSON>",
  "tags": [
    ["d", "skatespots-stash"],
    ["alt", "Private gear stash (encrypted)"]
  ]
}

Encrypted Content Structure

When decrypted, the content contains a JSON object with the user's inventory:

{
  "items": [
    {
      "id": "<uuid>",
      "title": "Powell Peralta Flight Deck 8.25",
      "category": "deck",
      "condition": "like_new",
      "size": "8.25\"",
      "brand": "Powell Peralta",
      "description": "Barely used, no chips or cracks",
      "minPrice": 50000,
      "maxPrice": null,
      "images": ["https://example.com/deck.jpg"],
      "willingToTrade": true,
      "willingToSell": true,
      "addedAt": 1703001600,
      "visibility": "friends",
      "itemType": "stash",
      "rarity": "rare"
    },
    {
      "id": "<uuid>",
      "title": "Looking for 8.0\" deck",
      "category": "deck",
      "condition": "good",
      "size": "8.0\"",
      "brand": "",
      "description": "Any brand, prefer minimal graphics",
      "minPrice": null,
      "maxPrice": 100000,
      "images": [],
      "willingToTrade": true,
      "willingToSell": false,
      "addedAt": 1703002600,
      "visibility": "friends",
      "itemType": "wtb",
      "rarity": null
    }
  ],
  "updatedAt": 1703002600
}

Item Types

Value Description
stash Regular inventory item that the user owns
wtb Want To Buy - item the user is looking for

Visibility Levels

Value Description
private Only visible to the owner. Shared manually via DM.
friends Visible to mutual follows. Enables P2P matching.
public Visible to anyone who views the user's profile.

Categories

Same as Gear Listings: deck, trucks, wheels, bearings, shoes, clothing, protective, complete, other

Conditions

Value Description
new Brand new, never used
like_new Barely used, excellent condition
good Normal wear, functions perfectly
well_used Shows wear but still works

Rarity (Optional)

Items can have an optional rarity tier for RPG-style display:

Value Description
common Standard items
uncommon Slightly rare brands/models
rare Hard to find items
epic Very rare or valuable items
legendary Extremely rare collector items

WTB Matching

The app performs client-side matching between WTB items and stash items from friends (mutual follows). Matches are based on:

  • Category (required match)
  • Brand (fuzzy match)
  • Size (within tolerance)
  • Price compatibility (WTB maxPrice >= stash minPrice)

When a match is found, the app can facilitate P2P trading via encrypted DMs (NIP-17).

Privacy Model

  • All stash data is NIP-44 encrypted to self
  • Private items are never shared without explicit user action
  • "Friends" visibility items can be queried by mutual follows through a discovery protocol
  • WTB matching happens client-side with no central server

Security Considerations

  1. The stash is encrypted at rest using the user's Nostr keys
  2. Selective disclosure allows users to share specific items via DM
  3. No price or inventory data is publicly visible unless explicitly set to "public"
  4. Group DMs (NIP-17) can be used for coordinating trades between multiple parties

Rider Cards

SkateSpots supports physical-print cards that are cryptographically signed on Nostr and verifiable by anyone.

Park Access Card (kind 35679)

Issued by a private skatepark owner to a specific rider, granting access rights. The park's Nostr key signs the event — anyone can verify the card is genuine by scanning the QR code.

Constraint: Park Access Cards for private parks (access=private in kind 30818) should only be issued by the park's owner pubkey. Clients verify the issuer's pubkey matches the park event's pubkey field.

{
  "kind": 35679,
  "content": "",
  "tags": [
    ["d", "<unique-id>"],
    ["p", "<rider-pubkey>"],
    ["t", "skatepark-access"],
    ["issuer", "<park name>"],
    ["access-level", "<Full Access|Supervised|Beginner Zone|Street Only|Bowl Only|Members Only>"],
    ["theme", "<thug|lime|chrome|wood|midnight|sunset>"],
    ["valid-thru", "<YYYY-MM-DD>"],
    ["alt", "Skatepark Access Card"]
  ]
}

Card Themes

Value Description
thug Black + Neon Red (default)
lime Black + Neon Lime
chrome Steel & Silver
wood Woodgrain Classic
midnight Deep Blue
sunset Orange & Pink

Rider License (client-side only)

The personal Rider License is generated client-side from the user's kind 0 profile metadata and on-chain stats. It is not a separate Nostr event — it renders from existing data:

  • Name, avatar, NIP-05: from kind 0
  • Spots added: count of kind 30818 authored by the user
  • Zaps received: count of kind 9735 targeting the user
  • Events created: count of kind 31923 authored by the user
  • Park cards held: count of kind 35679 with #p = user
  • Witnessed skill badges: count of kind 1293 with #p = user

The Rider License includes two categories of skill badges:

  • Self-claimed — selected by the rider themselves, shown in theme color
  • Witnessed — awarded by others (kind 1293), shown in gold ⭐

The "Rides For" field displays the local shop the rider represents, distinct from sponsors (brands).

Verification

The QR code on printed Park Access cards encodes the Nostr address (naddr) of the issuing event. Verifiers scan the QR, fetch the event from any relay, and confirm the issuer's signature.

URL format: https://skate-spots.org/verify/<naddr>

Skill Badge Awards (kind 1293)

Anyone who witnesses a rider performing a trick can award them a cryptographically signed skill badge. This creates an on-chain proof of skill — unfakeable because the witness's Nostr key signs the event.

Design Rationale

Skill badges are regular events (not addressable) because:

  • Multiple witnesses can each award the same skill to the same rider independently
  • Each badge is a distinct attestation with its own timestamp and witness
  • The full history of attestations is valuable (not just the latest)

Event Format

{
  "kind": 1293,
  "content": "",
  "tags": [
    ["p", "<rider-pubkey>"],
    ["skill", "<skill-id>"],
    ["skill", "<skill-id>"],
    ["spot", "<spot name (optional)>"],
    ["t", "skill-badge"],
    ["alt", "Skill badge awarded: <skill>, <skill>"]
  ]
}

Required Tags

Tag Description
p Pubkey of the rider receiving the badge
skill Skill identifier (one tag per skill, multiple allowed)
t Must be skill-badge for discoverability
alt NIP-31 human-readable description

Optional Tags

Tag Description
spot Name of the spot where the trick was witnessed
a Reference to a kind 30818 spot event

Skill Identifiers

Value Label
kickflip Kickflip
heelflip Heelflip
switch Switch stance
nollie Nollie
fakie Fakie
grind Grinds
slide Slides
manual Manuals
vert Vert
bowl Bowl
street Street
park Park
film Filmer
diy DIY Builder

Trust Model

The value of a badge is proportional to the reputation of the witness:

  • NIP-05 verified witnesses carry more weight
  • Witnesses known in the community (follows, mutual follows) carry more weight
  • The skill badge feed is public — anyone can query badges for any rider

Querying

All badges received by a rider:

{
  "kinds": [1293],
  "#p": ["<rider-pubkey>"],
  "#t": ["skill-badge"]
}

All badges issued by a witness:

{
  "kinds": [1293],
  "authors": ["<witness-pubkey>"],
  "#t": ["skill-badge"]
}

Badges for a specific skill:

{
  "kinds": [1293],
  "#p": ["<rider-pubkey>"],
  "#skill": ["kickflip"]
}

Example

{
  "kind": 1293,
  "pubkey": "witness-pubkey-hex",
  "created_at": 1703001600,
  "content": "",
  "tags": [
    ["p", "rider-pubkey-hex"],
    ["skill", "kickflip"],
    ["skill", "switch"],
    ["spot", "MACBA Barcelona"],
    ["a", "30818:abc123:macba"],
    ["t", "skill-badge"],
    ["alt", "Skill badge awarded: kickflip, switch"]
  ],
  "id": "...",
  "sig": "..."
}

The Lab — Service Profiles

The Lab connects skaters with service providers. Each provider type is an addressable event (kind 35001–35004) identified by pubkey + kind + d-tag. They share a common base schema and are distinguished by kind and a self-tag.

Kind Provider Self-tag
35001 Coach skatecoach
35002 Filmer (video) skatefilmer
35003 Video Editor skateeditor
35004 Photographer skatephotographer
35005 Builder (ramp/park/DIY) skatebuilder

Common Base Schema

{
  "kind": 35004,
  "content": "<bio / about the provider>",
  "tags": [
    ["d", "<profile-identifier>"],
    ["title", "<display name>"],
    ["t", "<self-tag>"],
    ["t", "<service/skill/gear tag>"],
    ["location", "<city / area>"],
    ["g", "<geohash>"],
    ["image", "<profile image url>"],
    ["experience", "<years>"],
    ["rate", "<freeform rate, e.g. 20000 sats/shoot>"],
    ["phone", "<phone>"],
    ["email", "<email>"],
    ["website", "<url>"],
    ["entity", "individual"],
    ["a", "30818:<pubkey>:<d-tag>"],
    ["alt", "<human-readable description>"]
  ]
}

Required Tags

Tag Description
d Unique profile identifier (e.g. photography-profile)
title Display name
t Must include the provider self-tag (see table above)

Shared Optional Tags

location, g (geohash), image, experience, rate, phone, email, website, noffer (CLINK payment code — see below), entity (see Entity Type below), p (collective member pubkeys — see Collective Membership below), a (spot references to kind 30818), alt.

Entity Type (individual vs. collective)

Every Lab profile carries an optional entity tag declaring whether it represents a single person or a group:

Tag Value Meaning
entity individual A single person (default if omitted).
entity collective A multi-person entity: crew, studio, or company.

"Company" is presented as cosmetic sub-styling of a collective, not a distinct trust tier — on Nostr a company and a collective share the same trust model (a shared brand backed by verifiable member keys).

["entity", "collective"]

Collective Membership (NIP-32 attestations)

A collective profile lists its claimed members via p tags (member pubkeys in hex). Because the collective's founder key alone signs the profile, a bare p tag is only a claim — it cannot prove the listed person consented.

To make membership unforgeable, each member independently publishes a NIP-32 label event (kind 1985) signed with their own key, referencing the collective's addressable coordinate:

{
  "kind": 1985,
  "content": "I'm a member of Barcelona Filmers",
  "tags": [
    ["L", "org.skatespots.membership"],
    ["l", "member", "org.skatespots.membership"],
    ["a", "35002:<founderPubkey>:<d-tag>"],
    ["p", "<founderPubkey>"],
    ["alt", "Skate collective membership confirmation for Barcelona Filmers"]
  ]
}

Client logic: cross-reference the collective's p tags with member attestations. A member is:

  • Confirmed — listed by the collective AND has published a matching membership label. Rendered with a green "Confirmed" chip.
  • Pending — listed by the collective but has not attested. Amber "Pending" chip.
  • A stray attestation from someone the collective does not list is ignored (prevents unsolicited membership claims).

Removing a member: the founder drops the p tag, or the member deletes their label event (NIP-09). Either direction flips the member out of "confirmed".

Photographer (kind 35004)

Skate photography is a distinct craft from filming — it is shot-, light-, and print-oriented. A photographer profile records services and gear as queryable t tags, plus a freeform gear tag and a portfolio/social link.

Categories encoded as t tags:

  • Services: action, sequence, portraits, editorial, product, events, lifestyle, prints
  • Camera bodies: dslr, mirrorless, medium, film35, filmmedium, point, phone
  • Lenses: fisheye, wide, standard, telephoto, macro, zoom
  • Lighting: strobes, speedlights, profoto, elinchrom, godox, triggers, continuous, reflectors, naturallight
  • Support: tripod, monopod, lightstands, gimbal, bag, laptop
  • Style: flashfisheye, natural, bw, film, longlens, studio, street

Photographer-specific optional tags:

Tag Description
gear Freeform "what's in the bag" (exact bodies, lenses, lights)
portfolio Portfolio link (Instagram, personal site, etc.)
social Additional social handle or link

Example: Photographer

{
  "kind": 35004,
  "content": "Shooting street and park sessions for 8 years. Love a lit flash fisheye at dusk.",
  "tags": [
    ["d", "photography-profile"],
    ["title", "Ana Flash"],
    ["t", "skatephotographer"],
    ["t", "action"], ["t", "sequence"], ["t", "editorial"],
    ["t", "mirrorless"], ["t", "fisheye"], ["t", "telephoto"],
    ["t", "godox"], ["t", "triggers"],
    ["t", "tripod"], ["t", "lightstands"],
    ["t", "flashfisheye"], ["t", "bw"],
    ["location", "Barcelona, ES"],
    ["g", "sp3e9"],
    ["experience", "8"],
    ["rate", "25000 sats/shoot"],
    ["gear", "Sony A7 IV, 15mm fisheye, 70-200mm f/2.8, 2× Godox AD200 Pro, remote triggers, Manfrotto tripod"],
    ["portfolio", "https://instagram.com/anaflash"],
    ["alt", "Skate photographer: Ana Flash"]
  ]
}

Builder (kind 35005)

Skate ramp / park / DIY builders. Most builders are crews or companies, so this category defaults to entity: collective. Build types, materials and scale are stored as queryable t tags, plus builder-specific optional tags.

Categories encoded as t tags:

  • What they build: diy, concrete, wood, bowls, transitions, rails, boxes, minis, pumptrack, foam, repairs, design
  • Materials / trade: concretework, welding, carpentry, formwork, masonry, coping, surfacing, earthworks
  • Project scale: backyard, diycommunity, publicpark, indoor, event

Builder-specific optional tags:

Tag Description
gear Freeform "how we work / equipment"
service_area How far they travel (freeform)
insured "true" if licensed/insured (relevant for public park work)
portfolio Link to past builds (Instagram, site)
social Additional social handle or link

The a (spot reference) tags are especially meaningful for builders — they link to the actual spots/parks (kind 30818) the builder has made, surfacing their work on the map.

Example: Builder (collective)

{
  "kind": 35005,
  "content": "Concrete crew out of Malmö. 12 years pouring parks and DIY across the EU.",
  "tags": [
    ["d", "builder-profile"],
    ["title", "Betong Bros"],
    ["t", "skatebuilder"],
    ["t", "concrete"], ["t", "bowls"], ["t", "transitions"], ["t", "repairs"],
    ["t", "concretework"], ["t", "formwork"], ["t", "coping"],
    ["t", "publicpark"], ["t", "diycommunity"],
    ["location", "Malmö, SE"],
    ["g", "u3bu5"],
    ["experience", "12"],
    ["rate", "per m² / by project"],
    ["service_area", "Scandinavia + will travel EU"],
    ["insured", "true"],
    ["gear", "Own mixer + shotcrete rig, mobile welding, handle drainage & permits"],
    ["portfolio", "https://instagram.com/betongbros"],
    ["entity", "collective"],
    ["p", "<member1-pubkey>"],
    ["p", "<member2-pubkey>"],
    ["a", "30818:<pubkey>:<d-tag>"],
    ["alt", "Skate ramp/park builder: Betong Bros"]
  ]
}

Querying Lab Profiles

{
  "kinds": [35001, 35002, 35003, 35004, 35005],
  "#t": ["skatecoach", "skatefilmer", "skateeditor", "skatephotographer", "skatebuilder"],
  "limit": 100
}

Skate Crews (kind 35006)

A Crew is a map-anchored collective identity — the crew you skate/roll with, not a service you hire. It answers "who skates here?" rather than "who can I hire?" (that's The Lab). Crews are addressable events identified by pubkey + kind + d-tag.

A crew is structurally a collective (it reuses the exact NIP-32 membership attestation mechanism defined in The Lab § Collective Membership) minus the service/rate fields, plus a home turf (map anchor) and optional home spots. Members are always attested per-member with their own key — a bare p tag on the crew is only a claim until the member self-labels.

Event Format

{
  "kind": 35006,
  "content": "Late-night ledge crew. We haunt the plaza. All welcome, no kooks.",
  "tags": [
    ["d", "barcelona-night-crew"],
    ["title", "Barcelona Night Crew"],
    ["t", "skatecrew"],
    ["image", "https://blossom.example/crew-logo.png"],
    ["g", "sp3e"],
    ["location", "Barcelona, Spain"],
    ["founded", "2019"],
    ["t", "street"],
    ["t", "transition"],
    ["p", "<memberPubkey1>"],
    ["p", "<memberPubkey2>"],
    ["a", "30818:<spotFounder>:<spotDtag>"],
    ["website", "https://bcnnightcrew.example"],
    ["noffer", "noffer1qqs..."],
    ["alt", "Skate crew: Barcelona Night Crew"]
  ]
}

Required Tags

Tag Description
d Unique crew identifier
title Crew name
t: skatecrew Self-tag for discovery/querying

Optional Tags

Tag Description
image Crew logo / photo
g Geohash of the crew's home turf (map anchor)
location Human-readable home location
founded Year founded (cosmetic)
t: <vibe> Style/focus tags: street, transition, diy, oldschool, allterrain, longboard, bmx-friendly, bowl, vert, cruising, downhill, etc.
p Member pubkeys (hex). Confirmed only when the member self-attests (see below).
a Home spots the crew claims/haunts — references to kind 30818 skate spots (up to 8). Cross-links the crew to the map.
website, instagram, social Links
noffer Optional shared tip jar (CLINK Offer — see below)

Membership

Crews reuse the collective membership model verbatim. A member confirms belonging by publishing a NIP-32 label (kind 1985) referencing the crew's a-coordinate, ["l", "member", "org.skatespots.membership"]. Confirmed / pending chips and removal semantics are identical to Lab collectives.

Map Integration

Crews with a g (home turf) render on the SkateSpots map as teal pins. Clicking a crew pin opens its profile. A crew's a-referenced home spots remain regular spots on the map and cross-link back to the crew.

Querying Crews

{
  "kinds": [35006],
  "#t": ["skatecrew"],
  "limit": 200
}

SkateSpots supports an optional, opt-in self-custodial payment code on Lab provider profiles (kinds 35001–35004), skate crews (kind 35006), and skate shop stalls (kind 30017). This is layered on top of — never a replacement for — the existing Lightning Address (lud16 from kind 0) and zap flows.

CLINK (Common Lightning Interface for Nostr Keys) defines Nostr-native Lightning standards. A CLINK Offer is a reusable, static payment code encoded as a bech32 string with an noffer1… prefix (CLINK kind 21001), analogous to an LNURL-Pay / Lightning Address but requiring no web server or custodian.

The noffer tag

Tag Value Description
noffer noffer1… A CLINK Offer code the provider/shop controls.
["noffer", "noffer1qqs..."]

Client behavior

  • The tag is optional. Its absence changes nothing.
  • Clients MUST validate the noffer1… prefix before rendering a payment affordance. SkateSpots stores it only if it matches /^noffer1[023456789acdefghjklmnpqrstuvwxyz]{20,}$/.
  • SkateSpots does not decode or transact the offer itself. It surfaces a "Pay via CLINK" affordance (QR of the code, copy button, and a nostr:noffer1… deep link) so a CLINK-compatible wallet (e.g. ShockWallet, Lightning.Pub) can request an invoice directly over Nostr.
  • The QR scanner also classifies scanned noffer1… (and ndebit1…) strings so they are recognized as CLINK codes.

Only noffer (CLINK Offers) is supported in this release. ndebit (CLINK Debits) and nmanage (CLINK Manage) are recognized by the scanner but not yet actioned in-app.