HatchedDocs
Concepts

Tokens

Two-tier token model — one primary spendable, one progression accumulator. Customers pick the names.

Tokens are the currencies attached to a buddy. Hatched ships a two-tier model: each customer configures exactly two token slots.

  • Primary — the spendable currency. Marketplace purchases, gate unlocks, and buddies.spend draw from this slot.
  • Progression — earn-only. Never spent; it accumulates and gates things like evolution readiness.

The token names are yours. Fantasy buddies use gems + mana. A fitness app might use reps + streaks. Pick whatever fits the product.

Why two tiers

A single wallet collapses two different motivations into one number: "what can I buy" and "how far have I come". Splitting them makes both feelings legible — you can spend your gems without feeling like your overall progress regressed.

Progression is deliberately unspendable. Attempting buddies.spend(buddyId, { token: '<progression_key>' }) throws progression_not_spendable. That keeps long-term progress monotonic while leaving the primary slot free for economy design.

Example

gems  (primary)      — earned from lesson_completed, spent on items + gates.
                       Capped at 500 per week.

mana  (progression)  — earned from quiz_passed, feeds "stage 3 at mana ≥ 1000"
                       evolution readiness. Cannot be spent.

How to set it up

  1. In the dashboard Tokens page, pick a token_key and label for each slot. Use snake_case for keys (gems, xp_coins, mana).
  2. Optionally set an icon and a max_balance (primary only).
  3. Attach earn rules per slot. Same shape as the rule engine elsewhere — event type, amount, optional cap.
  4. Reference primary.token_key in marketplace item prices and gate costs.

If you skip this step, the onboarding plan seeds both slots from a theme catalog — a fantasy buddy defaults to gems + mana, fitness to reps + streaks, and so on. See Token economy for the fallback rules.

At runtime

const summary = await hatched.buddies.tokens(buddyId);
// {
//   primary:     { key: 'gems', label: 'Gems', balance: 120, lifetimeEarned: 340, lifetimeSpent: 220 },
//   progression: { key: 'mana', label: 'Mana', balance: 480, lifetimeEarned: 480, lifetimeSpent: 0   },
// }

Spend against the primary by default — no token arg needed:

await hatched.buddies.spend(buddyId, { amount: 50, reason: 'gate:advanced_mode' });

Pass token only when you have a multi-primary setup (not supported today — reserved for a future expansion).

Gotchas

  • Progression is monotonic. Rule engine writes go through but spend attempts are rejected. If you want a "spend XP" mechanic, that currency belongs in the primary slot, not progression.
  • Token keys are immutable. Once rules and ledger rows reference a key, renaming it in the dashboard doesn't rewrite history. Pick the key you can live with.
  • Tokens emit ledger webhooks. token.earned and token.spent fire per slot; subscribe if you sync balances downstream.
  • The legacy 4-tuple is gone. Pre-0.3 customers used hatch_token/evolution_token/reroll_token/gift_token. Migration 024 introduced the kind column; existing configs default to primary.