Description
A bank/ATM script for ESX servers. The player walks up to any ATM prop, presses E, and a small NUI menu shows their cash and bank balances with deposit and withdraw fields. This is the most-requested money primitive on any roleplay server and the foundation every other economy feature builds on.
Prompt Template
You are writing a FiveM resource for es_extended (stable, exports getSharedObject).
Produce ONE file containing a client section and a server section, banner-separated.
Framework: ESX. Get the object with:
local ESX = exports['es_extended']:getSharedObject()
Client:
- A CreateThread loop that uses GetClosestObjectOfType against ATM prop hashes
[`prop_atm_01`, `prop_atm_02`, `prop_atm_03`, `prop_fleeca_atm`] within 2.0m of
GetEntityCoords(PlayerPedId()). When near, show a help notification and on
IsControlJustReleased(0, 38) TriggerServerEvent("bank:requestBalance").
- Sleep the loop (Wait(1500)) when no ATM is near; Wait(0) only when near.
- A "bank:openMenu" net event that SendNUIMessage opens the UI and SetNuiFocus(true,true).
- RegisterNUICallback handlers "deposit", "withdraw", "close" that tonumber the
amount and TriggerServerEvent. "close" calls SetNuiFocus(false,false).
Server:
- "bank:requestBalance": get xPlayer via ESX.GetPlayerFromId(source), send bank
account money and getMoney() back to the client.
- "bank:deposit": math.floor the amount; REJECT if amount <= 0 or amount >
xPlayer.getMoney(); else removeMoney + addAccountMoney("bank").
- "bank:withdraw": same clamps against the bank balance; removeAccountMoney +
addMoney.
Hard requirements:
- ALL money validation is server-side. Never trust the client amount.
- Use PlayerPedId(), not GetPlayerPed(-1).
- Register net events with RegisterNetEvent so the server actually receives them.
Expected Output
The reference Lua at content/expected-outputs/economy/01-bank-atm-deposit-withdraw.lua implements ATM proximity detection, a NUI open/deposit/withdraw/close flow on the client, and a server side that clamps every amount against the real cash/bank balances. In fxmanifest.lua the proximity + NUI callbacks are client_script 'client.lua' and the money mutations are server_script 'server.lua'.
-- Resource: bank-atm (ESX)
-- ATM/bank deposit + withdraw + balance check.
-- Client opens the ATM near a prop; server is authoritative over money.
-- ===== client.lua =====
local ESX = exports['es_extended']:getSharedObject()
local atmModels = { "prop_atm_01", "prop_atm_02", "prop_atm_03", "prop_fleeca_atm" }
local nearAtm = false
CreateThread(function()
while true do
local sleep = 1500
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
local obj = nil
for _, model in ipairs(atmModels) do
obj = GetClosestObjectOfType(coords.x, coords.y, coords.z, 2.0, model, false, false, false)
if obj ~= 0 then break end
end
if obj ~= 0 then
nearAtm = true
sleep = 0
ESX.ShowHelpNotification("Press ~INPUT_CONTEXT~ to use the ATM")
if IsControlJustReleased(0, 38) then
TriggerServerEvent("bank:requestBalance")
end
else
nearAtm = false
end
Wait(sleep)
end
end)
RegisterNetEvent("bank:openMenu", function(bankBalance, cashBalance)
SendNUIMessage({ action = "open", bank = bankBalance, cash = cashBalance })
SetNuiFocus(true, true)
end)
RegisterNUICallback("deposit", function(data, cb)
local amount = tonumber(data.amount) or 0
TriggerServerEvent("bank:deposit", amount)
cb("ok")
end)
RegisterNUICallback("withdraw", function(data, cb)
local amount = tonumber(data.amount) or 0
TriggerServerEvent("bank:withdraw", amount)
cb("ok")
end)
RegisterNUICallback("close", function(_, cb)
SetNuiFocus(false, false)
cb("ok")
end)
-- ===== server.lua =====
RegisterNetEvent("bank:requestBalance", function()
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer then return end
TriggerClientEvent("bank:openMenu", source, xPlayer.getAccount("bank").money, xPlayer.getMoney())
end)
RegisterNetEvent("bank:deposit", function(amount)
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer then return end
amount = math.floor(tonumber(amount) or 0)
if amount <= 0 or amount > xPlayer.getMoney() then return end
xPlayer.removeMoney(amount)
xPlayer.addAccountMoney("bank", amount)
end)
RegisterNetEvent("bank:withdraw", function(amount)
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer then return end
amount = math.floor(tonumber(amount) or 0)
local bank = xPlayer.getAccount("bank").money
if amount <= 0 or amount > bank then return end
xPlayer.removeAccountMoney("bank", amount)
xPlayer.addMoney(amount)
end)
Known Failure Modes
- Client-side money math, Claude sometimes calls addMoney on the client; it has no authority there. Require every balance change in the server section only.
- No amount clamp, without
amount <= 0andamount > balanceguards a cheater deposits/withdraws arbitrary sums. Demand explicit server-side bounds. - Stuck cursor, omitting
SetNuiFocus(false,false)on close traps the mouse. Require a "close" NUI callback that releases focus. - Heavy proximity loop, scanning every frame tanks FPS. Require the loop to
Wait(1500)unless an ATM is within range.
Integration Notes
Split into client.lua and server.lua at the banner comments. In fxmanifest.lua declare client_script 'client.lua', server_script 'server.lua', and a ui_page/files block if you ship the NUI. Depends on es_extended. Test on a dev server: walk to a Fleeca ATM, press E, deposit cash, then /setjob-independent, confirm bank rises and money falls and persists across a reconnect.
Profit Potential
$150-$1800/mo on Tebex (expected ~$500), priced inside the $50-389 standalone-script band against a corpus of comparable Tebex sellers (median ~$11.85K/mo) for a rising economy niche.
Trend Signal
↗ rising, banking/economy is core RP-server infrastructure, steady demand.
Sales Angle
Position as the drop-in banking primitive every ESX server needs, secure, server-validated deposit/withdraw with a tidy NUI, no heavyweight banking framework required. Recommended Tebex price: $59.
Difficulty & Ship Time
beginner · ships in 2-4h.
