Skip to content

Licensing & Updates (pumpkin-plugin-utils)

pumpkin-plugin-utils is an official utility crate for Pumpkin Rust plugins. It provides built-in tools for licensing checks, cryptographic signature verification, and update checks against the Pumpkin Marketplace.

Features

  • Offline Ed25519 Signature Verification: Verifies plugin integrity and embedded marketplace metadata in < 1ms on startup without blocking the server.
  • Global Metadata Caching: Call init(&context) once in your plugin's on_load lifecycle method to cache verified metadata globally.
  • Online License Verification: Query the marketplace /api/v1/rest/check-license endpoint with automatic 7-day lease caching (license_lease.json).
  • Offline Grace Period: Resilient offline lease evaluation ensures your plugin keeps working during marketplace maintenance or network outages.
  • Zero-Argument Update Checking: Easily check if newer plugin releases are available on the marketplace.
  • Dynamic Public Key Resolution: Resolves the marketplace public key dynamically via host WIT imports or HTTPS fallback without hardcoding keys.

Adding the Dependency

Add pumpkin-plugin-utils alongside pumpkin-plugin-api in your plugin's Cargo.toml:

toml
[package]
name = "my-plugin"
version = "0.1.0"
edition = "2024"

[lib]
crate-type = ["cdylib"]

[dependencies]
pumpkin-plugin-api = { version = "0.1.0", git = "https://github.com/Pumpkin-MC/Pumpkin", package = "pumpkin-plugin-api" }
pumpkin-plugin-utils = { version = "0.1.0", git = "https://github.com/Pumpkin-MC/Pumpkin", package = "pumpkin-plugin-utils" }
tracing = "0.1"

Initialization

Before performing license or update checks, initialize the utility crate inside your plugin's on_load method using the plugin's Context:

rust
use pumpkin_plugin_api::{Context, Plugin, PluginMetadata};
use tracing::{info, warn};

struct MyPlugin;

impl Plugin for MyPlugin {
    fn new() -> Self {
        Self
    }

    fn metadata(&self) -> PluginMetadata {
        PluginMetadata {
            name: "MyPlugin".into(),
            version: env!("CARGO_PKG_VERSION").into(),
            authors: vec!["Author".into()],
            description: "Plugin with license and update checks".into(),
        }
    }

    fn on_load(&mut self, context: Context) -> pumpkin_plugin_api::Result<()> {
        // Initialize pumpkin-plugin-utils (verifies signature & caches metadata)
        let metadata = pumpkin_plugin_utils::init(&context)
            .map_err(|e| format!("Initialization failed: {e}"))?;

        info!(
            "Loaded plugin '{}' v{} (Dev: {})",
            metadata.plugin_name, metadata.version, metadata.dev_name
        );

        Ok(())
    }
}

What happens during init?

  1. Locates the plugin's .wasm binary in the server directory.
  2. Extracts the pumpkin.metadata and wasm_signature custom sections.
  3. Cryptographically verifies the Ed25519 signature over the WASM binary and metadata.
  4. Caches the verified PumpkinMetadata globally for subsequent calls.

Checking for Updates

pumpkin-plugin-utils provides both zero-argument automatic checking and custom manual checking.

Once initialized, call pumpkin_plugin_utils::check_for_updates() without arguments. It automatically uses the cached plugin name, current version, and marketplace endpoint:

rust
use tracing::{info, warn};

fn check_updates() {
    match pumpkin_plugin_utils::check_for_updates() {
        Ok(update) => {
            if update.update_available {
                info!(
                    "A new update is available: {}!",
                    update.latest_version.as_deref().unwrap_or("unknown")
                );
            } else {
                info!("Plugin is up to date.");
            }
        }
        Err(err) => {
            warn!("Failed to check for updates: {err}");
        }
    }
}

2. Manual Update Check (UpdateChecker)

If you want to manually specify the plugin name, version, or a custom marketplace URL, use UpdateChecker:

rust
use pumpkin_plugin_utils::UpdateChecker;

let checker = UpdateChecker::new();
let response = checker.check_for_updates(
    "my-plugin",
    "1.0.0",
    "https://market.pumpkinmc.org",
)?;

if response.update_available {
    println!("Update found: {:?}", response.latest_version);
}

Update Response Structure

The update check returns a CheckUpdateResponse:

rust
pub struct CheckUpdateResponse {
    /// Whether a newer stable release exists on the marketplace.
    pub update_available: bool,
    /// The latest stable version string, if available.
    pub latest_version: Option<String>,
}

Checking and Verifying Licenses

pumpkin-plugin-utils supports online verification against the marketplace API as well as offline lease management with configurable grace periods.

1. Online License Check

To verify a license against the marketplace (GET /api/v1/rest/check-license):

rust
// Pass `None` to use the embedded license key from metadata
let response = pumpkin_plugin_utils::check_license_online(None)
    .map_err(|e| format!("License verification failed: {e}"))?;

if response.valid {
    info!("License is valid (status: {})", response.status);
} else {
    warn!("License is invalid: {}", response.status);
    return Err(format!("Invalid license status: {}", response.status).into());
}

Providing a Custom License Key Override

If your plugin allows server owners to configure a license key in a config.toml file, pass Some(&key):

rust
let config_key = "LIC-CUSTOM-KEY-12345";
let response = pumpkin_plugin_utils::check_license_online(Some(config_key))?;

7-Day Lease Caching

Successful online checks automatically save a verified lease file (license_lease.json) into the plugin's data folder with a 7-day expiration.

2. Evaluating License with Grace Period

To keep paid plugins functional when the server is offline or the marketplace is unreachable, use evaluate_license:

rust
use pumpkin_plugin_utils::LicenseStatus;
use tracing::{info, warn};

// Specify grace period duration in days (e.g., 7 days)
let grace_period_days = 7;

match pumpkin_plugin_utils::evaluate_license(grace_period_days) {
    LicenseStatus::Valid(meta) => {
        info!("License valid for buyer (User ID: {})", meta.user_id);
    }
    LicenseStatus::GracePeriod { metadata, days_remaining, reason } => {
        warn!(
            "Running in offline grace period ({} days remaining). Reason: {}",
            days_remaining, reason
        );
    }
    LicenseStatus::Invalid(reason) => {
        return Err(format!("Plugin license invalid or tampered: {reason}").into());
    }
    LicenseStatus::Unsigned => {
        info!("Plugin is running in unsigned / development mode.");
    }
}

LicenseStatus Enum

VariantDescription
LicenseStatus::Valid(PumpkinMetadata)License is valid, verified, or the plugin is free / open-source.
LicenseStatus::GracePeriod { metadata, days_remaining, reason }Operating offline with a previously validated lease that is within the allowable grace period.
LicenseStatus::Invalid(String)Signature is invalid, expired, revoked, or metadata was tampered with.
LicenseStatus::UnsignedBinary does not contain marketplace metadata/signature (useful during local development).

Accessing Plugin Metadata

You can access the verified plugin metadata anywhere in your codebase using get_metadata or metadata():

rust
if let Some(meta) = pumpkin_plugin_utils::get_metadata() {
    println!("Plugin Name: {}", meta.plugin_name);
    println!("Plugin Version: {}", meta.version);
    println!("Is Paid: {}", meta.is_paid);
    println!("License Key: {:?}", meta.license_key);
    println!("Buyer User ID: {}", meta.user_id);
    println!("Developer: {} (ID: {})", meta.dev_name, meta.dev_id);
    println!("Marketplace URL: {}", meta.marketplace_url);
    println!("Issued At: {}", meta.issued_at);
}

PumpkinMetadata Fields

FieldTypeDescription
plugin_nameStringCanonical plugin name on the marketplace.
versionStringSemVer version string of the plugin binary.
plugin_idi64Unique plugin identifier on the marketplace.
dev_idi64Developer account ID.
dev_nameStringDeveloper display name.
is_paidbooltrue if this is a premium/commercial plugin.
user_idi64Buyer / licensee user ID (0 for free plugins).
license_keyOption<String>Unique license key issued to the licensee.
marketplace_urlStringBase marketplace URL (https://market.pumpkinmc.org).
issued_atStringISO-8601 timestamp when the binary/license was issued.

Complete Example

Here is a full plugin implementation combining initialization, online licensing checks, offline grace periods, and update checking:

rust
use pumpkin_plugin_api::{Context, Plugin, PluginMetadata};
use pumpkin_plugin_utils::LicenseStatus;
use tracing::{error, info, warn};

struct CommercialPlugin;

impl Plugin for CommercialPlugin {
    fn new() -> Self {
        Self
    }

    fn metadata(&self) -> PluginMetadata {
        PluginMetadata {
            name: "CommercialPlugin".into(),
            version: env!("CARGO_PKG_VERSION").into(),
            authors: vec!["Developer".into()],
            description: "A commercial Pumpkin plugin".into(),
        }
    }

    fn on_load(&mut self, context: Context) -> pumpkin_plugin_api::Result<()> {
        // 1. Initialize plugin-utils (verifies Ed25519 signature & caches metadata)
        let meta = pumpkin_plugin_utils::init(&context).map_err(|err| {
            error!("Signature verification failed: {err}");
            format!("Security verification error: {err}")
        })?;

        info!("Loaded {} v{}", meta.plugin_name, meta.version);

        // 2. Validate license (offline evaluation + online check)
        match pumpkin_plugin_utils::evaluate_license(7) {
            LicenseStatus::Valid(_) => {
                info!("Local license status: Valid");
            }
            LicenseStatus::GracePeriod { days_remaining, .. } => {
                warn!("Local license in grace period: {days_remaining} days remaining");
            }
            LicenseStatus::Unsigned => {
                info!("Running in development mode (unsigned binary)");
            }
            LicenseStatus::Invalid(reason) => {
                error!("Invalid license: {reason}");
                return Err(format!("License verification failed: {reason}").into());
            }
        }

        // 3. Online license check if paid
        if meta.is_paid {
            if let Ok(check) = pumpkin_plugin_utils::check_license_online(None) {
                if !check.valid {
                    return Err(format!("Online license rejected: {}", check.status).into());
                }
                info!("Online license check confirmed: {}", check.status);
            } else {
                warn!("Online license verification unreachable, falling back to local lease");
            }
        }

        // 4. Check for updates
        if let Ok(update) = pumpkin_plugin_utils::check_for_updates() {
            if update.update_available {
                info!(
                    "A new version is available on the marketplace: {}",
                    update.latest_version.as_deref().unwrap_or("unknown")
                );
            }
        }

        Ok(())
    }

    fn on_unload(&mut self, _context: Context) -> pumpkin_plugin_api::Result<()> {
        info!("CommercialPlugin unloaded.");
        Ok(())
    }
}

pumpkin_plugin_api::register_plugin!(CommercialPlugin);

Released under the MIT License.