REC_Economy
See how much money actually exists on your server — cash, bank, black money, crypto — and what every society account is holding.
Category
PAIDESCROWEDInstallation
Download the dependencies
Download main resource
Copy and paste this on your server.cfg
# ox
ensure ox_lib
ensure oxmysql
# RE:CORD
ensure REC_Library
ensure REC_Utils
ensure REC_ItemManager
ensure REC_Economy
Give yourself the ACE permission
add_ace group.admin REC_Economy.command allow
add_principal identifier.fivem:1 group.admin
Done
enjoy it !
config.ignore.moneyItems = true if you do not use money items and want to run without REC_ItemManager.What it counts
| Source | Where it comes from |
|---|---|
cash / bank_money / crypto | The money column of the players table |
black_money and other money items | REC_ItemManager circulation × multiplier |
| Society balances | Every job from your framework × its bank account |
Only players who logged out within config.targetPlayerLastLoginDays days are counted, so abandoned characters do not inflate the totals.
Configuration
Register any item that should be treated as currency:
---@type table<string, REC_Economy.Server.Config.MoneyItem>
config.moneyItems = {
["black_money"] = {
---[[
--- Which currency this item counts as
---]]
type = moneyTypes.black_money,
---[[
--- Value per item. 1 item counts as this much currency.
---]]
multiplier = 1,
},
}
Available types are cash, bank_money, black_money and crypto.
Admin UI
The panel can be opened in two ways. Both show the same build, but they are authenticated differently.
| In-game (NUI) | Browser | |
|---|---|---|
| How to open | /economy | http://<host>:30120/REC_Economy/ |
| Authentication | ACE group | Authorization: Bearer <adminToken> |
| Default | Enabled | Disabled |
| Extra setup | None | See below |
/economy, you can skip the whole browser section.The screen shows total money supply, a breakdown per currency with its share, and a searchable, sortable list of society balances. Re-aggregate rebuilds everything from the database and the bank.
config.web.aceGroups is a list — a player passes when they hold any one of them.
config.web = {
---@type string[]
aceGroups = {
"admin",
-- "moderator",
},
}
Browser route
allowedAddresses (this machine only by default) and the token are all that stand between the panel and the internet.Reach the panel from an address you have allowed. Any one of these works:
- Reach it over a VPN (Tailscale, WireGuard)
- Reach it through an SSH port forward
- Put a TLS reverse proxy in front of it (see
trustedProxies) - Only reach it from the same LAN
If you are on shared hosting and can do none of these, leave the browser route disabled and use the in-game UI.
Enabling it
config.web.http.enabled = true
set REC_Economy:adminToken "a-random-string-of-32-chars-or-more"
48 characters, generated in your browser. Nothing is sent anywhere.
An empty token makes every /api/* call return 401. A token shorter than 32 characters prints a warning on startup. Give each RE:CORD resource its own token.
allowedAddresses
Requests from an address that is not on this list get 404 — before authentication, so the route stays invisible from the outside. The default allows this machine only.
allowedAddresses = {
"127.0.0.0/8", -- loopback
"::1",
-- "100.64.0.0/10", -- Tailscale (CGNAT range)
-- "192.168.1.0/24", -- LAN
-- "172.18.0.0/16", -- Docker bridge
}
Entries are IPv4 CIDR blocks or literal addresses. IPv6 has no CIDR support, so write the address in lowercase canonical form. An entry that can never match is reported on startup.
0.0.0.0/0 publishes the panel to the entire internet, leaving the token as the only protection.trustedProxies
Behind a reverse proxy every request arrives from the proxy itself, so the address check can no longer see the real client. Register the proxy instead:
trustedProxies = {
"127.0.0.1",
}
Requests from a registered proxy skip the address check, which means access control becomes the proxy's job — configure authentication there (Caddy basic_auth, nginx allow / deny, Cloudflare Access). X-Forwarded-For is only written to the debug log, never used to authorise, because any client can forge it.
cloudflared or ngrok on the same host, every visitor arrives as 127.0.0.1. The default loopback entry then no longer means "this machine only" — add authentication at the tunnel.Common setups
| Setup | Config | Notes |
|---|---|---|
| In-game only | Defaults | Nothing to configure |
| SSH port forward | enabled = true only | Source is loopback, already allowed |
| Tailscale / WireGuard | Add 100.64.0.0/10 | Or the single device address |
| Same LAN | Add your LAN range | e.g. 192.168.1.0/24 |
| Reverse proxy + HTTPS | trustedProxies = { "127.0.0.1" } | Authentication must be set on the proxy |
Endpoints
| Method | Path | Auth |
|---|---|---|
GET | /REC_Economy/api/moneys | Address check + token |
POST | /REC_Economy/api/refresh | Address check + token |
GET | /REC_Economy/locale.json | Address check only |
While REC_Economy is still waiting for REC_ItemManager, the API answers 503 { "error": "not ready" }.
Command
| Command | Permission | Description |
|---|---|---|
/economy | config.web.aceGroups | Opens the admin UI in-game |
Exports
getMoneys
Returns the aggregated amount of each currency, keyed by money type.
Takes no arguments. Returns table<MoneyTypes, MetaData>:
| Field | Type | Description |
|---|---|---|
key | MoneyTypes | cash / bank_money / black_money / crypto |
value | integer | Total amount of that currency |
exports.REC_Economy:getMoneys()
getSocieties
Returns every society account, keyed by society name.
Takes no arguments. Returns table<string, SocietyMetaData>:
| Field | Type | Description |
|---|---|---|
name | string | Society name |
label | string | Display label |
type | string? | Society type, when the bank provides one |
money | integer | Account balance |
exports.REC_Economy:getSocieties()
Stracture
---@type REC_Economy.Shared.Enum
local shEnums = require "@REC_Economy.shared.sh_enum"
local moneyTypes = shEnums.moneyTypes
---@class REC_Economy.Server.Config
local config = {}
---[[
--- Only count players who logged out within this many days
---]]
---@type integer
config.targetPlayerLastLoginDays = 3
---[[
--- Ignore certain checks
---]]
config.ignore = {
---@type boolean
moneyItems = false,
}
---[[
--- Items that should be counted as currency
---]]
---@type table<string, REC_Economy.Server.Config.MoneyItem>
config.moneyItems = {
-- ["black_money"] = {
-- type = moneyTypes.black_money,
-- multiplier = 1,
-- },
}
---[[
--- Admin UI
--- The in-game UI (/economy) is always available and is gated by aceGroups.
--- config.web.http below only affects the browser route.
---]]
config.web = {
---@type string[]
aceGroups = {
"admin",
},
http = {
---@type boolean
enabled = false,
---@type string
token = GetConvar(GetCurrentResourceName() .. ":adminToken", ""),
---@type string
distDir = "web/build",
---@type string[]
allowedAddresses = {
"127.0.0.0/8",
"::1",
},
---@type string[]
trustedProxies = {
},
},
}
---[[
--- Debug Mode
---]]
---@type boolean
config.debugMode = true
---@class REC_Economy.Server.Config.MoneyItem
---@field type REC_Economy.Shared.Enum.MoneyTypes
---@field multiplier integer
return config