diff --git a/catacomb-bukkit-api/src/main/java/org/minecraftoss/catacomb/bukkit/BukkitRequestContext.java b/catacomb-bukkit-api/src/main/java/org/minecraftoss/catacomb/bukkit/BukkitRequestContext.java
new file mode 100644
index 0000000..0ab9084
--- /dev/null
+++ b/catacomb-bukkit-api/src/main/java/org/minecraftoss/catacomb/bukkit/BukkitRequestContext.java
@@ -0,0 +1,20 @@
+package org.minecraftoss.catacomb.bukkit;
+
+import org.bukkit.plugin.Plugin;
+import org.minecraftoss.catacomb.account.request.RequestContext;
+
+import java.util.UUID;
+
+public interface BukkitRequestContext extends RequestContext {
+
+ static BukkitRequestContext pluginRequest(UUID accountId, Plugin requestId) {
+ return new BukkitRequestContextImpl(accountId, requestId);
+ }
+
+ Plugin getRequestIdentifier();
+
+ @Override
+ default String getRequesterIdentifier() {
+ return getRequestIdentifier().getName();
+ }
+}
diff --git a/catacomb-bukkit-api/src/main/java/org/minecraftoss/catacomb/bukkit/BukkitRequestContextFactory.java b/catacomb-bukkit-api/src/main/java/org/minecraftoss/catacomb/bukkit/BukkitRequestContextFactory.java
new file mode 100644
index 0000000..e7bcdd9
--- /dev/null
+++ b/catacomb-bukkit-api/src/main/java/org/minecraftoss/catacomb/bukkit/BukkitRequestContextFactory.java
@@ -0,0 +1,25 @@
+package org.minecraftoss.catacomb.bukkit;
+
+import org.bukkit.plugin.Plugin;
+import org.minecraftoss.catacomb.account.request.RequestContext;
+import org.minecraftoss.catacomb.account.request.RequestContextFactory;
+
+import java.util.UUID;
+
+public class BukkitRequestContextFactory implements RequestContextFactory {
+
+ private final Plugin plugin;
+
+ private BukkitRequestContextFactory(Plugin plugin) {
+ this.plugin = plugin;
+ }
+
+ public static BukkitRequestContextFactory forPlugin(Plugin plugin) {
+ return new BukkitRequestContextFactory(plugin);
+ }
+
+ @Override
+ public RequestContext fromAccountIdentifier(UUID accountIdentifier) {
+ return BukkitRequestContext.pluginRequest(accountIdentifier, this.plugin);
+ }
+}
diff --git a/catacomb-bukkit-api/src/main/java/org/minecraftoss/catacomb/bukkit/BukkitRequestContextImpl.java b/catacomb-bukkit-api/src/main/java/org/minecraftoss/catacomb/bukkit/BukkitRequestContextImpl.java
new file mode 100644
index 0000000..c84714a
--- /dev/null
+++ b/catacomb-bukkit-api/src/main/java/org/minecraftoss/catacomb/bukkit/BukkitRequestContextImpl.java
@@ -0,0 +1,39 @@
+package org.minecraftoss.catacomb.bukkit;
+
+import org.bukkit.plugin.Plugin;
+import org.minecraftoss.catacomb.account.request.RequestContextImpl;
+
+import java.util.UUID;
+
+class BukkitRequestContextImpl extends RequestContextImpl implements BukkitRequestContext {
+
+ private final Plugin requestIdentifier;
+
+ BukkitRequestContextImpl(UUID accountIdentifier, Plugin requestIdentifier) {
+ super(accountIdentifier, requestIdentifier.getName());
+ this.requestIdentifier = requestIdentifier;
+ }
+
+ @Override
+ public Plugin getRequestIdentifier() {
+ return this.requestIdentifier;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof BukkitRequestContextImpl)) return false;
+ if (!super.equals(o)) return false;
+
+ BukkitRequestContextImpl that = (BukkitRequestContextImpl) o;
+
+ return requestIdentifier.equals(that.requestIdentifier);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + requestIdentifier.hashCode();
+ return result;
+ }
+}
diff --git a/catacomb-bukkit/src/main/resources/plugin.yml b/catacomb-bukkit/src/main/resources/plugin.yml
index 136f9e1..18460cb 100644
--- a/catacomb-bukkit/src/main/resources/plugin.yml
+++ b/catacomb-bukkit/src/main/resources/plugin.yml
@@ -1,6 +1,6 @@
name: Catacomb
version: @version@
-main: org.mincraftoss.catacomb.CatacombBukkit
+main: org.minecraftoss.catacomb.CatacombBukkit
api-version: 1.13
authors: [mbaxter, md678685]
description: Totally not a replacement for Vault
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/Capability.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/Capability.java
new file mode 100644
index 0000000..62c1e2a
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/Capability.java
@@ -0,0 +1,33 @@
+package org.minecraftoss.catacomb;
+
+@SuppressWarnings("unused")
+public enum Capability {
+
+ /**
+ * Accounts that are provided by a third-party plugin.
+ *
+ *
If this capability isn't provided, third-party plugins
+ * can't use the implementation to have e.g. shared accounts.
+ */
+ PLUGIN_ACCOUNTS,
+
+ /**
+ * Different contexts for accounts.
+ *
+ *
If this capability isn't provided, third-party plugins
+ * can't use the implementation to have e.g. a bank account
+ * and a wallet for one player. Alternatively, {@link #PLUGIN_ACCOUNTS}
+ * could be used to achieve the same functionality in some cases.
+ */
+ CONTEXTS,
+
+ /**
+ * More than one currency.
+ *
+ *
If this capability isn't provided, third-party plugins
+ * can't use the implementation to have different currencies.
+ * Depending on the use case, {@link #PLUGIN_ACCOUNTS} could be
+ * used if provided.
+ */
+ MULTIPLE_CURRENCIES
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/CatacombService.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/CatacombService.java
new file mode 100644
index 0000000..997a758
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/CatacombService.java
@@ -0,0 +1,25 @@
+package org.minecraftoss.catacomb;
+
+import org.minecraftoss.catacomb.account.AccountManager;
+
+import java.util.Set;
+
+public interface CatacombService {
+
+ default boolean queryCapabilities(Capability capability) {
+ return false;
+ }
+
+ default boolean queryCapabilities(Set capabilities) {
+ for (Capability capability : capabilities) {
+ if (!queryCapabilities(capability)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ EconomyManager getEconomyManager();
+
+ AccountManager getAccountManager();
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/EconomyManager.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/EconomyManager.java
new file mode 100644
index 0000000..0292073
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/EconomyManager.java
@@ -0,0 +1,18 @@
+package org.minecraftoss.catacomb;
+
+import org.minecraftoss.catacomb.currency.Currency;
+import org.minecraftoss.catacomb.transaction.Transaction;
+import org.minecraftoss.catacomb.transaction.TransactionCondition;
+import org.minecraftoss.catacomb.transaction.TransactionContext;
+import org.minecraftoss.catacomb.transaction.result.TransactionResult;
+
+import java.math.BigDecimal;
+import java.util.Set;
+import java.util.UUID;
+
+public interface EconomyManager {
+
+ TransactionResult handle(Transaction transaction, TransactionCondition condition);
+
+ BigDecimal getBalance(UUID identifier, Currency currency, Set contexts);
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/SimpleCatacombService.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/SimpleCatacombService.java
new file mode 100644
index 0000000..879229d
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/SimpleCatacombService.java
@@ -0,0 +1,39 @@
+package org.minecraftoss.catacomb;
+
+import org.minecraftoss.catacomb.account.AccountManager;
+
+import java.util.EnumSet;
+import java.util.Set;
+
+public class SimpleCatacombService implements CatacombService {
+ private final EconomyManager economyManager;
+ private final AccountManager accountManager;
+ private final Set capabilities;
+
+ public SimpleCatacombService(EconomyManager economyManager,
+ AccountManager accountManager, Set capabilities) {
+ this.economyManager = economyManager;
+ this.accountManager = accountManager;
+ this.capabilities = EnumSet.copyOf(capabilities);
+ }
+
+ @Override
+ public boolean queryCapabilities(Capability capability) {
+ return this.capabilities.contains(capability);
+ }
+
+ @Override
+ public boolean queryCapabilities(Set capabilities) {
+ return this.capabilities.containsAll(capabilities);
+ }
+
+ @Override
+ public EconomyManager getEconomyManager() {
+ return this.economyManager;
+ }
+
+ @Override
+ public AccountManager getAccountManager() {
+ return this.accountManager;
+ }
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/Account.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/Account.java
new file mode 100644
index 0000000..a66afda
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/Account.java
@@ -0,0 +1,164 @@
+package org.minecraftoss.catacomb.account;
+
+import org.minecraftoss.catacomb.CatacombService;
+import org.minecraftoss.catacomb.currency.Currency;
+import org.minecraftoss.catacomb.transaction.Transaction;
+import org.minecraftoss.catacomb.transaction.TransactionContext;
+import org.minecraftoss.catacomb.transaction.TransactionCondition;
+import org.minecraftoss.catacomb.transaction.result.TransactionResult;
+
+import java.math.BigDecimal;
+import java.util.Set;
+import java.util.UUID;
+
+public interface Account {
+
+ UUID getIdentifier();
+
+ default BigDecimal getBalance(Currency currency, Set contexts) {
+ return getCatacombService().getEconomyManager().getBalance(getIdentifier(), currency, contexts);
+ }
+
+ default BigDecimal getBalance(Currency currency) {
+ return getBalance(currency, getAccountContext().getDefaultContexts());
+ }
+
+ default BigDecimal getBalance() {
+ return getBalance(getAccountContext().getDefaultCurrency());
+ }
+
+ BigDecimal getInitialBalance(Currency currency);
+
+ default BigDecimal getInitialBalance() {
+ return getInitialBalance(getAccountContext().getDefaultCurrency());
+ }
+
+ // account reset
+
+ TransactionResult resetBalance(Currency currency, Set contexts);
+
+ default TransactionResult resetBalance(Currency currency) {
+ return resetBalance(currency, getAccountContext().getDefaultContexts());
+ }
+
+ default TransactionResult resetBalance() {
+ return resetBalance(getAccountContext().getDefaultCurrency());
+ }
+
+ // transfer
+
+ default TransactionResult transfer(Account to, Currency currency, BigDecimal amount, TransactionCondition condition, Set contexts) {
+ Transaction transaction = Transaction.of(this, to, currency, amount, contexts);
+ TransactionCondition andDefault = condition.and(getAccountContext().getDefaultTransactionCondition());
+ return getCatacombService().getEconomyManager().handle(transaction, andDefault);
+ }
+
+ default TransactionResult transfer(Account to, BigDecimal amount, TransactionCondition condition, Set contexts) {
+ return transfer(to, getAccountContext().getDefaultCurrency(), amount, condition, contexts);
+ }
+
+ default TransactionResult transfer(Account to, Currency currency, BigDecimal amount, Set contexts) {
+ return transfer(to, currency, amount, getAccountContext().getDefaultTransactionCondition(), contexts);
+ }
+
+ default TransactionResult transfer(Account to, BigDecimal amount, Set contexts) {
+ return transfer(to, getAccountContext().getDefaultCurrency(), amount, contexts);
+ }
+
+ default TransactionResult transfer(Account to, Currency currency, BigDecimal amount, TransactionCondition condition) {
+ return transfer(to, currency, amount, condition, getAccountContext().getDefaultContexts());
+ }
+
+ default TransactionResult transfer(Account to, BigDecimal amount, TransactionCondition condition) {
+ return transfer(to, getAccountContext().getDefaultCurrency(), amount, condition);
+ }
+
+ default TransactionResult transfer(Account to, Currency currency, BigDecimal amount) {
+ return transfer(to, currency, amount, getAccountContext().getDefaultTransactionCondition());
+ }
+
+ default TransactionResult transfer(Account to, BigDecimal amount) {
+ return transfer(to, getAccountContext().getDefaultCurrency(), amount);
+ }
+
+ // withdraw
+
+ default TransactionResult withdraw(Currency currency, BigDecimal amount, TransactionCondition condition, Set contexts) {
+ return transfer(getAccountContext().getInfiniteAccount(), currency, amount, condition, contexts);
+ }
+
+ default TransactionResult withdraw(BigDecimal amount, TransactionCondition condition, Set contexts) {
+ return withdraw(getAccountContext().getDefaultCurrency(), amount, condition, contexts);
+ }
+
+ default TransactionResult withdraw(Currency currency, BigDecimal amount, Set contexts) {
+ return withdraw(currency, amount, getAccountContext().getDefaultTransactionCondition(), contexts);
+ }
+
+ default TransactionResult withdraw(BigDecimal amount, Set contexts) {
+ return withdraw(getAccountContext().getDefaultCurrency(), amount, contexts);
+ }
+
+ default TransactionResult withdraw(Currency currency, BigDecimal amount, TransactionCondition condition) {
+ return withdraw(currency, amount, condition, getAccountContext().getDefaultContexts());
+ }
+
+ default TransactionResult withdraw(BigDecimal amount, TransactionCondition condition) {
+ return withdraw(getAccountContext().getDefaultCurrency(), amount, condition);
+ }
+
+ default TransactionResult withdraw(Currency currency, BigDecimal amount) {
+ return withdraw(currency, amount, getAccountContext().getDefaultTransactionCondition());
+ }
+
+ default TransactionResult withdraw(BigDecimal amount) {
+ return withdraw(getAccountContext().getDefaultCurrency(), amount);
+ }
+
+ // deposit
+
+ default TransactionResult deposit(Currency currency, BigDecimal amount, TransactionCondition condition, Set contexts) {
+ return getAccountContext().getInfiniteAccount().transfer(this, currency, amount, condition, contexts);
+ }
+
+ default TransactionResult deposit(BigDecimal amount, TransactionCondition condition, Set contexts) {
+ return deposit(getAccountContext().getDefaultCurrency(), amount, condition, contexts);
+ }
+
+ default TransactionResult deposit(Currency currency, BigDecimal amount, Set contexts) {
+ return deposit(currency, amount, getAccountContext().getDefaultTransactionCondition(), contexts);
+ }
+
+ default TransactionResult deposit(BigDecimal amount, Set contexts) {
+ return deposit(getAccountContext().getDefaultCurrency(), amount, contexts);
+ }
+
+ default TransactionResult deposit(Currency currency, BigDecimal amount, TransactionCondition condition) {
+ return deposit(currency, amount, condition, getAccountContext().getDefaultContexts());
+ }
+
+ default TransactionResult deposit(BigDecimal amount, TransactionCondition condition) {
+ return deposit(getAccountContext().getDefaultCurrency(), amount, condition);
+ }
+
+ default TransactionResult deposit(Currency currency, BigDecimal amount) {
+ return deposit(currency, amount, getAccountContext().getDefaultTransactionCondition());
+ }
+
+ default TransactionResult deposit(BigDecimal amount) {
+ return deposit(getAccountContext().getDefaultCurrency(), amount);
+ }
+
+ CatacombService getCatacombService();
+
+ /**
+ * The context of this account. While an account is uniquely identified
+ * by {@link #getIdentifier()}, this allows to provide different context
+ * to different users (e.g. third-party plugins).
+ *
+ * Implementations of Catacomb might allow settings so different plugins
+ * use different {@link Currency}, different default {@link TransactionCondition}.
+ * @return the context of this account.
+ */
+ AccountContext getAccountContext();
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/AccountContext.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/AccountContext.java
new file mode 100644
index 0000000..da91495
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/AccountContext.java
@@ -0,0 +1,24 @@
+package org.minecraftoss.catacomb.account;
+
+import org.minecraftoss.catacomb.currency.Currency;
+import org.minecraftoss.catacomb.transaction.TransactionCondition;
+import org.minecraftoss.catacomb.transaction.TransactionContext;
+
+import java.util.Set;
+
+public interface AccountContext {
+
+ TransactionCondition getDefaultTransactionCondition();
+
+ Currency getDefaultCurrency();
+
+ Set getDefaultContexts();
+
+ // TODO better name?
+ /**
+ * The account that is used for withdraw/deposit transactions as target/source.
+ *
+ * @return an infinite account.
+ */
+ Account getInfiniteAccount();
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/AccountManager.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/AccountManager.java
new file mode 100644
index 0000000..1842671
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/AccountManager.java
@@ -0,0 +1,8 @@
+package org.minecraftoss.catacomb.account;
+
+import org.minecraftoss.catacomb.account.request.RequestContext;
+
+public interface AccountManager {
+
+ Account getAccount(RequestContext context);
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/InfiniteAccount.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/InfiniteAccount.java
new file mode 100644
index 0000000..8b17f65
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/InfiniteAccount.java
@@ -0,0 +1,56 @@
+package org.minecraftoss.catacomb.account;
+
+import org.minecraftoss.catacomb.CatacombService;
+import org.minecraftoss.catacomb.currency.Currency;
+import org.minecraftoss.catacomb.transaction.Transaction;
+import org.minecraftoss.catacomb.transaction.TransactionCondition;
+import org.minecraftoss.catacomb.transaction.TransactionContext;
+import org.minecraftoss.catacomb.transaction.result.TransactionResult;
+
+import java.math.BigDecimal;
+import java.util.Set;
+import java.util.UUID;
+
+public enum InfiniteAccount implements Account {
+ INSTANCE;
+
+ // TODO
+ private final UUID id = UUID.nameUUIDFromBytes(new byte[0]);
+
+ @Override
+ public UUID getIdentifier() {
+ return this.id;
+ }
+
+ @Override
+ public BigDecimal getBalance(Currency currency) {
+ return getInitialBalance(currency);
+ }
+
+ @Override
+ public BigDecimal getInitialBalance(Currency currency) {
+ return BigDecimal.valueOf(Double.MAX_VALUE);
+ }
+
+ @Override
+ public TransactionResult resetBalance(Currency currency, Set contexts) {
+ throw new UnsupportedOperationException("Cannot reset infinite account");
+ }
+
+ @Override
+ public TransactionResult transfer(Account to, Currency currency, BigDecimal amount,
+ TransactionCondition condition, Set contexts) {
+ Transaction transaction = Transaction.of(this, to, currency, amount, contexts);
+ return to.getCatacombService().getEconomyManager().handle(transaction, condition);
+ }
+
+ @Override
+ public CatacombService getCatacombService() {
+ throw new UnsupportedOperationException("No associated catacomb service for infinite account");
+ }
+
+ @Override
+ public AccountContext getAccountContext() {
+ throw new UnsupportedOperationException("No associated account context");
+ }
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/request/RequestContext.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/request/RequestContext.java
new file mode 100644
index 0000000..bcfbdfa
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/request/RequestContext.java
@@ -0,0 +1,34 @@
+package org.minecraftoss.catacomb.account.request;
+
+import org.minecraftoss.catacomb.account.Account;
+import org.minecraftoss.catacomb.account.AccountContext;
+import org.minecraftoss.catacomb.account.AccountManager;
+
+import java.util.UUID;
+
+/**
+ * The context of an {@link Account} request.
+ *
+ * When requesting an account, a {@link UUID} and a {@link RequestContext}
+ * is required. Depending on the context, the {@link AccountContext} associated with
+ * the returned account might vary.
+ *
+ * @see AccountManager#getAccount(RequestContext)
+ * @see Account#getAccountContext()
+ */
+public interface RequestContext {
+
+ static RequestContext rawRequest(UUID accountId, String requestId) {
+ return new RequestContextImpl(accountId, requestId);
+ }
+
+ /**
+ * The identifier used to identify the request. For example, this
+ * could be a plugin's name.
+ *
+ * @return the identifier of this context.
+ */
+ String getRequesterIdentifier(); // TODO maybe use something like a "hierarchical key" (similar to permissions) or namespaced
+
+ UUID getAccountIdentifier();
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/request/RequestContextFactory.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/request/RequestContextFactory.java
new file mode 100644
index 0000000..e53a193
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/request/RequestContextFactory.java
@@ -0,0 +1,8 @@
+package org.minecraftoss.catacomb.account.request;
+
+import java.util.UUID;
+
+public interface RequestContextFactory {
+
+ RequestContext fromAccountIdentifier(UUID accountIdentifier);
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/request/RequestContextImpl.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/request/RequestContextImpl.java
new file mode 100644
index 0000000..3155193
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/account/request/RequestContextImpl.java
@@ -0,0 +1,42 @@
+package org.minecraftoss.catacomb.account.request;
+
+import java.util.UUID;
+
+public class RequestContextImpl implements RequestContext {
+
+ private final UUID accountIdentifier;
+ private final String requestIdentifier;
+
+ protected RequestContextImpl(UUID accountIdentifier, String requestIdentifier) {
+ this.accountIdentifier = accountIdentifier;
+ this.requestIdentifier = requestIdentifier;
+ }
+
+ @Override
+ public UUID getAccountIdentifier() {
+ return this.accountIdentifier;
+ }
+
+ @Override
+ public String getRequesterIdentifier() {
+ return this.requestIdentifier;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof RequestContextImpl)) return false;
+
+ RequestContextImpl that = (RequestContextImpl) o;
+
+ if (!accountIdentifier.equals(that.accountIdentifier)) return false;
+ return requestIdentifier.equals(that.requestIdentifier);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = accountIdentifier.hashCode();
+ result = 31 * result + requestIdentifier.hashCode();
+ return result;
+ }
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/currency/Currency.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/currency/Currency.java
new file mode 100644
index 0000000..994a497
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/currency/Currency.java
@@ -0,0 +1,5 @@
+package org.minecraftoss.catacomb.currency;
+
+public interface Currency {
+ // TODO
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/Transaction.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/Transaction.java
new file mode 100644
index 0000000..5f86964
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/Transaction.java
@@ -0,0 +1,25 @@
+package org.minecraftoss.catacomb.transaction;
+
+import org.minecraftoss.catacomb.currency.Currency;
+import org.minecraftoss.catacomb.account.Account;
+
+import java.math.BigDecimal;
+import java.util.HashSet;
+import java.util.Set;
+
+public interface Transaction {
+
+ static Transaction of(Account accountFrom, Account accountTo, Currency currency, BigDecimal amount, Set contexts) {
+ return new TransactionImpl(accountFrom, accountTo, currency, amount, new HashSet<>(contexts));
+ }
+
+ Account getAccountFrom();
+
+ Account getAccountTo();
+
+ Currency getCurrency();
+
+ BigDecimal getAmount();
+
+ Set getContexts();
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/TransactionCondition.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/TransactionCondition.java
new file mode 100644
index 0000000..94728a5
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/TransactionCondition.java
@@ -0,0 +1,67 @@
+package org.minecraftoss.catacomb.transaction;
+
+import java.util.function.Predicate;
+
+/**
+ * A TransactionCondition checks if a {@link Transaction} is valid or not.
+ *
+ * As an example, if the balance of an account might not be below 0,
+ * a TransactionCondition can be used to check if the {@link Transaction}
+ * fulfills that requirement.
+ */
+@FunctionalInterface
+public interface TransactionCondition extends Predicate {
+
+ static TransactionCondition allOf(Iterable extends TransactionCondition> conditions) {
+ return transaction -> {
+ for (TransactionCondition condition : conditions) {
+ if (!condition.isFulfilled(transaction)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ }
+
+ static TransactionCondition anyOf(Iterable extends TransactionCondition> conditions) {
+ return transaction -> {
+ for (TransactionCondition condition : conditions) {
+ if (condition.isFulfilled(transaction)) {
+ return true;
+ }
+ }
+ return false;
+ };
+ }
+
+ static TransactionCondition always() {
+ return transaction -> true;
+ }
+
+ static TransactionCondition never() {
+ return transaction -> false;
+ }
+
+ boolean isFulfilled(Transaction transaction);
+
+ @Override
+ default boolean test(Transaction transaction) {
+ return isFulfilled(transaction);
+ }
+
+ default TransactionCondition negate() {
+ return transaction -> !isFulfilled(transaction);
+ }
+
+ default TransactionCondition and(TransactionCondition other) {
+ return transaction -> isFulfilled(transaction) && other.isFulfilled(transaction);
+ }
+
+ default TransactionCondition or(TransactionCondition other) {
+ return transaction -> isFulfilled(transaction) || other.isFulfilled(transaction);
+ }
+
+ default TransactionCondition xor(TransactionCondition other) {
+ return transaction -> isFulfilled(transaction) ^ other.isFulfilled(transaction);
+ }
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/TransactionContext.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/TransactionContext.java
new file mode 100644
index 0000000..5998460
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/TransactionContext.java
@@ -0,0 +1,12 @@
+package org.minecraftoss.catacomb.transaction;
+
+public interface TransactionContext {
+
+ static TransactionContext of(String key, String value) {
+ return new TransactionContextImpl(key, value);
+ }
+
+ String getKey();
+
+ String getValue();
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/TransactionContextImpl.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/TransactionContextImpl.java
new file mode 100644
index 0000000..64cdb1d
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/TransactionContextImpl.java
@@ -0,0 +1,39 @@
+package org.minecraftoss.catacomb.transaction;
+
+class TransactionContextImpl implements TransactionContext {
+ private final String key;
+ private final String value;
+
+ TransactionContextImpl(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ @Override
+ public String getKey() {
+ return this.key;
+ }
+
+ @Override
+ public String getValue() {
+ return this.value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TransactionContextImpl)) return false;
+
+ TransactionContextImpl that = (TransactionContextImpl) o;
+
+ if (!key.equals(that.key)) return false;
+ return value.equals(that.value);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = key.hashCode();
+ result = 31 * result + value.hashCode();
+ return result;
+ }
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/TransactionImpl.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/TransactionImpl.java
new file mode 100644
index 0000000..5fbd502
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/TransactionImpl.java
@@ -0,0 +1,73 @@
+package org.minecraftoss.catacomb.transaction;
+
+import org.minecraftoss.catacomb.currency.Currency;
+import org.minecraftoss.catacomb.account.Account;
+
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.Set;
+
+class TransactionImpl implements Transaction {
+ private final Account accountFrom;
+ private final Account accountTo;
+ private final Currency currency;
+ private final BigDecimal amount;
+ private final Set contexts;
+
+ TransactionImpl(Account accountFrom, Account accountTo, Currency currency, BigDecimal amount, Set contexts) {
+ this.accountFrom = accountFrom;
+ this.accountTo = accountTo;
+ this.currency = currency;
+ this.amount = amount;
+ this.contexts = Collections.unmodifiableSet(contexts);
+ }
+
+ @Override
+ public Account getAccountFrom() {
+ return this.accountFrom;
+ }
+
+ @Override
+ public Account getAccountTo() {
+ return this.accountTo;
+ }
+
+ @Override
+ public Currency getCurrency() {
+ return this.currency;
+ }
+
+ @Override
+ public BigDecimal getAmount() {
+ return this.amount;
+ }
+
+ @Override
+ public Set getContexts() {
+ return this.contexts;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TransactionImpl)) return false;
+
+ TransactionImpl that = (TransactionImpl) o;
+
+ if (!accountFrom.equals(that.accountFrom)) return false;
+ if (!accountTo.equals(that.accountTo)) return false;
+ if (!currency.equals(that.currency)) return false;
+ if (!amount.equals(that.amount)) return false;
+ return contexts.equals(that.contexts);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = accountFrom.hashCode();
+ result = 31 * result + accountTo.hashCode();
+ result = 31 * result + currency.hashCode();
+ result = 31 * result + amount.hashCode();
+ result = 31 * result + contexts.hashCode();
+ return result;
+ }
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/result/BuiltinTransactionResultState.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/result/BuiltinTransactionResultState.java
new file mode 100644
index 0000000..b329711
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/result/BuiltinTransactionResultState.java
@@ -0,0 +1,12 @@
+package org.minecraftoss.catacomb.transaction.result;
+
+public enum BuiltinTransactionResultState implements TransactionResultState {
+ ACCEPTED,
+ DECLINED,
+ FAILED;
+
+ @Override
+ public BuiltinTransactionResultState getParent() {
+ return this;
+ }
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/result/TransactionResult.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/result/TransactionResult.java
new file mode 100644
index 0000000..26a20b0
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/result/TransactionResult.java
@@ -0,0 +1,46 @@
+package org.minecraftoss.catacomb.transaction.result;
+
+import org.minecraftoss.catacomb.transaction.Transaction;
+
+public class TransactionResult {
+ private final TransactionResultState> transactionResultState;
+ private final Transaction transaction;
+
+ private TransactionResult(TransactionResultState> transactionResultState, Transaction transaction) {
+ this.transactionResultState = transactionResultState;
+ this.transaction = transaction;
+ }
+
+ public static TransactionResult of(TransactionResultState> transactionResultState, Transaction transaction) {
+ return new TransactionResult(transactionResultState, transaction);
+ }
+
+ public TransactionResultState> getTransactionResultState() {
+ return this.transactionResultState;
+ }
+
+ public Transaction getTransaction() {
+ return this.transaction;
+ }
+
+ // TODO track changes?
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TransactionResult)) return false;
+
+ TransactionResult that = (TransactionResult) o;
+
+ if (!transactionResultState.equals(that.transactionResultState)) return false;
+ return transaction.equals(that.transaction);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = transactionResultState.hashCode();
+ result = 31 * result + transaction.hashCode();
+ return result;
+ }
+}
diff --git a/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/result/TransactionResultState.java b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/result/TransactionResultState.java
new file mode 100644
index 0000000..d21cea1
--- /dev/null
+++ b/catacomb-common-api/src/main/java/org/minecraftoss/catacomb/transaction/result/TransactionResultState.java
@@ -0,0 +1,6 @@
+package org.minecraftoss.catacomb.transaction.result;
+
+public interface TransactionResultState & TransactionResultState> {
+
+ BuiltinTransactionResultState getParent();
+}
diff --git a/catacomb-sponge-api/src/main/java/org/minecraftoss/catacomb/sponge/SpongeRequestContext.java b/catacomb-sponge-api/src/main/java/org/minecraftoss/catacomb/sponge/SpongeRequestContext.java
new file mode 100644
index 0000000..09a9441
--- /dev/null
+++ b/catacomb-sponge-api/src/main/java/org/minecraftoss/catacomb/sponge/SpongeRequestContext.java
@@ -0,0 +1,20 @@
+package org.minecraftoss.catacomb.sponge;
+
+import org.minecraftoss.catacomb.account.request.RequestContext;
+import org.spongepowered.api.plugin.PluginContainer;
+
+import java.util.UUID;
+
+public interface SpongeRequestContext extends RequestContext {
+
+ static SpongeRequestContext pluginRequest(UUID accountId, PluginContainer plugin) {
+ return new SpongeRequestContextImpl(accountId, plugin);
+ }
+
+ PluginContainer getPlugin();
+
+ @Override
+ default String getRequesterIdentifier() {
+ return getPlugin().getId();
+ }
+}
diff --git a/catacomb-sponge-api/src/main/java/org/minecraftoss/catacomb/sponge/SpongeRequestContextFactory.java b/catacomb-sponge-api/src/main/java/org/minecraftoss/catacomb/sponge/SpongeRequestContextFactory.java
new file mode 100644
index 0000000..49831c1
--- /dev/null
+++ b/catacomb-sponge-api/src/main/java/org/minecraftoss/catacomb/sponge/SpongeRequestContextFactory.java
@@ -0,0 +1,24 @@
+package org.minecraftoss.catacomb.sponge;
+
+import org.minecraftoss.catacomb.account.request.RequestContext;
+import org.minecraftoss.catacomb.account.request.RequestContextFactory;
+import org.spongepowered.api.plugin.PluginContainer;
+
+import java.util.UUID;
+
+public class SpongeRequestContextFactory implements RequestContextFactory {
+ private final PluginContainer plugin;
+
+ SpongeRequestContextFactory(PluginContainer plugin) {
+ this.plugin = plugin;
+ }
+
+ static SpongeRequestContextFactory forPlugin(PluginContainer plugin) {
+ return new SpongeRequestContextFactory(plugin);
+ }
+
+ @Override
+ public RequestContext fromAccountIdentifier(UUID accountIdentifier) {
+ return SpongeRequestContext.pluginRequest(accountIdentifier, plugin);
+ }
+}
diff --git a/catacomb-sponge-api/src/main/java/org/minecraftoss/catacomb/sponge/SpongeRequestContextImpl.java b/catacomb-sponge-api/src/main/java/org/minecraftoss/catacomb/sponge/SpongeRequestContextImpl.java
new file mode 100644
index 0000000..915399c
--- /dev/null
+++ b/catacomb-sponge-api/src/main/java/org/minecraftoss/catacomb/sponge/SpongeRequestContextImpl.java
@@ -0,0 +1,38 @@
+package org.minecraftoss.catacomb.sponge;
+
+import org.minecraftoss.catacomb.account.request.RequestContextImpl;
+import org.spongepowered.api.plugin.PluginContainer;
+
+import java.util.UUID;
+
+class SpongeRequestContextImpl extends RequestContextImpl implements SpongeRequestContext {
+ private final PluginContainer plugin;
+
+ SpongeRequestContextImpl(UUID accountIdentifier, PluginContainer requestIdentifier) {
+ super(accountIdentifier, requestIdentifier.getId());
+ this.plugin = requestIdentifier;
+ }
+
+ @Override
+ public PluginContainer getPlugin() {
+ return this.plugin;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SpongeRequestContextImpl)) return false;
+ if (!super.equals(o)) return false;
+
+ SpongeRequestContextImpl that = (SpongeRequestContextImpl) o;
+
+ return plugin.equals(that.plugin);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + plugin.hashCode();
+ return result;
+ }
+}