Java license SDK for validating against a self-hosted
OPLicense backend. Zero third-party dependencies —
HttpURLConnection + JDK crypto only.
Gradle (build.gradle.kts):
repositories {
maven("https://repo.mastersmp.net/releases")
// Optional: snapshots from CI commits
maven("https://repo.mastersmp.net/snapshots")
}
dependencies {
implementation("net.opmasterleo:OPlicense-client:2.0.0")
// or snapshot: "net.opmasterleo:OPlicense-client:2.0.0-SNAPSHOT"
}Publishing:
- Commits / CI →
https://repo.mastersmp.net/snapshots(./gradlew publish -Preposilite.target=snapshots) - Manual release →
https://repo.mastersmp.net/releases(./gradlew publish)
Maven:
<repositories>
<repository>
<id>mastersmp</id>
<url>https://repo.mastersmp.net/releases</url>
</repository>
</repositories>
<dependency>
<groupId>net.opmasterleo</groupId>
<artifactId>OPlicense-client</artifactId>
<version>2.0.0</version>
</dependency>- Public API stable —
LicenseClient.withEd25519(...),validate().run(callbacks),ValidationCallbacks,LicenseResult/LicenseOutcomenames unchanged from 1.x - Fail closed — unsigned, replayed, or product-mismatched responses never count as valid
- Ed25519 signed responses — verify every body against your product public key
- Anti-replay — nonce echo + issuedAt clock window (±120s)
- Obfuscation-friendly — no lambdas /
invokedynamicin the SDK; prefer named nested callback classes in your plugin
net.opmasterleo.license/
LicenseClient.java
net.opmasterleo.license.api/
ValidationRequest.java
ValidationCallbacks.java
Ed25519ResponseVerifier.java
net.opmasterleo.license.model/
LicenseResult.java / LicenseOutcome / LicenseUpdate / LicenseEnvironment
net.opmasterleo.license.exception/
LicenseException.java
net.opmasterleo.license.internal/
core/ ClientConfig, RequestContext, ValidationEngine
http/ LicenseHttp, OutcomeReporter
security/ Ed25519, Nonce, ResponseGuard
probe/ HardwareId, EnvironmentProbe
json/ Json
util/ Digests, Io, Numbers, Strings
crypto/ Concealed (optional string helper)
Keep API URL, product slug, and Ed25519 public key as constants in plugin
source. Only the license key belongs in config.yml.
private static final String API_URL = "https://your-api.example";
private static final String PRODUCT = "your-product-slug";
private static final String PUBLIC_KEY = "MCowBQYDK2VwAyEA...";
LicenseClient client = LicenseClient.withEd25519(
API_URL,
getConfig().getString("license-key"),
PRODUCT,
PUBLIC_KEY
);Prefer HTTPS in production. HTTP is accepted for local / IP endpoints.
client.setProductVersion(getDescription().getVersion())
.setServerSoftware(Bukkit.getName(), Bukkit.getVersion());
client.validate().run(new PluginValidationCallbacks(this));
private static final class PluginValidationCallbacks extends ValidationCallbacks {
private final JavaPlugin plugin;
PluginValidationCallbacks(JavaPlugin plugin) {
this.plugin = plugin;
}
@Override
public void onValid(LicenseResult result) {
// register listeners / load features here
}
@Override
public void onExpired(LicenseResult result) {
Bukkit.getPluginManager().disablePlugin(plugin);
}
@Override
public void onSignatureInvalid(LicenseResult result) {
Bukkit.getPluginManager().disablePlugin(plugin);
}
@Override
public void onNetworkError(Exception exception) {
Bukkit.getPluginManager().disablePlugin(plugin);
}
}Call once at the top of onEnable. There is no offline cache or last-known-good fallback.
| Callback | When |
|---|---|
onValid |
License accepted |
onExpired / onRevoked / onDeactivated / onDeleted |
License state |
onIpNotWhitelisted |
IP not on whitelist |
onHwidRequired / onMaxHwidExceeded |
HWID policy |
onBlacklistedIp / onBlacklistedHwid |
Blacklist |
onProductMismatch / onProductArchived / onLicenseNotFound |
Product / key |
onTimestampDesync / onRateLimited |
Request rejected |
onSignatureInvalid |
Signature / replay / nonce failure (local) |
onNetworkError |
Unreachable API / HTTP 5xx |
Optional overrides before validate():
client.setProductVersion(getDescription().getVersion())
.setServerSoftware(Bukkit.getName(), Bukkit.getVersion())
.setContainer("pterodactyl")
.setHwid("your-stable-server-id");Default HWID order: explicit override → container env → /etc/machine-id → MAC → legacy.
Values are hashed (HWID-...) before leave the client.
result.update() exposes plugin version update hints when you set setProductVersion.
Every validate request includes BuiltByBit placeholders by default
(%%__USER__%%, %%__NONCE__%%, …). When the plugin JAR is downloaded from BBB,
those tokens are replaced with real buyer values. If not injected (local/dev build),
the literal placeholders are sent and shown in the Discord Environment log.
Optional overrides still exist:
client.setBuiltByBit(
"%%__USER__%%",
"%%__USERNAME__%%",
"%%__RESOURCE__%%",
"%%__RESOURCE_TITLE__%%",
"%%__VERSION__%%",
"%%__VERSION_NUMBER__%%",
"%%__TIMESTAMP__%%",
"%%__NONCE__%%"
);Exclude %%__*__%% strings from obfuscation so BBB can still inject them.
Shade this SDK into your plugin JAR so the defaults live in the uploaded artifact.
- Response body capped at 1 MiB
- Redirects disabled on validate requests
- Signature required; algorithm must be
ed25519 - Nonce compared in constant time
- SDK
User-Agent:OPLicense-Client/2.0.0 - Local signature / replay failures reported to
/client-outcome