Skip to Content
Resourcesqb-pawnshop Reference

qb-pawnshop

qb-pawnshop buys stolen goods for cash and melts jewellery into raw materials over a timer. It is the usual sink for goldchain, diamond_ring, and rolex, and the usual source of goldbar and diamond on a stock server.

It is the cash-out counterpart to the robbery resources. Compare with qb-shops, which sells legal goods to players rather than buying illegal ones from them.

Before deploying it, read Client-supplied arguments. The selling event trusts price and reward values sent by the client. The server-authority checklist covers what to check instead.

The manifest at the commit linked above declares version 1.5.0.

Dependencies and start order

From the manifest: @qb-core/shared/locale.lua, config.lua, and locales as shared scripts, @oxmysql/lib/MySQL.lua and server.lua on the server, and PolyZone client.lua, BoxZone.lua, ComboZone.lua plus client.lua on the client.

From the implementation: qb-inventory for item mutation, qb-log for the ban log line, and the bans table for the ban itself.

Start order: oxmysql, qb-core, qb-inventory, PolyZone, then qb-pawnshop.

Configuration

KeyDefaultMeaning
Config.PawnLocationtableShop coordinates used for the proximity check
Config.BankMoneyfalsetrue pays into the bank instead of cash
Config.UseTimesfalsetrue restricts the shop to opening hours
Config.TimeOpen7Opening hour, only used when UseTimes is true
Config.TimeClosed17Closing hour
Config.SendMeltingEmailtrueEmail the player when melting finishes
Config.UseTargetfrom the UseTarget convarSet setr UseTarget true in server.cfg
Config.PawnItemseight entriesItem to buy price
Config.MeltingItemstableItem to reward list and meltTime in minutes

Each Config.PawnItems price is written as math.random(50, 100). That expression is evaluated once when the file loads, so every item gets a fixed price for the lifetime of the resource, and every item is priced in the same 50 to 100 band. Restarting the resource reshuffles them. Replace the calls with literals if you want stable, per-item pricing.

meltTime is in minutes and the shipped jewellery entries use 0.15, which is nine seconds. Raise it before going live if melting is meant to be a real time cost.

Server surface

HandlerKindArguments
qb-pawnshop:server:sellPawnItemsnet eventitemName, itemAmount, itemPrice
qb-pawnshop:server:meltItemRemovenet eventitemName, itemAmount, item
qb-pawnshop:server:pickupMeltednet eventitem
qb-pawnshop:server:getInvcallbacknone, returns the player’s items

Client-supplied arguments

This is the part to address before running the resource on a public server. Three of the four handlers take values from the client and use them without comparing them to the server’s own config.

Sell price

sellPawnItems computes tonumber(itemAmount) * itemPrice and pays that amount. itemPrice is never checked against Config.PawnItems. Look the price up on the server instead:

-- patched into server.lua local function GetPawnPrice(itemName) for _, entry in pairs(Config.PawnItems) do if entry.item == itemName then return entry.price end end end local price = GetPawnPrice(itemName) if not price then return end local amount = math.floor(tonumber(itemAmount) or 0) if amount <= 0 then return end -- remove the items first, then pay amount * price

Melting rewards

pickupMelted receives an item table and iterates its nested reward entries, granting meltedAmount * rewardAmount of whatever item name the table names. Nothing in that table is validated against Config.MeltingItems, so the reward item and quantity are both decided by the caller.

The fix is the same shape: have the server record what a given player put into the melter, keyed by source, and read the reward list from Config.MeltingItems on pickup. The one-shot token pattern in qb-vineyard is a direct model for this.

Melt input

meltItemRemove takes an item table and uses item.time to compute the melt duration sent back to the client. A forged value shortens the wait.

The proximity check

All three money and item paths run the same loop:

local dist for _, value in pairs(Config.PawnLocation) do dist = #(playerCoords - value.coords) if #(playerCoords - value.coords) < 2 then dist = #(playerCoords - value.coords) break end end if dist > 5 then exploitBan(src, '...') return end

Position is read from the server’s own view of the ped, which is right. The loop itself is not: when no location is within 2 units it does not break, so dist ends up holding the distance to whichever location pairs happened to visit last. With more than one shop configured, a player standing at shop A can be measured against shop B and banned. With exactly one shop it behaves as intended.

Bans are written to the bans table with bannedby = 'qb-pawnshop' and expire = 2147483647. That table is not created by this resource. Confirm it exists before enabling.

Troubleshooting

SymptomLikely cause
Players banned at a legitimate shopMore than one entry in Config.PawnLocation, see the loop above
Prices change after every restartConfig.PawnItems prices are math.random calls evaluated at load
All items sell for a similar amountSame cause. Replace the calls with literals
Melting finishes almost instantlymeltTime is in minutes and ships as 0.15
Implausible payouts or reward itemsPrice and reward tables are accepted from the client. Apply the fixes above
Ban insert failsThe bans table is missing or has different columns
Shop always closedConfig.UseTimes is true and the in-game hour is outside TimeOpen to TimeClosed

Proposed manual smoke test

Not yet executed. On staging, with a database backup:

  1. Confirm the bans table exists, and that every item in Config.PawnItems and Config.MeltingItems exists in your shared items.
  2. Sell one item and record the price, restart the resource, and confirm the price changed.
  3. Melt one item and confirm the reward matches Config.MeltingItems.
  4. Configure two shop locations and confirm whether legitimate selling triggers a ban.
  5. After applying the server-side price and reward lookups, confirm payouts match the config.
  6. Clear any test bans afterwards.

Sources