Skip to main content

Nexal Withdraw

Economy plugin allowing players to withdraw server currencies into physical redeemable items. Supports multiple currency providers including Vault, TokenManager, PlayerPoints, VotingPlugin, and CoinsEngine. Features anti-dupe protection via PDC validation, transaction logging, an in-game admin menu, and backward compatibility with AetherWithdraw and HyperWithdraw notes.

Features​

  1. Money checks: Withdraw Vault money into redeemable checks
  2. Bottled experience: Withdraw experience into bottles
  3. Tokens, points, vote points and coins: Withdraw TokenManager tokens, PlayerPoints, VotingPlugin vote points and CoinsEngine coins into vouchers and notes
  4. Tax per currency: Optional tax charged on top of the withdrawn amount; the note keeps the amount the player asked for
  5. Anti-dupe protection: Every note carries a unique ID in its PDC, checked against the registry when redeemed. A claim is saved at once, so a copied note is refused
  6. Network ready: With SQL storage, a note withdrawn on one server redeems on every server sharing the database
  7. Per-currency toggle: Disable a currency to stop withdrawals; its notes are kept and redeem again once it is enabled
  8. Admin menu: /withdraw admin opens an overview, the currency types with an editor for each (enabled, tax, tax rate, note item and name, price format), statistics, export and import
  9. Transaction logs: Per-player and global withdraw and claim history with an in-game log explorer
  10. Statistics: Withdrawal and claim counts, totals and averages per currency
  11. Customizable notes: Item type, name and lore per currency with MiniMessage gradients and hex colors
  12. Customizable messages: Every message and sound lives in messages.yml
  13. Dual storage: Keep data in a single data.json or in a MySQL/MariaDB database, with export and import to move between them
  14. Backward compatible with AetherWithdraw and HyperWithdraw: Existing notes still work
  15. Number formatting: A price format per currency ($1,000, 1,000 exp) and one number format for notes, messages, the admin menu and the logs
  16. Developer API: Other plugins register their own currency types, which show up in the admin menu like the built-in ones

Technologies​

  • Paper API 1.20.1+: Minecraft server API (Spigot compatible)
  • Utilizer: Configuration, messages, items, GUIs
  • ACF: Command framework
  • nexal-core: JSON and SQL persistence (MariaDB/MySQL), help menu, licensing
  • Vault, TokenManager, PlayerPoints, VotingPlugin, CoinsEngine: Currency providers (all optional)

Commands​

The command also answers to /wd.

Player Commands​

CommandDescriptionPermission
/withdraw or /withdraw help [page]Show the help menuNone
/withdraw money \<amount>Withdraw money into a checknexalwithdraw.use
/withdraw exp \<amount>Withdraw experience into a bottlenexalwithdraw.use
/withdraw tokens \<amount>Withdraw tokens into a vouchernexalwithdraw.use
/withdraw points \<amount>Withdraw points into a vouchernexalwithdraw.use
/withdraw votes \<amount>Withdraw vote points into a notenexalwithdraw.use
/withdraw coins \<amount>Withdraw coins into a notenexalwithdraw.use

Right-click a note to redeem it.

Admin Commands​

Admin commands are hidden from players without the nexalwithdraw.admin permission.

CommandDescriptionPermission
/withdraw adminOpen the admin menu (console: admin help)nexalwithdraw.admin
/withdraw admin help [page]Show the admin commandsnexalwithdraw.admin
/withdraw admin statsView withdrawal and claim statisticsnexalwithdraw.admin
/withdraw admin logs [player] [page]View transaction logsnexalwithdraw.admin
/withdraw admin exportExport user records and withdrawn notes to export/nexalwithdraw.admin
/withdraw admin importImport user records and withdrawn notes from export/nexalwithdraw.admin
/withdraw admin reloadConfirm the configuration is reloaded (both files also reload automatically when saved)nexalwithdraw.admin

Admin Menu​

ScreenContents
Admin menu (/withdraw admin)Overview (storage, available currency types, notes, players), Configuration, Statistics, Export Data, Import Data (shift+left click), Close
Configuration: Currency TypesEvery registered currency type, built-in or added through the API, with its status (✔ Enabled / ✘ Disabled), its provider (✔ Available / ✘ plugin missing), its tax and an example amount. Left click opens its editor. Paginated beyond 21 types. Console Logging, the general setting, sits in the bottom row. Back, Close
Type editorA preview of the note. Enabled, Tax (on/off), Tax Rate (left/right ±1, shift ±10, 0-100%), Note Item (the item in your main hand), Note Name (typed in chat), Price Format (typed in chat) and the type's own text settings (the CoinsEngine currency for coins). Back to the list, Close
StatisticsPer currency type: notes and amounts withdrawn, claimed and still in circulation, with averages. Back, Close

Every change is saved to config.yml under modules.\<id> at once and applies immediately. Values typed in chat take MiniMessage colors; type cancel to keep the old value. The note's lore stays in config.yml. Every text of the menu is in the gui section of messages.yml.

Permissions​

PermissionDescriptionDefault
nexalwithdraw.useUse the withdraw commandstrue
nexalwithdraw.adminAdmin menu and all admin commands (stats, logs, export, import, reload)op

Configuration​

Edit plugins/NexalWithdraw/config.yml on your server. Changes apply without a restart, except the storage type.

Storage​

storage:
type: json # "json" (flat file) or "sql" (MySQL/MariaDB)
sql:
host: localhost
port: 3306
database: minecraft
username: root
password: ""

If the storage cannot be opened (for example wrong SQL credentials), the plugin logs the error and disables itself.

Tax System​

Each module supports an optional tax that is charged on top of the withdrawal amount. This means players pay extra for the tax while receiving a note worth exactly the amount they requested.

Formula:

total_cost = requested_amount + (requested_amount × tax_percentage / 100)

Example: With a 10% tax, withdrawing $100 will:

  • Deduct $110 from the player's balance ($100 + $10 tax)
  • Give them a check worth exactly $100

Amounts so large that the total cost cannot be computed are refused.

Module Configuration​

Each module follows this structure:

modules:
\<module_id>:
enabled: true # false stops withdrawals; existing notes are kept but cannot be redeemed until re-enabled
price-format: "$%amount%" # How an amount reads: %amount% is the number, %currency% the currency name
tax:
enabled: false # Whether tax is enabled
percentage: 10 # Tax percentage (0-100)
item:
type: PAPER # Bukkit Material type
name: "<gradient:#9B59B6:#A569BD>\<bold>Bank Check\</bold>\</gradient> \<dark_gray>»\</dark_gray> <gradient:#FFC107:#FFCD38>%price%\</gradient>"
lore:
- "\<gray>A signed check, payable to\</gray>"
- "\<gray>whoever holds it.\</gray>"
- ""
- "<gradient:#AAAAAA:#BBBBBB>● Value »\</gradient> <gradient:#FFC107:#FFCD38>%price%\</gradient>"
- "<gradient:#AAAAAA:#BBBBBB>● Signed by »\</gradient> \<white>%player%\</white>"
- ""
- "<gradient:#2ECC71:#52D68A>▶ Right Click\</gradient> \<gray>to redeem\</gray>"

The note's name and lore take %price% (the amount in the price format), %amount% (the number alone), %currency% and %player% (who withdrew it).

Module IDs: money, exp, tokens, points, votes, coins, plus the IDs of currency types other plugins add (see Developer API). A key left out uses the type's default: the built-in defaults match the bundled config.yml.

The coins module also takes the CoinsEngine currency to use:

modules:
coins:
currency-id: "coins" # The currency ID from CoinsEngine

General Settings​

enable-logging: true  # Log withdrawals and consumptions to console

number-format: "#,##0.00" # The number in every amount, as a Java DecimalFormat pattern
hide-zero-decimals: true # 1,000 rather than 1,000.00 for whole amounts (but 1,000.50)

With the defaults and money's price-format: "$%amount%", 100 shows as $100, 1000 as $1,000 and 1000.5 as $1,000.50. The format applies to notes, messages, the admin menu, /withdraw admin stats and the logs.

messages.yml​

All messages are fully customizable with MiniMessage colors and gradients, and sounds. The color reference at the top of the file lists every gradient used, so colors can be changed with find-and-replace. Messages added in an update that your messages.yml does not have yet use the bundled text; the file itself is not changed.

The layout of /withdraw admin logs (entry line, labels, hover lines, "Withdrawn by" and "Claimed by" lines, the clickable next-page footer) is under logs.

Each currency has withdraw-success (no tax charged) and withdraw-success-taxed (tax charged), plus consume for redeeming. Their placeholders:

PlaceholderDescription
%price%The note value in the currency's price format ($1,000)
%total_price%The total cost paid (amount + tax), in the price format
%tax_price%The tax charged, in the price format
%amount%, %total%, %tax%The same three as bare numbers (1,000)
%currency%The currency name (the CoinsEngine currency for coins)

A messages.yml from an older version that writes $%amount% keeps working: %amount% follows number-format too.

Moving between JSON and SQL​

  1. Run /withdraw admin export
  2. Change storage.type in config.yml
  3. Restart the server
  4. Run /withdraw admin import

Developer API​

Other plugins add a currency by extending com.nexalstudios.withdraw.modules.StorageType and registering it on enable:

public class GemsType extends StorageType &#123;
public GemsType() &#123; super("gems"); &#125;

@Override public String getDisplayName() &#123; return "Gems"; &#125;
@Override public String getRequiredPlugin() &#123; return "MyGemsPlugin"; &#125; // shown when missing
@Override public boolean isProviderAvailable() &#123; return gemsApi() != null; &#125;
@Override protected String getDefaultPriceFormat() &#123; return "%amount% gems"; &#125;
@Override protected Material getDefaultItemMaterial() &#123; return Material.AMETHYST_SHARD; &#125;

@Override public boolean onWithdraw(Player player, long amount) &#123; /* take amount + calculateTax(amount), return true if taken */ &#125;
@Override public boolean onConsume(Player player, long amount) &#123; /* pay amount, return true if paid */ &#125;
&#125;

NexalWithdrawPlugin.getStorageManager().register(new GemsType());
  • Where its settings live: in config.yml under modules.\<id>, like the built-in types. The plugin does not write a section when the type registers. Every key it lacks (enabled, price-format, tax, item.type, item.name, item.lore) comes from the type's defaults in code (getDefaultPriceFormat(), getDefaultItemMaterial(), getDefaultItemName(), getDefaultItemLore(); enabled, tax off at 10%). The first change in the admin menu writes that one key to modules.\<id>.
  • Admin menu: the type appears in Configuration and Statistics with its name, provider state and tax, and its editor works like the built-in ones. Extra text settings the editor should offer come from getTextSettings().
  • Notes: getStorageItemStack(amount, uniqueId, playerName) builds a redeemable note; register it first with NexalWithdrawPlugin.getWithdrawnItemRegistry().registerItem(...) so it passes the anti-dupe check. /withdraw has subcommands for the built-in types only, so a registering plugin offers its own command.
  • Events: WithdrawEvent and ConsumeEvent fire for every type and can be cancelled.

Database Schema​

When using SQL storage, the plugin creates the following tables. With JSON storage the same data lives in plugins/NexalWithdraw/data.json, one top-level key per table.

withdraw_users​

ColumnTypeKeyDescription
idVARCHAR(36)PKPlayer UUID
withdraw_recordsMEDIUMTEXTJSON array of withdrawal records
consume_recordsMEDIUMTEXTJSON array of consume/claim records

withdraw_notes​

Registry of every withdrawn note, used for anti-dupe protection and statistics.

ColumnTypeKeyDescription
idVARCHAR(36)PKUnique ID stored in the note
storage_typeVARCHAR(32)Currency (money, exp, ...)
amountBIGINTNote value
date_createdBIGINTWithdraw timestamp (ms)
withdrawer_uuidVARCHAR(36)UUID of the player who withdrew it
withdrawer_nameVARCHAR(36)Name of the player who withdrew it
date_claimedBIGINTClaim timestamp (ms), 0 while unclaimed
claimer_uuidVARCHAR(36)UUID of the player who claimed it
claimer_nameVARCHAR(36)Name of the player who claimed it

Key Legend: PK = Primary Key

Versions up to 1.0.3 kept this registry in withdrawn-items.json. On the first start of a newer version it is moved into the configured storage and renamed to withdrawn-items.json.migrated.

Installation​

  1. Download the plugin JAR
  2. Place it in the /plugins folder, with the currency plugins you use (Vault and an economy for money)
  3. Start or restart the server
  4. Configure config.yml or use /withdraw admin (changes apply automatically; a storage type change needs a restart)