Chat / Messenger API¶
The chat dApp is built on a thin layer over real on-chain transactions: every message is an ML-DSA-87-signed transfer of 0 WAVE with the message body in tx.data.memo. The "chat room" is a view of the chain.
Onboarding¶
POST /api/signup¶
Request access. Two paths:
Path A — admin gate (no invite code): the signup lands in a pending queue. An admin manually approves via the admin API.
Path B — invite code (instant): if the request includes a valid invite_code, the signup is auto-approved on the spot. A wallet is generated, the faucet drips 100 WAVE, a session cookie is set, and the response carries the login token in case the user wants to log in from another device.
Request¶
| Field | Required | Notes |
|---|---|---|
name | yes | 2–32 chars, [A-Za-z0-9_-] only |
invite_code | no | If provided and valid, path B; otherwise path A |
Response (path A — pending)¶
Response (path B — auto-approved)¶
{
"status": "approved",
"auto": true,
"name": "alice",
"address": "34378b1ba5be9d0999acd60be3a8a1f1",
"login_token": "AWeRfZo5skP6HHGIzV6whU1q0HJFw6rN",
"invite_code": "WAVE-ABC123"
}
Set-Cookie: session=... is sent with this response — the browser is already logged in and can redirect to /chat.
Errors¶
| Status | Body |
|---|---|
| 400 | {"error":"name must be 2-32 chars, alphanumeric / - / _"} |
| 403 | {"error":"this name is blocked"} |
| 403 | {"error":"invalid or revoked invite code"} |
| 403 | {"error":"invite code has reached its usage limit"} |
| 409 | {"error":"name already taken"} |
POST /api/login¶
Log in as an approved user using their login token.
Request¶
Response¶
Set-Cookie: session=... is set.
Errors¶
| Status | Body |
|---|---|
| 400 | {"error":"name and token required"} |
| 401 | {"error":"unknown name or wrong token"} |
| 403 | {"error":"this name has been blocked"} |
GET /api/me¶
Returns the current logged-in user's name + address + balance.
Response¶
Used by the UI to populate the header. Returns 401 if no session.
Sending + reading messages¶
POST /api/send¶
Submit a chat message. Builds a tx, signs it with the user's wallet, submits it to the mempool.
Request¶
| Field | Required | Notes |
|---|---|---|
text | yes | 1–512 chars. Stored in tx.data.memo. |
Response¶
Rate limit¶
1 message per 5 seconds per address. Exceeding this returns:
The window is per-address; sessions don't matter (so an attacker can't just open more sessions to bypass).
GET /api/messages¶
Returns the most recent messages (confirmed + pending), newest first.
Query params¶
| Param | Default | Max | Notes |
|---|---|---|---|
limit | 50 | 200 | Number of messages |
Response¶
{
"count": 42,
"messages": [
{
"sender": "alice",
"address": "34378b1b...",
"text": "hello world",
"timestamp": 1780002999.123,
"status": "confirmed",
"block": 42,
"tx_id": "abc123..."
},
{
"sender": "39848b500426d8f1...",
"address": "39848b50...",
"text": "pending one",
"timestamp": 1780003001.5,
"status": "pending",
"block": null,
"tx_id": "def456..."
}
]
}
| Field | Notes |
|---|---|
sender | Display name if known, else a shortened address |
address | Full sender address |
status | "confirmed" (in a block) or "pending" (mempool only) |
block | Block height or null if pending |
tx_id | First 16 chars (full id available via /api/explorer/tx/{id}) |
Messages are identified by walking the chain backwards looking for tx.data.memo entries, then prepending the mempool. They're not stored in a separate table.
Real-time updates¶
See Server-Sent Events for the /api/stream endpoint that pushes new messages to connected clients as they appear.