Economy Module — EmojiPay
Location: apps/api/src/modules/economy/
Aggregate roots: EmojiWallet, EmojiPayment (append-only), PostBoost
Feature flag: emoji-pay — must be enabled via admin settings.
Core Concepts
EmojiPay is a mana-based micro-economy that lets users express appreciation for content through emotional resonance. Mana is not real money in Alpha — no fiat conversion until Wave 3.
Wallet
Every user receives an EmojiWallet on registration via the identity.user_registered event.
| Property | Value |
|---|---|
| Starter balance | 50 mana |
| Daily regeneration | +5 mana |
| Regeneration cap | 50 mana (regen does NOT apply if balance ≥ 50) |
| Regeneration interval | 24 hours (last_regen_at guard) |
The nightly cron job processes all wallets that are below cap and have last_regen_at older than 24h.
Wallet Fields
{
balance: number; // Current mana
lifetimeEarned: number; // Total mana received ever
lifetimeSpent: number; // Total mana sent ever
lastRegenAt: Date; // Guard for regen cadence
}Emotions
8 canonical emotions on the emotion wheel:
| Slug | Display |
|---|---|
love | Love |
awe | Awe |
joy | Joy |
curiosity | Curiosity |
gratitude | Gratitude |
insight | Insight |
calm | Calm |
courage | Courage |
Payment Tiers
6 tiers with different mana costs and reputation requirements:
| Tier | Name | Mana | Rep Required* |
|---|---|---|---|
| 0 | Spark | 1 | 0 |
| 1 | Wave | 3 | 0 |
| 2 | Storm | 10 | 10 |
| 3 | Surge | 25 | 20 |
| 4 | Tempest | 50 | 35 |
| 5 | Nova | 100 | 50 |
*Rep required = minimum reputation in the post's primary interest. Only enforced when postId is provided (post context).
Payment Flow
sequenceDiagram
participant Sender
participant API
participant Receiver
Sender->>API: POST /economy/pay {postId, emotion, tier}
Note over API: Validate tier rep threshold<br/>Debit sender wallet<br/>Split across co-authors<br/>Credit receiver(s)
Note over API: Append-only EmojiPayment row
API-->>Sender: {payment, senderBalance}
API->>Receiver: notification (via EventBus)Co-Author Mana Split
When a post has accepted PostCoAuthor rows, the payment is split:
- Primary author receives:
(100 - sum(accepted splitShare)) %of the mana - Each co-author receives their
splitShare % - Server enforces: total accepted splitShare ≤ 90 (primary always keeps ≥ 10 %)
Example: post with one co-author at splitShare = 30, payment of 10 mana:
- Co-author receives: 3 mana
- Primary author receives: 7 mana
Append-Only Ledger
Never UPDATE an emoji_payments row.
Reversals are a new row with reversed: true. The balance re-credit is a separate transaction. This ensures the full payment history is always auditable.
Boost System
Authors can spend mana to boost their post's reach in the feed algorithm.
| Tier | Cost | Score Multiplier | Duration |
|---|---|---|---|
| 1 | 10 mana | ×1.5 | 24 hours |
| 2 | 25 mana | ×2.0 | 48 hours |
Post.boostScoreis the materialised multiplier (0 = not boosted)Post.boostExpiresAtis when the current boost expires- A cron job runs to clear expired boosts (set
boostScore = 0,boostExpiresAt = null) post_booststable stores all boost purchases — append-only
Boost API
POST /posts/:id/boost
Body: { tier: 1 | 2 }EmojiBreakdown
All post listing endpoints include an emojiBreakdown object showing aggregated payments per emotion:
{
"emojiBreakdown": {
"love": { "count": 12, "total": 45 },
"curiosity": { "count": 3, "total": 9 },
"awe": { "count": 1, "total": 10 }
}
}API Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /economy/wallet | My current wallet state |
| POST | /economy/pay | Send EmojiPay (body: postId?, replyId?, emotion, tier) |
| GET | /economy/payments?direction=sent|received | Payment history |
| GET | /economy/payments/:postId/breakdown | Emoji breakdown for a post |
Admin
The admin Economy page shows:
- Wallet balances sorted by balance / lifetime earned
- Recent payment ledger (all transactions)
- Boost activity log
- Ability to trigger manual regen for testing