A peer-to-peer messaging platform that monetizes attention by requiring payment for message delivery. Built on Nostr protocol principles with a value-based communication model.
Modern messaging platforms suffer from spam, unsolicited messages, and attention exploitation. Users receive countless unwanted messages daily, with no mechanism to filter based on sender commitment. Current solutions either require complex filtering rules or rely on centralized moderation systems.
SuneriChat introduces an economic barrier to messaging, ensuring every communication carries inherent value. Senders pay recipients directly to read their messages, creating a natural spam deterrent while allowing recipients to monetize their attention.
The platform operates on a simple principle: your attention has value. When someone sends you a message, they pay a price you set. This creates three fundamental dynamics:
- Senders only reach out when the message matters
- Recipients get compensated for reading messages
- Spam becomes economically unviable
Unlike traditional chat applications where messages are free and abundant, SuneriChat creates scarcity through economics. Each message represents a value transfer, making communication more intentional and meaningful.
graph TB
Client[React Frontend] -->|WebSocket| Server[Node.js Server]
Client -->|HTTP/REST| API[Next.js API Routes]
Server -->|Read/Write| DB[(JSON Database)]
API -->|Read/Write| DB
Server -->|Socket.IO| Client
subgraph Backend
Server
API
DB
end
subgraph Frontend
Client
Storage[LocalStorage]
Client -->|Store Keys| Storage
end
Frontend Layer
- React 18 with TypeScript for type safety
- Vite for fast development and optimized builds
- Zustand for state management
- Socket.IO client for real-time communication
- LocalStorage for keypair persistence
Backend Layer
- Next.js API routes for RESTful endpoints
- Node.js HTTP server with Socket.IO integration
- JSON file-based database for data persistence
- WebSocket server for real-time message delivery
Data Flow
sequenceDiagram
participant Sender
participant SenderWS as Sender WebSocket
participant Server
participant DB as Database
participant RecipientWS as Recipient WebSocket
participant Recipient
Sender->>SenderWS: Send message + payment
SenderWS->>Server: Message data
Server->>DB: Validate balance
DB-->>Server: Balance confirmed
Server->>DB: Deduct from sender
Server->>DB: Add to recipient
Server->>DB: Store message
Server->>RecipientWS: New message event
RecipientWS->>Recipient: Display message
Server->>SenderWS: Balance update
SenderWS->>Sender: Update UI
The application uses a JSON-based database with the following structure:
{
"users": {
"npub...": {
"npub": "string",
"username": "string",
"message_price": "number",
"balance": "number",
"created_at": "timestamp"
}
},
"messages": [
{
"id": "string",
"sender": "npub",
"recipient": "npub",
"content": "string",
"amount": "number",
"timestamp": "number",
"read": "boolean"
}
],
"transactions": [
{
"id": "string",
"from_npub": "string",
"to_npub": "string",
"amount": "number",
"type": "string",
"status": "string",
"created_at": "timestamp"
}
]
}SuneriChat uses the Nostr protocol's key management approach:
- Users generate secp256k1 keypairs locally
- Public keys (npub) serve as user identifiers
- Private keys (nsec) remain client-side for signing
- No centralized authentication required
graph LR
A[Generate Keypair] --> B[Store in LocalStorage]
B --> C[Create Account]
C --> D[Set Username]
D --> E[Ready to Message]
F[Returning User] --> G[Load from LocalStorage]
G --> H[Auto-login]
- Node.js version 18 or higher
- npm or yarn package manager
- Git for version control
Clone the repository:
git clone <repository-url>
cd XYZInstall backend dependencies:
npm installInstall frontend dependencies:
cd frontend
npm install
cd ..Create a .env file in the root directory:
NODE_ENV=development
PORT=8081
Create a .env file in the frontend directory:
VITE_API_URL=http://localhost:8081/api
VITE_WS_URL=http://localhost:8081
Start the backend server:
node server-combined.jsThe server runs on port 8081 and handles both HTTP requests and WebSocket connections.
In a separate terminal, start the frontend development server:
cd frontend
npm run devThe frontend runs on port 8080 by default.
Access the application at http://localhost:8080
- Navigate to the application URL
- Click "Generate New Keys" on the welcome screen
- Save your private key securely - it cannot be recovered
- Set a username for your account
- You receive 1000 SUNC as starting balance
Your message price determines what others pay to message you:
- Open the sidebar menu
- Locate the "Message Price" section
- Set a value between 10 and 1000 SUNC
- Price updates immediately for new conversations
- Navigate to the Discover page
- Search for users by username or public key
- Click "Message" on any user card
- Type your message in the chat interface
- Payment is deducted automatically upon sending
When you send a message:
- Your balance decreases by the recipient's message price
- The recipient's balance increases by the same amount
- The message is delivered instantly if recipient is online
- Messages persist and load on recipient's next login
Your balance represents SUNC tokens:
- Starting balance: 1000 SUNC
- Earned by receiving messages
- Spent by sending messages
- Cannot send if balance is insufficient
The application uses Socket.IO for bidirectional communication:
Client Events:
send-message: Transmit message with paymentuser-updated: Broadcast profile changes
Server Events:
balance-update: Notify balance changesnew-message: Deliver incoming messagesmessage-history: Send conversation historyuser-updated: Propagate user profile updates
Zustand provides centralized state with minimal boilerplate:
interface AppState {
currentUser: User | null
messages: Message[]
allUsers: User[]
isConnected: boolean
}State updates trigger React re-renders automatically. WebSocket events modify state through defined actions, maintaining consistency between server and client.
Key Storage: Keys are stored in browser LocalStorage. While convenient for development, production systems should consider:
- Hardware wallet integration
- Encrypted storage with user-provided passphrase
- Session-based key management
Message Privacy: Messages are currently stored in plaintext. End-to-end encryption can be implemented using recipient's public key for encryption and sender's private key for signing.
Balance Manipulation: The current implementation trusts server-side balance calculations. Production systems should:
- Implement cryptographic proofs of payment
- Use blockchain or distributed ledger for immutability
- Add transaction verification mechanisms
XYZ/
├── app/
│ └── api/ # Next.js API routes
│ ├── messages/ # Message endpoints
│ ├── nostr/ # Key generation
│ ├── user/ # User management
│ └── users/ # User listing
├── frontend/
│ └── src/
│ ├── components/ # React components
│ │ ├── chat/ # Chat interface
│ │ ├── discover/ # User discovery
│ │ └── onboarding/ # Setup flow
│ ├── hooks/ # Custom React hooks
│ ├── lib/ # Utilities and services
│ └── store/ # State management
├── lib/
│ └── database.js # Database operations
├── server-combined.js # WebSocket server
└── sunerichat-db.json # Database file
The JSON database reloads on each operation to ensure consistency:
const db = initDatabase() // Reads from disk
updateBalance(db, npub, amount) // Modifies in memory
saveDatabase(db) // Writes to diskThis approach prioritizes simplicity over performance. For production, consider:
- PostgreSQL or MongoDB for scalability
- Connection pooling for efficiency
- Transaction support for atomicity
Messages follow optimistic UI updates:
- Client adds message to local state immediately
- WebSocket sends message to server
- Server validates and stores message
- Server emits to recipient only (not back to sender)
- Sender sees their message from optimistic update
This prevents duplicate messages while maintaining responsive UI.
Usernames are enforced unique at the database level:
function isUsernameTaken(db, username, excludeNpub) {
return Object.values(db.users).some(u =>
u.username.toLowerCase() === username.toLowerCase() &&
u.npub !== excludeNpub
)
}Case-insensitive comparison prevents "Alice" and "alice" from coexisting.
Each user maintains one persistent WebSocket connection. The server tracks connections in a Map:
const users = new Map() // npub -> socketId
users.set(npub, socket.id)This enables direct message routing without broadcasting to all connections.
On connection, the server sends full message history. For large histories:
- Implement pagination
- Load recent messages first
- Fetch older messages on scroll
- Consider message retention policies
Balance updates occur synchronously during message send. This ensures:
- Atomic balance changes
- No race conditions
- Immediate feedback to users
For high-frequency messaging, consider:
- Batch balance updates
- Delayed consistency models
- Optimistic balance predictions
Lightning Network Integration: Replace internal SUNC tokens with real Bitcoin payments via Lightning Network. This requires:
- Lightning node connection
- Invoice generation per message
- Payment verification before delivery
Nostr Event Publishing: Publish messages as Nostr events to relay servers, enabling:
- Decentralized message storage
- Cross-client compatibility
- Censorship resistance
Group Messaging: Split payment across group members based on configurable rules:
- Equal split among all members
- Weighted by member-set prices
- Flat fee per group message
Message Reactions: Allow recipients to react to messages with optional tip:
- Standard reactions (like, dislike)
- Custom emoji reactions
- Additional payment with reaction
Reputation System: Track user behavior metrics:
- Message quality ratings
- Response time statistics
- Blocked user lists
When contributing code, follow these guidelines:
- Use TypeScript for type safety
- Write descriptive commit messages
- Add comments for complex logic
- Test WebSocket events thoroughly
- Validate balance calculations
- Check username uniqueness
This project is provided as-is for educational and development purposes.