
NoieDigitalSystem API for Minecraft
A Next-Generation Economy Protocol for Minecraft Server Ecosystems
NDS-API is a high-performance, async-first economy protocol designed as a modern replacement for Vault. Built with native Folia support, PostgreSQL JSONB persistence, Redis-based cross-server synchronization, and BigDecimal precision for all monetary operations.
Architecture Overview
┌─────────────────────────────────────────────────────────────────────────────┐
│ NDS-API Architecture │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Your Plugin │ │ Your Plugin │ │ Your Plugin │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │ │
│ └──────────────────────┼──────────────────────┘ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ NDS-API Plugin Layer │ │
│ │ ┌─────────────────────┐ ┌─────────────────────────────────────┐ │ │
│ │ │ NdsApiPlugin │───▶│ Shared Configuration Manager │ │ │
│ │ │ (Entry Point) │ │ (/plugins/NoieDigitalSystem/) │ │ │
│ │ └─────────────────────┘ └─────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ Core Manager Layer │ │
│ │ ┌─────────────────────┐ ┌─────────────────────────────────────┐ │ │
│ │ │ PlayerDigitalManager│ │ DigitalManager (Server Digitals) │ │ │
│ │ │ (Player Currencies) │ │ + GlobalDigitalManager │ │ │
│ │ └──────────┬──────────┘ └──────────────────┬──────────────────┘ │ │
│ │ │ │ │ │
│ │ └───────────────┬───────────────────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Repository Layer (Facade) │ │ │
│ │ │ • Cache Manager (In-Memory + Redis) │ │ │
│ │ │ • JSONB Parser (PostgreSQL Native) │ │ │
│ │ │ • Transaction Pipeline (Optimistic Locking) │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────┼──────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ PostgreSQL │ │ Redis │ │ Virtual │ │
│ │ (JSONB) │ │ (Pub/Sub) │ │ Threads │ │
│ │ Primary Store │ │ Cross-Server │ │ (Java 21) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Key Features
| Feature | Description |
|---|---|
| Async-First Design | All database operations return CompletableFuture<T> for non-blocking I/O. Zero main-thread blocking. |
| Native Folia Support | Automatic detection with RegionScheduler and GlobalRegionScheduler integration. |
| PostgreSQL JSONB Backend | Flexible schema with optimistic locking (version column) for data integrity. |
| Redis Cross-Server Sync | Real-time state synchronization via Pub/Sub channels for multi-server deployments. |
| BigDecimal Precision | All monetary operations use java.math.BigDecimal. No floating-point rounding errors. |
| Virtual Threads (Java 21) | Leverages Project Loom's virtual threads for efficient I/O-bound operations. |
| Shared Configuration | Single config.yml shared across NDS ecosystem plugins at /plugins/NoieDigitalSystem/. |
| Facade Pattern Architecture | Clean separation of concerns with manager facades delegating to specialized components. |
System Requirements
| Component | Minimum Version | Recommended | Notes |
|---|---|---|---|
| Java | 21 | 21+ | Virtual threads require Java 21+ |
| Server | Paper 1.21.4 | Paper/Folia 1.21.4+ | Folia for multi-threaded region support |
| PostgreSQL | 12 | 15+ | JSONB and optimistic locking support |
| Redis | 6.0 | 7.0+ | Optional; required for cross-server sync |
| Memory | 512MB | 1GB+ | Varies with player count and cache size |
Installation
Step 1: Download
Download the latest NoieDigitalSystem-API-x.x.x.jar from GitHub Releases.
Step 2: Deploy
Place the JAR file in your server's plugins/ directory.
Step 3: Configure
The plugin uses a shared configuration architecture. Configuration is stored at:
plugins/NoieDigitalSystem/config.yml
Minimal Required Configuration:
# PostgreSQL Connection (Required)
postgresql:
host: "localhost"
port: 5432
database: "minecraft"
username: "postgres"
password: "your_secure_password"
poolSize: 50
# Redis Connection (Optional - for cross-server sync)
redis:
enabled: true
host: "localhost"
port: 6379
password: ""
timeout: 3000
Step 4: Verify
Restart the server and check for successful initialization in the console:
[NoieDigitalSystem-API] Detected server type: Folia
[NoieDigitalSystem-API] Using shared configuration at: /plugins/NoieDigitalSystem/config.yml
[NoieDigitalSystem-API] Digital Manager initialization completed.
[NoieDigitalSystem-API] NDS API Plugin enabled successfully!
Developer Integration
Dependency Setup
Gradle (Kotlin DSL)
repositories {
mavenCentral()
}
dependencies {
compileOnly("io.github.misty4119:noiedigitalsystem-api:2.0.0")
}
Gradle (Groovy DSL)
repositories {
mavenCentral()
}
dependencies {
compileOnly 'io.github.misty4119:noiedigitalsystem-api:2.0.0'
}
Maven
<dependency>
<groupId>io.github.misty4119</groupId>
<artifactId>noiedigitalsystem-api</artifactId>
<version>2.0.0</version>
<scope>provided</scope>
</dependency>
Plugin Configuration
paper-plugin.yml (Paper 1.19.4+)
name: YourPlugin
version: '1.0.0'
main: com.example.yourplugin.YourPlugin
api-version: '1.21'
dependencies:
server:
NoieDigitalSystem-API:
load: BEFORE
required: true
plugin.yml (Legacy)
name: YourPlugin
version: 1.0.0
main: com.example.yourplugin.YourPlugin
api-version: '1.21'
depend: [NoieDigitalSystem-API]
API Usage
Obtaining the API Instance
import noie.linmimeng.noiedigitalsystem.api.plugin.NdsApiPlugin;
import noie.linmimeng.noiedigitalsystem.manager.DigitalManager;
import noie.linmimeng.noiedigitalsystem.manager.PlayerDigitalManager;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
public class YourPlugin extends JavaPlugin {
private PlayerDigitalManager playerDigitalManager;
private DigitalManager digitalManager;
@Override
public void onEnable() {
// Obtain the NDS-API plugin instance
Plugin ndsPlugin = getServer().getPluginManager().getPlugin("NoieDigitalSystem-API");
// Validate plugin availability and type
if (ndsPlugin == null) {
getLogger().severe("NoieDigitalSystem-API is not installed!");
getServer().getPluginManager().disablePlugin(this);
return;
}
if (!(ndsPlugin instanceof NdsApiPlugin)) {
getLogger().severe("NoieDigitalSystem-API is not the expected type!");
getServer().getPluginManager().disablePlugin(this);
return;
}
// Cast and obtain managers
NdsApiPlugin ndsApi = (NdsApiPlugin) ndsPlugin;
this.playerDigitalManager = ndsApi.getPlayerDigitalManager();
this.digitalManager = ndsApi.getDigitalManager();
getLogger().info("Successfully integrated with NoieDigitalSystem-API v2.0");
}
}
Player Digital Operations
All player operations are asynchronous and return CompletableFuture<T>.
import java.math.BigDecimal;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
// ═══════════════════════════════════════════════════════════════════════════
// GET BALANCE
// ═══════════════════════════════════════════════════════════════════════════
CompletableFuture<BigDecimal> balanceFuture = playerDigitalManager.getDigital(playerUUID, "coins");
balanceFuture.thenAccept(balance -> {
// Executes on async thread - DO NOT call Bukkit API directly here
getLogger().info("Player balance: " + balance.toPlainString());
});
// ═══════════════════════════════════════════════════════════════════════════
// GIVE DIGITAL (Add to balance)
// ═══════════════════════════════════════════════════════════════════════════
BigDecimal amountToGive = new BigDecimal("100.50");
playerDigitalManager.giveDigital(playerUUID, "coins", amountToGive)
.thenRun(() -> {
getLogger().info("Successfully credited " + amountToGive + " coins");
})
.exceptionally(ex -> {
getLogger().severe("Failed to credit coins: " + ex.getMessage());
return null;
});
// ═══════════════════════════════════════════════════════════════════════════
// TAKE DIGITAL (Subtract from balance)
// Returns false if insufficient balance
// ═══════════════════════════════════════════════════════════════════════════
BigDecimal amountToTake = new BigDecimal("50.00");
playerDigitalManager.takeDigital(playerUUID, "coins", amountToTake)
.thenAccept(success -> {
if (success) {
getLogger().info("Successfully debited " + amountToTake + " coins");
} else {
getLogger().warning("Insufficient balance for debit operation");
}
});
// ═══════════════════════════════════════════════════════════════════════════
// SET DIGITAL (Absolute value assignment)
// ═══════════════════════════════════════════════════════════════════════════
BigDecimal newBalance = new BigDecimal("1000.00");
playerDigitalManager.setDigital(playerUUID, "coins", newBalance)
.thenRun(() -> {
getLogger().info("Balance set to " + newBalance);
});
// ═══════════════════════════════════════════════════════════════════════════
// GET ALL PLAYER DIGITALS
// ═══════════════════════════════════════════════════════════════════════════
playerDigitalManager.getPlayerDigitals(playerUUID)
.thenAccept(digitals -> {
digitals.forEach((name, value) -> {
getLogger().info(name + ": " + value);
});
});
Server Digital Operations
Server digitals are server-wide variables (e.g., world boss HP, event counters).
// ═══════════════════════════════════════════════════════════════════════════
// GET SERVER DIGITAL
// Note: This is a synchronous operation that reads from cache
// ═══════════════════════════════════════════════════════════════════════════
BigDecimal worldBossHp = digitalManager.getDigitalMap().get("world_boss_hp");
// ═══════════════════════════════════════════════════════════════════════════
// MODIFY SERVER DIGITAL (Async operations)
// ═══════════════════════════════════════════════════════════════════════════
digitalManager.giveDigital("world_boss_hp", new BigDecimal("1000"))
.thenRun(() -> getLogger().info("World boss HP increased"));
digitalManager.takeDigital("world_boss_hp", new BigDecimal("500"))
.thenAccept(success -> {
if (success) {
getLogger().info("World boss HP decreased");
}
});
digitalManager.setDigital("world_boss_hp", new BigDecimal("10000"))
.thenRun(() -> getLogger().info("World boss HP reset"));
Global Player Digitals
Global digitals are currencies available to all players (e.g., "coins", "gems"). Once created, every player automatically has access to this digital type.
// ═══════════════════════════════════════════════════════════════════════════
// CREATE GLOBAL DIGITAL
// Parameters: name, initialAmount, limit (-1 = no limit)
// ═══════════════════════════════════════════════════════════════════════════
digitalManager.createGlobalDigital("gems", 0.0, -1.0)
.thenRun(() -> {
getLogger().info("Created global digital 'gems' - available to all players");
});
// ═══════════════════════════════════════════════════════════════════════════
// CHECK IF GLOBAL DIGITAL EXISTS
// ═══════════════════════════════════════════════════════════════════════════
digitalManager.isGlobalDigitalExists("gems")
.thenAccept(exists -> {
if (exists) {
getLogger().info("'gems' is a registered global digital");
}
});
// ═══════════════════════════════════════════════════════════════════════════
// GET ALL GLOBAL DIGITALS
// ═══════════════════════════════════════════════════════════════════════════
digitalManager.getGlobalDigitals()
.thenAccept(globals -> {
globals.forEach((name, defaultAmount) -> {
getLogger().info("Global: " + name + " (default: " + defaultAmount + ")");
});
});
Error Handling Patterns
All async operations should implement proper exception handling:
playerDigitalManager.getDigital(playerUUID, "coins")
.thenAccept(balance -> {
// Success path
processBalance(balance);
})
.exceptionally(ex -> {
// Error path - database connection, timeout, etc.
getLogger().severe("Database operation failed: " + ex.getMessage());
// Log full stack trace for debugging
if (getConfig().getBoolean("debug", false)) {
ex.printStackTrace();
}
// Return null for Void-returning futures
return null;
});
// ═══════════════════════════════════════════════════════════════════════════
// CHAINED OPERATIONS WITH ERROR PROPAGATION
// ═══════════════════════════════════════════════════════════════════════════
playerDigitalManager.getDigital(playerUUID, "coins")
.thenCompose(balance -> {
if (balance.compareTo(new BigDecimal("100")) >= 0) {
return playerDigitalManager.takeDigital(playerUUID, "coins", new BigDecimal("100"));
}
return CompletableFuture.completedFuture(false);
})
.thenAccept(success -> {
if (success) {
// Proceed with purchase logic
}
})
.exceptionally(ex -> {
getLogger().severe("Transaction failed: " + ex.getMessage());
return null;
});
Core API Reference
PlayerDigitalManager
Manages player-specific digitals (currencies, points, custom variables).
| Method Signature | Return Type | Description |
|---|---|---|
getDigital(UUID playerUUID, String digitalName) | CompletableFuture<BigDecimal> | Retrieves the player's balance for the specified digital. Returns BigDecimal.ZERO if not found. |
setDigital(UUID playerUUID, String digitalName, BigDecimal amount) | CompletableFuture<Void> | Sets the player's balance to an exact value. Auto-creates the digital if it doesn't exist. |
giveDigital(UUID playerUUID, String digitalName, BigDecimal amount) | CompletableFuture<Void> | Adds the specified amount to the player's balance. Respects configured limits. |
takeDigital(UUID playerUUID, String digitalName, BigDecimal amount) | CompletableFuture<Boolean> | Subtracts the amount from balance. Returns false if insufficient funds. Atomic operation. |
getLimit(UUID playerUUID, String digitalName) | CompletableFuture<BigDecimal> | Gets the maximum balance limit. Returns null if no limit is set. |
setLimit(UUID playerUUID, String digitalName, BigDecimal limit) | CompletableFuture<Void> | Sets the maximum balance limit. Pass null to remove the limit. |
getPlayerDigitals(UUID playerUUID) | CompletableFuture<Map<String, Double>> | Returns all digitals and their values for a player. |
getPlayerDigitalLimits(UUID playerUUID) | CompletableFuture<Map<String, Double>> | Returns all digitals and their limits for a player. |
isDigitalExists(UUID playerUUID, String digitalName) | CompletableFuture<Boolean> | Checks if the digital exists for the player (includes global digitals). |
createDigital(UUID playerUUID, String digitalName, BigDecimal initialAmount, BigDecimal limit) | CompletableFuture<Void> | Creates a player-specific digital (rarely needed; auto-created on first use). |
removeDigital(UUID playerUUID, String digitalName) | CompletableFuture<Void> | Removes a player-specific digital entry. |
grantDigital(UUID playerUUID, String digitalName, BigDecimal amount) | CompletableFuture<Void> | Grants a special digital to a player (creates if not exists). |
revokeDigital(UUID playerUUID, String digitalName) | CompletableFuture<Void> | Revokes/removes a digital from a player. |
loadPlayerData(UUID playerUUID) | CompletableFuture<Void> | Pre-loads player data into cache. Called automatically on player join. |
unloadPlayerData(UUID playerUUID) | void | Removes player data from memory cache. Called automatically on player quit. |
clearCache() | void | Clears all cached player data. Use with caution. |
DigitalManager
Manages server-level digitals and global player digital definitions.
| Method Signature | Return Type | Description |
|---|---|---|
getDigitalMap() | Map<String, BigDecimal> | Returns all server digitals and their current values (synchronous cache read). |
getDigitalLimitMap() | Map<String, BigDecimal> | Returns all server digitals and their limits. |
setDigital(String digitalName, BigDecimal amount) | CompletableFuture<Void> | Sets a server digital to an exact value. |
giveDigital(String digitalName, BigDecimal amount) | CompletableFuture<Void> | Adds to a server digital's value. |
takeDigital(String digitalName, BigDecimal amount) | CompletableFuture<Boolean> | Subtracts from a server digital. Returns false if insufficient. |
removeDigital(String digitalName) | CompletableFuture<Void> | Removes a server digital entirely. |
getLimit(String digitalName) | BigDecimal | Gets the limit for a server digital (synchronous). |
setLimit(String digitalName, BigDecimal limit) | CompletableFuture<Void> | Sets the limit for a server digital. |
isDigitalExists(String digitalName) | CompletableFuture<Boolean> | Checks if a server digital exists. |
renameDigital(String oldName, String newName) | CompletableFuture<Void> | Renames a server digital. Updates cache and Redis. |
Global Digital Methods
| Method Signature | Return Type | Description |
|---|---|---|
createGlobalDigital(String digitalName, double initialAmount, double limit) | CompletableFuture<Void> | Creates a global digital available to all players. Pass -1 for no limit. |
isGlobalDigitalExists(String digitalName) | CompletableFuture<Boolean> | Checks if a global digital definition exists. |
removeGlobalDigital(String digitalName) | CompletableFuture<Void> | Removes a global digital definition. Does not delete player data. |
getGlobalDigitals() | CompletableFuture<Map<String, BigDecimal>> | Returns all global digital definitions. |
getGlobalDigitalAmount(String digitalName) | CompletableFuture<BigDecimal> | Gets the default amount for a global digital. |
getGlobalDigitalLimit(String digitalName) | CompletableFuture<BigDecimal> | Gets the limit for a global digital. |
Thread Safety & Concurrency
Async-First Architecture
Critical Rule: All NDS-API methods that perform I/O operations are asynchronous and return CompletableFuture<T>.
// ╔═══════════════════════════════════════════════════════════════════════════╗
// ║ CORRECT: Non-blocking async pattern ║
// ╚═══════════════════════════════════════════════════════════════════════════╝
playerDigitalManager.getDigital(playerUUID, "coins")
.thenAccept(balance -> {
// This callback executes on a virtual thread (Java 21)
// Safe for I/O operations, NOT safe for Bukkit API calls
processBalanceAsync(balance);
});
// ╔═══════════════════════════════════════════════════════════════════════════╗
// ║ INCORRECT: Blocking the main thread ║
// ╚═══════════════════════════════════════════════════════════════════════════╝
// ⚠️ NEVER DO THIS - Will cause server lag/freeze
BigDecimal balance = playerDigitalManager.getDigital(playerUUID, "coins").get();
Bukkit/Paper Thread Model
When you need to interact with the Bukkit API from an async callback, you must schedule the operation on the main thread:
playerDigitalManager.getDigital(playerUUID, "coins")
.thenAccept(balance -> {
// Schedule Bukkit API calls on the main thread
Bukkit.getScheduler().runTask(plugin, () -> {
Player player = Bukkit.getPlayer(playerUUID);
if (player != null && player.isOnline()) {
player.sendMessage("Your balance: " + balance.toPlainString());
}
});
});
Folia Region Scheduler
For Folia servers, use the entity/region scheduler instead of the global scheduler:
playerDigitalManager.getDigital(playerUUID, "coins")
.thenAccept(balance -> {
// For Folia: Use the entity's scheduler
Player player = Bukkit.getPlayer(playerUUID);
if (player != null && player.isOnline()) {
player.getScheduler().run(plugin, scheduledTask -> {
player.sendMessage("Your balance: " + balance.toPlainString());
}, null);
}
});
Thread Model Summary:
| Environment | Callback Thread | Bukkit API Access |
|---|---|---|
| Bukkit/Paper | Virtual Thread (async) | Requires Bukkit.getScheduler().runTask() |
| Folia | Virtual Thread (async) | Requires entity.getScheduler().run() or RegionScheduler |
Data Model
Digital Types
| Type | Scope | Use Case | Example |
|---|---|---|---|
| Player Digital | Per-player | Player-specific currencies/variables | Player's private bank balance |
| Global Player Digital | Definition → All Players | Standard currencies everyone has | coins, gems, points |
| Server Digital | Server-wide | World state, event counters | world_boss_hp, server_event_score |
JSONB Structure
Player Data Example:
{
"coins": 1500.50,
"gems": 42,
"stamina": 100,
"_limits": {
"coins": 1000000,
"stamina": 160
}
}
Server Data Example (uuid = '00000000-0000-0000-0000-000000000000'):
{
"world_boss_hp": 50000,
"event_score": 12500,
"_global_digitals": {
"coins": { "initial": 0, "limit": -1 },
"gems": { "initial": 0, "limit": 9999 }
}
}
Configuration Reference
Complete configuration with all available options:
# ═══════════════════════════════════════════════════════════════════════════
# PostgreSQL Configuration (Required)
# ═══════════════════════════════════════════════════════════════════════════
postgresql:
host: "localhost"
port: 5432
database: "minecraft"
username: "postgres"
password: "your_secure_password"
# Connection pool settings
poolSize: 50 # Maximum connections in pool
connectionTimeout: 30000 # Connection timeout (ms)
idleTimeout: 600000 # Idle connection timeout (ms)
maxLifetime: 1800000 # Maximum connection lifetime (ms)
# ═══════════════════════════════════════════════════════════════════════════
# Redis Configuration (Optional - for cross-server sync)
# ═══════════════════════════════════════════════════════════════════════════
redis:
enabled: true
host: "localhost"
port: 6379
password: ""
timeout: 3000
# Channel prefixes for Pub/Sub
channels:
player: "nds:player:"
server: "nds:server:"
global: "nds:global:"
# ═══════════════════════════════════════════════════════════════════════════
# Digital Definitions
# ═══════════════════════════════════════════════════════════════════════════
digitals:
coins:
display_name: "Coins"
persist_on_zero: false # Delete entry when balance reaches 0
gems:
display_name: "Gems"
persist_on_zero: true # Keep entry even at 0 balance
# ═══════════════════════════════════════════════════════════════════════════
# Vault Integration (Compatibility Layer)
# ═══════════════════════════════════════════════════════════════════════════
vault:
enabled: true
enabled_digitals:
- "coins"
default_currency: "coins"
Protocol Specification
NDS-API is built on a cross-platform protocol specification designed for multi-language SDK support.
Protocol Domains
| Domain | Description |
|---|---|
| Identity | Player/Server/System identity abstraction with UUID-based addressing |
| Asset | Currency/variable definitions with scopes (Player, Global, Server) |
| Event | Append-only event sourcing for immutable audit trails |
| Transaction | Atomic operations with configurable consistency modes |
| Result | Standardized success/failure response envelope |
Protocol Repository
For the full protocol specification, multi-language SDK documentation, and Protocol Buffers definitions:
Migration from Vault
NDS-API includes a Vault compatibility layer for gradual migration.
Enabling Vault Compatibility
vault:
enabled: true
enabled_digitals:
- "coins"
default_currency: "coins"
Migration Strategy
- Install NDS-API alongside your existing Vault provider
- Enable Vault compatibility in NDS-API configuration
- Test thoroughly in a staging environment
- Migrate data using the built-in migration tools
- Remove legacy Vault provider once verification is complete
Troubleshooting
Common Issues
| Symptom | Cause | Solution |
|---|---|---|
NoieDigitalSystem-API not found! | Plugin not installed or load order issue | Ensure JAR is in plugins/ and check paper-plugin.yml dependencies |
| Connection pool exhausted | Too many concurrent database operations | Increase poolSize in config; check for unclosed connections |
| Redis sync not working | Incorrect Redis configuration or firewall | Verify Redis connection; check redis.enabled: true |
ClassCastException on API access | Multiple NDS versions loaded | Ensure only one NDS JAR is in plugins/ |
| Slow performance | Blocking main thread with .get() | Never use .get() on main thread; use async callbacks |
Debug Mode
Enable debug logging for detailed diagnostics:
debug:
enabled: true
log_level: "DEBUG"
log_sql: true
Support
- Issues & Bug Reports: GitHub Issues
- Protocol SDK: nds-api Repository
- Documentation: Wiki
License
Copyright 2024-2026 Noie Linmimeng
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
NoieDigitalSystem — Next-generation economy protocol for Minecraft.
Designed for the future. Built for today.
