# How to add an in-game store and currency (optional token economy)

> Declare store items, coin prices and earn rules in src/game.ts with store.sku and earn.rule. Locally they run with play money; on the hosted Net Forge service a game can optionally upgrade its coins to its own token.

Net Forge, the open-source multiplayer browser game engine built for AI coding agents, is published on npm under `@netforge` with source at https://github.com/netnetdev/net-forge.

Source: https://forge.netnet.capital/docs/guides/in-game-store-and-currency  
Last updated: 2026-10-23. Checked against @netforge/engine 0.2.0.

In Net Forge, a game's store and currency are declarations in `src/game.ts`: `earn.rule()` says what a run pays in coins, and `store.sku()` lists what coins buy (a revive, a cosmetic, an unlock). Every template already has both, and in local play they run with play money, so you can build and test the whole economy without a wallet or a server of ours.

```sh
npm create netforge@latest my-game -- --template survivor --yes
cd my-game
npm run play
```

The on-chain part (coins that are a real game token, with rewards) is an optional upgrade that runs only on the hosted Net Forge service and is decided by its server. Nothing on this page needs it.

## What you get

| Declaration | What it does | In local play |
|---|---|---|
| `earn.rule(id, { when, n, pay, oncePer })` | Pays `pay` coins when the event `when` reaches count `n`, once per run, day or lifetime | Fires and shows, pays play money |
| `earn.stake({ min, max, multiple })` | The buy-in run every game declares | Play money only |
| `store.sku(id, { coins })` | An in-run consumable, such as a revive | Buyable with play money |
| `store.sku(id, { coins, grants: { cosmetic: n } })` | A look owned for good | Owned until `kit-play` stops |
| `store.sku(id, { coins, grants: { keeper: id } })` | An unlock (a playable character) | Owned until `kit-play` stops |
| `economy({ store: "cosmetics" })` | Restricts the store to cosmetics only | Same |

Coins buy cosmetics, unlocks and in-run consumables, and never permanent stat power, unless a game opts in with `economy({ metaUpgrades: true })`, and then only under the engine's caps (+15% to one stat, +30% across all of them).

## 1. Declare what a run pays

Earn rules hang off game events. Templates emit events such as `wave.cleared`, `boss.killed`, `enemy.killed` and `opponent.killed`, and a `system()` can emit your own.

```ts title=src/game.ts
import { earn } from "@netforge/engine";

earn.rule("first_light", { when: "wave.cleared", n: 2, pay: 5, oncePer: "run" });
earn.rule("night_watch", { when: "wave.cleared", n: 7, pay: 20, oncePer: "run" });
earn.rule("bell_breaker", { when: "boss.killed", n: 1, pay: 50, oncePer: "run" });
earn.rule("daily_sweep", { when: "enemy.killed", n: 500, pay: 5, oncePer: "day" });
earn.stake({ min: 20, max: 200, multiple: 2 });
```

Every game needs at least one `earn.rule` and one `earn.stake`, and the engine refuses a file without them with a line that says so.

## 2. Declare the store

```ts title=src/game.ts
import { store } from "@netforge/engine";

store.sku("revive", { coins: 50 }); // in-run consumable
store.sku("skin_aurora", { coins: 300, grants: { cosmetic: 1 } }); // a look, owned for good
```

To sell a playable character, declare it with `keeper("ranger", { ... })` and add `store.sku("unlock_ranger", { coins: 400, grants: { keeper: "ranger" } })`. The engine refuses a SKU that sells an undeclared keeper or the free default one. A coin-priced item id is 2 to 31 characters of `a-z`, `0-9` and `_`. Add a label for each id in `src/names.json` so players see a name, not the id.

## 3. Try it with play money

```sh
npm run play
npm test
```

In the browser, coins earned by your rules show in the HUD under the game's coin name, and the store spends them. `npm test` plays the game with bots and reports which earn rules fired and how often, so you can see how fast coins come in before anyone plays. In local play everything is play money: there is no wallet, no chain and no payout, and progress is gone when `kit-play` stops.

## 4. Optional: upgrade to a game token on the hosted service

A game published through the hosted Net Forge studio can launch its own token. Then 1 coin is 1 game token, shown under the token's own name, symbol and logo, and the store can also price items in dollars. Rewards are decided by Net Forge's referee on its servers, which pays only for runs it can verify by replaying them on the Net Forge engine. A copy of your code cannot pay anyone, and nothing in `src/game.ts` can change that decision.

The template clients call the store through the Net Forge Kit's API names (`kit.coins.balance()`, `kit.coins.spend()`, `kit.store.buy()`), which run on a mock backend in local play. The Kit as a separate package for games on other engines (`@netforge/kit`) is not published to npm yet.

## How it works

The room server is the referee. Earn rules are counted per player per run by the engine (`EarnTracker`) from events the authoritative simulation emits, never from the browser. Bots never earn, and in the arena shooter killing a bot pays no rule. In the shared-world genres, world progress never pays a run that started after the world began. See [The typed game API](https://forge.netnet.capital/docs/concepts/game-api.md).

## Alternatives

- **[PlayFab](https://learn.microsoft.com/en-us/gaming/playfab/)** from Microsoft provides virtual currencies, catalogs and player inventory as a hosted backend for games on any engine.
- **[Nakama](https://heroiclabs.com/docs/nakama/getting-started/)** includes wallets, storage and server-side logic in an open-source game backend you can run yourself.
- **[Colyseus](https://docs.colyseus.io/)** rooms can hold any game state, including a currency, with your own persistence behind them.

Pick Net Forge when the store and the currency should be part of the game's own declarations, checked by the engine and paid out only from server-verified runs.

## FAQ

### Do I need crypto or a wallet to have a store in my game?

No. Coins, earn rules and the store work with play money in local play, and a game in the hosted studio can stay that way. A game token is an optional upgrade.

### Can players buy power with coins?

Not by default. Coins buy cosmetics, unlocks and in-run consumables. Permanent stat upgrades need `economy({ metaUpgrades: true })` and are capped by the engine at +15% per stat and +30% in total.

### Where do earn events come from?

From the authoritative simulation: the template emits events like `wave.cleared` and `boss.killed`, and your own `system()` can emit more with `emit("streak.five", [playerId])`.

### Can I test the economy without publishing?

Yes. `npm test` reports which earn rules fired and how often in a bot playtest, and `npm run play` lets you earn and spend play money in the browser.

### Can a modified client grant itself coins?

No. Earn is counted on the server from the simulation's events. In local play nothing is real money anyway, and on the hosted service the referee replays runs before it pays.
