{"id":109192,"date":"2026-06-06T13:07:45","date_gmt":"2026-06-06T10:07:45","guid":{"rendered":"https:\/\/vpesports.com\/minecraft\/mods\/nds-api\/"},"modified":"2026-06-06T13:07:45","modified_gmt":"2026-06-06T10:07:45","slug":"nds-api","status":"publish","type":"mod","link":"https:\/\/vpesports.com\/minecraft\/mods\/nds-api\/","title":{"rendered":"NDS-API"},"content":{"rendered":"<div align=\"center\">\n<p># NoieDigitalSystem API for Minecraft<\/p>\n<p>### A Next-Generation Economy Protocol for Minecraft Server Ecosystems<\/p>\n<p>![Paper](https:\/\/papermc.io\/)<br \/>\n![Folia](https:\/\/papermc.io\/software\/folia)<br \/>\n![Java](https:\/\/adoptium.net\/)<br \/>\n![PostgreSQL](https:\/\/www.postgresql.org\/)<br \/>\n![Redis](https:\/\/redis.io\/)<br \/>\n![bStats](https:\/\/bstats.org\/plugin\/bukkit\/NoieDigitalSystem-API\/28509)<br \/>\n![License](https:\/\/github.com\/Misty4119\/nds-api\/blob\/main\/LICENSE)<\/p>\n<p>**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.<\/p>\n<\/div>\n<p>&#8212;<\/p>\n<p>## Architecture Overview<\/p>\n<p>&#8220;`<br \/>\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510<br \/>\n\u2502                           NDS-API Architecture                              \u2502<br \/>\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br \/>\n\u2502                                                                             \u2502<br \/>\n\u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510         \u2502<br \/>\n\u2502  \u2502   Your Plugin   \u2502    \u2502   Your Plugin   \u2502    \u2502   Your Plugin   \u2502         \u2502<br \/>\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518         \u2502<br \/>\n\u2502           \u2502                      \u2502                      \u2502                   \u2502<br \/>\n\u2502           \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518                   \u2502<br \/>\n\u2502                                  \u25bc                                          \u2502<br \/>\n\u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510  \u2502<br \/>\n\u2502  \u2502                        NDS-API Plugin Layer                           \u2502  \u2502<br \/>\n\u2502  \u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510   \u2502  \u2502<br \/>\n\u2502  \u2502  \u2502  NdsApiPlugin       \u2502\u2500\u2500\u2500\u25b6\u2502  Shared Configuration Manager       \u2502   \u2502  \u2502<br \/>\n\u2502  \u2502  \u2502  (Entry Point)      \u2502    \u2502  (\/plugins\/NoieDigitalSystem\/)      \u2502   \u2502  \u2502<br \/>\n\u2502  \u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518   \u2502  \u2502<br \/>\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518  \u2502<br \/>\n\u2502                                  \u2502                                          \u2502<br \/>\n\u2502                                  \u25bc                                          \u2502<br \/>\n\u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510  \u2502<br \/>\n\u2502  \u2502                          Core Manager Layer                           \u2502  \u2502<br \/>\n\u2502  \u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510   \u2502  \u2502<br \/>\n\u2502  \u2502  \u2502 PlayerDigitalManager\u2502    \u2502 DigitalManager (Server Digitals)    \u2502   \u2502  \u2502<br \/>\n\u2502  \u2502  \u2502 (Player Currencies) \u2502    \u2502 + GlobalDigitalManager              \u2502   \u2502  \u2502<br \/>\n\u2502  \u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518   \u2502  \u2502<br \/>\n\u2502  \u2502             \u2502                                   \u2502                      \u2502  \u2502<br \/>\n\u2502  \u2502             \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518                      \u2502  \u2502<br \/>\n\u2502  \u2502                             \u25bc                                          \u2502  \u2502<br \/>\n\u2502  \u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510   \u2502  \u2502<br \/>\n\u2502  \u2502  \u2502                    Repository Layer (Facade)                    \u2502   \u2502  \u2502<br \/>\n\u2502  \u2502  \u2502  \u2022 Cache Manager (In-Memory + Redis)                            \u2502   \u2502  \u2502<br \/>\n\u2502  \u2502  \u2502  \u2022 JSONB Parser (PostgreSQL Native)                             \u2502   \u2502  \u2502<br \/>\n\u2502  \u2502  \u2502  \u2022 Transaction Pipeline (Optimistic Locking)                    \u2502   \u2502  \u2502<br \/>\n\u2502  \u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518   \u2502  \u2502<br \/>\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518  \u2502<br \/>\n\u2502                                  \u2502                                          \u2502<br \/>\n\u2502           \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510                   \u2502<br \/>\n\u2502           \u25bc                      \u25bc                      \u25bc                   \u2502<br \/>\n\u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510         \u2502<br \/>\n\u2502  \u2502   PostgreSQL    \u2502    \u2502     Redis       \u2502    \u2502   Virtual       \u2502         \u2502<br \/>\n\u2502  \u2502   (JSONB)       \u2502    \u2502   (Pub\/Sub)     \u2502    \u2502   Threads       \u2502         \u2502<br \/>\n\u2502  \u2502   Primary Store \u2502    \u2502   Cross-Server  \u2502    \u2502   (Java 21)     \u2502         \u2502<br \/>\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518         \u2502<br \/>\n\u2502                                                                             \u2502<br \/>\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<br \/>\n&#8220;`<\/p>\n<p>&#8212;<\/p>\n<p>## Key Features<\/p>\n<p>| Feature | Description |<br \/>\n|&#8212;&#8212;&#8212;|&#8212;&#8212;&#8212;&#8212;-|<br \/>\n| **Async-First Design** | All database operations return `CompletableFuture<T>` for non-blocking I\/O. Zero main-thread blocking. |<br \/>\n| **Native Folia Support** | Automatic detection with `RegionScheduler` and `GlobalRegionScheduler` integration. |<br \/>\n| **PostgreSQL JSONB Backend** | Flexible schema with optimistic locking (`version` column) for data integrity. |<br \/>\n| **Redis Cross-Server Sync** | Real-time state synchronization via Pub\/Sub channels for multi-server deployments. |<br \/>\n| **BigDecimal Precision** | All monetary operations use `java.math.BigDecimal`. No floating-point rounding errors. |<br \/>\n| **Virtual Threads (Java 21)** | Leverages Project Loom&#8217;s virtual threads for efficient I\/O-bound operations. |<br \/>\n| **Shared Configuration** | Single `config.yml` shared across NDS ecosystem plugins at `\/plugins\/NoieDigitalSystem\/`. |<br \/>\n| **Facade Pattern Architecture** | Clean separation of concerns with manager facades delegating to specialized components. |<\/p>\n<p>&#8212;<\/p>\n<p>## System Requirements<\/p>\n<p>| Component | Minimum Version | Recommended | Notes |<br \/>\n|&#8212;&#8212;&#8212;&#8211;|&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;|&#8212;&#8212;&#8212;&#8212;-|&#8212;&#8212;-|<br \/>\n| **Java** | 21 | 21+ | Virtual threads require Java 21+ |<br \/>\n| **Server** | Paper 1.21.4 | Paper\/Folia 1.21.4+ | Folia for multi-threaded region support |<br \/>\n| **PostgreSQL** | 12 | 15+ | JSONB and optimistic locking support |<br \/>\n| **Redis** | 6.0 | 7.0+ | Optional; required for cross-server sync |<br \/>\n| **Memory** | 512MB | 1GB+ | Varies with player count and cache size |<\/p>\n<p>&#8212;<\/p>\n<p>## Installation<\/p>\n<p>### Step 1: Download<\/p>\n<p>Download the latest `NoieDigitalSystem-API-x.x.x.jar` from GitHub Releases.<\/p>\n<p>### Step 2: Deploy<\/p>\n<p>Place the JAR file in your server&#8217;s `plugins\/` directory.<\/p>\n<p>### Step 3: Configure<\/p>\n<p>The plugin uses a **shared configuration architecture**. Configuration is stored at:<\/p>\n<p>&#8220;`<br \/>\nplugins\/NoieDigitalSystem\/config.yml<br \/>\n&#8220;`<\/p>\n<p>**Minimal Required Configuration:**<\/p>\n<p>&#8220;`yaml<br \/>\n# PostgreSQL Connection (Required)<br \/>\npostgresql:<br \/>\n  host: &#8220;localhost&#8221;<br \/>\n  port: 5432<br \/>\n  database: &#8220;minecraft&#8221;<br \/>\n  username: &#8220;postgres&#8221;<br \/>\n  password: &#8220;your_secure_password&#8221;<br \/>\n  poolSize: 50<\/p>\n<p># Redis Connection (Optional &#8211; for cross-server sync)<br \/>\nredis:<br \/>\n  enabled: true<br \/>\n  host: &#8220;localhost&#8221;<br \/>\n  port: 6379<br \/>\n  password: &#8220;&#8221;<br \/>\n  timeout: 3000<br \/>\n&#8220;`<\/p>\n<p>### Step 4: Verify<\/p>\n<p>Restart the server and check for successful initialization in the console:<\/p>\n<p>&#8220;`<br \/>\n[NoieDigitalSystem-API] Detected server type: Folia<br \/>\n[NoieDigitalSystem-API] Using shared configuration at: \/plugins\/NoieDigitalSystem\/config.yml<br \/>\n[NoieDigitalSystem-API] Digital Manager initialization completed.<br \/>\n[NoieDigitalSystem-API] NDS API Plugin enabled successfully!<br \/>\n&#8220;`<\/p>\n<p>&#8212;<\/p>\n<p>## Developer Integration<\/p>\n<p>### Dependency Setup<\/p>\n<p>#### Gradle (Kotlin DSL)<\/p>\n<p>&#8220;`kotlin<br \/>\nrepositories {<br \/>\n    mavenCentral()<br \/>\n}<\/p>\n<p>dependencies {<br \/>\n    compileOnly(&#8220;io.github.misty4119:noiedigitalsystem-api:2.0.0&#8221;)<br \/>\n}<br \/>\n&#8220;`<\/p>\n<p>#### Gradle (Groovy DSL)<\/p>\n<p>&#8220;`groovy<br \/>\nrepositories {<br \/>\n    mavenCentral()<br \/>\n}<\/p>\n<p>dependencies {<br \/>\n    compileOnly &#8216;io.github.misty4119:noiedigitalsystem-api:2.0.0&#8217;<br \/>\n}<br \/>\n&#8220;`<\/p>\n<p>#### Maven<\/p>\n<p>&#8220;`xml<br \/>\n<dependency><br \/>\n    <groupId>io.github.misty4119<\/groupId><br \/>\n    <artifactId>noiedigitalsystem-api<\/artifactId><br \/>\n    <version>2.0.0<\/version><br \/>\n    <scope>provided<\/scope><br \/>\n<\/dependency><br \/>\n&#8220;`<\/p>\n<p>### Plugin Configuration<\/p>\n<p>#### paper-plugin.yml (Paper 1.19.4+)<\/p>\n<p>&#8220;`yaml<br \/>\nname: YourPlugin<br \/>\nversion: &#8216;1.0.0&#8217;<br \/>\nmain: com.example.yourplugin.YourPlugin<br \/>\napi-version: &#8216;1.21&#8217;<br \/>\ndependencies:<br \/>\n  server:<br \/>\n    NoieDigitalSystem-API:<br \/>\n      load: BEFORE<br \/>\n      required: true<br \/>\n&#8220;`<\/p>\n<p>#### plugin.yml (Legacy)<\/p>\n<p>&#8220;`yaml<br \/>\nname: YourPlugin<br \/>\nversion: 1.0.0<br \/>\nmain: com.example.yourplugin.YourPlugin<br \/>\napi-version: &#8216;1.21&#8217;<br \/>\ndepend: [NoieDigitalSystem-API]<br \/>\n&#8220;`<\/p>\n<p>&#8212;<\/p>\n<p>## API Usage<\/p>\n<p>### Obtaining the API Instance<\/p>\n<p>&#8220;`java<br \/>\nimport noie.linmimeng.noiedigitalsystem.api.plugin.NdsApiPlugin;<br \/>\nimport noie.linmimeng.noiedigitalsystem.manager.DigitalManager;<br \/>\nimport noie.linmimeng.noiedigitalsystem.manager.PlayerDigitalManager;<br \/>\nimport org.bukkit.plugin.Plugin;<br \/>\nimport org.bukkit.plugin.java.JavaPlugin;<\/p>\n<p>public class YourPlugin extends JavaPlugin {<\/p>\n<p>    private PlayerDigitalManager playerDigitalManager;<br \/>\n    private DigitalManager digitalManager;<\/p>\n<p>    @Override<br \/>\n    public void onEnable() {<br \/>\n        \/\/ Obtain the NDS-API plugin instance<br \/>\n        Plugin ndsPlugin = getServer().getPluginManager().getPlugin(&#8220;NoieDigitalSystem-API&#8221;);<\/p>\n<p>        \/\/ Validate plugin availability and type<br \/>\n        if (ndsPlugin == null) {<br \/>\n            getLogger().severe(&#8220;NoieDigitalSystem-API is not installed!&#8221;);<br \/>\n            getServer().getPluginManager().disablePlugin(this);<br \/>\n            return;<br \/>\n        }<\/p>\n<p>        if (!(ndsPlugin instanceof NdsApiPlugin)) {<br \/>\n            getLogger().severe(&#8220;NoieDigitalSystem-API is not the expected type!&#8221;);<br \/>\n            getServer().getPluginManager().disablePlugin(this);<br \/>\n            return;<br \/>\n        }<\/p>\n<p>        \/\/ Cast and obtain managers<br \/>\n        NdsApiPlugin ndsApi = (NdsApiPlugin) ndsPlugin;<br \/>\n        this.playerDigitalManager = ndsApi.getPlayerDigitalManager();<br \/>\n        this.digitalManager = ndsApi.getDigitalManager();<\/p>\n<p>        getLogger().info(&#8220;Successfully integrated with NoieDigitalSystem-API v2.0&#8221;);<br \/>\n    }<br \/>\n}<br \/>\n&#8220;`<\/p>\n<p>### Player Digital Operations<\/p>\n<p>All player operations are **asynchronous** and return `CompletableFuture<T>`.<\/p>\n<p>&#8220;`java<br \/>\nimport java.math.BigDecimal;<br \/>\nimport java.util.UUID;<br \/>\nimport java.util.concurrent.CompletableFuture;<\/p>\n<p>\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\n\/\/ GET BALANCE<br \/>\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\nCompletableFuture<BigDecimal> balanceFuture = playerDigitalManager.getDigital(playerUUID, &#8220;coins&#8221;);<\/p>\n<p>balanceFuture.thenAccept(balance -> {<br \/>\n    \/\/ Executes on async thread &#8211; DO NOT call Bukkit API directly here<br \/>\n    getLogger().info(&#8220;Player balance: &#8221; + balance.toPlainString());<br \/>\n});<\/p>\n<p>\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\n\/\/ GIVE DIGITAL (Add to balance)<br \/>\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\nBigDecimal amountToGive = new BigDecimal(&#8220;100.50&#8221;);<\/p>\n<p>playerDigitalManager.giveDigital(playerUUID, &#8220;coins&#8221;, amountToGive)<br \/>\n    .thenRun(() -> {<br \/>\n        getLogger().info(&#8220;Successfully credited &#8221; + amountToGive + &#8221; coins&#8221;);<br \/>\n    })<br \/>\n    .exceptionally(ex -> {<br \/>\n        getLogger().severe(&#8220;Failed to credit coins: &#8221; + ex.getMessage());<br \/>\n        return null;<br \/>\n    });<\/p>\n<p>\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\n\/\/ TAKE DIGITAL (Subtract from balance)<br \/>\n\/\/ Returns false if insufficient balance<br \/>\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\nBigDecimal amountToTake = new BigDecimal(&#8220;50.00&#8221;);<\/p>\n<p>playerDigitalManager.takeDigital(playerUUID, &#8220;coins&#8221;, amountToTake)<br \/>\n    .thenAccept(success -> {<br \/>\n        if (success) {<br \/>\n            getLogger().info(&#8220;Successfully debited &#8221; + amountToTake + &#8221; coins&#8221;);<br \/>\n        } else {<br \/>\n            getLogger().warning(&#8220;Insufficient balance for debit operation&#8221;);<br \/>\n        }<br \/>\n    });<\/p>\n<p>\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\n\/\/ SET DIGITAL (Absolute value assignment)<br \/>\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\nBigDecimal newBalance = new BigDecimal(&#8220;1000.00&#8221;);<\/p>\n<p>playerDigitalManager.setDigital(playerUUID, &#8220;coins&#8221;, newBalance)<br \/>\n    .thenRun(() -> {<br \/>\n        getLogger().info(&#8220;Balance set to &#8221; + newBalance);<br \/>\n    });<\/p>\n<p>\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\n\/\/ GET ALL PLAYER DIGITALS<br \/>\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\nplayerDigitalManager.getPlayerDigitals(playerUUID)<br \/>\n    .thenAccept(digitals -> {<br \/>\n        digitals.forEach((name, value) -> {<br \/>\n            getLogger().info(name + &#8220;: &#8221; + value);<br \/>\n        });<br \/>\n    });<br \/>\n&#8220;`<\/p>\n<p>### Server Digital Operations<\/p>\n<p>Server digitals are **server-wide variables** (e.g., world boss HP, event counters).<\/p>\n<p>&#8220;`java<br \/>\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\n\/\/ GET SERVER DIGITAL<br \/>\n\/\/ Note: This is a synchronous operation that reads from cache<br \/>\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\nBigDecimal worldBossHp = digitalManager.getDigitalMap().get(&#8220;world_boss_hp&#8221;);<\/p>\n<p>\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\n\/\/ MODIFY SERVER DIGITAL (Async operations)<br \/>\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\ndigitalManager.giveDigital(&#8220;world_boss_hp&#8221;, new BigDecimal(&#8220;1000&#8221;))<br \/>\n    .thenRun(() -> getLogger().info(&#8220;World boss HP increased&#8221;));<\/p>\n<p>digitalManager.takeDigital(&#8220;world_boss_hp&#8221;, new BigDecimal(&#8220;500&#8221;))<br \/>\n    .thenAccept(success -> {<br \/>\n        if (success) {<br \/>\n            getLogger().info(&#8220;World boss HP decreased&#8221;);<br \/>\n        }<br \/>\n    });<\/p>\n<p>digitalManager.setDigital(&#8220;world_boss_hp&#8221;, new BigDecimal(&#8220;10000&#8221;))<br \/>\n    .thenRun(() -> getLogger().info(&#8220;World boss HP reset&#8221;));<br \/>\n&#8220;`<\/p>\n<p>### Global Player Digitals<\/p>\n<p>Global digitals are **currencies available to all players** (e.g., &#8220;coins&#8221;, &#8220;gems&#8221;). Once created, every player automatically has access to this digital type.<\/p>\n<p>&#8220;`java<br \/>\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\n\/\/ CREATE GLOBAL DIGITAL<br \/>\n\/\/ Parameters: name, initialAmount, limit (-1 = no limit)<br \/>\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\ndigitalManager.createGlobalDigital(&#8220;gems&#8221;, 0.0, -1.0)<br \/>\n    .thenRun(() -> {<br \/>\n        getLogger().info(&#8220;Created global digital &#8216;gems&#8217; &#8211; available to all players&#8221;);<br \/>\n    });<\/p>\n<p>\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\n\/\/ CHECK IF GLOBAL DIGITAL EXISTS<br \/>\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\ndigitalManager.isGlobalDigitalExists(&#8220;gems&#8221;)<br \/>\n    .thenAccept(exists -> {<br \/>\n        if (exists) {<br \/>\n            getLogger().info(&#8220;&#8216;gems&#8217; is a registered global digital&#8221;);<br \/>\n        }<br \/>\n    });<\/p>\n<p>\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\n\/\/ GET ALL GLOBAL DIGITALS<br \/>\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\ndigitalManager.getGlobalDigitals()<br \/>\n    .thenAccept(globals -> {<br \/>\n        globals.forEach((name, defaultAmount) -> {<br \/>\n            getLogger().info(&#8220;Global: &#8221; + name + &#8221; (default: &#8221; + defaultAmount + &#8220;)&#8221;);<br \/>\n        });<br \/>\n    });<br \/>\n&#8220;`<\/p>\n<p>### Error Handling Patterns<\/p>\n<p>**All async operations should implement proper exception handling:**<\/p>\n<p>&#8220;`java<br \/>\nplayerDigitalManager.getDigital(playerUUID, &#8220;coins&#8221;)<br \/>\n    .thenAccept(balance -> {<br \/>\n        \/\/ Success path<br \/>\n        processBalance(balance);<br \/>\n    })<br \/>\n    .exceptionally(ex -> {<br \/>\n        \/\/ Error path &#8211; database connection, timeout, etc.<br \/>\n        getLogger().severe(&#8220;Database operation failed: &#8221; + ex.getMessage());<\/p>\n<p>        \/\/ Log full stack trace for debugging<br \/>\n        if (getConfig().getBoolean(&#8220;debug&#8221;, false)) {<br \/>\n            ex.printStackTrace();<br \/>\n        }<\/p>\n<p>        \/\/ Return null for Void-returning futures<br \/>\n        return null;<br \/>\n    });<\/p>\n<p>\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\n\/\/ CHAINED OPERATIONS WITH ERROR PROPAGATION<br \/>\n\/\/ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\nplayerDigitalManager.getDigital(playerUUID, &#8220;coins&#8221;)<br \/>\n    .thenCompose(balance -> {<br \/>\n        if (balance.compareTo(new BigDecimal(&#8220;100&#8221;)) >= 0) {<br \/>\n            return playerDigitalManager.takeDigital(playerUUID, &#8220;coins&#8221;, new BigDecimal(&#8220;100&#8221;));<br \/>\n        }<br \/>\n        return CompletableFuture.completedFuture(false);<br \/>\n    })<br \/>\n    .thenAccept(success -> {<br \/>\n        if (success) {<br \/>\n            \/\/ Proceed with purchase logic<br \/>\n        }<br \/>\n    })<br \/>\n    .exceptionally(ex -> {<br \/>\n        getLogger().severe(&#8220;Transaction failed: &#8221; + ex.getMessage());<br \/>\n        return null;<br \/>\n    });<br \/>\n&#8220;`<\/p>\n<p>&#8212;<\/p>\n<p>## Core API Reference<\/p>\n<p>### PlayerDigitalManager<\/p>\n<p>Manages **player-specific digitals** (currencies, points, custom variables).<\/p>\n<p>| Method Signature | Return Type | Description |<br \/>\n|&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;|&#8212;&#8212;&#8212;&#8212;-|&#8212;&#8212;&#8212;&#8212;-|<br \/>\n| `getDigital(UUID playerUUID, String digitalName)` | `CompletableFuture<BigDecimal>` | Retrieves the player&#8217;s balance for the specified digital. Returns `BigDecimal.ZERO` if not found. |<br \/>\n| `setDigital(UUID playerUUID, String digitalName, BigDecimal amount)` | `CompletableFuture<Void>` | Sets the player&#8217;s balance to an exact value. Auto-creates the digital if it doesn&#8217;t exist. |<br \/>\n| `giveDigital(UUID playerUUID, String digitalName, BigDecimal amount)` | `CompletableFuture<Void>` | Adds the specified amount to the player&#8217;s balance. Respects configured limits. |<br \/>\n| `takeDigital(UUID playerUUID, String digitalName, BigDecimal amount)` | `CompletableFuture<Boolean>` | Subtracts the amount from balance. Returns `false` if insufficient funds. **Atomic operation.** |<br \/>\n| `getLimit(UUID playerUUID, String digitalName)` | `CompletableFuture<BigDecimal>` | Gets the maximum balance limit. Returns `null` if no limit is set. |<br \/>\n| `setLimit(UUID playerUUID, String digitalName, BigDecimal limit)` | `CompletableFuture<Void>` | Sets the maximum balance limit. Pass `null` to remove the limit. |<br \/>\n| `getPlayerDigitals(UUID playerUUID)` | `CompletableFuture<Map<String, Double>>` | Returns all digitals and their values for a player. |<br \/>\n| `getPlayerDigitalLimits(UUID playerUUID)` | `CompletableFuture<Map<String, Double>>` | Returns all digitals and their limits for a player. |<br \/>\n| `isDigitalExists(UUID playerUUID, String digitalName)` | `CompletableFuture<Boolean>` | Checks if the digital exists for the player (includes global digitals). |<br \/>\n| `createDigital(UUID playerUUID, String digitalName, BigDecimal initialAmount, BigDecimal limit)` | `CompletableFuture<Void>` | Creates a player-specific digital (rarely needed; auto-created on first use). |<br \/>\n| `removeDigital(UUID playerUUID, String digitalName)` | `CompletableFuture<Void>` | Removes a player-specific digital entry. |<br \/>\n| `grantDigital(UUID playerUUID, String digitalName, BigDecimal amount)` | `CompletableFuture<Void>` | Grants a special digital to a player (creates if not exists). |<br \/>\n| `revokeDigital(UUID playerUUID, String digitalName)` | `CompletableFuture<Void>` | Revokes\/removes a digital from a player. |<br \/>\n| `loadPlayerData(UUID playerUUID)` | `CompletableFuture<Void>` | Pre-loads player data into cache. Called automatically on player join. |<br \/>\n| `unloadPlayerData(UUID playerUUID)` | `void` | Removes player data from memory cache. Called automatically on player quit. |<br \/>\n| `clearCache()` | `void` | Clears all cached player data. Use with caution. |<\/p>\n<p>### DigitalManager<\/p>\n<p>Manages **server-level digitals** and **global player digital definitions**.<\/p>\n<p>| Method Signature | Return Type | Description |<br \/>\n|&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;|&#8212;&#8212;&#8212;&#8212;-|&#8212;&#8212;&#8212;&#8212;-|<br \/>\n| `getDigitalMap()` | `Map<String, BigDecimal>` | Returns all server digitals and their current values (synchronous cache read). |<br \/>\n| `getDigitalLimitMap()` | `Map<String, BigDecimal>` | Returns all server digitals and their limits. |<br \/>\n| `setDigital(String digitalName, BigDecimal amount)` | `CompletableFuture<Void>` | Sets a server digital to an exact value. |<br \/>\n| `giveDigital(String digitalName, BigDecimal amount)` | `CompletableFuture<Void>` | Adds to a server digital&#8217;s value. |<br \/>\n| `takeDigital(String digitalName, BigDecimal amount)` | `CompletableFuture<Boolean>` | Subtracts from a server digital. Returns `false` if insufficient. |<br \/>\n| `removeDigital(String digitalName)` | `CompletableFuture<Void>` | Removes a server digital entirely. |<br \/>\n| `getLimit(String digitalName)` | `BigDecimal` | Gets the limit for a server digital (synchronous). |<br \/>\n| `setLimit(String digitalName, BigDecimal limit)` | `CompletableFuture<Void>` | Sets the limit for a server digital. |<br \/>\n| `isDigitalExists(String digitalName)` | `CompletableFuture<Boolean>` | Checks if a server digital exists. |<br \/>\n| `renameDigital(String oldName, String newName)` | `CompletableFuture<Void>` | Renames a server digital. Updates cache and Redis. |<\/p>\n<p>#### Global Digital Methods<\/p>\n<p>| Method Signature | Return Type | Description |<br \/>\n|&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;|&#8212;&#8212;&#8212;&#8212;-|&#8212;&#8212;&#8212;&#8212;-|<br \/>\n| `createGlobalDigital(String digitalName, double initialAmount, double limit)` | `CompletableFuture<Void>` | Creates a global digital available to all players. Pass `-1` for no limit. |<br \/>\n| `isGlobalDigitalExists(String digitalName)` | `CompletableFuture<Boolean>` | Checks if a global digital definition exists. |<br \/>\n| `removeGlobalDigital(String digitalName)` | `CompletableFuture<Void>` | Removes a global digital definition. **Does not delete player data.** |<br \/>\n| `getGlobalDigitals()` | `CompletableFuture<Map<String, BigDecimal>>` | Returns all global digital definitions. |<br \/>\n| `getGlobalDigitalAmount(String digitalName)` | `CompletableFuture<BigDecimal>` | Gets the default amount for a global digital. |<br \/>\n| `getGlobalDigitalLimit(String digitalName)` | `CompletableFuture<BigDecimal>` | Gets the limit for a global digital. |<\/p>\n<p>&#8212;<\/p>\n<p>## Thread Safety &#038; Concurrency<\/p>\n<p>### Async-First Architecture<\/p>\n<p>**Critical Rule:** All NDS-API methods that perform I\/O operations are asynchronous and return `CompletableFuture<T>`.<\/p>\n<p>&#8220;`java<br \/>\n\/\/ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557<br \/>\n\/\/ \u2551 CORRECT: Non-blocking async pattern                                       \u2551<br \/>\n\/\/ \u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d<br \/>\nplayerDigitalManager.getDigital(playerUUID, &#8220;coins&#8221;)<br \/>\n    .thenAccept(balance -> {<br \/>\n        \/\/ This callback executes on a virtual thread (Java 21)<br \/>\n        \/\/ Safe for I\/O operations, NOT safe for Bukkit API calls<br \/>\n        processBalanceAsync(balance);<br \/>\n    });<\/p>\n<p>\/\/ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557<br \/>\n\/\/ \u2551 INCORRECT: Blocking the main thread                                       \u2551<br \/>\n\/\/ \u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d<br \/>\n\/\/ \u26a0\ufe0f NEVER DO THIS &#8211; Will cause server lag\/freeze<br \/>\nBigDecimal balance = playerDigitalManager.getDigital(playerUUID, &#8220;coins&#8221;).get();<br \/>\n&#8220;`<\/p>\n<p>### Bukkit\/Paper Thread Model<\/p>\n<p>When you need to interact with the Bukkit API from an async callback, you **must** schedule the operation on the main thread:<\/p>\n<p>&#8220;`java<br \/>\nplayerDigitalManager.getDigital(playerUUID, &#8220;coins&#8221;)<br \/>\n    .thenAccept(balance -> {<br \/>\n        \/\/ Schedule Bukkit API calls on the main thread<br \/>\n        Bukkit.getScheduler().runTask(plugin, () -> {<br \/>\n            Player player = Bukkit.getPlayer(playerUUID);<br \/>\n            if (player != null &#038;&#038; player.isOnline()) {<br \/>\n                player.sendMessage(&#8220;Your balance: &#8221; + balance.toPlainString());<br \/>\n            }<br \/>\n        });<br \/>\n    });<br \/>\n&#8220;`<\/p>\n<p>### Folia Region Scheduler<\/p>\n<p>For **Folia** servers, use the entity\/region scheduler instead of the global scheduler:<\/p>\n<p>&#8220;`java<br \/>\nplayerDigitalManager.getDigital(playerUUID, &#8220;coins&#8221;)<br \/>\n    .thenAccept(balance -> {<br \/>\n        \/\/ For Folia: Use the entity&#8217;s scheduler<br \/>\n        Player player = Bukkit.getPlayer(playerUUID);<br \/>\n        if (player != null &#038;&#038; player.isOnline()) {<br \/>\n            player.getScheduler().run(plugin, scheduledTask -> {<br \/>\n                player.sendMessage(&#8220;Your balance: &#8221; + balance.toPlainString());<br \/>\n            }, null);<br \/>\n        }<br \/>\n    });<br \/>\n&#8220;`<\/p>\n<p>**Thread Model Summary:**<\/p>\n<p>| Environment | Callback Thread | Bukkit API Access |<br \/>\n|&#8212;&#8212;&#8212;&#8212;-|&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;|&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-|<br \/>\n| Bukkit\/Paper | Virtual Thread (async) | Requires `Bukkit.getScheduler().runTask()` |<br \/>\n| Folia | Virtual Thread (async) | Requires `entity.getScheduler().run()` or `RegionScheduler` |<\/p>\n<p>&#8212;<\/p>\n<p>## Data Model<\/p>\n<p>### Digital Types<\/p>\n<p>| Type | Scope | Use Case | Example |<br \/>\n|&#8212;&#8212;|&#8212;&#8212;-|&#8212;&#8212;&#8212;-|&#8212;&#8212;&#8212;|<br \/>\n| **Player Digital** | Per-player | Player-specific currencies\/variables | Player&#8217;s private bank balance |<br \/>\n| **Global Player Digital** | Definition \u2192 All Players | Standard currencies everyone has | `coins`, `gems`, `points` |<br \/>\n| **Server Digital** | Server-wide | World state, event counters | `world_boss_hp`, `server_event_score` |<\/p>\n<p>### JSONB Structure<\/p>\n<p>**Player Data Example:**<\/p>\n<p>&#8220;`json<br \/>\n{<br \/>\n    &#8220;coins&#8221;: 1500.50,<br \/>\n    &#8220;gems&#8221;: 42,<br \/>\n    &#8220;stamina&#8221;: 100,<br \/>\n    &#8220;_limits&#8221;: {<br \/>\n        &#8220;coins&#8221;: 1000000,<br \/>\n        &#8220;stamina&#8221;: 160<br \/>\n    }<br \/>\n}<br \/>\n&#8220;`<\/p>\n<p>**Server Data Example (uuid = &#8216;00000000-0000-0000-0000-000000000000&#8217;):**<\/p>\n<p>&#8220;`json<br \/>\n{<br \/>\n    &#8220;world_boss_hp&#8221;: 50000,<br \/>\n    &#8220;event_score&#8221;: 12500,<br \/>\n    &#8220;_global_digitals&#8221;: {<br \/>\n        &#8220;coins&#8221;: { &#8220;initial&#8221;: 0, &#8220;limit&#8221;: -1 },<br \/>\n        &#8220;gems&#8221;: { &#8220;initial&#8221;: 0, &#8220;limit&#8221;: 9999 }<br \/>\n    }<br \/>\n}<br \/>\n&#8220;`<\/p>\n<p>&#8212;<\/p>\n<p>## Configuration Reference<\/p>\n<p>Complete configuration with all available options:<\/p>\n<p>&#8220;`yaml<br \/>\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\n# PostgreSQL Configuration (Required)<br \/>\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\npostgresql:<br \/>\n  host: &#8220;localhost&#8221;<br \/>\n  port: 5432<br \/>\n  database: &#8220;minecraft&#8221;<br \/>\n  username: &#8220;postgres&#8221;<br \/>\n  password: &#8220;your_secure_password&#8221;<\/p>\n<p>  # Connection pool settings<br \/>\n  poolSize: 50                    # Maximum connections in pool<br \/>\n  connectionTimeout: 30000        # Connection timeout (ms)<br \/>\n  idleTimeout: 600000             # Idle connection timeout (ms)<br \/>\n  maxLifetime: 1800000            # Maximum connection lifetime (ms)<\/p>\n<p># \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\n# Redis Configuration (Optional &#8211; for cross-server sync)<br \/>\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\nredis:<br \/>\n  enabled: true<br \/>\n  host: &#8220;localhost&#8221;<br \/>\n  port: 6379<br \/>\n  password: &#8220;&#8221;<br \/>\n  timeout: 3000<\/p>\n<p>  # Channel prefixes for Pub\/Sub<br \/>\n  channels:<br \/>\n    player: &#8220;nds:player:&#8221;<br \/>\n    server: &#8220;nds:server:&#8221;<br \/>\n    global: &#8220;nds:global:&#8221;<\/p>\n<p># \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\n# Digital Definitions<br \/>\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\ndigitals:<br \/>\n  coins:<br \/>\n    display_name: &#8220;Coins&#8221;<br \/>\n    persist_on_zero: false        # Delete entry when balance reaches 0<\/p>\n<p>  gems:<br \/>\n    display_name: &#8220;Gems&#8221;<br \/>\n    persist_on_zero: true         # Keep entry even at 0 balance<\/p>\n<p># \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\n# Vault Integration (Compatibility Layer)<br \/>\n# \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550<br \/>\nvault:<br \/>\n  enabled: true<br \/>\n  enabled_digitals:<br \/>\n    &#8211; &#8220;coins&#8221;<br \/>\n  default_currency: &#8220;coins&#8221;<br \/>\n&#8220;`<\/p>\n<p>&#8212;<\/p>\n<p>## Protocol Specification<\/p>\n<p>NDS-API is built on a **cross-platform protocol specification** designed for multi-language SDK support.<\/p>\n<p>### Protocol Domains<\/p>\n<p>| Domain | Description |<br \/>\n|&#8212;&#8212;&#8211;|&#8212;&#8212;&#8212;&#8212;-|<br \/>\n| **Identity** | Player\/Server\/System identity abstraction with UUID-based addressing |<br \/>\n| **Asset** | Currency\/variable definitions with scopes (Player, Global, Server) |<br \/>\n| **Event** | Append-only event sourcing for immutable audit trails |<br \/>\n| **Transaction** | Atomic operations with configurable consistency modes |<br \/>\n| **Result** | Standardized success\/failure response envelope |<\/p>\n<p>### Protocol Repository<\/p>\n<p>For the full protocol specification, multi-language SDK documentation, and Protocol Buffers definitions:<\/p>\n<p>**github.com\/Misty4119\/nds-api**<\/p>\n<p>&#8212;<\/p>\n<p>## Migration from Vault<\/p>\n<p>NDS-API includes a **Vault compatibility layer** for gradual migration.<\/p>\n<p>### Enabling Vault Compatibility<\/p>\n<p>&#8220;`yaml<br \/>\nvault:<br \/>\n  enabled: true<br \/>\n  enabled_digitals:<br \/>\n    &#8211; &#8220;coins&#8221;<br \/>\n  default_currency: &#8220;coins&#8221;<br \/>\n&#8220;`<\/p>\n<p>### Migration Strategy<\/p>\n<p>1. **Install NDS-API** alongside your existing Vault provider<br \/>\n2. **Enable Vault compatibility** in NDS-API configuration<br \/>\n3. **Test thoroughly** in a staging environment<br \/>\n4. **Migrate data** using the built-in migration tools<br \/>\n5. **Remove legacy Vault provider** once verification is complete<\/p>\n<p>&#8212;<\/p>\n<p>## Troubleshooting<\/p>\n<p>### Common Issues<\/p>\n<p>| Symptom | Cause | Solution |<br \/>\n|&#8212;&#8212;&#8212;|&#8212;&#8212;-|&#8212;&#8212;&#8212;-|<br \/>\n| `NoieDigitalSystem-API not found!` | Plugin not installed or load order issue | Ensure JAR is in `plugins\/` and check `paper-plugin.yml` dependencies |<br \/>\n| Connection pool exhausted | Too many concurrent database operations | Increase `poolSize` in config; check for unclosed connections |<br \/>\n| Redis sync not working | Incorrect Redis configuration or firewall | Verify Redis connection; check `redis.enabled: true` |<br \/>\n| `ClassCastException` on API access | Multiple NDS versions loaded | Ensure only one NDS JAR is in `plugins\/` |<br \/>\n| Slow performance | Blocking main thread with `.get()` | Never use `.get()` on main thread; use async callbacks |<\/p>\n<p>### Debug Mode<\/p>\n<p>Enable debug logging for detailed diagnostics:<\/p>\n<p>&#8220;`yaml<br \/>\ndebug:<br \/>\n  enabled: true<br \/>\n  log_level: &#8220;DEBUG&#8221;<br \/>\n  log_sql: true<br \/>\n&#8220;`<\/p>\n<p>&#8212;<\/p>\n<p>## Support<\/p>\n<p>&#8211; **Issues &#038; Bug Reports:** GitHub Issues<br \/>\n&#8211; **Protocol SDK:** nds-api Repository<br \/>\n&#8211; **Documentation:** Wiki<\/p>\n<p>&#8212;<\/p>\n<p>## License<\/p>\n<p>&#8220;`<br \/>\nCopyright 2024-2026 Noie Linmimeng<\/p>\n<p>Licensed under the Apache License, Version 2.0 (the &#8220;License&#8221;);<br \/>\nyou may not use this file except in compliance with the License.<br \/>\nYou may obtain a copy of the License at<\/p>\n<p>    http:\/\/www.apache.org\/licenses\/LICENSE-2.0<\/p>\n<p>Unless required by applicable law or agreed to in writing, software<br \/>\ndistributed under the License is distributed on an &#8220;AS IS&#8221; BASIS,<br \/>\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.<br \/>\nSee the License for the specific language governing permissions and<br \/>\nlimitations under the License.<br \/>\n&#8220;`<\/p>\n<p>&#8212;<\/p>\n<div align=\"center\">\n<p>**NoieDigitalSystem** \u2014 Next-generation economy protocol for Minecraft.<\/p>\n<p>*Designed for the future. Built for today.*<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Async-first, Redis Sync, High Performance Economy Engine for Developers.<\/p>\n","protected":false},"featured_media":109193,"template":"","meta":{"_minecraft_slug":"nds-api","_minecraft_project_id":"p5KYrpRK","_minecraft_author":"misty4119","_minecraft_license":"Apache-2.0","_minecraft_downloads":13,"_minecraft_follows":0,"_minecraft_client_side":"unsupported","_minecraft_server_side":"required","_minecraft_storage_type":"zip","_minecraft_archive_name":"nds-api.zip","_minecraft_size_bytes":10682123,"_minecraft_version_count":1,"_minecraft_updated_at":"2026-05-15T22:58:37.690967Z","_minecraft_date_created":"2026-03-02T22:18:10.207826Z","_minecraft_date_modified":"2026-01-29T17:10:26.257467Z","_minecraft_project_url":"https:\/\/modrinth.com\/mod\/nds-api","_minecraft_icon_attachment_id":109193,"_minecraft_imported_at":"2026-06-06T10:07:47+00:00","_minecraft_import_hash":"da1e8ca5a2aa9edd4cc1c3239f18e3e5","_minecraft_versions":"","_minecraft_links":"","_minecraft_gallery":"","_minecraft_api_metadata":""},"mod_category":[844,125,127],"mod_loader":[131,132],"minecraft_version":[62,37,40,41,73,74,38,78,79,80,81,82],"class_list":["post-109192","mod","type-mod","status-publish","has-post-thumbnail","hentry","mod_category-economy","mod_category-folia","mod_category-paper","mod_loader-folia","mod_loader-paper","minecraft_version-1-21","minecraft_version-1-21-1","minecraft_version-1-21-10","minecraft_version-1-21-11","minecraft_version-1-21-2","minecraft_version-1-21-3","minecraft_version-1-21-4","minecraft_version-1-21-5","minecraft_version-1-21-6","minecraft_version-1-21-7","minecraft_version-1-21-8","minecraft_version-1-21-9"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.6 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>NDS-API - Minecraft<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/vpesports.com\/minecraft\/mods\/nds-api\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"NDS-API - Minecraft\" \/>\n<meta property=\"og:description\" content=\"Async-first, Redis Sync, High Performance Economy Engine for Developers.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/vpesports.com\/minecraft\/mods\/nds-api\/\" \/>\n<meta property=\"og:site_name\" content=\"Minecraft\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/mods\\\/nds-api\\\/\",\"url\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/mods\\\/nds-api\\\/\",\"name\":\"NDS-API - Minecraft\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/mods\\\/nds-api\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/mods\\\/nds-api\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/nds-api-icon-7fbbf5cfd05c02d48203855572838fd9e4eedbda_96.webp\",\"datePublished\":\"2026-06-06T10:07:45+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/mods\\\/nds-api\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/mods\\\/nds-api\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/mods\\\/nds-api\\\/#primaryimage\",\"url\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/nds-api-icon-7fbbf5cfd05c02d48203855572838fd9e4eedbda_96.webp\",\"contentUrl\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/nds-api-icon-7fbbf5cfd05c02d48203855572838fd9e4eedbda_96.webp\",\"width\":96,\"height\":96},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/mods\\\/nds-api\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Mods\",\"item\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/mods\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"NDS-API\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/#website\",\"url\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/\",\"name\":\"Minecraft\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/vpesports.com\\\/minecraft\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"NDS-API - Minecraft","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/vpesports.com\/minecraft\/mods\/nds-api\/","og_locale":"en_US","og_type":"article","og_title":"NDS-API - Minecraft","og_description":"Async-first, Redis Sync, High Performance Economy Engine for Developers.","og_url":"https:\/\/vpesports.com\/minecraft\/mods\/nds-api\/","og_site_name":"Minecraft","twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/vpesports.com\/minecraft\/mods\/nds-api\/","url":"https:\/\/vpesports.com\/minecraft\/mods\/nds-api\/","name":"NDS-API - Minecraft","isPartOf":{"@id":"https:\/\/vpesports.com\/minecraft\/#website"},"primaryImageOfPage":{"@id":"https:\/\/vpesports.com\/minecraft\/mods\/nds-api\/#primaryimage"},"image":{"@id":"https:\/\/vpesports.com\/minecraft\/mods\/nds-api\/#primaryimage"},"thumbnailUrl":"https:\/\/vpesports.com\/minecraft\/wp-content\/uploads\/2026\/06\/nds-api-icon-7fbbf5cfd05c02d48203855572838fd9e4eedbda_96.webp","datePublished":"2026-06-06T10:07:45+00:00","breadcrumb":{"@id":"https:\/\/vpesports.com\/minecraft\/mods\/nds-api\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/vpesports.com\/minecraft\/mods\/nds-api\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/vpesports.com\/minecraft\/mods\/nds-api\/#primaryimage","url":"https:\/\/vpesports.com\/minecraft\/wp-content\/uploads\/2026\/06\/nds-api-icon-7fbbf5cfd05c02d48203855572838fd9e4eedbda_96.webp","contentUrl":"https:\/\/vpesports.com\/minecraft\/wp-content\/uploads\/2026\/06\/nds-api-icon-7fbbf5cfd05c02d48203855572838fd9e4eedbda_96.webp","width":96,"height":96},{"@type":"BreadcrumbList","@id":"https:\/\/vpesports.com\/minecraft\/mods\/nds-api\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/vpesports.com\/minecraft\/"},{"@type":"ListItem","position":2,"name":"Mods","item":"https:\/\/vpesports.com\/minecraft\/mods\/"},{"@type":"ListItem","position":3,"name":"NDS-API"}]},{"@type":"WebSite","@id":"https:\/\/vpesports.com\/minecraft\/#website","url":"https:\/\/vpesports.com\/minecraft\/","name":"Minecraft","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/vpesports.com\/minecraft\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"}]}},"_links":{"self":[{"href":"https:\/\/vpesports.com\/minecraft\/wp-json\/wp\/v2\/mod\/109192","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vpesports.com\/minecraft\/wp-json\/wp\/v2\/mod"}],"about":[{"href":"https:\/\/vpesports.com\/minecraft\/wp-json\/wp\/v2\/types\/mod"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vpesports.com\/minecraft\/wp-json\/wp\/v2\/media\/109193"}],"wp:attachment":[{"href":"https:\/\/vpesports.com\/minecraft\/wp-json\/wp\/v2\/media?parent=109192"}],"wp:term":[{"taxonomy":"mod_category","embeddable":true,"href":"https:\/\/vpesports.com\/minecraft\/wp-json\/wp\/v2\/mod_category?post=109192"},{"taxonomy":"mod_loader","embeddable":true,"href":"https:\/\/vpesports.com\/minecraft\/wp-json\/wp\/v2\/mod_loader?post=109192"},{"taxonomy":"minecraft_version","embeddable":true,"href":"https:\/\/vpesports.com\/minecraft\/wp-json\/wp\/v2\/minecraft_version?post=109192"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}