Licensing
Offline-verified EdDSA license keys + cloud-issued entitlement snapshots that feed plugin-billing; pricing decided server-side, never baked into a published version.
@voltro/plugin-licensing lets a cloud decide a customer's plan entitlements and pricing, and enforces them in a running app — without baking anything into an immutable published version. The license is a compact EdDSA (Ed25519) token, verified offline with a public key the app embeds. It's asymmetric on purpose: the signing secret never leaves the cloud, so a customer cannot forge a license.
The plugin does not enforce quotas itself — it feeds plugin-billing, which meters and enforces them via a new resolveEntitlementLimit seam.
Wiring
// app.config.ts
import { licensingPlugin, entitlementResolver } from '@voltro/plugin-licensing'
import { billingPlugin } from '@voltro/plugin-billing'
export default {
type: 'api' as const,
name: 'api',
plugins: [
licensingPlugin(), // reads VOLTRO_LICENSE_* env
billingPlugin({
plans: { free: { entitlements: { projects: 1 } } }, // static fallback
resolveEntitlementLimit: entitlementResolver, // license overrides per-tenant
}),
],
}With a valid pro license granting { projects: 3 }, requireEntitlement(ctx, 'projects', 1) allows three and then fails EntitlementExceeded. With no license, the static free plan (projects: 1) applies — the resolver returns null and billing falls back to its own registry.
Keys
Generate a signing keypair:
voltro secret keypair
# VOLTRO_LICENSE_SIGNING_KEY (Ed25519 private, cloud-only — signs licenses, NEVER ship)
# VOLTRO_LICENSE_PUBLIC_KEY (Ed25519 public — ship to apps for offline verify)Keep the private key in the cloud; the public key is safe to embed. The app reads VOLTRO_LICENSE_PUBLIC_KEY + VOLTRO_LICENSE_KEY (or the publicKey / licenseKey options).
Options
| Option | Env fallback | Meaning |
|---|---|---|
publicKey |
VOLTRO_LICENSE_PUBLIC_KEY |
Ed25519 public key (SPKI/PEM). |
licenseKey |
VOLTRO_LICENSE_KEY |
The signed license token, verified at boot. |
snapshotUrl |
— | URL to periodically re-fetch a fresh license (entitlement sync). |
refreshMs |
— | Refresh cadence for snapshotUrl (default 15 min, cluster-coordinated). |
graceSeconds |
— | Seconds an expired license is still honored (default 0). |
API
entitlementResolver(tenantId, key)— pass tobillingPlugin({ resolveEntitlementLimit }). Returns the license's limit for the pair, ornullto fall back to the static plan registry.LicenseService—yield* LicenseServicein a handler to readlimitFor/hasFeature/allowsPlugin/edition/snapshotdirectly.licenseAllowsPlugin(name)/requireLicensedPlugin(name)— the plugin gate ("installed ≠ billable"). A license carries aplugins: string[]grant;requireLicensedPluginfailsPluginNotLicensedwhen a plugin's production use isn't licensed. Metered plugin pricing uses entitlement keys (plugin:<name>:<meter>); plan-gated plugins use the grant. Pricing is decided server-side and travels in the signed license — noplugin_definitionstable needed in the app.verifyLicense(token, publicKeyPem)— offline EdDSA verify →LicenseClaims.@voltro/plugin-licensing/sign→signLicense(input, privateKeyPem)— server-only signer for the cloud/CLI to mint licenses.
Notes
- Server-only (imports
jose) — never import from a browser-loaded descriptor. - Fail-closed: an invalid/expired token grants nothing; billing falls back to the static plan. The last-good snapshot is honored within
graceSeconds. - Cloud-side issuance (a license endpoint +
voltro cloud register) ships in a later milestone; today licenses are provided via env / config.