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
- Money checks: Withdraw Vault money into redeemable checks
- Bottled experience: Withdraw experience into bottles
- Tokens, points, vote points and coins: Withdraw TokenManager tokens, PlayerPoints, VotingPlugin vote points and CoinsEngine coins into vouchers and notes
- Tax per currency: Optional tax charged on top of the withdrawn amount; the note keeps the amount the player asked for
- 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
- Network ready: With SQL storage, a note withdrawn on one server redeems on every server sharing the database
- Per-currency toggle: Disable a currency to stop withdrawals; its notes are kept and redeem again once it is enabled
- Admin menu:
/withdraw adminopens an overview, the currency types with an editor for each (enabled, tax, tax rate, note item and name, price format), statistics, export and import - Transaction logs: Per-player and global withdraw and claim history with an in-game log explorer
- Statistics: Withdrawal and claim counts, totals and averages per currency
- Customizable notes: Item type, name and lore per currency with MiniMessage gradients and hex colors
- Customizable messages: Every message and sound lives in
messages.yml - Dual storage: Keep data in a single
data.jsonor in a MySQL/MariaDB database, with export and import to move between them - Backward compatible with AetherWithdraw and HyperWithdraw: Existing notes still work
- 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 - 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
| Command | Description | Permission |
|---|---|---|
/withdraw or /withdraw help [page] | Show the help menu | None |
/withdraw money \<amount> | Withdraw money into a check | nexalwithdraw.use |
/withdraw exp \<amount> | Withdraw experience into a bottle | nexalwithdraw.use |
/withdraw tokens \<amount> | Withdraw tokens into a voucher | nexalwithdraw.use |
/withdraw points \<amount> | Withdraw points into a voucher | nexalwithdraw.use |
/withdraw votes \<amount> | Withdraw vote points into a note | nexalwithdraw.use |
/withdraw coins \<amount> | Withdraw coins into a note | nexalwithdraw.use |
Right-click a note to redeem it.
Admin Commands
Admin commands are hidden from players without the nexalwithdraw.admin permission.
| Command | Description | Permission |
|---|---|---|
/withdraw admin | Open the admin menu (console: admin help) | nexalwithdraw.admin |
/withdraw admin help [page] | Show the admin commands | nexalwithdraw.admin |
/withdraw admin stats | View withdrawal and claim statistics | nexalwithdraw.admin |
/withdraw admin logs [player] [page] | View transaction logs | nexalwithdraw.admin |
/withdraw admin export | Export user records and withdrawn notes to export/ | nexalwithdraw.admin |
/withdraw admin import | Import user records and withdrawn notes from export/ | nexalwithdraw.admin |
/withdraw admin reload | Confirm the configuration is reloaded (both files also reload automatically when saved) | nexalwithdraw.admin |
Admin Menu
| Screen | Contents |
|---|---|
Admin menu (/withdraw admin) | Overview (storage, available currency types, notes, players), Configuration, Statistics, Export Data, Import Data (shift+left click), Close |
| Configuration: Currency Types | Every 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 editor | A 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 |
| Statistics | Per 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
| Permission | Description | Default |
|---|---|---|
nexalwithdraw.use | Use the withdraw commands | true |
nexalwithdraw.admin | Admin 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:
| Placeholder | Description |
|---|---|
%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
- Run
/withdraw admin export - Change
storage.typeinconfig.yml - Restart the server
- 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 {
public GemsType() { super("gems"); }
@Override public String getDisplayName() { return "Gems"; }
@Override public String getRequiredPlugin() { return "MyGemsPlugin"; } // shown when missing
@Override public boolean isProviderAvailable() { return gemsApi() != null; }
@Override protected String getDefaultPriceFormat() { return "%amount% gems"; }
@Override protected Material getDefaultItemMaterial() { return Material.AMETHYST_SHARD; }
@Override public boolean onWithdraw(Player player, long amount) { /* take amount + calculateTax(amount), return true if taken */ }
@Override public boolean onConsume(Player player, long amount) { /* pay amount, return true if paid */ }
}
NexalWithdrawPlugin.getStorageManager().register(new GemsType());
- Where its settings live: in
config.ymlundermodules.\<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 tomodules.\<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 withNexalWithdrawPlugin.getWithdrawnItemRegistry().registerItem(...)so it passes the anti-dupe check./withdrawhas subcommands for the built-in types only, so a registering plugin offers its own command. - Events:
WithdrawEventandConsumeEventfire 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
| Column | Type | Key | Description |
|---|---|---|---|
id | VARCHAR(36) | PK | Player UUID |
withdraw_records | MEDIUMTEXT | JSON array of withdrawal records | |
consume_records | MEDIUMTEXT | JSON array of consume/claim records |
withdraw_notes
Registry of every withdrawn note, used for anti-dupe protection and statistics.
| Column | Type | Key | Description |
|---|---|---|---|
id | VARCHAR(36) | PK | Unique ID stored in the note |
storage_type | VARCHAR(32) | Currency (money, exp, ...) | |
amount | BIGINT | Note value | |
date_created | BIGINT | Withdraw timestamp (ms) | |
withdrawer_uuid | VARCHAR(36) | UUID of the player who withdrew it | |
withdrawer_name | VARCHAR(36) | Name of the player who withdrew it | |
date_claimed | BIGINT | Claim timestamp (ms), 0 while unclaimed | |
claimer_uuid | VARCHAR(36) | UUID of the player who claimed it | |
claimer_name | VARCHAR(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
- Download the plugin JAR
- Place it in the
/pluginsfolder, with the currency plugins you use (Vault and an economy for money) - Start or restart the server
- Configure
config.ymlor use/withdraw admin(changes apply automatically; a storage type change needs a restart)