> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nmcrate.com/llms.txt
> Use this file to discover all available pages before exploring further.

# NMKey plugin integration

> Add the NMKey library to your Paper plugin — Gradle setup, Kotlin one-liner, Java usage, configuration, and obfuscation.

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

```kotlin theme={null}
// 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)

```kotlin theme={null}
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:

```java theme={null}
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:

```kotlin theme={null}
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

1. Fetches your plugin's Ed25519 public key from the API.
2. Reads the buyer's `nmkey.txt` bundled inside the jar (NMCrate bakes it in at download).
3. Generates an in-memory heuristic fingerprint of the host.
4. Posts the payload and a nonce to the validation endpoint.
5. Verifies the Ed25519 signature and encrypts the response to disk using the fingerprint as the AES-GCM key.
6. 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)

<Warning>
  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.
</Warning>

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](https://www.spigotmc.org/wiki/premium-resource-placeholders-identifiers/) protection (also supported when adding a license) makes stripped copies traceable even if the check is removed.
