Economy API usage.

Every function the library exposes, with the code that calls it. The server computes and validates every balance; the client only ever displays what the server has decided.

Server API — EconomyProvider

src/server/EconomyProvider.luau · server-only

The single entry point trusted server modules (Market, Trading, Rewards) require. Every mutating call returns a MutationResult — branch on .ok, do not rely on raised errors.

local ServerScriptService = game:GetService("ServerScriptService")
local EconomyProvider = require(ServerScriptService.Server.EconomyProvider)

-- Give a player currency.
local result = EconomyProvider.addMoney(player.UserId, "gold", 50)
if result.ok then
    print("New gold balance:", result.balance)
else
    warn("Add failed:", result.reason)
end

-- Charge a player. Fails without mutating if funds are insufficient.
local charge = EconomyProvider.minusMoney(player.UserId, "coin", 10)
if not charge.ok then
    warn(charge.reason) -- e.g. "insufficient funds"
end

-- Read a single balance, or the whole wallet.
local gold = EconomyProvider.getBalance(player.UserId, "gold") or 0
local wallet = EconomyProvider.getWallet(player.UserId)

-- Pre-check affordability before a purchase (not atomic; re-check with minusMoney).
if EconomyProvider.canAfford(player.UserId, "gold", price) then
    local purchase = EconomyProvider.minusMoney(player.UserId, "gold", price)
    if purchase.ok then grantItem(player) end
end

Currency Registry — EconomyProvider & CurrencyConfig

at boot, before any profile loads · server & client

Four base currencies (coin, copper, silver, gold) always exist. Register a custom currency on both sides so the client can label it — the server through EconomyProvider, the client through CurrencyConfig directly.

-- Server: registered through the EconomyProvider facade.
EconomyProvider.registerCurrency({ id = "gem", displayName = "Gem", max = 1_000_000 })

-- Client: EconomyProvider does not exist here, so register through CurrencyConfig.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CurrencyConfig = require(ReplicatedStorage.EconomyShared.CurrencyConfig)
CurrencyConfig.register({ id = "gem", displayName = "Gem", max = 1_000_000 })

-- Either side: list or look up currencies.
for _, definition in CurrencyConfig.all() do
    print(definition.displayName, definition.default, definition.max)
end

Client API — WalletController (read-only)

src/client/WalletController.luau · client-only, zero trust

The client never mutates a balance and never decides one — it only mirrors what the server already replicated. There is no addMoney here on purpose.

local StarterPlayerScripts = game:GetService("StarterPlayer").StarterPlayerScripts
local WalletController = require(StarterPlayerScripts.Client.WalletController)

-- Last synced balance, or the currency's default if nothing has synced yet.
local gold = WalletController.getBalance("gold")

-- React to every change the server pushes.
local disconnect = WalletController.onChanged(function(currency, balance)
    if currency == "gold" then
        updateGoldLabel(balance)
    end
end)
-- later: disconnect()