Skip to content

Developer API

joogiebear edited this page Jul 14, 2026 · 1 revision

Developer API

EconGuard exposes a small public API so any plugin can report money movements into the shared ledger. Nothing here is tied to the Royal* suite — the API is generic.

The whole integration is one statement, and it's safe whether or not EconGuard is installed.

Reporting an event

import com.mystipixel.econguard.api.*;

EconGuard.get().ifPresent(api -> api.record(
    MoneyEvent.builder(player.getUniqueId(), player.getName())
              .source(Sources.BANK)          // bank pay auction shop admin interest other
              .action("deposit")             // free-form; shown in /eg history
              .amount(amount)                // positive magnitude (see note)
              .incoming(false)               // is the player RECEIVING money?
              .build()));

EconGuard.get() returns Optional<EconGuardAPI> via the Bukkit services manager — empty when EconGuard isn't installed. So the ifPresent above is the entire guard: no isPluginEnabled check, no soft-depend requirement, no reflection. Your plugin compiles and runs identically with or without EconGuard present.

A transfer with a counterparty

// Seller receives money from a buyer via an auction sale:
EconGuard.get().ifPresent(api -> api.record(
    MoneyEvent.builder(seller.getUniqueId(), seller.getName())
              .source(Sources.AUCTION)
              .action("sale")
              .amount(price)
              .incoming(true)                              // seller is RECEIVING
              .counterparty(buyer.getUniqueId(), buyer.getName())   // the other party
              .item("ecoitems:brazen_sword")
              .note("won auction #1234")
              .build()));

MoneyEvent.Builder

Create with MoneyEvent.builder(UUID player, String playerName), then chain:

Method Default Meaning
.source(String) "other" Where the movement came from — use a Sources constant.
.action(String) "transaction" Free-form label shown in /eg history (e.g. deposit, sale).
.amount(double) 0 The magnitude. Stored as Math.abs(amount) — sign is ignored; use incoming for direction.
.incoming(boolean) true true = money arrived to the player; false = money left them.
.counterparty(UUID, String) none The other party in a transfer, with their name. Omit when there isn't one.
.item(String) none Optional item id involved (e.g. an auction item).
.balanceAfter(double) NaN Optional running balance after the movement; NaN if you don't track one.
.note(String) none Optional free-text note.
.build() Produce the immutable MoneyEvent.

EconGuardAPI

public interface EconGuardAPI {
    void record(MoneyEvent event);              // persist + run anti-abuse analysis
    List<Flag> getFlags();                      // flags awaiting review
    boolean clearFlag(UUID player);             // clear one player's flag(s); true if any cleared
    void clearFlags();                          // clear all
    List<MoneyEvent> getHistory(UUID player, int limit);   // recent entries, newest first
}

All methods are safe to call from the main server thread.

Sources

Conventional source labels. These are just strings — you may use any label — but sticking to the constants keeps histories consistent across plugins.

Sources.BANK      // "bank"
Sources.PAY       // "pay"
Sources.AUCTION   // "auction"
Sources.SHOP      // "shop"
Sources.ADMIN     // "admin"
Sources.INTEREST  // "interest"
Sources.OTHER     // "other"

Flag

public record Flag(
    UUID player,
    String playerName,
    String type,        // which signal fired
    String reason,      // human-readable explanation
    long timestamp
) {}

Two things to get right

These are the two mistakes that quietly break detection:

1. incoming is from the reporting player's perspective

A bank deposit is money leaving the player's purse → incoming(false). An auction sale pays the seller → incoming(true) on the seller's event. Get this backwards and the velocity and young-incoming signals point at the wrong side of every trade.

2. Set counterparty whenever there is one

Counterparty correlation is the signal that catches RMT rings, and it goes silently dead if the field is null. A shop purchase has no counterparty (the server is the other side); a /pay or an auction sale absolutely does — record it, with both the UUID and the name.

Setup (Maven)

EconGuard isn't on a public repo; add it as a provided dependency from your local build, or shade only the tiny api package. Either way, mark it soft:

# plugin.yml
softdepend: [EconGuard]

You don't need depend: — the EconGuard.get() guard means a missing EconGuard is a no-op, not an error. Reporting is fire-and-forget: build the event, hand it to record(), done.

Clone this wiki locally