Skip to content

Conversation

@aljazceru
Copy link

@aljazceru aljazceru commented Nov 29, 2025

PR Type

Enhancement, Tests, Bug fix, Documentation


Description

  • Business Listings Feature: Complete implementation of NIP-15 marketplace stalls directory with create, read, update, delete operations

  • New Hooks: Added useListings, useListingsByAuthor, useListingSubmissionPayment, useListingFlags, useCuratorFlags, and useDeleteListing for listings management

  • UI Components: New listing display components (ListingCard, ListingListItem), forms (BusinessListingForm), and dialogs (ListingSubmissionPaymentDialog, ListingFlagDialog)

  • Pages: New pages for listings directory (ListingsPage), submission (SubmitListingPage), details (ListingDetailPage), and editing (EditListingPage)

  • Payment Integration: Lightning payment support for listing submissions with NIP-57 zaps and configurable relay endpoints

  • Content Curation: Client-side curation system with curator flags (kind 1984) and flag-based content filtering

  • Site Configuration: Enhanced siteConfig with curation settings, section visibility controls, and curator pubkey management

  • Router Updates: Added listing routes and changed default landing page from /apps to /listings

  • Bug Fixes: Fixed client tag publishing logic to prevent empty tags when VITE_SITE_NAME is not configured; fixed regex escaping in GitHub readme display

  • Testing: Added comprehensive tests for listings filtering, payment handling, and author page integration

  • Documentation: Updated README with listings feature documentation, configuration details, and environment variables; added security policy

  • Styling: Added background pattern CSS utility class and updated layout with listings navigation

  • Code Quality: Removed unused imports, improved type safety, and added debug logging utility


Diagram Walkthrough

flowchart LR
  A["User"] -->|"Submit Listing"| B["SubmitListingPage"]
  B -->|"Create Event"| C["BusinessListingForm"]
  C -->|"Pay with Lightning"| D["ListingSubmissionPaymentDialog"]
  D -->|"NIP-57 Zap"| E["Nostr Network"]
  E -->|"Store Event"| F["Relays"]
  F -->|"Fetch Listings"| G["useListings Hook"]
  G -->|"Display"| H["ListingsPage"]
  H -->|"View Details"| I["ListingDetailPage"]
  I -->|"Flag Content"| J["ListingFlagDialog"]
  J -->|"Report Event"| E
  K["Curator"] -->|"Flag Stalls"| L["useCuratorFlags Hook"]
  L -->|"Filter Content"| G
Loading

File Walkthrough

Relevant files
Enhancement
25 files
useListingSubmissionPayment.ts
Add listing submission payment hook with NIP-57 zap support

src/hooks/useListingSubmissionPayment.ts

  • New hook for handling Lightning payment flow for listing submissions
    using NIP-57 zaps
  • Implements payment creation, verification, and state management with
    relay queries
  • Includes helper functions for Lightning address profile fetching and
    LNURL encoding
  • Supports configurable payment amounts and relay endpoints via
    environment variables
+468/-0 
useListings.ts
Add hook for fetching and filtering business listings       

src/hooks/useListings.ts

  • New hook to fetch and parse NIP-15 business stall events (kind 30017)
  • Implements client tag filtering and curator-based curation filtering
  • Validates stall events and parses shipping zones, images, and website
    data
  • Returns sorted stalls with 5-minute cache stale time
+183/-0 
useListingsByAuthor.ts
Add hook for fetching author-specific business listings   

src/hooks/useListingsByAuthor.ts

  • New hook to fetch business stalls filtered by author pubkey
  • Implements same validation, parsing, and curation logic as useListings
  • Supports client tag filtering and curator flag-based content filtering
  • Returns author-specific stalls sorted by creation date
+173/-0 
useListingFlags.ts
Add hook for listing flag management and reporting             

src/hooks/useListingFlags.ts

  • New hook for querying and managing listing flags (kind 1984 events)
  • Supports multiple report types (fraud, spam, scam, duplicate,
    inappropriate, impersonation)
  • Implements flag creation via Nostr event publishing with proper NIP-15
    references
  • Provides flag statistics and user flag tracking
+172/-0 
useCuratorFlags.ts
Add hook for curator-based content curation flags               

src/hooks/useCuratorFlags.ts

  • New hook to fetch curator flags for content curation from configured
    curator pubkeys
  • Queries kind 1984 events from curator accounts with 10-second timeout
  • Includes helper hook useIsStallFlagged to check if specific stall is
    flagged
  • Only runs when client-side curation is enabled
+123/-0 
siteConfig.ts
Enhance site config with curation and section visibility settings

src/lib/siteConfig.ts

  • Updated default site name to 'The Lookup' and URL to
    'https://lookup.hivetalk.org'
  • Modified getClientTag to return null when VITE_SITE_NAME is not
    explicitly set
  • Added client-side curation configuration functions and section
    visibility controls
  • Implemented curator pubkey conversion from npub format using
    nip19.decode
+92/-4   
useAppSubmissionPayment.ts
Add configurable zap receipt relay support                             

src/hooks/useAppSubmissionPayment.ts

  • Added support for configurable zap receipt relay via
    VITE_ZAP_RECEIPT_RELAY environment variable
  • Updated relay selection logic to use configured relay if provided,
    fallback to Primal
  • Improved WebSocket availability check using typeof operator instead of
    casting
+7/-5     
useDeleteListing.ts
Add hook for deleting business listings                                   

src/hooks/useDeleteListing.ts

  • New hook for deleting business listings using NIP-09 deletion events
  • Creates deletion event with proper NIP-15 address tag references
  • Invalidates business-stalls query cache on successful deletion
+31/-0   
debug.ts
Add debug logging utility module                                                 

src/lib/debug.ts

  • New debug logging utility module with conditional logging based on
    VITE_DEBUG_LOGGING
  • Exports three functions: debugLog, debugWarn, and debugError
  • Provides consistent debug output across the application
+26/-0   
index.css
Add background pattern CSS utility class                                 

src/index.css

  • Added new .bg-bkg-pattern CSS class for background pattern styling
  • Uses /bkg.svg as repeating background with fixed attachment
  • Configured with 120px spacing and fixed scroll behavior
+7/-0     
BusinessListingForm.tsx
Add business listing form component with payment integration

src/components/BusinessListingForm.tsx

  • New form component for creating and editing NIP-15 business stall
    listings
  • Supports shipping zone management with cost and region configuration
  • Integrates with payment system for new listing submissions
  • Includes image preview, website URL, currency, and tag management
+615/-0 
AuthorPage.tsx
Add business listings section to author profile page         

src/pages/AuthorPage.tsx

  • Added listings section to author profile with full CRUD operations
  • Integrated useListingsByAuthor hook and ListingCard component
  • Added delete listing functionality with confirmation dialog
  • Implemented section visibility checks using isSectionVisible function
+195/-7 
ListingDetailPage.tsx
Add business listing detail page with flag integration     

src/pages/ListingDetailPage.tsx

  • New page component for displaying detailed business listing
    information
  • Shows listing image, description, shipping zones, and Nostr event data
  • Integrates flag system with flag statistics display
  • Provides links to Plebeian Market and edit functionality for listing
    owner
+338/-0 
Layout.tsx
Add listings navigation and update layout styling               

src/components/Layout.tsx

  • Added 'Listings' navigation section with Store icon to main navigation
  • Changed background from gradient to .bg-bkg-pattern CSS class
  • Added explicit background color to header for better contrast
  • Removed unused ThemeSwitcher import
+5/-5     
ListingSubmissionPaymentDialog.tsx
Lightning payment dialog for listing submissions                 

src/components/ListingSubmissionPaymentDialog.tsx

  • New component for handling Lightning payments for business listing
    submissions
  • Implements payment flow with QR code generation, invoice copying, and
    WebLN integration
  • Includes countdown timer for payment expiration and automatic payment
    verification polling
  • Displays success confirmation when payment is verified
+329/-0 
ListingsPage.tsx
Business directory listing page with search and filters   

src/pages/ListingsPage.tsx

  • New page displaying business directory with NIP-15 marketplace stalls
  • Implements search, tag filtering, and dual view modes (cards and list)
  • Shows relay information and total business count
  • Includes helper function for rendering clickable URLs in descriptions
+272/-0 
ListingFlagDialog.tsx
Content flagging dialog for business listings                       

src/components/ListingFlagDialog.tsx

  • New component for flagging/reporting business listings using NIP-1984
  • Supports multiple report types (fraud, spam, scam, duplicate,
    inappropriate, impersonation)
  • Shows existing user flags and prevents duplicate submissions
  • Includes character limit and form validation
+161/-0 
EditListingPage.tsx
Edit and delete page for business listings                             

src/pages/EditListingPage.tsx

  • New page for editing existing business listings with owner
    verification
  • Implements delete functionality using NIP-09 deletion events
  • Shows permission error if user is not the listing owner
  • Integrates with BusinessListingForm component for editing
+155/-0 
ListingListItem.tsx
List item component for business listings display               

src/components/ListingListItem.tsx

  • New component for displaying business listings in list view format
  • Shows listing image, name, author profile link, description, and tags
  • Includes website link button and details navigation
  • Displays currency information and truncated descriptions
+102/-0 
ListingCard.tsx
Card component for business listings display                         

src/components/ListingCard.tsx

  • New component for displaying business listings in card view format
  • Shows listing image, name, author profile link, description, and tags
  • Includes website link button and details navigation
  • Responsive card layout with hover effects
+100/-0 
SubmitListingPage.tsx
Submit new business listing page                                                 

src/pages/SubmitListingPage.tsx

  • New page for submitting new business listings
  • Displays informational card about NIP-15 marketplace stalls
  • Shows payment requirement details when enabled
  • Integrates BusinessListingForm component for submission
+72/-0   
AppsPage.tsx
Relay information display on apps page                                     

src/pages/AppsPage.tsx

  • Added relay information display showing current relay and label
  • Integrated useAppConfig hook to display relay context
  • Updated description to show which relay apps are being viewed from
+17/-1   
ZapButton.tsx
Zap button refactoring and variant support                             

src/components/ZapButton.tsx

  • Removed unused imports for useCurrentUser and useAuthor hooks
  • Refactored button styling to support multiple variants (outline,
    default, ghost)
  • Improved conditional styling with variant-based classes
+4/-6     
Index.tsx
Index page title and description updates                                 

src/pages/Index.tsx

  • Updated page title to reference businesses and apps
  • Updated meta description to include businesses in discovery message
+2/-2     
avatar.tsx
Avatar image styling with object-cover                                     

src/components/ui/avatar.tsx

  • Added object-cover class to avatar image styling
  • Ensures proper image cropping and aspect ratio maintenance
+1/-1     
Tests
6 files
useListings.test.ts
Add tests for listing client tag filtering logic                 

src/hooks/useListings.test.ts

  • New test file for useListings hook client tag filtering functionality
  • Tests filtering stalls by client tag, empty results, and all-matching
    scenarios
  • Includes test for behavior when no VITE_SITE_NAME is configured
  • Mocks getClientTag function from siteConfig
+234/-0 
useAppSubmissionPayment.test.ts
Refactor app payment tests with QueryClient wrapper           

src/hooks/useAppSubmissionPayment.test.ts

  • Updated test file to use QueryClientProvider wrapper for React Query
  • Simplified tests to focus on configuration state consistency
    invariants
  • Removed environment variable mutation tests in favor of property-based
    assertions
  • Added helper function createWrapper for test setup
+33/-35 
EditNipPage.test.tsx
Fix NIP edit page tests for async mutations                           

src/pages/EditNipPage.test.tsx

  • Updated mock return value from mutate to mutateAsync for
    useNostrPublish
  • Changed test assertions to use waitFor for async mutation calls
  • Updated all test cases to properly await async publish operations
+21/-26 
AuthorPage.test.tsx
Test updates for author page listings integration               

src/pages/AuthorPage.test.tsx

  • Added mocks for useListingsByAuthor hook across all test cases
  • Updated empty state test to reflect new generic content message
  • Ensures listings data is properly mocked in author profile tests
+37/-1   
NipPage.test.tsx
NIP page test mock for zap component                                         

src/pages/NipPage.test.tsx

  • Added mock for ZapCustomNip component to avoid wallet logic in tests
  • Prevents NWC context from being pulled into test environment
+5/-0     
Index.test.tsx
Kind badge hover color test update                                             

src/pages/Index.test.tsx

  • Updated test assertion for kind badge hover color
  • Changed from hover:bg-primary/20 to hover:bg-purple-200
+1/-1     
Formatting
4 files
useGitHubRepository.ts
Remove unused imports from repository hook                             

src/hooks/useGitHubRepository.ts

  • Removed unused imports useState, useEffect, and useQueryClient
  • Kept only necessary imports for the hook functionality
+2/-10   
useLocalStorage.ts
Remove unused import from local storage hook                         

src/hooks/useLocalStorage.ts

  • Removed unused useEffect import from React
+1/-1     
EditRepositoryPage.tsx
Unused import cleanup in edit repository page                       

src/pages/EditRepositoryPage.tsx

  • Removed unused Alert import from alert component
  • Kept AlertDescription import for actual usage
+1/-1     
FlagDialog.tsx
Unused error parameter cleanup in flag dialog                       

src/components/FlagDialog.tsx

  • Removed unused error parameter in catch block
  • Improved code quality by prefixing with underscore or removing
+1/-1     
Bug fix
3 files
useNostrPublish.ts
Fix client tag publishing when not configured                       

src/hooks/useNostrPublish.ts

  • Updated client tag logic to only add tag when getClientTag() returns
    non-null value
  • Prevents adding empty client tags when VITE_SITE_NAME is not
    configured
+4/-3     
useRepositories.ts
Fix client tag in repository issue creation                           

src/hooks/useRepositories.ts

  • Updated useCreateIssue to only add client tag when getClientTag()
    returns non-null
  • Prevents adding empty client tags to issue events
+6/-1     
GitHubReadmeDisplay.tsx
GitHub readme display fixes and cleanup                                   

src/components/GitHubReadmeDisplay.tsx

  • Fixed regex patterns by escaping forward slashes properly
  • Prefixed unused parameters with underscore (_repositoryNaddr,
    _repositoryOwnerPubkey)
  • Improved code quality and linting compliance
+3/-3     
Configuration changes
4 files
test-nip57-flow.js
Update NIP-57 test to use standard relays                               

tests/nip57/test-nip57-flow.js

  • Removed 'wss://hivetalk.nostr1.com' relay from test configuration
  • Updated relay list to use standard relays (relay.nostr.net,
    relay.primal.net, relay.nostr.band)
  • Changed default relay references in zap request and payment
    verification
+3/-4     
AppRouter.tsx
Router configuration for business listings                             

src/AppRouter.tsx

  • Added routes for business listings feature (/listings,
    /listings/submit, /listings/:stallId, /listings/:stallId/edit)
  • Changed home route from /apps to /listings (new default landing page)
  • Imported new listing page components
+9/-1     
.env.example
Environment configuration updates for listings and curation

.env.example

  • Updated site configuration defaults to The Lookup
  • Added business listing payment environment variables
  • Added client-side curation configuration options
  • Added debug logging configuration
  • Removed old VITE_SITE_NAME from top-level config
+19/-3   
site.webmanifest
Web manifest description update                                                   

public/site.webmanifest

  • Updated manifest description to include businesses and apps
  • Changed from "Nostr Implementation Possibilities" to "Nostr
    Businesses, apps and Implementation Possibilities"
+1/-1     
Documentation
3 files
index.html
Update HTML meta tags for business/app focus                         

index.html

  • Updated page title and meta descriptions from "Nostr Implementation
    Possibilities" to "Nostr Businesses and Apps"
  • Changed Open Graph and Twitter card descriptions to reflect
    business/app focus
  • Updated all SEO meta tags consistently across title, description, and
    image alt text
+7/-7     
README.md
Documentation updates for business listings and configuration

README.md

  • Added comprehensive documentation for business listings directory
    feature
  • Documented client tag filtering for curated vs open directories
  • Added section configuration environment variable documentation
  • Updated URL structure and feature descriptions to include listings
  • Added Lightning payments configuration details for both apps and
    listings
+117/-9 
SECURITY.md
Security policy documentation                                                       

SECURITY.md

  • New security policy file with version support table
  • Includes template sections for vulnerability reporting
  • Defines supported versions and security update information
+21/-0   
Additional files
6 files
_redirects +0/-8     
quick-zap-test.js +0/-1     
App.tsx +0/-1     
AppProvider.ts +0/-1     
Footer.tsx +0/-1     
test-payment-detection.js +0/-1     

bitkarrot and others added 22 commits November 18, 2025 18:24
Add Business Directory Listing
Add a security policy document outlining supported versions and vulnerability reporting.
Bumps [glob](https://github.com/isaacs/node-glob) from 10.4.5 to 10.5.0.
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](isaacs/node-glob@v10.4.5...v10.5.0)

---
updated-dependencies:
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
- Update getClientTag() to return null when no VITE_SITE_NAME is configured
- Modify useListings to show all listings when no client tag is set
- Show only listings with matching client tag when VITE_SITE_NAME is configured
- Fix useNostrPublish and useRepositories to handle null client tag
- Add comprehensive tests for client tag filtering scenarios
- Update .env.example with clearer documentation
build(deps): bump glob from 10.4.5 to 10.5.0
- Add client tag filtering as key feature in business listings section
- Document flexible directory configurations (open vs curated)
- Add comprehensive Client Tag Filtering section with examples
- Update environment variables documentation
- Clarify use cases for different deployment scenarios
- Update important note to reflect new filtering capabilities
- Fix filtering logic to check actual 'client' tag from event instead of 't' tags
- Update useListings to properly match client tag values from event.tags
- Fix all test cases to use correct client tag format in mock events
- Resolves issue where listings with client tags were not showing up
- All tests passing (200/200)
- Add CLIENT_SIDE_CURATION and NPUB_CURATORS environment variables
- Create useCuratorFlags hook to fetch flags from trusted curators
- Update useListingFlags to query actual kind 1984 events
- Modify useListings to filter out curator-flagged listings
- Add npub to pubkey conversion using nostr-tools
- Include comprehensive debug logging for troubleshooting
- Support configurable curation via environment variables
- Modify renderDescription function to optionally disable clickable links
- Set makeLinksClickable=false when rendering descriptions inside Link components
- Prevents nested <a> tags that cause DOM validation warnings
- URLs still display with primary color styling but aren't clickable in card/list views
- Remove debug utility imports from all files
- Replace debugLog/debugWarn/debugError with console.log/warn/error
- Keep debug.ts file for future use
- Add example curator npub to .env.example
- Replace console.log/warn/error with debugLog/debugWarn/debugError
- Add debug utility imports to all curation-related files
- Debug logging now controlled by VITE_DEBUG_LOGGING environment variable
- Resolve merge conflict in .env.example keeping curator npub
- Add section visibility utilities to siteConfig.ts based on VITE_SECTIONS
- Create useListingsByAuthor hook to fetch author's business listings
- Add listings section to profile page with full CRUD support
- Respect VITE_SECTIONS configuration to show only enabled sections
- Profile page now shows listings with same profile links as main page
- Add object-cover class to AvatarImage component
- Images now crop to fit circular avatars without stretching
- Shows center portion of images while maintaining aspect ratio
- Affects all avatars across listings, profiles, apps, and other components
@qodo-merge-for-open-source qodo-merge-for-open-source bot changed the title upstream sync upstream sync Nov 29, 2025
@qodo-merge-for-open-source
Copy link

qodo-merge-for-open-source bot commented Nov 29, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🔴
Browser incompatibility issue

Description: The encodeLnurl function uses Node.js Buffer API which is not available in browser
environments, causing runtime errors. This breaks the payment flow and prevents users from
submitting listings with payment.
useListingSubmissionPayment.ts [402-405]

Referred Code
function encodeLnurl(url: string): string {
  const words = Buffer.from(url, 'utf8');
  return `lnurl1${words.toString('hex')}`;
}
Client-side payment bypass

Description: Payment verification logic validates zap receipts by checking multiple fields but relies
on client-side validation only. An attacker could craft fake zap receipt events matching
all validation criteria without actual payment, bypassing the payment requirement for
listing submissions.
useListingSubmissionPayment.ts [250-301]

Referred Code
const validReceipt = uniqueReceipts.find((receipt) => {
  try {
    const bolt11Tag = receipt.tags?.find((tag) => tag[0] === 'bolt11');
    const descriptionTag = receipt.tags?.find((tag) => tag[0] === 'description');
    const pTag = receipt.tags?.find((tag) => tag[0] === 'p');

    if (!bolt11Tag || !descriptionTag || !pTag) {
      return false;
    }

    if (pTag[1] !== lightningProfile.pubkey) {
      return false;
    }

    if (bolt11Tag[1] !== paymentState.invoice) {
      return false;
    }

    let zapRequestFromReceipt;
    try {
      zapRequestFromReceipt = JSON.parse(descriptionTag[1]);


 ... (clipped 31 lines)
Payment bypass via edits

Description: Payment requirement check only applies to new listings, not edits. An attacker could
create a minimal paid listing then edit it to include malicious content without additional
payment, bypassing payment-based spam prevention.
BusinessListingForm.tsx [283-296]

Referred Code
  // Only require payment for initial submissions, not edits
  const shouldRequirePayment = !existingStall && isPaymentRequired && paymentConfig;

  if (shouldRequirePayment) {
    setPendingFormData(data);
    setShowPaymentDialog(true);
    toast({
      title: 'Payment Required',
      description: `A payment of ${paymentConfig.feeAmount} sats is required for new listings.`,
    });
  } else {
    submitListingToRelay(data);
  }
};
Payment relay injection

Description: Lightning payment configuration including relay URLs and zap endpoints are constructed
from user-controlled environment variables without validation. Malicious relay URLs could
redirect payment requests to attacker-controlled servers, enabling invoice substitution
attacks.
useListingSubmissionPayment.ts [86-99]

Referred Code
const lightningProfile = await fetchLightningAddressProfile(config.lightningAddress);

const amountMsats = config.feeAmount * 1000;

const [name, domain] = config.lightningAddress.split('@');
const lnurlPayUrl = `https://${domain}/.well-known/lnurlp/${name}`;
const lnurl = encodeLnurl(lnurlPayUrl);

const zapRelaysEnv = import.meta.env.VITE_ZAP_RECEIPT_RELAY;
const zapRelays = [
  import.meta.env.VITE_RELAY_URL || 'wss://relay.nostr.net',
  'wss://relay.primal.net',
  ...(zapRelaysEnv ? [zapRelaysEnv] : []),
];
WebSocket resource exhaustion

Description: The queryRelayForReceipts function creates WebSocket connections to arbitrary relay URLs
from configuration without timeout limits on connection attempts. Malicious relay URLs
could cause denial of service through connection exhaustion or indefinite hangs.
useListingSubmissionPayment.ts [407-468]

Referred Code
async function queryRelayForReceipts(relayUrl: string, queries: object[]): Promise<NostrEvent[]> {
  return new Promise((resolve) => {
    const WebSocketCtor: typeof WebSocket | undefined =
      typeof window !== 'undefined' ? window.WebSocket : undefined;

    if (!WebSocketCtor) {
      console.log(`WebSocket not available for ${relayUrl}`);
      resolve([]);
      return;
    }

    const ws = new WebSocketCtor(relayUrl);
    const subId = 'zap-receipt-' + Math.random().toString(36).substring(7);
    const allReceipts: NostrEvent[] = [];
    let queryCount = 0;
    let completedQueries = 0;

    const timeout = setTimeout(() => {
      ws.close();
      resolve(allReceipts);
    }, 10000);


 ... (clipped 41 lines)
Unsafe WebSocket URL handling

Description: WebSocket constructor type checking uses runtime typeof check but doesn't validate the
relay URL format or protocol. Malicious URLs with non-WebSocket protocols could cause
unexpected behavior or security issues.
useAppSubmissionPayment.ts [597-610]

Referred Code
// Helper function to query a relay for zap receipts using WebSocket
async function queryRelayForReceipts(relayUrl: string, queries: object[]): Promise<NostrEvent[]> {
  return new Promise((resolve, _reject) => {
    const WebSocketCtor = typeof WebSocket !== 'undefined' ? WebSocket : undefined;
    if (!WebSocketCtor) {
      console.log(`WebSocket not available for ${relayUrl}`);
      resolve([]);
      return;
    }

    const ws = new WebSocketCtor(relayUrl);
    const subId = 'zap-receipt-' + Math.random().toString(36).substring(7);
    const allReceipts: NostrEvent[] = [];
    let queryCount = 0;
Curator key validation missing

Description: The convertNpubToPubkey function decodes npub keys from environment configuration but
doesn't validate the decoded pubkey format or length. Malformed npub values could result
in invalid curator pubkeys being used for content filtering, potentially bypassing
curation controls.
siteConfig.ts [122-136]

Referred Code
function convertNpubToPubkey(npub: string): string | null {
  try {
    const decoded = nip19.decode(npub);

    if (decoded.type === 'npub') {
      return decoded.data as string;
    }

    debugWarn(`Invalid npub type: ${decoded.type}, expected 'npub'`);
    return null;
  } catch (error) {
    debugWarn(`Failed to decode npub ${npub}:`, error);
    return null;
  }
}
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status:
Generic variable names: Variables like 'c' (line 140) and 'ws' (line 607) use non-descriptive
single-letter or abbreviated names that don't clearly express their purpose

Referred Code
title: 'Invoice Created',
description: `Payment invoice for ${config.feeAmount} sats created successfully. You can pay with any Lightning wallet.`,

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Silent error handling: Multiple catch blocks (lines 20, 71, 272, 298) silently ignore errors without logging or
providing context about what failed

Referred Code
callback: string;
minSendable: number;

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Missing input validation: Lightning address parsing (line 366) and URL construction (lines 91-92, 123) lack
validation to prevent injection attacks or malformed data processing

Referred Code
const [name, domain] = lightningAddress.split('@');
if (!name || !domain) {
  throw new Error('Invalid lightning address format');
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Potential sensitive data logging: Lines 159, 234, 453 log error objects and event data that may contain sensitive payment
information or user data without sanitization

Referred Code
console.error('Listing payment creation error:', error);
toast({

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-merge-for-open-source
Copy link

qodo-merge-for-open-source bot commented Nov 29, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix incorrect LNURL encoding scheme

Update the encodeLnurl function to use bech32 encoding via nostr-tools instead
of the incorrect hex encoding to comply with the LNURL specification.

src/hooks/useListingSubmissionPayment.ts [402-405]

+import { nip19 } from 'nostr-tools';
+
 function encodeLnurl(url: string): string {
-  const words = Buffer.from(url, 'utf8');
-  return `lnurl1${words.toString('hex')}`;
+  const words = nip19.bech32.toWords(Buffer.from(url, 'utf8'));
+  return nip19.bech32.encode('lnurl', words, 2000);
 }
  • Apply / Chat
Suggestion importance[1-10]: 10

__

Why: The suggestion correctly identifies a critical bug in the LNURL encoding that would cause the entire payment feature, a major part of this PR, to fail.

High
Prevent accidental data loss on edits

Prevent accidental data loss when editing a listing by conditionally updating
optional fields like image, website, and location only if their values have
changed.

src/components/BusinessListingForm.tsx [193-215]

-const shippingUnchanged =
-  existingStall && JSON.stringify(shippingZones) === JSON.stringify(initialShippingZones);
-
 const stallContent: StallContent = {
   ...(existingStall ? baseContent : {}),
   id: stallId,
   name: data.title.trim(),
   description: data.description.trim() || undefined,
   currency: (data.currency || 'USD').trim() || 'USD',
-  image: data.image.trim() || undefined,
-  website: data.website.trim() || undefined,
-  location: data.location.trim() || undefined,
   status: data.status,
 };
 
 if (existingStall) {
+  // Only update fields if they have changed to avoid unintentional overwrites with undefined
+  if (data.image.trim() !== (existingStall.image || '')) {
+    stallContent.image = data.image.trim() || undefined;
+  }
+  if (data.website.trim() !== (existingStall.website || '')) {
+    stallContent.website = data.website.trim() || undefined;
+  }
+  if (data.location.trim() !== ((baseContent.location as string) || '')) {
+    stallContent.location = data.location.trim() || undefined;
+  }
+  
+  const shippingUnchanged = JSON.stringify(shippingZones) === JSON.stringify(initialShippingZones);
   if (!shippingUnchanged) {
     stallContent.shipping = parsedShipping;
   }
-  // if shipping is unchanged, leave baseContent.shipping as-is to preserve countries/other fields
 } else {
+  // For new stalls, set all fields
+  stallContent.image = data.image.trim() || undefined;
+  stallContent.website = data.website.trim() || undefined;
+  stallContent.location = data.location.trim() || undefined;
   stallContent.shipping = parsedShipping;
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a bug that could lead to unintentional data loss when a user edits a listing, which is a significant issue for data integrity.

Medium
Fix inefficient timer re-creation

Remove timeRemaining from the useEffect dependency array to prevent the
countdown timer from being inefficiently re-created every second.

src/components/ListingSubmissionPaymentDialog.tsx [55-71]

 useEffect(() => {
   if (!open || !paymentState.invoice || paymentState.paid || timeRemaining <= 0) {
     return;
   }
 
   const timer = setInterval(() => {
     setTimeRemaining((prev) => {
       if (prev <= 1) {
         clearInterval(timer);
         return 0;
       }
       return prev - 1;
     });
   }, 1000);
 
   return () => clearInterval(timer);
-}, [open, paymentState.invoice, paymentState.paid, timeRemaining]);
+}, [open, paymentState.invoice, paymentState.paid]);
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a React anti-pattern where including timeRemaining in the useEffect dependency array causes an inefficient re-creation of the timer on every tick.

Medium
High-level
Consolidate duplicated listing parsing logic

Extract the duplicated validateStallEvent and parseStallEvent helper functions
from useListings.ts and useListingsByAuthor.ts into a shared utility file to
improve maintainability.

Examples:

src/hooks/useListings.ts [32-130]
function validateStallEvent(event: NostrEvent): boolean {
  if (event.kind !== 30017) return false;

  const dTag = event.tags.find(([name]) => name === 'd')?.[1];
  if (!dTag) return false;

  // Basic content validation: must parse as JSON with at least id + name + currency
  try {
    const content = JSON.parse(event.content || '{}') as {
      id?: string;

 ... (clipped 89 lines)
src/hooks/useListingsByAuthor.ts [17-115]
function validateStallEvent(event: NostrEvent): boolean {
  if (event.kind !== 30017) return false;

  const dTag = event.tags.find(([name]) => name === 'd')?.[1];
  if (!dTag) return false;

  // Basic content validation: must parse as JSON with at least id + name + currency
  try {
    const content = JSON.parse(event.content || '{}') as {
      id?: string;

 ... (clipped 89 lines)

Solution Walkthrough:

Before:

// In 'src/hooks/useListings.ts'
function validateStallEvent(event) {
  // ... validation logic
}
function parseStallEvent(event) {
  // ... parsing logic
}
export function useListings() {
  // ...
  const validEvents = events.filter(validateStallEvent);
  const stalls = validEvents.map(parseStallEvent);
  // ...
}

// In 'src/hooks/useListingsByAuthor.ts'
function validateStallEvent(event) { // Duplicated
  // ... identical validation logic
}
function parseStallEvent(event) { // Duplicated
  // ... identical parsing logic
}
export function useListingsByAuthor(pubkey) {
  // ...
  const validEvents = events.filter(validateStallEvent);
  const stalls = validEvents.map(parseStallEvent);
  // ...
}

After:

// In a new file 'src/lib/listings.ts'
export interface BusinessStallInfo { ... }
export function validateStallEvent(event) {
  // ... validation logic
}
export function parseStallEvent(event) {
  // ... parsing logic
}

// In 'src/hooks/useListings.ts'
import { validateStallEvent, parseStallEvent } from '@/lib/listings';
export function useListings() {
  // ...
  const validEvents = events.filter(validateStallEvent);
  const stalls = validEvents.map(parseStallEvent);
  // ...
}

// In 'src/hooks/useListingsByAuthor.ts'
import { validateStallEvent, parseStallEvent } from '@/lib/listings';
export function useListingsByAuthor(pubkey) {
  // ...
  const validEvents = events.filter(validateStallEvent);
  const stalls = validEvents.map(parseStallEvent);
  // ...
}
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies significant code duplication for parsing and validation logic between the useListings and useListingsByAuthor hooks, and centralizing it would improve maintainability.

Low
General
Remove unnecessary hardcoded payment delay

Remove the unnecessary hardcoded 5-second setTimeout from verifyPaymentMutation
to improve the user experience during payment verification.

src/hooks/useListingSubmissionPayment.ts [181]

-await new Promise((resolve) => setTimeout(resolve, 5000));
+// await new Promise((resolve) => setTimeout(resolve, 5000));
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a hardcoded delay that negatively impacts user experience and proposes removing it, which will make payment verification feel faster.

Low
Memoize filtering to improve performance

Wrap the filteredListings calculation in a useMemo hook to prevent unnecessary
re-filtering on every component render, improving performance.

src/pages/ListingsPage.tsx [77-87]

-const filteredListings =
-  listings?.filter((listing) => {
-    const matchesSearch =
-      !searchTerm ||
-      listing.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
-      listing.description?.toLowerCase().includes(searchTerm.toLowerCase());
+const filteredListings = useMemo(
+  () =>
+    listings?.filter((listing) => {
+      const searchTermLower = searchTerm.toLowerCase();
+      const matchesSearch =
+        !searchTerm ||
+        listing.name.toLowerCase().includes(searchTermLower) ||
+        listing.description?.toLowerCase().includes(searchTermLower);
 
-    const matchesTag = !selectedTag || listing.tags?.includes(selectedTag);
+      const matchesTag = !selectedTag || listing.tags?.includes(selectedTag);
 
-    return matchesSearch && matchesTag;
-  }) || [];
+      return matchesSearch && matchesTag;
+    }) || [],
+  [listings, searchTerm, selectedTag],
+);
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly proposes using useMemo to prevent re-calculating the filteredListings on every render, which is a standard and valuable performance optimization.

Low
Memoize listing lookup to improve performance

Wrap the listing lookup logic in a useMemo hook to prevent it from re-running
unnecessarily on every component render, improving performance.

src/pages/EditListingPage.tsx [34-40]

-const normalizedParam = stallId ? decodeURIComponent(stallId).toLowerCase() : '';
-const listing =
-  listings?.find((item) => {
-    const stallIdLower = item.stallId.toLowerCase();
-    const dTagLower = item.dTag.toLowerCase();
-    return stallIdLower === normalizedParam || dTagLower === normalizedParam;
-  }) || null;
+const listing = useMemo(() => {
+  if (!listings || !stallId) return null;
+  const normalizedParam = decodeURIComponent(stallId).toLowerCase();
+  return (
+    listings.find((item) => {
+      const stallIdLower = item.stallId.toLowerCase();
+      const dTagLower = item.dTag.toLowerCase();
+      return stallIdLower === normalizedParam || dTagLower === normalizedParam;
+    }) || null
+  );
+}, [listings, stallId]);
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that the listing lookup can be memoized with useMemo to avoid re-computation on every render, which is a good performance optimization.

Low
  • Update

dependabot bot and others added 4 commits December 2, 2025 04:26
Bumps [mdast-util-to-hast](https://github.com/syntax-tree/mdast-util-to-hast) from 13.2.0 to 13.2.1.
- [Release notes](https://github.com/syntax-tree/mdast-util-to-hast/releases)
- [Commits](syntax-tree/mdast-util-to-hast@13.2.0...13.2.1)

---
updated-dependencies:
- dependency-name: mdast-util-to-hast
  dependency-version: 13.2.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
…til-to-hast-13.2.1

build(deps): bump mdast-util-to-hast from 13.2.0 to 13.2.1
Bumps [express](https://github.com/expressjs/express) from 4.21.2 to 4.22.1.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/v4.22.1/History.md)
- [Commits](expressjs/express@4.21.2...v4.22.1)

---
updated-dependencies:
- dependency-name: express
  dependency-version: 4.22.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
…-4.22.1

build(deps-dev): bump express from 4.21.2 to 4.22.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants