How-To Recipes
Task-oriented recipes for common economy operations.
Task-oriented answers to "how do I X?" Each recipe assumes you have resolved an Economy (see the Consumer Guide) and imports are omitted for brevity. All amounts are BigDecimal; all calls are async.
How do I check a player's balance?
economy.getBalance(uuid).thenAccept(balance -> {
BigDecimal amount = balance.amount();
// marshal to main thread before messaging the player
});How do I give or take money?
economy.deposit(uuid, new BigDecimal("250"), "quest reward");
economy.withdraw(uuid, new BigDecimal("50"), "repair cost");Handle the returned EconomyResult if you care about success or failure.
How do I safely pay one player from another?
Use atomic transfer, never manual withdraw+deposit:
economy.transfer(fromUuid, toUuid, price, "trade")
.thenAccept(result -> {
if (result instanceof EconomyResult.InsufficientFunds f) {
// payer is broke; nothing was moved
}
});How do I check whether a player can afford something?
if (economy.supports(Capability.ECONOMY_PREFLIGHT)) {
economy.canWithdraw(uuid, price).thenAccept(ok -> { /* ... */ });
} else {
// fall back to reading the balance, or just attempt the withdraw and
// branch on InsufficientFunds
economy.getBalance(uuid).thenAccept(b -> {
boolean affordable = b.amount().compareTo(price) >= 0;
});
}Prefer attempting the operation and branching on InsufficientFunds: it avoids a check-then-act race.
How do I format money for display?
String text = economy.format(amount); // uses the active currency's rulesHow do I run code as soon as an economy is available (no load-order pain)?
Conduit.whenProviderAvailable(Economy.class, economy -> {
// runs immediately if present, otherwise when one registers
});How do I react when any money moves?
Listen to the post-commit event:
@EventHandler
public void onTxn(EconomyTransactionEvent event) {
Transaction t = event.transaction();
// analytics, quests, Discord feed
}How do I block a transaction before it happens?
Register an interceptor (synchronous, vetoing):
registry.registerInterceptor(ctx -> isAllowed(ctx), this, ServicePriority.High);Return false to abort; the operation resolves to EconomyResult.Rejected. See Events & Interceptors.
How do I attribute transactions to my plugin?
CallerToken token = Conduit.getRegistry().registerCaller(this); // once
CallerToken.runWith(token, () -> economy.deposit(uuid, amount, "sale"));See Caller Identity for executor-boundary rules.
How do I work with banks?
Banks are a structural capability; check for the interface first:
Conduit.getRegistry().getProvider(BankingEconomy.class).ifPresent(bank -> {
bank.createBank("town_spawn", ownerUuid);
bank.bankDeposit("town_spawn", playerUuid, new BigDecimal("100"));
bank.setBankMemberPermission("town_spawn", memberUuid, AccountPermission.DEPOSIT, true);
});How do I use multiple currencies?
Conduit.getRegistry().getProvider(MultiCurrencyEconomy.class).ifPresent(multi -> {
Currency gems = multi.supportedCurrencies().stream()
.filter(c -> c.id().equals("gems")).findFirst().orElseThrow();
multi.deposit(uuid, new BigDecimal("5"), gems);
});How do I read transaction history?
Conduit.getRegistry().getProvider(TransactionalEconomy.class).ifPresent(txnEco -> {
txnEco.getTransactionHistory(uuid, TransactionFilter.recent(20))
.thenAccept(list -> { /* render statement */ });
});How do I make a payout that is safe to retry (exactly-once)?
Use the idempotent variants with a stable operationId:
UUID opId = deterministicIdFor(payout); // same id for the same logical payout
txnEco.depositIdempotent(uuid, amount, opId)
.thenAccept(result -> { /* re-submitting opId returns the original result */ });Re-submitting the same opId with the same parameters returns the original result without double-paying. Different parameters under the same opId fail with IdempotencyMismatchException. See Extension Interfaces.
How do I build a leaderboard?
Conduit.getRegistry().getProvider(LeaderboardEconomy.class).ifPresent(board -> {
board.getTopBalances(10).thenAccept(top -> {
for (RankedBalance rb : top) {
// rb.rank(), rb.owner(), rb.amount()
}
});
});How do I attach analytics tags to a transaction?
Use the TransactionBuilder:
economy.transaction()
.withdraw(uuid, price)
.reason("shop purchase")
.metadata("shop_id", "spawn-market")
.metadata("item_id", "diamond_sword")
.execute()
.thenAccept(result -> { /* ... */ });The metadata rides along on the Transaction and is visible to event listeners.
How do I degrade gracefully when a feature is missing?
Branch on presence and hide the feature:
boolean banks = Conduit.getRegistry().getProvider(BankingEconomy.class).isPresent();
if (banks) registerBankCommands(); else getLogger().info("Active economy has no banks; bank UI disabled.");How do I do a Bukkit action (message, item) from an async callback?
Marshal back to the main/region thread first:
economy.deposit(uuid, amount, "reward").thenAccept(result -> {
getServer().getScheduler().runTask(this, () -> {
Player p = getServer().getPlayer(uuid);
if (p != null) p.sendMessage("You received " + economy.format(amount));
});
});On Folia, use the appropriate region/entity scheduler instead of the global scheduler.
How do I handle "no economy installed" cleanly?
Optional<Economy> maybe = Conduit.findEconomy();
if (maybe.isEmpty()) {
getLogger().warning("No economy provider installed; economy features disabled.");
return;
}Or set required: false in paper-plugin.yml and feature-gate at runtime.