Astral Realms Documentation Help

Overview

AstralEconomy is the multi-currency economy plugin for the AstralRealms network. It provides player and town balances, an admin edit command, a player-to-player pay command, %economy_*% placeholders, and a developer-facing service for every other plugin that needs to move money.

The MariaDB database is the sole money-safety authority. Every balance mutation runs as one guarded SQL transaction (UPDATE accounts SET balance = balance + ? ... WHERE balance + ? >= 0) that atomically checks funds and applies the delta at the storage layer — there is no in-memory balance, no lease, and no write-behind. A negative balance is structurally impossible: an overdrawn withdrawal, /eco take, transfer leg, or SPI call simply fails the guard and the transaction reports INSUFFICIENT_FUNDS with nothing written.

Currencies

Currencies are entirely config-driven, defined in currencies.yml. Each entry is a display name, a symbol, a starting balance, a transferable flag, and an optional leaderboard id — and gets its own balance row per account (accounts is keyed on (id, currency), not just id). The shipped defaults:

id

name

symbol

default-balance

transferable

default

leaderboard-id

default

Coins

$

1000

true

true

currency-default

astralys

Astralys

A$

0

false

Exactly one currency should be marked default: true — it is what /pay, /balance (no argument), and every no-currency SPI/API overload resolve to. See Currencies for the full config shape.

Core surfaces at a glance

Surface

Summary

/eco (/economy)

Admin set/add \|give/take/balance/reload on any player's balance, gated by economy.command plus a permission per subcommand.

/pay

Player-initiated transfer of the default currency, respecting the currency's transferable flag.

/balance (/bal)

Self-service balance check — no argument reports the default currency, /balance <currency> reports another; /balance others <player> <currency> is the permissioned admin-view variant.

%economy_*%

%economy_balance_<currency>%, %economy_currency_<id>%, %economy_currencies%. See Placeholders.

EconomyService (AstralCore SPI)

The framework-wide, double-based economy contract other plugins resolve via AstralPaperAPI.getService(EconomyService.class).

EconomyAPI

This plugin's own static facade for pinning town balances into the cache.

See Commands and Developer API for the full surface.

Reads: an in-memory cache, DB on miss

Balance reads are served from a Caffeine AsyncLoadingCache keyed on (uuid, currency), loading from the database on a miss. Two populations behave differently:

  • Pinned entries — online players (warmed on join, dropped on quit) and towns registered through EconomyAPI.loadTown/loadTowns — never TTL-expire and carry cache weight 0, so they are exempt from size-based eviction. They are refreshed in place after a local mutation or a cross-server hint, so a pinned account is never left cold.

  • Everyone else (offline lookups) keeps the configured cache.read-ttl (default 5s) and is capped by cache.max-size (default 500000) entries.

The synchronous placeholder/action-bar read path (cachedBalance) never blocks: it returns the cached value or zero, and self-heals a cold pinned account by kicking off an async re-warm in the background. Anything that needs a guaranteed-correct figure (/balance, /eco balance, the SPI's getBalance) goes through the asynchronous, authoritative balance()/lookup() path instead, which loads from the database on a miss.

Cross-server model

Each mutation (deposit, withdraw, set, transfer) is its own independent, retried database transaction — retried up to 4 attempts on a transient InnoDB deadlock/serialization failure or a MariaDB snapshot-isolation conflict (error 1020), and idempotent on its transaction id so a retried or replayed attempt never double-applies. On success the acting server:

  1. Writes the just-committed (balance, version) straight into its own cache (a version-guarded write-through — an out-of-order write can never clobber a fresher value already cached).

  2. Broadcasts a BalanceInvalidatePacket on messaging channel economy.balance, carrying only the (uuid, currency) cache key — never an amount — so every other server drops (or, if pinned, reloads) that entry and re-reads the database before its own read-ttl would have expired it anyway.

The hint is best-effort: if it's lost, nothing is wrong — the receiving server simply keeps serving a stale cached value until its read-ttl naturally expires, at which point the next read re-loads from the database and self-corrects. No packet ever carries money; the database is always re-consulted for the real value.

Action-bar notifications

A player's net balance change is shown on their action bar, network-wide, regardless of which server actually applied the mutation. Each successful player-account deposit/withdraw/transfer leg (a set has no meaningful delta and never notifies) is handed to a debounce window per player: changes merge into one net figure per currency over a 500 ms window, then render as a single line (e.g. +1 200$ -50A$). If the player is online on the acting server the change accumulates locally; otherwise it is broadcast as a BalanceNotifyPacket on messaging channel economy.notify — carrying a delta for display only, never applied to a balance — so whichever server actually holds the player shows it. The rendered line is wrapped in an AstralHud background before being sent via sendActionBar.

Ledger & history

Every applied mutation writes one row (two, for a transfer — one debit, one credit) to transactions: the transaction id, the account and currency, the signed delta, the resulting balance_after, the acting server_id, and a timestamp. This is an append-only audit trail — it is never read to compute a balance and pruning it never touches accounts. A background task (TransactionCleanupTask) periodically deletes rows older than cleanup.retention (default 14d) every cleanup.interval (default 6h); the whole job can be turned off with cleanup.enabled: false. See Configuration.

Dependencies

Dependency

Required

Notes

Paper 1.21+

Yes

api-version: '1.21'.

AstralCore

Yes

Configuration loading, commands, placeholders, messaging, the EconomyService SPI registration.

AstralHud

Yes

Backgrounds for the action-bar balance-change notification.

MariaDB / MySQL

Yes

Sole money-safety authority; connection configured in database.properties (JDBC URL, pool sizing). Two tables: accounts, transactions.

Where to go next

Last modified: 25 July 2026