Skip to content

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.

PropertyValue
Starter balance50 mana
Daily regeneration+5 mana
Regeneration cap50 mana (regen does NOT apply if balance ≥ 50)
Regeneration interval24 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

typescript
{
  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:

SlugDisplay
loveLove
aweAwe
joyJoy
curiosityCuriosity
gratitudeGratitude
insightInsight
calmCalm
courageCourage

Payment Tiers

6 tiers with different mana costs and reputation requirements:

TierNameManaRep Required*
0Spark10
1Wave30
2Storm1010
3Surge2520
4Tempest5035
5Nova10050

*Rep required = minimum reputation in the post's primary interest. Only enforced when postId is provided (post context).


Payment Flow

mermaid
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.

Concurrency Invariant

Every mana-moving operation runs inside a transaction that pessimistically locks the wallet row before reading the balance:

sql
SELECT user_id FROM "emoji_wallets" WHERE "user_id" = $1::uuid FOR UPDATE;

This closes a TOCTOU (time-of-check / time-of-use) race where two concurrent debits could both pass the balance check and overdraw the wallet. The same lock pattern guards pay, boost, and the guild-treasury operations below.


Boost System

Authors can spend mana to boost their post's reach in the feed algorithm.

TierCostScore MultiplierDuration
110 mana×1.524 hours
225 mana×2.048 hours
  • Post.boostScore is the materialised multiplier (0 = not boosted)
  • Post.boostExpiresAt is when the current boost expires
  • A cron job runs to clear expired boosts (set boostScore = 0, boostExpiresAt = null)
  • post_boosts table 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:

json
{
  "emojiBreakdown": {
    "love": { "count": 12, "total": 45 },
    "curiosity": { "count": 3, "total": 9 },
    "awe": { "count": 1, "total": 10 }
  }
}

API Endpoints

MethodPathDescription
GET/economy/walletMy current wallet state
POST/economy/paySend EmojiPay (body: postId?, replyId?, emotion, tier)
GET/economy/payments?direction=sent|receivedPayment history
GET/economy/payments/:postId/breakdownEmoji breakdown for a post

Guild Treasury

Guilds hold a shared mana treasury (guild_treasuries) managed under /api/v1/treasury. Members contribute mana; stewards disburse it. See Guilds for the governance model.

MethodPathDescription
GET/treasury/:guildIdTreasury balance + ledger
POST/treasury/:guildId/contributeMember donates mana into the treasury
POST/treasury/:guildId/disburseSteward pays a single recipient
POST/treasury/:guildId/distribute-by-contributionSplit a pool across members weighted by their contribution score

Invariant: disburse and distributeByContribution lock the treasury row (SELECT guild_id … FOR UPDATE) inside the transaction — this closes a TOCTOU overdraw race where two stewards could both disburse the same balance. Contributions debit the contributor's wallet and credit the treasury in one atomic step.


Marketplace

Mana also settles peer offerings in the marketplace (/api/v1/economy/market via MarketplaceService). Listings are part of the unified content stream (offering type) and surface in admin under the Market content kind. See Content Contexts for the listing model and lifecycle.


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

Regulus — invite-only social-knowledge platform