Skip to content

Latest commit

 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vault3

A drop-in Vault economy provider. No Vault.jar required.

Version Java Platform License

English · 繁體中文

Documentation · Issues


Contents


What it is

Vault3 fills two roles at once:

  • A complete economy — balances, transfers, charge requests, leaderboard, GUI menus, loans
  • A Vault API provider — registered through the net.milkbowl.vault interfaces

Plugins that declare depend: [Vault] and call net.milkbowl.vault.economy.Economy keep working exactly as before, but you do not need a separate Vault.jar — the API ships inside this plugin.

⚠️ For that same reason, do not install Vault.jar alongside it. Both declare the plugin name Vault, so Bukkit treats them as duplicates and one will fail to load — and you cannot predict which one.


Design

Most economy plugins are fine on a small server. Things break once traffic picks up, so this project is built around the two failures that actually matter: blocking the main thread and losing money.

Transactions perform no I/O

A transaction marks the account dirty and returns immediately. Writes happen asynchronously, debounced, and cover only the balances that changed — never the whole table.

On a 200-player server a shop purchase used to mean the main thread waiting on a 200-row batch upsert. Now it is a single row, off-thread.

Transactions are atomic

withdrawPlayer and depositPlayer are single atomic operations on the account, implemented with ConcurrentHashMap.compute() so the read, the sufficient-funds check and the write all happen under the same lock.

Two plugins touching the same balance at once — a shop and a daily-reward task, say — cannot overwrite each other. Under a stress test of 8 threads × 25,000 concurrent withdrawals the final balance is exact.

Name lookups do not scan the data directory

Resolving a player name goes from cheapest to most expensive:

  1. Exact match against online players
  2. Local name→UUID cache, populated on join so renames stay correct
  3. The server's own user cache (getOfflinePlayerIfCached, called reflectively so older APIs still work)
  4. A full directory scan — at most once every 60 seconds, and its results warm the cache

Failed lookups are negatively cached for 60 seconds, so a name that does not exist cannot trigger repeated scans. In testing, 210,000 lookups triggered a single directory scan.

Performance


Features

Area Detail
Storage MySQL (HikariCP) or flat file. Drivers download at runtime, nothing to bundle
Payments /pay with a paginated player picker, or direct /pay <player> <amount>
Charge requests Request money from a player. Offline targets receive it on their next join
Loans Optional loan system with installments, interval charging and penalties
Leaderboard /vaultop, paginated
Admin /eco give|take, in-game config editor GUI, /vault reload
PlaceholderAPI %vault_*% and %vault3_*%, plus %vault2_*% as a legacy alias
Languages English, 繁體中文, 简体中文, Español, Français, Deutsch, Nederlands, Polski, Português, Русский, हिन्दी
Currency Symbol, prefix/suffix position, decimal pattern, 1.2k / 3.4m abbreviation
Import One-shot import from Essentials userdata

Installation

  1. Stop the server cleanly (/stop — not a forced kill)
  2. Drop the jar into plugins/
  3. Start the server; plugins/Vault/config.yml is generated
  4. Edit the config, then /vault reload

Upgrading

If the new jar has a different filename from the old one it will not replace it. Delete the old jar first, or both will load and collide as duplicate plugins.


Configuration

Storage

storage:
  # Persist shortly after each transaction. Asynchronous and only writes changed
  # rows, so it never blocks the main thread
  save_on_transaction: true
  # Debounce window in ticks. Lower is safer, higher means fewer round trips
  transaction_flush_delay_ticks: 20
  # Periodic asynchronous safety net, in seconds. 0 disables it
  autosave_seconds: 60
  # On startup, re-apply balances a previous shutdown could not write
  recover_emergency_dumps: true

  use_mysql: true
  mysql:
    host: localhost
    port: 3306
    database: vault
    username: root
    password: ""
    params: useSSL=false&serverTimezone=UTC
    pool:
      max: 10
      min_idle: 2
      connection_timeout_ms: 10000
      idle_timeout_ms: 600000
      max_lifetime_ms: 1800000

Two keys are easy to get wrong, and getting them wrong fails silently:

  • The connection string key is params, not properties
  • Pool settings must be nested under pool:, not flat under mysql:

A wrong key logs no error. It quietly falls back to the default, so the settings look applied when they are not.

Other common settings

language: en              # see the messages/ directory
currency:
  symbol: "$"
  position: suffix        # prefix / suffix / none
  space: true
  abbreviate:
    enabled: false        # 1.2k / 3.4m
pay_limits:
  min: 10
  max: 100000
loans:
  enabled: false          # disables the whole subsystem, scheduler included
update_check: false

Commands and permissions

Command Description Permission
/balance Show your balance vault.balance
/pay [player] [amount] Pay a player, or open the payment menu vault.pay
/vaultop [page] Richest players vault.top
/eco give|take <player> <amount> Adjust a balance vault.eco
/vault [reload|loan] Main menu and admin actions vault.admin
/loan Loan menu vault.loan

vault.pay.bypass_min and vault.pay.bypass_max exempt a player from pay_limits.


PlaceholderAPI

%vault_balance%                     %vault_balance_formatted%
%vault_eco_balance%                 %vault_eco_balance_formatted%
%vault_eco_balance_fixed%           %vault_eco_balance_commas%
%vault_eco_balance_short%           %vault_ecobalance<0-8>dp%
%vault_currency_symbol%
%vault_balance_<player>%            %vault_balance_formatted_<player>%
%vault_top%                         %vault_top_<n>%
%vault_top_name_<n>%                %vault_top_amount_<n>%

Every placeholder is also available as %vault3_*%, and as %vault2_*% for configs written against the older identifier.


Data safety

Clean shutdown

Every balance is written synchronously, before the connection pool closes. If the database write fails:

  1. It is retried 3 times, 400 ms apart
  2. If it still fails, balances are written to balances-emergency-<timestamp>.yml
  3. The console reports Saved N player balances before shutdown.

Automatic recovery on the next start

Startup scans for emergency dumps, applies them oldest to newest so a later dump wins, writes them back to the database, then renames each file to .recovered so it can never be applied twice. If the write-back still fails the files are kept for the following start.

Controlled by storage.recover_emergency_dumps.

Set it to false if several servers share one database — a stale dump from one server could overwrite newer values written by another.

Crash, OOM, kill -9

onDisable() does not run, so transactions since the last flush are lost. With the defaults that window is about one second; tune it with transaction_flush_delay_ticks.

A normal /stop, a panel shutdown and docker stop (SIGTERM) all run the full shutdown save. Only SIGKILL and the OOM killer skip it.

Data safety


Building

Requires JDK 21 (modern) or JDK 17 (legacy).

mvn -P modern clean package   # → target-java21/vault-3.0.0.jar   Java 21 / Spigot 1.21.4
mvn -P legacy clean package   # → target-java17/vault-3.0.0.jar   Java 17 / Spigot 1.8.8

Both profiles produce the same filename but write to different directories, so they never clobber each other. If you copy them into one place, tell them apart by the class file major version: 0x3d is Java 17, 0x41 is Java 21.

Changing the version

Two places must be updated together, or /version Vault reports the wrong number:

  • <version> and the three <jar.finalName> entries in pom.xml
  • version: in src/main/resources/plugin.yml

Compatibility

  • Server: Spigot / Paper. Builds are provided for Java 21 and Java 17
  • Optional hooks: PlaceholderAPI, LuckPerms, SkinsRestorer, Essentials (import only)
  • Never install Vault.jar as well — see What it is

Project layout

src/main/java/
├── kevin/vault3/
│   ├── VaultPlugin.java        plugin entry point, lifecycle, scheduling
│   ├── economy/                SimpleEconomy — balances, transactions, persistence
│   ├── storage/                Database — MySQL access
│   ├── commands/               balance, pay, vaultop, eco, vault
│   ├── menu/                   payment, loan, config editor and charge request GUIs
│   ├── loans/                  loan logic and storage
│   ├── placeholder/            PlaceholderAPI expansions
│   ├── importer/               Essentials import
│   ├── i18n/                   message loading
│   └── util/                   name cache, colour, input sanitising
└── net/milkbowl/vault/
    └── Vault.java              plugin main class, extends VaultPlugin

src/main/resources/
├── config.yml
├── plugin.yml
└── messages/                   11 languages

Contributing notes

These are external contracts. Changing any of them breaks other plugins:

Item Why
name: Vault in plugin.yml Other plugins hook in via depend: [Vault]
main: net.milkbowl.vault.Vault Same, and it is the plugin main class
The net.milkbowl.vault.** package Other plugins import these interfaces directly
PlaceholderAPI ids vault / vault3 / vault2 Already written into users' scoreboard configs

Implementation constraints:

  • The transaction path (withdrawPlayer / depositPlayer) must contain no I/O
  • flushDirty() may only be called from an asynchronous thread
  • save() / saveAllNow() are blocking, and exist for shutdown, reload and import only
  • Every balance mutation must be atomic — never get() then put()

bStats

bstats-bukkit 3.2.1 is bundled at build time and relocated to kevin.vault3.metrics so it cannot clash with another plugin's copy. Two custom charts are collected: storage backend and configured language.

Server owners can disable it globally with enabled: false in plugins/bStats/config.yml. This plugin does not overwrite that setting.


License

Distributed under AGPL-3.0.

In practice this means: if you modify Vault3 and let people interact with it over a network, you must offer those users the source of your modified version (AGPL-3.0 §13). Running an unmodified copy triggers no obligation.

The jar bundles MilkBowl/VaultAPI (net.milkbowl.vault.**), which is licensed LGPL-3.0. LGPL-3.0 is GPL-3.0 plus additional permissions, so it combines cleanly into an AGPL-3.0 work — but that portion remains under its own licence and its notice must be retained when redistributing this jar.

Other bundled components:

Component License
MilkBowl/VaultAPI LGPL-3.0
bStats MIT
Libby MIT

Acknowledgements

  • MilkBowl/VaultAPI — the economy, permission and chat interfaces. This API is the shared language of the entire Minecraft economy ecosystem; the fact that Vault3 can stand in for it without touching any other plugin rests entirely on that work.
  • bStats — anonymous usage statistics.
  • Libby — runtime dependency loading, which keeps HikariCP and the MySQL driver out of the jar.
  • The author of the original Vault 2.0 economy plugin — the feature set and configuration structure originate there; the storage layer and transaction path were reimplemented on top of it. Thanks for allowing release under this name.

About

Standalone Vault-compatible economy provider with GUI menus, loans, MySQL storage and PlaceholderAPI support. No Vault.jar required.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages