NMKey is designed as a fire-and-forget library: validate once in onEnable() and you’re done. The Self-Cleaning architecture releases the license seat automatically on shutdown or /reload, so you do not need any cleanup in onDisable().
Installation
// build.gradle.kts
plugins {
id("com.gradleup.shadow") version "8.3.0"
}
repositories {
mavenCentral()
maven("https://www.nmcrate.com/reposilite/releases")
}
dependencies {
implementation("com.nmcrate.key:NMKey:1.1.0")
}
// NMKey uses the native Java 17 HttpClient (no Ktor). Shade + relocate its
// Kotlin runtime so it can't clash with other plugins on the shared classpath.
tasks.shadowJar {
relocate("kotlin", "com.yourplugin.libs.kotlin")
relocate("kotlinx", "com.yourplugin.libs.kotlinx")
}
Your plugin id comes from the Licensing tab in your studio dashboard (shown after you protect the product with NMKey).
Kotlin (one-liner)
import com.nmcrate.key.nmKeyAsync
import org.bukkit.plugin.java.JavaPlugin
class MyPlugin : JavaPlugin() {
override fun onEnable() {
// Validates async, handles disabling on failure, and auto-cleans on shutdown.
nmKeyAsync("your-plugin-id")
}
}
Java
Wrap the blocking check call in Bukkit’s async scheduler:
import com.nmcrate.key.NMKey;
import org.bukkit.plugin.java.JavaPlugin;
public final class MyPlugin extends JavaPlugin {
private static final String PLUGIN_ID = "your-plugin-id";
@Override
public void onEnable() {
getServer().getScheduler().runTaskAsynchronously(this, () -> {
// NMKey will automatically release the license seat on shutdown
// if Config.autoReleaseOnDisable is true (default).
boolean valid = NMKey.check(this, PLUGIN_ID);
getServer().getScheduler().runTask(this, () -> {
if (!valid && NMKey.Config.getAutoDisablePlugin()) {
getLogger().severe("Invalid license key. Disabling plugin...");
getServer().getPluginManager().disablePlugin(this);
}
});
});
}
}
Configuration
Customize global behavior before validation runs:
NMKey.Config.useOfflineCache = true // AES-GCM encrypted offline cache
NMKey.Config.gracePeriodHours = 24L // survive API outages, up to 48h
NMKey.Config.autoDisablePlugin = true // disable the plugin on invalid key
NMKey.Config.autoReleaseOnDisable = true // free the seat on shutdown/reload
How validation works
- Fetches your plugin’s Ed25519 public key from the API.
- Reads the buyer’s
nmkey.txt bundled inside the jar (NMCrate bakes it in at download).
- Generates an in-memory heuristic fingerprint of the host.
- Posts the payload and a nonce to the validation endpoint.
- Verifies the Ed25519 signature and encrypts the response to disk using the fingerprint as the AES-GCM key.
- If the network fails, decrypts the local cache — validation succeeds offline while the fingerprint matches and the cache is younger than
gracePeriodHours.
NMKey.check and NMKey.release are blocking; run them async (the Kotlin extension does this for you). Manual release is no longer necessary — the Self-Cleaning listener handles it.
Obfuscation (crucial)
NMKey’s cryptography only protects against network-level spoofing. Anyone can open your compiled jar in a bytecode editor and delete the NMKey.check() call, bypassing licensing entirely.
Run your final jar through an obfuscator (ProGuard, Zelix KlassMaster, Stringer) and make sure the shaded com.nmcrate.key package is heavily obfuscated:
- Control-flow obfuscation to scramble the signature-checking logic.
- String encryption to hide the API endpoints and JSON keys.
Combining NMKey with Spigot placeholders protection (also supported when adding a license) makes stripped copies traceable even if the check is removed.