gta6/prompts
economy
Freebeginnereconomy

Bank / ATM — Deposit, Withdraw & Balance

ESX ATM that lets players check balance, deposit cash into bank and withdraw, with all money math validated server-side.

NON-TESTÉ — the reference Lua is syntax-validated, not run in a live FiveM server. Adapt and test on your own dev server before shipping.
Est. Lua
~80 loc
Claude
Claude Opus 4.x / Sonnet 4.x
Validation
syntax-validated
Updated
2026-06-24

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'.

01-bank-atm-deposit-withdraw.lua81 lines
-- 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 <= 0 and amount > balance guards 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). [INFERRED] priced inside the $50-389 standalone-script band against the signal-scraper corpus (tebex_snapshot n=100, median seller $11.85K/mo) for a rising economy niche.

Trend Signal

rising — [INFERRED] 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.