Skip to Content
Resourcesqb-crypto Reference

qb-crypto

qb-crypto implements the in-game cryptocurrency qbit: a fluctuating price, a per-character portfolio, wallet-to-wallet transfers, and a cryptostick item that can be exchanged for coins.

The cryptostick reward appears in several other resources, including qb-garbagejob, qb-towjob, and qb-houserobbery, which makes this resource the sink at the end of those loops.

The manifest at the commit linked above declares version 1.2.1.

Dependencies and start order

The manifest declares dependency 'qb-minigames' and loads @qb-core/shared/locale.lua, locales and config.lua as shared scripts, @oxmysql/lib/MySQL.lua and server.lua on the server, and client.lua on the client.

The README adds qb-phone and states plainly that buying and selling do not work without it. The buy, sell, and transfer paths all call qb-phone:client:AddTransaction, and the trading UI itself is a phone app.

Start order: oxmysql, qb-core, qb-minigames, qb-phone, then qb-crypto.

Required shared item: cryptostick.

Database

No SQL file ships with the resource, but it reads and writes a crypto table with at least the columns crypto, worth, and history, where history holds JSON. At this commit the code will INSERT a row for the configured coin if the UPDATE affects no rows, so the table must exist but the row does not have to.

It also reads and writes the players table directly when transferring coins to an offline wallet, rewriting the money JSON column. That path bypasses the normal money API, so it produces no transaction log entry in your core.

Configuration

config.lua defines two global tables, Crypto and Ticker. Note it does not use the usual Config name.

Crypto

KeyDefaultMeaning
Lower500Lower bound for the simulated price
Upper5000Upper bound
Worth['qbit']1000Starting value, overwritten from the database at start
History['qbit']{}Price history, persisted as JSON
Labels['qbit']'Qbit'Display name
Exchange.coordsvector3(1276.21, -1709.88, 54.57)Where a cryptostick is exchanged
Coin'qbit'The coin this resource manages
RefreshTimer10Minutes between simulated price moves
ChanceOfCrashOrLuck2Percent chance of a large move
Crash{ 20, 80 }Percent range of a crash
Luck{ 20, 45 }Percent range of a spike
ChanceOfDown30Threshold for a small downward move
ChanceOfUp60Threshold for a small upward move
CasualDown{ 1, 10 }Percent range of a small drop
CasualUp{ 1, 10 }Percent range of a small rise

Ticker, optional

Setting Ticker.Enabled = true replaces the simulated price with a real market price pulled from the CryptoCompare API.

KeyDefaultMeaning
EnabledfalseOff by default
coin'BTC'Symbol to track, use the ticker symbol not the full name
currency'USD'Quote currency
tick_time2Minutes between requests, the documented minimum
Api_key'put_api_key_here'Your CryptoCompare key
Error_handletableMaps API error strings to readable messages

The README notes the free tier allows 100,000 calls per month and 250,000 lifetime, and that a two minute interval lasts roughly a year against the lifetime cap.

Enabling this makes your server issue outbound HTTP requests to a third party on a timer. Confirm that is acceptable for your hosting before switching it on, and keep the API key out of any public repository.

Commands

CommandPermissionEffect
/setcryptoworth <crypto> <value>adminSets the coin’s value, appends a history entry, broadcasts the change, and persists it
/checkcryptoworthuserReports the current qbit value
/cryptouserReports the caller’s qbit balance and its cash value

All three are registered through QBCore.Commands.Add, so /setcryptoworth is gated by the qbcore.admin ace. See Commands and key mappings.

/setcryptoworth calls math.ceil(tonumber(args[2])) without checking that tonumber succeeded, so a non-numeric second argument raises a server-side Lua error rather than the intended notification.

Server surface

HandlerKindArguments
qb-crypto:server:FetchWorthserver eventnone
qb-crypto:server:ExchangeFailserver eventnone, consumes one cryptostick
qb-crypto:server:TransferCryptocallbackdata with Coins and WalletId
qb-crypto:client:UpdateCryptoWorthclient eventcoin, worth, history

Transfers

TransferCrypto is the most interesting handler. Before using the supplied wallet ID in a SQL LIKE pattern it strips the characters %, $, and ; from both the wallet ID and the coin amount, then builds the pattern and passes it as a bound parameter:

local query = '%"walletid":"' .. data.WalletId .. '"%' local result = MySQL.query.await('SELECT * FROM `players` WHERE `metadata` LIKE ?', { query })

The value is bound, not concatenated into the statement, so this is not string-built SQL. The character stripping exists to stop % from being used as a wildcard inside the LIKE pattern itself, which would otherwise let one lookup match many players.

The balance check is Player.PlayerData.money.crypto >= tonumber(data.Coins). A negative Coins value satisfies that comparison. Clamp it before shipping:

-- patched into server.lua local coins = math.floor(tonumber(data.Coins) or 0) if coins <= 0 then return cb('notenough') end

If the recipient is offline, the resource decodes their players.money JSON, adds the coins, and writes it back. If that character is online elsewhere, the write can be overwritten by the normal save path. Prefer transferring to online players, or add your own offline handling.

Troubleshooting

SymptomLikely cause
Cannot buy or sellqb-phone is not started. The README states this explicitly
Price never changesRefreshTimer has not elapsed, or Ticker.Enabled is true and the API request is failing
Ticker errors in the consoleWrong coin symbol, wrong currency, or a missing API key. Ticker.Error_handle maps the common ones to readable text
Value resets after a restartThe crypto table row is missing, so the starting Crypto.Worth from the config is used
Server error on /setcryptoworthThe second argument was not numeric
Exchange does nothingThe player holds no cryptostick, or the item is missing from your shared items
Offline transfer lostThe recipient’s players.money write was overwritten by their own save

Proposed manual smoke test

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

  1. Confirm the crypto table exists and that cryptostick is in your shared items.
  2. Run /checkcryptoworth, wait out RefreshTimer, and confirm the value moved.
  3. Set a value with /setcryptoworth, restart the server, and confirm it persisted.
  4. Transfer coins between two online characters and confirm both balances.
  5. Repeat with the recipient offline and confirm the balance after they reconnect.
  6. Exchange a cryptostick and confirm the coins arrive.

Sources