RSG Framework
Core

Money System

Complete guide to the RSG Framework money system with multiple bank types and physical money items

Introduction

The RSG Framework features a comprehensive money system designed for RedM that includes multiple bank types, physical money items, and bloodmoney for illegal activities. Unlike traditional frameworks with a single bank account, RSG provides location-specific banks throughout the map.

Version: 2.3.6+

The legacy bank money type is DEPRECATED and set to 0 by default. Use location-specific banks instead!


Core Features

Money Types

The framework supports these money types:

  1. cash - Physical money in hand (can be dropped/stolen)
  2. gold - Gold currency (works exactly like cash — see below)
  3. valbank - Valentine Bank account
  4. rhobank - Rhodes Bank account
  5. blkbank - Blackwater Bank account
  6. armbank - Armadillo Bank account
  7. bloodmoney - Illegal money from criminal activities
  8. bank - DEPRECATED (legacy, set to 0)

New: Gold currency

The latest update adds a gold currency. It behaves just like any other money type (such as cash or bloodmoney) and uses the same APIAddMoney, RemoveMoney, SetMoney, and GetMoney. Like cash, gold can also be represented as a physical inventory item. See Gold currency below.

Physical Money Items

When EnableMoneyItems is true (default):

  • cash becomes a physical inventory item (dollar)
  • bloodmoney becomes a physical inventory item (blood_dollar)
  • gold becomes a physical inventory item when EnableGoldItems is true
  • Players can see, drop, and trade physical money
  • Money can be stolen from players when downed/robbed

Money Logging

All money transactions are automatically logged:

  • Transactions over $100,000 trigger special high-value logging
  • All AddMoney/RemoveMoney/SetMoney calls include reason tracking
  • Integration with rsg-log for audit trails

How It Works

Money Storage

Money is stored in the player's data as a JSON object:

PlayerData.money = {
    cash = 50,          -- Starting cash
    gold = 0,           -- Gold currency
    valbank = 0,        -- Valentine Bank
    rhobank = 0,        -- Rhodes Bank
    blkbank = 0,        -- Blackwater Bank
    armbank = 0,        -- Armadillo Bank
    bloodmoney = 0,     -- Bloodstained money
    bank = 0            -- DEPRECATED
}

Default Starting Money

Configured in rsg-core/config.lua:

RSGConfig.Money.MoneyTypes = {
    cash = 50,           -- $50 starting cash
    gold = 0,            -- Gold currency
    bank = 0,            -- Deprecated
    valbank = 0,
    rhobank = 0,
    blkbank = 0,
    armbank = 0,
    bloodmoney = 0
}

Physical Money Items

Enable/disable in rsg-core/config.lua:

RSGConfig.Money.EnableMoneyItems = true  -- Default: true

When enabled:

  • cashdollar item (weight: 1g each)
  • bloodmoneyblood_dollar item (weight: 1g each)

Item definitions in rsg-core/shared/items.lua:

['dollar'] = {
    name = 'dollar',
    label = 'Dollars',
    weight = 1,
    type = 'item',
    image = 'dollar.png',
    unique = false,
    useable = false,
    shouldClose = false,
    combinable = nil,
    description = 'US Dollar'
},
['blood_dollar'] = {
    name = 'blood_dollar',
    label = 'Bloodstained Dollars',
    weight = 1,
    type = 'item',
    image = 'bloodmoney.png',
    unique = false,
    useable = false,
    shouldClose = false,
    combinable = nil,
    description = 'Blood-stained money from illegal activities'
}

Gold currency

Gold is a first-class money type introduced in the latest update. It behaves exactly like cash — you add, remove, set, and read it with the same player money functions — and, like cash, it can optionally be represented as a physical inventory item.

Configuration

Gold is configured in rsg-core/config.lua:

-- The inventory item used to represent gold when EnableGoldItems is true
RSGConfig.Money.GoldItem = 'gold'

RSGConfig.Gold = {}
RSGConfig.Gold.EnableGoldItems = false  -- Default: false
  • RSGConfig.Money.GoldItem — the item name (from shared/items.lua) used when gold is a physical item.
  • RSGConfig.Gold.EnableGoldItems — when true, gold becomes a physical inventory item (like dollar for cash). When false (default), gold is a virtual balance only.

Add gold = 0 to RSGConfig.Money.MoneyTypes so new characters start with a gold balance, and make sure the gold item exists in shared/items.lua if you enable EnableGoldItems.

Using gold

Because gold shares the standard money API, all the usual functions work — just pass 'gold' as the money type:

-- SERVER SIDE
local Player = RSGCore.Functions.GetPlayer(source)
if not Player then return end

-- Reward the player with gold
Player.Functions.AddMoney('gold', 5, 'quest-reward')

-- Charge gold for a premium purchase
if Player.Functions.GetMoney('gold') >= 10 then
    Player.Functions.RemoveMoney('gold', 10, 'premium-shop')
end

-- Read the current gold balance
local gold = Player.Functions.GetMoney('gold')
print('Player has '.. gold ..' gold')

Everything else in this guide — logging, DontAllowMinus, the OnMoneyChange events — applies to gold exactly as it does to cash and bloodmoney.


Player Money Functions

Add Money

Adds money to a player's account.

Player.Functions.AddMoney(moneytype, amount, reason)

Parameters:

  • moneytype (string) - Type of money ('cash', 'valbank', etc.)
  • amount (number) - Amount to add
  • reason (string) - Reason for logging (optional)

Returns: boolean - True if successful, false if failed

Example:

-- SERVER SIDE
RegisterNetEvent('job:server:payPlayer', function(amount)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Add cash
    if Player.Functions.AddMoney('cash', amount, 'job-payment') then
        TriggerClientEvent('ox_lib:notify', src, {
            title = 'Payment Received',
            description = 'You received $'.. amount,
            type = 'success',
            duration = 5000
        })
    end
end)

-- Add to Valentine Bank
Player.Functions.AddMoney('valbank', 500, 'bank-deposit')

-- Add bloodmoney from robbery
Player.Functions.AddMoney('bloodmoney', 250, 'store-robbery')

Money over $100,000 triggers special high-value logging for admin monitoring


Remove Money

Removes money from a player's account.

Player.Functions.RemoveMoney(moneytype, amount, reason)

Parameters:

  • moneytype (string) - Type of money
  • amount (number) - Amount to remove
  • reason (string) - Reason for logging (optional)

Returns: boolean - True if successful, false if insufficient funds

Example:

-- SERVER SIDE
RegisterNetEvent('shop:server:purchase', function(itemPrice)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Check if player has enough cash
    if Player.Functions.GetMoney('cash') < itemPrice then
        TriggerClientEvent('ox_lib:notify', src, {
            title = 'Not Enough Money',
            description = 'You need $'.. itemPrice,
            type = 'error',
            duration = 5000
        })
        return
    end

    -- Remove money
    if Player.Functions.RemoveMoney('cash', itemPrice, 'shop-purchase') then
        -- Give item
        exports['rsg-inventory']:AddItem(src, 'bread', 1, nil, nil, 'purchased')
        TriggerClientEvent('ox_lib:notify', src, {
            title = 'Purchase Successful',
            type = 'success',
            duration = 5000
        })
    end
end)

Money types in DontAllowMinus config cannot go below 0. Transaction will fail instead.


Set Money

Sets a specific money amount (overwrites current value).

Player.Functions.SetMoney(moneytype, amount, reason)

Parameters:

  • moneytype (string) - Type of money
  • amount (number) - New amount
  • reason (string) - Reason for logging (optional)

Returns: boolean - True if successful, false if failed

Example:

-- SERVER SIDE
-- Admin command to set player money
RegisterCommand('setmoney', function(source, args)
    local src = source
    if not RSGCore.Functions.HasPermission(src, 'admin') then return end

    local targetId = tonumber(args[1])
    local moneyType = args[2] or 'cash'
    local amount = tonumber(args[3]) or 0

    local Target = RSGCore.Functions.GetPlayer(targetId)
    if not Target then return end

    if Target.Functions.SetMoney(moneyType, amount, 'admin-set') then
        TriggerClientEvent('ox_lib:notify', src, {
            description = 'Set '.. moneyType ..' to $'.. amount,
            type = 'success'
        })
    end
end, false)

SetMoney completely overwrites the current amount. Use AddMoney/RemoveMoney for relative changes.


Get Money

Gets the current amount of a specific money type.

Player.Functions.GetMoney(moneytype)

Parameters:

  • moneytype (string) - Type of money to check

Returns: number|boolean - Amount or false if invalid type

Example:

-- SERVER SIDE
local Player = RSGCore.Functions.GetPlayer(source)

-- Check cash
local cashAmount = Player.Functions.GetMoney('cash')
print('Player has $'.. cashAmount ..' cash')

-- Check Valentine Bank balance
local valbankAmount = Player.Functions.GetMoney('valbank')
print('Valentine Bank: $'.. valbankAmount)

-- Check total money across all accounts
local totalMoney = 0
for moneyType, _ in pairs(Player.PlayerData.money) do
    if moneyType ~= 'bank' then  -- Skip deprecated bank
        totalMoney = totalMoney + Player.Functions.GetMoney(moneyType)
    end
end
print('Total wealth: $'.. totalMoney)

Money Configuration

Config Settings

Located in rsg-core/config.lua:

RSGConfig.Money = {
    -- Starting amounts for new players
    MoneyTypes = {
        cash = 50,
        gold = 0,            -- Gold currency
        bank = 0,            -- DEPRECATED
        valbank = 0,
        rhobank = 0,
        blkbank = 0,
        armbank = 0,
        bloodmoney = 0
    },

    -- Enable physical money items
    EnableMoneyItems = true,

    -- Inventory item used to represent gold when EnableGoldItems is true
    GoldItem = 'gold',

    -- Money types that cannot go below 0
    DontAllowMinus = { 'cash', 'gold', 'valbank', 'rhobank', 'blkbank', 'armbank', 'bloodmoney' },

    -- Minimum balance allowed (for types not in DontAllowMinus)
    MinusLimit = -5000,

    -- Paycheck settings
    PayCheckTimeOut = 30,        -- Minutes between paychecks
    PayCheckSociety = false      -- Deduct paychecks from society accounts
}

-- Gold currency settings
RSGConfig.Gold = {}
RSGConfig.Gold.EnableGoldItems = false  -- true = gold becomes a physical inventory item

Money Item Configuration

Physical money behavior:

  • When player receives cash → automatically converted to dollar item
  • When player spends cash → dollar items are removed
  • Can drop/trade dollar items like any other inventory item
  • Death/robbery can result in cash loss

Bank System

Location-Specific Banks

Each major town has its own bank:

Bank TypeLocationConfig Key
Valentine BankValentinevalbank
Rhodes BankRhodesrhobank
Blackwater BankBlackwaterblkbank
Armadillo BankArmadilloarmbank

Why Multiple Banks?

  1. Realism: Players must travel to specific towns to access their accounts
  2. Gameplay: Creates opportunities for bank robberies and escorts
  3. Economy: Different regions can have different banking features
  4. Risk: Traveling with cash adds danger and roleplay opportunities

Bank Account Management

-- SERVER SIDE
-- Deposit cash into Valentine Bank
RegisterNetEvent('bank:server:deposit', function(bankType, amount)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Validate bank type
    local validBanks = { 'valbank', 'rhobank', 'blkbank', 'armbank' }
    if not lib.table.contains(validBanks, bankType) then
        return
    end

    -- Check player has enough cash
    if Player.Functions.GetMoney('cash') < amount then
        TriggerClientEvent('ox_lib:notify', src, {
            description = 'Not enough cash',
            type = 'error'
        })
        return
    end

    -- Process deposit
    if Player.Functions.RemoveMoney('cash', amount, 'bank-deposit') then
        if Player.Functions.AddMoney(bankType, amount, 'bank-deposit') then
            TriggerClientEvent('ox_lib:notify', src, {
                description = 'Deposited $'.. amount,
                type = 'success'
            })
        end
    end
end)

-- Withdraw from Rhodes Bank
RegisterNetEvent('bank:server:withdraw', function(bankType, amount)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Check bank balance
    if Player.Functions.GetMoney(bankType) < amount then
        TriggerClientEvent('ox_lib:notify', src, {
            description = 'Insufficient funds',
            type = 'error'
        })
        return
    end

    -- Process withdrawal
    if Player.Functions.RemoveMoney(bankType, amount, 'bank-withdrawal') then
        if Player.Functions.AddMoney('cash', amount, 'bank-withdrawal') then
            TriggerClientEvent('ox_lib:notify', src, {
                description = 'Withdrew $'.. amount,
                type = 'success'
            })
        end
    end
end)

Bloodmoney System

What is Bloodmoney?

Bloodmoney is illegal currency obtained through:

  • Store/bank robberies
  • Drug sales
  • Gang activities
  • Stolen goods fencing

Bloodmoney Features

  • Separate Currency: Cannot be deposited in legitimate banks
  • Physical Item: Appears as blood_dollar when money items enabled
  • Illegal Use: Used for black market purchases
  • Risk Factor: Can be confiscated by law enforcement

Using Bloodmoney

-- SERVER SIDE
-- Award bloodmoney from robbery
RegisterNetEvent('robbery:server:complete', function()
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    local bloodReward = math.random(100, 500)

    if Player.Functions.AddMoney('bloodmoney', bloodReward, 'store-robbery') then
        TriggerClientEvent('ox_lib:notify', src, {
            description = 'You got $'.. bloodReward ..' in bloodmoney',
            type = 'error',
            duration = 5000
        })
    end
end)

-- Black market shop accepting bloodmoney
RegisterNetEvent('blackmarket:server:purchase', function(itemName, price)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Check bloodmoney balance
    if Player.Functions.GetMoney('bloodmoney') < price then
        TriggerClientEvent('ox_lib:notify', src, {
            description = 'Not enough bloodmoney',
            type = 'error'
        })
        return
    end

    -- Process purchase
    if Player.Functions.RemoveMoney('bloodmoney', price, 'blackmarket-purchase') then
        exports['rsg-inventory']:AddItem(src, itemName, 1, nil, nil, 'blackmarket')
    end
end)

-- Law enforcement confiscation
RegisterNetEvent('police:server:confiscateBloodmoney', function(targetId)
    local src = source
    local Officer = RSGCore.Functions.GetPlayer(src)
    local Target = RSGCore.Functions.GetPlayer(targetId)

    if not Officer or not Target then return end

    -- Check officer is law enforcement
    if Officer.PlayerData.job.type ~= 'leo' then return end

    local bloodAmount = Target.Functions.GetMoney('bloodmoney')
    if bloodAmount > 0 then
        Target.Functions.SetMoney('bloodmoney', 0, 'police-confiscation')
        TriggerClientEvent('ox_lib:notify', targetId, {
            description = 'Police confiscated $'.. bloodAmount ..' bloodmoney',
            type = 'error'
        })
        TriggerClientEvent('ox_lib:notify', src, {
            description = 'Confiscated $'.. bloodAmount ..' bloodmoney',
            type = 'success'
        })
    end
end)

Money Events

Server Events

Listen for money changes in your resources:

-- Triggered when money is added/removed/set
AddEventHandler('RSGCore:Server:OnMoneyChange', function(source, moneyType, amount, changeType, reason)
    print('Player '.. source ..' '.. changeType ..' $'.. amount ..' '.. moneyType)
    print('Reason: '.. reason)

    -- changeType can be: 'add', 'remove', 'set'
end)

Client Events

-- CLIENT SIDE
RegisterNetEvent('RSGCore:Client:OnMoneyChange', function(moneyType, amount, changeType, reason)
    print('Your '.. moneyType ..' was '.. changeType ..'ed by $'.. amount)

    -- Optional: Update custom UI
    if moneyType == 'cash' then
        -- Update cash display
    end
end)

Complete Examples

Example 1: Paycheck System

-- SERVER SIDE
-- Automatic paycheck (called by rsg-core every X minutes)
function PaycheckInterval()
    if next(RSGCore.Players) then
        for _, Player in pairs(RSGCore.Players) do
            if Player then
                local job = Player.PlayerData.job
                local payment = RSGShared.Jobs[job.name]['grades'][tostring(job.grade.level)].payment

                -- Only pay if on duty (or job allows off-duty pay)
                if payment > 0 and (RSGShared.Jobs[job.name].offDutyPay or job.onduty) then
                    Player.Functions.AddMoney('cash', payment, 'paycheck')
                    TriggerClientEvent('ox_lib:notify', Player.PlayerData.source, {
                        title = 'Paycheck',
                        description = 'Received $'.. payment,
                        type = 'info',
                        duration = 5000
                    })
                end
            end
        end
    end
    SetTimeout(RSGCore.Config.Money.PayCheckTimeOut * 60000, PaycheckInterval)
end

Example 2: ATM/Bank UI

-- SERVER SIDE
RegisterNetEvent('bank:server:getBalance', function()
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    local bankData = {
        cash = Player.Functions.GetMoney('cash'),
        valbank = Player.Functions.GetMoney('valbank'),
        rhobank = Player.Functions.GetMoney('rhobank'),
        blkbank = Player.Functions.GetMoney('blkbank'),
        armbank = Player.Functions.GetMoney('armbank'),
        bloodmoney = Player.Functions.GetMoney('bloodmoney')
    }

    TriggerClientEvent('bank:client:displayBalance', src, bankData)
end)

-- Transfer between accounts
RegisterNetEvent('bank:server:transfer', function(fromBank, toBank, amount)
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Validate
    if Player.Functions.GetMoney(fromBank) < amount then
        TriggerClientEvent('ox_lib:notify', src, {
            description = 'Insufficient funds',
            type = 'error'
        })
        return
    end

    -- Process transfer
    if Player.Functions.RemoveMoney(fromBank, amount, 'bank-transfer') then
        if Player.Functions.AddMoney(toBank, amount, 'bank-transfer') then
            TriggerClientEvent('ox_lib:notify', src, {
                description = 'Transfer successful',
                type = 'success'
            })
        end
    end
end)

Example 3: Money Drop on Death

-- SERVER SIDE
RegisterNetEvent('player:server:onDeath', function()
    local src = source
    local Player = RSGCore.Functions.GetPlayer(src)
    if not Player then return end

    -- Drop random percentage of cash (10-30%)
    local cashAmount = Player.Functions.GetMoney('cash')
    if cashAmount > 0 then
        local dropPercent = math.random(10, 30) / 100
        local dropAmount = math.floor(cashAmount * dropPercent)

        if Player.Functions.RemoveMoney('cash', dropAmount, 'death-penalty') then
            -- Create ground drop
            local playerPed = GetPlayerPed(src)
            local coords = GetEntityCoords(playerPed)

            exports['rsg-inventory']:ForceDropItem(src, 'dollar', dropAmount, {}, 'death')

            TriggerClientEvent('ox_lib:notify', src, {
                description = 'You dropped $'.. dropAmount ..' when you died',
                type = 'error',
                duration = 5000
            })
        end
    end
end)

Best Practices

Tip

Always validate before transactions: Check if player has sufficient funds before attempting to remove money

Use the deprecated 'bank' type carefully: It's set to 0 by default. Migrate to location-specific banks

Include descriptive reasons: Always provide a reason parameter for audit logging and debugging

Common Patterns

  1. Check before removing:
if Player.Functions.GetMoney('cash') >= price then
    Player.Functions.RemoveMoney('cash', price, 'purchase')
end
  1. Handle failures:
if not Player.Functions.AddMoney('cash', reward, 'quest-reward') then
    -- Player inventory might be full if money items enabled
    -- Money will be dropped on ground automatically
end
  1. Use appropriate money type:
-- Legal job payment
Player.Functions.AddMoney('cash', payment, 'job-salary')

-- Illegal activity
Player.Functions.AddMoney('bloodmoney', reward, 'drug-sale')

-- Bank deposit
Player.Functions.AddMoney('valbank', amount, 'deposit')

Troubleshooting


Migration from Old Bank System

If migrating from an older framework with single 'bank' type:

-- SERVER SIDE - Migration script
RegisterCommand('migratebanks', function(source)
    if not RSGCore.Functions.HasPermission(source, 'admin') then return end

    MySQL.query('SELECT citizenid, money FROM players', {}, function(result)
        if result then
            for i = 1, #result do
                local money = json.decode(result[i].money)

                if money.bank and money.bank > 0 then
                    -- Transfer old 'bank' to 'valbank' (or chosen default)
                    money.valbank = money.valbank + money.bank
                    money.bank = 0

                    MySQL.update('UPDATE players SET money = ? WHERE citizenid = ?', {
                        json.encode(money),
                        result[i].citizenid
                    })
                end
            end
            print('Bank migration complete!')
        end
    end)
end, false)

Next Steps


Need help? Join the RSG Framework Discord!

On this page