Skip to content

Economy

Reece Williams edited this page Sep 2, 2026 · 2 revisions

Economy

ServerTools ships a full Vault economy provider, so you can drop EssentialsX Economy and let ServerTools handle balances, /pay, /eco, and every %vault_eco_*% placeholder.

Requires: Vault (hard requirement - economy won't load without it).

What it is

ServerTools registers itself as the Vault economy provider. During onLoad it hands a ServerToolsEconomy (a Vault AbstractEconomy) to Bukkit's ServicesManager at ServicePriority.Highest, so /pay, PlaceholderAPI's %vault_eco_*% placeholders, shops, and anything else that talks to Vault reads straight from us.

A few things that make it solid:

  • Whole cents, no floats. Every balance lives in SQLite as a whole-cent long. We only convert to the double Vault demands at the API boundary. That kills the classic $0.1 + $0.2 rounding bugs. See Money.java.
  • SQLite storage. Balances go in plugins/ServerTools/data/economy.db, table balances (uuid, cents, name). Reads hit an in-memory cache; writes queue onto a single background thread so the main thread never blocks on disk IO. See EconomyStorage.java.
  • UUID-keyed. Everything's keyed by UUID, so offline players and name changes just work. The deprecated name-based Vault methods resolve a UUID first.
  • sqlite-jdbc is runtime-loaded by Paper, not shaded. It's declared in plugin.yml under libraries: (org.xerial:sqlite-jdbc:3.46.1.3), so Paper pulls it at startup.

Note: there's no bank support (hasBankSupport() returns false).

Setup

  1. Install Vault. Without it the economy skips loading entirely (it logs Economy enabled but Vault is not installed - skipping. and moves on). Vault's the only hard requirement here.
  2. Set Economy.Enabled: true (it's on by default).
  3. Tune StartingBalance, CurrencySymbol, and the currency name.
  4. Restart. You should see Economy provider registered with Vault. in console.

Heads up on how Vault's required: the code doesn't use a @RequiresPlugin annotation. It's a plain runtime check in Main.setupEconomy():

if (Bukkit.getPluginManager().getPlugin("Vault") == null) {
    Util.consoleMSG("&cEconomy enabled but Vault is not installed - skipping.");
    return;
}

In plugin.yml, Vault sits under softdepend, so ServerTools still boots without it - the economy module just stays off.

Commands and permissions

Permission nodes come from the config, not hardcoded, so you can remap them.

Command What it does Permission
/pay <player> <amount> Send money to another player servertools.pay
/eco <give|take|set> <player> <amount> Admin balance management servertools.eco.admin
/balance [player] Check your balance or someone else's (none by default)
/baltop Top balances leaderboard (none by default)

Notes:

  • /pay is atomic and cent-accurate - it calls storage.transfer() directly instead of round-tripping through Vault's double API. You can't pay yourself, and the payee has to have joined before.
  • /eco accepts give/add, take/remove, and set. Tab-completes the action and online player names. Aliased to /economy.
  • /balance aliases: bal, money. /baltop aliases: balancetop, moneytop. Both share one executor and branch on the label.

Config reference

The real Economy: block from config.yml. The Messages list is trimmed here (there are ~19 keys) but the shape's the same - &-color codes plus %player% %amount% %balance% %input% placeholders (%rank% only works in BalTop.Line). Every message defaults to what's shown in code, so a missing key won't break anything.

Economy:
  # ServerTools acts as the Vault economy provider.
  # Balances are stored in economy.db (SQLite) as whole cents, so no float rounding bugs.
  # Needs Vault installed. PlaceholderAPI %vault_eco_balance_formatted% reads from here.
  Enabled: true
  StartingBalance: 0.0
  CurrencySymbol: '$'
  CurrencyNameSingular: 'Dollar'
  CurrencyNamePlural: 'Dollars'
  Pay:
    # /pay <player> <amount>
    Enabled: true
    Permission: servertools.pay
  Admin:
    # /eco <give|take|set> <player> <amount>  (admin)
    Enabled: true
    Permission: servertools.eco.admin
  Balance:
    # /balance [player] and /baltop
    Enabled: true

  # All economy messages. &-color codes and these %placeholders% are supported:
  #   %player% %amount% %balance% %input% (%rank% only in BalTop.Line)
  Messages:
    PlayersOnly: '&c[!] Only players can pay.'
    NotEnabled: '&c[!] The economy is not enabled.'
    NeverJoined: '&c[!] &f%player% &chas never joined the server.'
    InvalidAmount: '&c[!] &f%input% &cis not a valid amount.'
    PaySent: '&aYou paid &f%player% %amount%&a. New balance: &f%balance%'
    PayReceived: '&aYou received %amount% &afrom &f%player%&a. New balance: &f%balance%'
    PayInsufficient: "&c[!] You can't afford that. Balance: &f%balance%"
    PayOverflow: '&c[!] That would put them over the balance limit.'
    EcoSet: "&aSet &f%player%&a's balance. Now: &f%balance%"
    EcoOverflow: '&c[!] That exceeds the balance limit.'
    # ...more keys, see config.yml

  BalTop:
    Limit: 10
    Header: '&e&lTop Balances'
    Line: '&e%rank%. &f%player% &7- &a%balance%'
    Empty: '&7No accounts yet.'

BalTop.Limit caps how many rows /baltop shows (default 10).

PlaceholderAPI

Once ServerTools is the Vault provider, PAPI's Vault expansion reads from it. So %vault_eco_balance_formatted% gives a player's balance formatted with your currency symbol. This is called out right in the config comment: # Needs Vault installed. PlaceholderAPI %vault_eco_balance_formatted% reads from here.

Balance limit and overflow

There's a hard ceiling so a single account can't overflow a long:

public static final long MAX_CENTS = 100_000_000_000_000L; // 1 trillion dollars

How it's enforced:

  • Input parsing (Money.parse) rejects anything over MAX_CENTS, plus negatives, exponent notation like 1e3, and amounts finer than a cent like 10.999. It does accept $, commas, and decimals ("1,000.99", "$5").
  • Deposits, set, and transfers check the cap before applying. If it'd blow past MAX_CENTS, they return OVERFLOW instead of mutating.
  • That OVERFLOW result is what surfaces the PayOverflow message (/pay) and EcoOverflow message (/eco). So a payment or admin grant that'd push someone over the cap just gets refused - nothing wraps around or corrupts.

Withdrawals check funds and return INSUFFICIENT_FUNDS if you're short.

Clone this wiki locally