-
Notifications
You must be signed in to change notification settings - Fork 0
Consumer Guide
This guide is for plugin developers who want to spend, check, or move money: shops, jobs, rewards, casinos, quests, anything that consumes an economy. If you are implementing the economy itself, see the Provider Guide.
Add conduit-api as a compileOnly dependency. The actual classes are provided at runtime by the installed Conduit plugin, so you never bundle them.
repositories {
maven("https://repo.alaz.so/releases")
}
dependencies {
compileOnly("so.alaz.conduit:conduit-api:0.3.1-beta")
}Declare Conduit as a dependency in your paper-plugin.yml:
name: MyPlugin
version: "1.0.0"
main: com.example.MyPlugin
api-version: "26.1"
dependencies:
server:
Conduit:
load: BEFORE
required: true # use false if your plugin should still load without an economyYou almost never need the onEnable dance Vault forced on you. Use whenProviderAvailable: it runs your code as soon as a provider exists, whether that is before or after your plugin enables.
import so.alaz.conduit.api.Conduit;
import so.alaz.conduit.api.economy.Economy;
Conduit.whenProviderAvailable(Economy.class, economy -> {
// economy is ready and dispatch-decorated (validation + events + interceptors)
});Other resolution options:
Conduit.getEconomy(); // active Economy, or throws ProviderNotFoundException
Conduit.findEconomy(); // Optional<Economy>, empty if none registered
Conduit.isInitialized(); // false before Conduit enables / after it disablesUse getEconomy() when an economy is mandatory and you want to fail loudly. Use findEconomy() when you can degrade gracefully. Use whenProviderAvailable(...) in startup paths to avoid any ordering assumptions.
Every mutation returns CompletableFuture<EconomyResult>. EconomyResult is sealed, so you handle each case explicitly.
import so.alaz.conduit.api.result.EconomyResult;
import java.math.BigDecimal;
economy.deposit(playerUuid, new BigDecimal("100.00"), "daily reward")
.thenAccept(result -> {
switch (result) {
case EconomyResult.Success s ->
getLogger().info("New balance: " + economy.format(s.newBalance()));
case EconomyResult.InsufficientFunds f ->
getLogger().warning("Had " + f.balance() + ", needed " + f.requested());
case EconomyResult.AccountNotFound nf ->
getLogger().warning("No account for " + nf.uuid());
case EconomyResult.CurrencyNotSupported c ->
getLogger().warning("Unsupported currency: " + c.currency().id());
case EconomyResult.Rejected r ->
getLogger().info("Vetoed by policy: " + r.reason());
case EconomyResult.ProviderError e ->
getLogger().severe("Backend error: " + e.message());
}
});If you only care about success:
economy.deposit(uuid, amount, "reward")
.thenAccept(r -> r.ifSuccess(s ->
player.sendMessage("You received " + economy.format(amount))));r.success() returns Optional<EconomyResult.Success> if you prefer that style.
The resolve-and-handle pattern is worth wrapping once in your plugin so call sites stay short. Keep the wrapper async and BigDecimal (never add a blocking or double variant):
public final class Econ {
public static CompletableFuture<EconomyResult> deposit(UUID uuid, BigDecimal amount, String reason) {
CompletableFuture<EconomyResult> out = new CompletableFuture<>();
Conduit.whenProviderAvailable(Economy.class, economy ->
economy.deposit(uuid, amount, reason).whenComplete((r, err) -> {
if (err != null) out.completeExceptionally(err); else out.complete(r);
}));
return out;
}
}Then call Econ.deposit(uuid, amount, "reward").thenAccept(...) everywhere.
On the base Economy:
| Operation | Returns | Notes |
|---|---|---|
hasAccount(uuid) |
CompletableFuture<Boolean> |
|
createAccount(uuid) / deleteAccount(uuid)
|
EconomyResult |
|
getBalance(uuid) |
CompletableFuture<Balance> |
Balance(owner, currency, amount) |
getBalances(collection) |
CompletableFuture<Map<UUID,Balance>> |
batch read |
deposit(uuid, amount[, reason]) |
EconomyResult |
amount > 0 |
withdraw(uuid, amount[, reason]) |
EconomyResult |
amount > 0 |
set(uuid, amount) |
EconomyResult |
amount >= 0 |
transfer(from, to, amount[, reason]) |
EconomyResult |
atomic, amount > 0 |
canDeposit/canWithdraw(uuid, amount) |
CompletableFuture<Boolean> |
requires ECONOMY_PREFLIGHT
|
format(amount) |
String |
currency-aware formatting |
Prefer transfer over manual withdraw-then-deposit. transfer is atomic: the classic "withdraw succeeded, deposit failed, money vanished" bug cannot happen.
import so.alaz.conduit.api.capability.Capability;
if (economy.supports(Capability.ECONOMY_PREFLIGHT)) {
economy.canWithdraw(uuid, price).thenAccept(ok -> { /* ... */ });
}For structural features, ask the registry:
import so.alaz.conduit.api.economy.BankingEconomy;
Conduit.getRegistry().getProvider(BankingEconomy.class)
.ifPresent(bank -> bank.getBankBalance("spawn_bank").thenAccept(...));See Capabilities and Extension Interfaces.
Conduit fires post-commit events. Listen to them like any Bukkit event:
@EventHandler
public void onTxn(EconomyTransactionEvent event) {
Transaction t = event.transaction();
// analytics, logging, quest progress, achievements...
}Events are non-cancellable: the money already moved. To block an operation before it happens, register an interceptor instead.
Bind a CallerToken so events and audits know the money came from you:
CallerToken token = Conduit.getRegistry().registerCaller(this); // once, in onEnable
CallerToken.runWith(token, () -> economy.deposit(uuid, amount, "shop sale"));Without this, your transactions are attributed to CallerToken.ANONYMOUS. See Caller Identity for the threading rules.
- Continuations on the default
CompletableFutureexecutor are fine; do not block them with.join()in production. - If you hop to your own executor with
thenApplyAsync(fn, myExecutor), theCallerTokendoes not propagate automatically. Rebind withCallerToken.runWith(...)or useCallerToken.propagating(executor). - Bukkit API calls (sending messages, world edits) must happen on the main/region thread. Marshal back with your scheduler before touching the world from a future callback.
- How-To Recipes: concrete "how do I X" answers.
- Migrating from Vault: side-by-side porting.
- API Reference: every type at a glance.
Conduit is a modern Economy abstraction for Minecraft (Paper/Folia, Java 25). Released under the MIT License. · Docs · Developer Guide · GitHub
Start here
For server operators
Concepts
For plugin developers (consumers)
For backend developers (providers)
Reference