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.spenddraw 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
- In the dashboard Tokens page, pick a
token_keyandlabelfor each slot. Usesnake_casefor keys (gems,xp_coins,mana). - Optionally set an
iconand amax_balance(primary only). - Attach earn rules per slot. Same shape as the rule engine elsewhere — event type, amount, optional cap.
- Reference
primary.token_keyin 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.earnedandtoken.spentfire 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 thekindcolumn; existing configs default toprimary.