How to add an in-game store and currency (optional token economy)
Net Forge, the open-source multiplayer browser game engine built for AI coding agents, is published on npm under @netforge with source at github.com/netnetdev/net-forge.
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.
npm create netforge@latest my-game -- --template survivor --yes
cd my-game
npm run playThe 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.
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
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 goodTo 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
npm run play
npm testIn 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.
Alternatives
- PlayFab from Microsoft provides virtual currencies, catalogs and player inventory as a hosted backend for games on any engine.
- Nakama includes wallets, storage and server-side logic in an open-source game backend you can run yourself.
- Colyseus 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.