@btc-vision/cli
v1.1.0
Published
CLI for the OPNet plugin ecosystem - scaffolding, compilation, signing, and registry interaction
Maintainers
Readme
@btc-vision/cli
Official command-line interface for the OPNet plugin ecosystem. Build, sign, verify, and publish plugins with
quantum-resistant MLDSA signatures. Register .btc domains and deploy decentralized websites on Bitcoin L1.
Table of Contents
- Installation
- Quick Start
- Commands
- Configuration Reference
- Environment Variables
- Credential Storage
- MLDSA Security Levels
- .opnet Binary Format (OIP-0003)
- Plugin Manifest (plugin.json)
- Workflow Examples
- Security
- License
- Links
Installation
npm install -g @btc-vision/cliOr use npx without installing:
npx @btc-vision/cli <command>Requirements: Node.js >= 20.0.0
Quick Start
# 1. Generate a new wallet mnemonic
opnet keygen mnemonic
# 2. Configure your wallet
opnet login
# 3. Initialize a new plugin project
opnet init my-plugin
# 4. Build and compile
cd my-plugin
npm install
npm run build
opnet compile
# 5. Verify the compiled binary
opnet verify build/my-plugin.opnet
# 6. Publish to the registry
opnet publishCommands
Configuration
opnet config
Manage CLI configuration stored in ~/.opnet/config.json.
| Subcommand | Description |
|---|---|
| config get [key] | Get a configuration value (or all config if no key) |
| config set <key> <value> | Set a configuration value |
| config list | Display all configuration values |
| config reset | Reset configuration to defaults |
| config path | Show configuration file path |
Examples:
# Show all configuration
opnet config list
# Get a specific value (dot notation supported)
opnet config get defaultNetwork
opnet config get rpcUrls.mainnet
# Set a value
opnet config set defaultNetwork testnet
opnet config set rpcUrls.regtest "http://localhost:9001"
opnet config set ipfsPinningApiKey "your-api-key"
# Reset to defaults (requires confirmation)
opnet config reset
opnet config reset --yes
# Show config file path
opnet config pathOptions for config reset:
| Option | Description |
|---|---|
| -y, --yes | Skip confirmation prompt |
Authentication
opnet login
Configure wallet credentials for signing and publishing. Credentials are stored in ~/.opnet/credentials.json with restricted permissions (owner read/write only).
Syntax: opnet login [options]
Options:
| Option | Description | Default |
|---|---|---|
| -m, --mnemonic <phrase> | BIP-39 mnemonic phrase (12 or 24 words) | — |
| --wif <key> | Bitcoin WIF private key (advanced) | — |
| --mldsa <key> | MLDSA private key hex (advanced, requires --wif) | — |
| -l, --mldsa-level <level> | MLDSA security level: 44, 65, or 87 | 44 |
| -n, --network <network> | Network: mainnet, testnet, or regtest | mainnet |
Examples:
# Interactive mode (recommended) - prompts for mnemonic and network
opnet login
# With mnemonic phrase directly
opnet login --mnemonic "your twelve or twenty four word phrase here ..."
# With specific network
opnet login --mnemonic "your phrase ..." --network regtest
# Advanced: WIF + standalone MLDSA key
opnet login --wif "KwDiBf..." --mldsa "hex-private-key..."After login, the CLI displays your wallet identity (P2TR address and MLDSA public key hash) for verification before saving.
opnet logout
Remove stored wallet credentials from ~/.opnet/credentials.json. If credentials are set via environment variables, it displays instructions for unsetting them instead.
Syntax: opnet logout [options]
Options:
| Option | Description |
|---|---|
| -y, --yes | Skip confirmation prompt |
Examples:
opnet logout
opnet logout --yesopnet whoami
Display current wallet identity and configuration details.
Syntax: opnet whoami [options]
Options:
| Option | Description |
|---|---|
| -v, --verbose | Show detailed information (auth method, masked mnemonic/WIF, public key size) |
| --public-key | Show full MLDSA public key (hex) |
Examples:
# Basic identity
opnet whoami
# Detailed info including auth method and masked secrets
opnet whoami --verbose
# Include full MLDSA public key
opnet whoami --public-keyOutput includes:
- Network
- MLDSA security level
- Auth source (file or environment variable)
- P2TR address
- MLDSA public key hash
Key Generation
opnet keygen
Generate cryptographic keys. Has three subcommands: mnemonic, mldsa, and info.
opnet keygen mnemonic
Generate a new BIP-39 mnemonic phrase.
Options:
| Option | Description |
|---|---|
| -o, --output <file> | Write mnemonic to file (with secure 0600 permissions) |
# Display mnemonic in terminal
opnet keygen mnemonic
# Save to file
opnet keygen mnemonic --output my-mnemonic.txtopnet keygen mldsa
Generate a standalone MLDSA keypair.
Options:
| Option | Description | Default |
|---|---|---|
| -l, --level <level> | MLDSA security level: 44, 65, or 87 | 44 |
| -o, --output <prefix> | Write keys to <prefix>.private.key and <prefix>.public.key | — |
| --json | Output as JSON | — |
# Generate and display MLDSA-44 keypair
opnet keygen mldsa
# Generate MLDSA-65 keypair
opnet keygen mldsa --level 65
# Save to files
opnet keygen mldsa --output my-key
# Creates: my-key.private.key (0600) and my-key.public.key (0644)
# JSON output
opnet keygen mldsa --jsonopnet keygen info
Display information about MLDSA key sizes across all security levels.
opnet keygen infoOutput:
Level Public Key Private Key Signature
────────────────────────────────────────────────────────────
MLDSA-44 1,312 bytes 2,560 bytes 2,420 bytes
MLDSA-65 1,952 bytes 4,032 bytes 3,309 bytes
MLDSA-87 2,592 bytes 4,896 bytes 4,627 bytesPlugin Development
opnet init
Scaffold a new OPNet plugin project in the current directory.
Syntax: opnet init [name] [options]
Arguments:
| Argument | Description | Default |
|---|---|---|
| [name] | Plugin name | Current directory name (interactive prompt) |
Options:
| Option | Description | Default |
|---|---|---|
| -t, --template <type> | Template type: standalone or library | standalone |
| -y, --yes | Skip prompts and use defaults | — |
| --force | Overwrite existing files | — |
Examples:
# Interactive mode - prompts for name, description, author, type
opnet init
# With name
opnet init my-plugin
# Library template with defaults
opnet init my-lib --template library --yes
# Overwrite existing project files
opnet init my-plugin --forceGenerated files:
| File | Description |
|---|---|
| plugin.json | Plugin manifest with permissions, resources, and lifecycle configuration |
| package.json | Node.js package configuration with OPNet dependencies |
| tsconfig.json | TypeScript configuration (ES2022, NodeNext) |
| src/index.ts | Entry point (extends PluginBase for standalone, exports for library) |
| eslint.config.js | ESLint configuration with typescript-eslint |
| .prettierrc.json | Prettier formatting configuration |
| .gitignore | Git ignore rules |
| README.md | Basic readme |
Created directories: src/, dist/, build/, test/
opnet compile
Compile a plugin to the .opnet binary format. This command:
- Loads and validates
plugin.json - Bundles TypeScript with esbuild (CJS format)
- Compiles to V8 bytecode via bytenode
- Optionally signs the binary with your MLDSA key
- Assembles the final
.opnetbinary
Syntax: opnet compile [options]
Options:
| Option | Description | Default |
|---|---|---|
| -o, --output <path> | Output file path | ./build/<name>.opnet |
| -d, --dir <path> | Plugin directory | Current directory |
| --no-sign | Skip signing (produce unsigned binary) | Signs by default |
| --minify | Minify the bundled code | true |
| --sourcemap | Generate source maps | false |
Examples:
# Compile current directory (signed)
opnet compile
# Compile a specific directory
opnet compile --dir ./my-plugin
# Custom output path
opnet compile --output ./dist/plugin.opnet
# Unsigned binary (for testing)
opnet compile --no-sign
# With source maps, no minification
opnet compile --sourcemap --no-minifyPrerequisites:
plugin.jsonmust exist in the project directorysrc/index.tsmust exist as the entry point- For signed binaries, wallet must be configured (
opnet login)
Output information:
- Output file path and size
- Plugin name, version, and type
- MLDSA level, SHA-256 checksum, and signing status
Note: Unsigned binaries cannot be published to the registry. Use
opnet signto sign them later.
opnet verify
Verify a .opnet binary's signature and integrity. Checks both the SHA-256 checksum and the MLDSA signature.
Syntax: opnet verify <file> [options]
Arguments:
| Argument | Description |
|---|---|
| <file> | Path to .opnet file |
Options:
| Option | Description |
|---|---|
| -v, --verbose | Show detailed information (sizes, checksums, author, permissions) |
| --json | Output results as JSON |
Examples:
# Basic verification
opnet verify build/my-plugin.opnet
# Detailed output
opnet verify build/my-plugin.opnet --verbose
# JSON output (useful for CI/CD)
opnet verify build/my-plugin.opnet --jsonExit codes:
0- Binary is valid and properly signed1- Verification failed (invalid checksum, invalid signature, or unsigned)
Verification checks:
- SHA-256 checksum integrity
- MLDSA signature validity
- Unsigned binary detection
opnet info
Display information about a plugin project or a compiled .opnet file.
Syntax: opnet info [path] [options]
Arguments:
| Argument | Description | Default |
|---|---|---|
| [path] | Path to plugin directory, plugin.json, or .opnet file | Current directory |
Options:
| Option | Description |
|---|---|
| --json | Output as JSON |
Examples:
# Show project info (current directory)
opnet info
# Show info for a specific directory
opnet info ./my-plugin
# Show compiled binary info
opnet info build/my-plugin.opnet
# JSON output
opnet info --json
opnet info build/my-plugin.opnet --jsonFor project directories, shows:
- Plugin name, version, type, OPNet version compatibility
- Author information
- Source/dependencies/compiled status
- Permissions, resources, and lifecycle configuration
- Plugin dependencies
For .opnet files, shows:
- File size and format version
- Plugin metadata (name, version, type)
- Cryptographic info (MLDSA level, signing status, publisher hash)
- Component sizes (bytecode, metadata, proto)
- Permissions and dependencies
opnet sign
Sign or re-sign a .opnet binary with your MLDSA key. This rebuilds the binary with your key and signature.
Syntax: opnet sign <file> [options]
Arguments:
| Argument | Description |
|---|---|
| <file> | Path to .opnet file |
Options:
| Option | Description | Default |
|---|---|---|
| -o, --output <path> | Output file path | Overwrites input file |
| --force | Force re-signing even if already signed by a different key | — |
Examples:
# Sign an unsigned binary (overwrites in-place)
opnet sign build/my-plugin.opnet
# Sign and save to a different file
opnet sign build/my-plugin.opnet --output build/signed.opnet
# Re-sign a binary that was signed by someone else
opnet sign build/plugin.opnet --forceBehavior:
- If the binary is unsigned, it signs it with your key
- If already signed by the same key, it re-signs (refreshes the signature)
- If signed by a different key, it refuses unless
--forceis used - Requires wallet credentials (
opnet login)
Registry - Publishing
opnet publish
Publish a plugin to the OPNet on-chain registry. This command:
- Parses and validates the
.opnetbinary - Verifies checksum and signature
- Confirms your wallet matches the signer
- Checks scope registration (for scoped packages)
- Uploads the binary to IPFS
- Registers the package on-chain (if new)
- Publishes the version on-chain
Syntax: opnet publish [file] [options]
Arguments:
| Argument | Description | Default |
|---|---|---|
| [file] | Path to .opnet file | Auto-detected from plugin.json (./build/<name>.opnet) |
Options:
| Option | Description | Default |
|---|---|---|
| -n, --network <network> | Network to publish to | mainnet |
| --dry-run | Show what would be published without actually publishing | — |
| -y, --yes | Skip confirmation prompts | — |
Examples:
# Publish from current directory (auto-detects .opnet file)
opnet publish
# Publish specific file
opnet publish build/my-plugin.opnet
# Dry run to preview
opnet publish --dry-run
# Publish to regtest
opnet publish --network regtest
# Non-interactive
opnet publish --yes --network regtestPrerequisites:
- Binary must be signed (run
opnet compileoropnet sign) - Wallet must match the binary signer
- For scoped packages (
@scope/name), the scope must be registered first - Wallet must have sufficient balance for transaction fees
opnet deprecate
Mark a package version as deprecated. Only works within the 72-hour mutability window after publishing.
Syntax: opnet deprecate <package> [version] [options]
Arguments:
| Argument | Description | Default |
|---|---|---|
| <package> | Package name (e.g., @scope/name or name) | — |
| [version] | Version to deprecate | Latest version |
Options:
| Option | Description | Default |
|---|---|---|
| -m, --message <message> | Deprecation reason/message | Interactive prompt (or "No reason provided") |
| -n, --network <network> | Network | mainnet |
| -y, --yes | Skip confirmation | — |
Examples:
# Deprecate latest version (prompts for reason)
opnet deprecate @myscope/plugin
# Deprecate specific version with reason
opnet deprecate @myscope/plugin 1.0.0 --message "Security vulnerability found"
# Non-interactive
opnet deprecate @myscope/plugin 1.0.0 -m "Use v2.0.0 instead" --yesNote: Versions past the 72-hour mutability window cannot be deprecated.
opnet undeprecate
Remove deprecation from a package version. Only works within the 72-hour mutability window.
Syntax: opnet undeprecate <package> <version> [options]
Arguments:
| Argument | Description |
|---|---|
| <package> | Package name (e.g., @scope/name or name) |
| <version> | Version to undeprecate |
Options:
| Option | Description | Default |
|---|---|---|
| -n, --network <network> | Network | mainnet |
| -y, --yes | Skip confirmation | — |
Examples:
opnet undeprecate @myscope/plugin 1.0.0
opnet undeprecate @myscope/plugin 1.0.0 --network regtest --yesRegistry - Package Management
opnet install
Download and verify a plugin from the registry. Automatically removes older versions of the same plugin from the output directory.
Syntax: opnet install <package> [options]
Arguments:
| Argument | Description |
|---|---|
| <package> | Package name (optionally with @version), or a raw IPFS CID |
Options:
| Option | Description | Default |
|---|---|---|
| -o, --output <path> | Output directory | ./plugins/ |
| -n, --network <network> | Network | mainnet |
| --skip-verify | Skip signature verification | — |
Examples:
# Install latest version
opnet install @scope/plugin
# Install specific version
opnet install @scope/[email protected]
# Install an unscoped package
opnet install my-plugin
# Install directly from IPFS CID
opnet install QmXyz...
# Custom output directory
opnet install @scope/plugin --output ./my-plugins
# Skip signature verification (not recommended)
opnet install @scope/plugin --skip-verifyWhat happens:
- Resolves the package and version from the on-chain registry
- Downloads the binary from IPFS
- Verifies checksum integrity
- Verifies MLDSA signature (unless
--skip-verify) - Removes older versions of the same plugin from the output directory
- Saves to
<output>/<package>-<version>.opnet
opnet update
Check and update installed plugins to their latest versions.
Syntax: opnet update [package] [options]
Arguments:
| Argument | Description | Default |
|---|---|---|
| [package] | Specific package to update | All installed plugins |
Options:
| Option | Description | Default |
|---|---|---|
| -d, --dir <path> | Plugins directory | ./plugins/ |
| -n, --network <network> | Network | mainnet |
| --skip-verify | Skip signature verification | — |
Examples:
# Update all installed plugins
opnet update
# Update a specific plugin
opnet update @scope/plugin
# Custom plugins directory
opnet update --dir ./my-plugins
# Different network
opnet update --network regtestopnet list
List all installed plugins in a directory. Alias: opnet ls.
Syntax: opnet list [options]
Options:
| Option | Description | Default |
|---|---|---|
| -d, --dir <path> | Plugins directory | ./plugins/ |
| --json | Output as JSON | — |
| -v, --verbose | Show detailed information per plugin | — |
Examples:
# List plugins (table view)
opnet list
opnet ls
# Detailed list
opnet list --verbose
# JSON output
opnet list --json
# Custom directory
opnet list --dir ./my-pluginsTable output columns: Name, Version, Type, Size, Signed
opnet search
Search for a plugin in the on-chain registry by exact package name.
Syntax: opnet search <query> [options]
Arguments:
| Argument | Description |
|---|---|
| <query> | Package name or @scope/name |
Options:
| Option | Description | Default |
|---|---|---|
| -n, --network <network> | Network | mainnet |
| --json | Output as JSON | — |
Examples:
opnet search my-plugin
opnet search @scope/plugin
opnet search @scope/plugin --json
opnet search my-plugin --network regtestOutput includes:
- Package name, latest version, total version count
- Owner address
- Latest version details: type, MLDSA level, OPNet version range, IPFS CID, deprecation status, publish block
- Install command
Registry - Ownership
opnet scope:register
Register a new scope (namespace) in the on-chain registry. Scopes are required for publishing scoped packages (@scope/name). Registration has a one-time fee.
Syntax: opnet scope:register <name> [options]
Arguments:
| Argument | Description |
|---|---|
| <name> | Scope name (without @ prefix) |
Options:
| Option | Description | Default |
|---|---|---|
| -n, --network <network> | Network | mainnet |
| -y, --yes | Skip confirmation | — |
Scope name rules:
- Must start with a lowercase letter
- Can contain only lowercase letters, numbers, and hyphens
- Must end with a letter or number
Examples:
# Register a scope
opnet scope:register myscope
# With @ prefix (auto-stripped)
opnet scope:register @myscope
# On regtest
opnet scope:register myscope --network regtest --yesopnet transfer
Initiate ownership transfer of a package or scope. The new owner must call opnet accept to complete the transfer. You can also cancel a pending transfer.
Syntax: opnet transfer <name> [newOwner] [options]
Arguments:
| Argument | Description |
|---|---|
| <name> | Package name or @scope (scope if starts with @ and no /) |
| [newOwner] | New owner address (prompted if not provided) |
Options:
| Option | Description | Default |
|---|---|---|
| -n, --network <network> | Network | mainnet |
| -y, --yes | Skip confirmation | — |
| --cancel | Cancel a pending transfer instead of initiating one | — |
Examples:
# Transfer a package
opnet transfer my-plugin bc1p...
# Transfer a scope
opnet transfer @myscope bc1p...
# Interactive (prompts for new owner address)
opnet transfer my-plugin
# Cancel a pending transfer
opnet transfer my-plugin --cancel
opnet transfer @myscope --cancelopnet accept
Accept a pending ownership transfer for a package or scope.
Syntax: opnet accept <name> [options]
Arguments:
| Argument | Description |
|---|---|
| <name> | Package name or @scope |
Options:
| Option | Description | Default |
|---|---|---|
| -n, --network <network> | Network | mainnet |
| -y, --yes | Skip confirmation | — |
Examples:
# Accept package transfer
opnet accept my-plugin
# Accept scope transfer
opnet accept @myscope
# Non-interactive
opnet accept my-plugin --network regtest --yesDomains & Websites
opnet domain register
Register a new .btc domain on the BTC Name Resolver. Domain pricing is tiered based on name length and keyword value.
Syntax: opnet domain register <name> [options]
Arguments:
| Argument | Description |
|---|---|
| <name> | Domain name to register (without .btc suffix) |
Options:
| Option | Description | Default |
|---|---|---|
| -n, --network <network> | Network | mainnet |
| --dry-run | Show what would happen without registering | — |
| -y, --yes | Skip confirmation prompts | — |
Pricing tiers:
| Tier | Price | Description | |---|---|---| | Ultra Legendary (Tier 0) | 10 BTC | Iconic crypto/tech names | | Legendary (Tier 1) | 1 BTC | Single character or top keywords | | Premium (Tier 2) | 0.25 BTC | Two character or major protocols | | High Value (Tier 3) | 0.1 BTC | Three character or valuable keywords | | Valuable (Tier 4) | 0.05 BTC | Four character or common keywords | | Common Premium (Tier 5) | 0.01 BTC | Five character domains | | Notable (Tier 6) | 0.005 BTC | Notable keywords | | Standard | 0.001 BTC | Standard domains (6+ characters) |
Examples:
# Register a domain
opnet domain register mysite
# Preview without registering
opnet domain register mysite --dry-run
# Register on regtest
opnet domain register mysite --network regtest --yesNote: Subdomains cannot be registered directly. Register the parent domain first.
opnet domain info
Look up information about a .btc domain. Shows registration status, owner, contenthash (website), and pricing for available domains.
Syntax: opnet domain info <name> [options]
Arguments:
| Argument | Description |
|---|---|
| <name> | Domain name (with or without .btc suffix) |
Options:
| Option | Description | Default |
|---|---|---|
| -n, --network <network> | Network | mainnet |
Examples:
# Look up a domain
opnet domain info mysite
opnet domain info mysite.btc
# Check on regtest
opnet domain info mysite --network regtestFor registered domains, shows:
- Owner address
- Creation block
- TTL
- Website contenthash (if set) with IPFS/IPNS gateway URL
For unregistered domains, shows:
- Availability status
- Registration price and pricing tier
- Registration command
opnet website
Publish a website to a .btc domain by setting its contenthash. Supports IPFS CIDv0, CIDv1, IPNS, and SHA256 content hashes.
Syntax: opnet website <domain> <contenthash> [options]
Arguments:
| Argument | Description |
|---|---|
| <domain> | Domain name (e.g., mysite or mysite.btc) |
| <contenthash> | IPFS CID, IPNS ID, or SHA256 hash |
Options:
| Option | Description | Default |
|---|---|---|
| -n, --network <network> | Network | mainnet |
| --dry-run | Show what would be published without publishing | — |
| -y, --yes | Skip confirmation prompts | — |
| -t, --type <type> | Contenthash type: cidv0, cidv1, ipns, sha256 | Auto-detected |
Examples:
# Publish with IPFS CID (auto-detected type)
opnet website mysite QmXyz...abc
opnet website mysite bafybeiabc...
# Publish with IPNS
opnet website mysite k51qzi5uqu5d... --type ipns
# Publish with SHA256 hash
opnet website mysite abc123...def --type sha256
# Dry run
opnet website mysite QmXyz... --dry-run
# Subdomain support
opnet website blog.mysite QmXyz...Contenthash type auto-detection:
- Starts with
Qm→ CIDv0 - Starts with
bafy→ CIDv1 - Starts with
k51or12D3→ IPNS - 64 hex characters → SHA256
opnet deploy
Upload a website directory or file to IPFS and publish the resulting CID to a .btc domain in a single command. Combines IPFS upload + on-chain contenthash update.
Syntax: opnet deploy <domain> <path> [options]
Arguments:
| Argument | Description |
|---|---|
| <domain> | Domain name (e.g., mysite or mysite.btc) |
| <path> | Path to website directory or HTML file |
Options:
| Option | Description | Default |
|---|---|---|
| -n, --network <network> | Network | mainnet |
| --dry-run | Upload to IPFS but don't update on-chain | — |
| -y, --yes | Skip confirmation prompts | — |
Examples:
# Deploy a directory
opnet deploy mysite ./dist
# Deploy a single HTML file
opnet deploy mysite ./index.html
# Upload to IPFS only (no on-chain update)
opnet deploy mysite ./dist --dry-run
# Non-interactive deployment to regtest
opnet deploy mysite ./dist --network regtest --yesWhat happens:
- Validates domain ownership
- Uploads the directory/file to IPFS
- Sets the CIDv1 contenthash on-chain
- Waits for transaction confirmation
Configuration Reference
Configuration is stored in ~/.opnet/config.json. All values can be set with opnet config set or overridden with environment variables.
{
"defaultNetwork": "regtest",
"rpcUrls": {
"mainnet": "https://api.opnet.org",
"testnet": "https://testnet.opnet.org",
"regtest": "https://regtest.opnet.org"
},
"ipfsGateway": "https://ipfs.opnet.org/ipfs/",
"ipfsGateways": ["https://ipfs.opnet.org/ipfs/"],
"ipfsPinningEndpoint": "https://ipfs.opnet.org/api/v0/add",
"ipfsPinningApiKey": "",
"ipfsPinningSecret": "",
"ipfsPinningAuthHeader": "Authorization",
"registryAddresses": {
"mainnet": "",
"testnet": "",
"regtest": "0x0737..."
},
"resolverAddresses": {
"mainnet": "",
"testnet": "",
"regtest": "0x271e..."
},
"defaultMldsaLevel": 44,
"indexerUrl": "https://indexer.opnet.org"
}| Key | Description |
|---|---|
| defaultNetwork | Default network for all commands (mainnet, testnet, regtest) |
| rpcUrls.<network> | JSON-RPC endpoint for each network |
| ipfsGateway | Primary IPFS gateway for downloads |
| ipfsGateways | Fallback IPFS gateways |
| ipfsPinningEndpoint | IPFS pinning service URL |
| ipfsPinningApiKey | IPFS pinning API key or JWT token |
| ipfsPinningSecret | IPFS pinning API secret (for key+secret auth) |
| ipfsPinningAuthHeader | Authorization header name for pinning service |
| registryAddresses.<network> | On-chain package registry contract address |
| resolverAddresses.<network> | On-chain BTC Name Resolver contract address |
| defaultMldsaLevel | Default MLDSA security level for key generation |
| indexerUrl | Indexer API URL for search operations |
Environment Variables
Environment variables override file-based configuration and credentials. Useful for CI/CD pipelines.
| Variable | Description | Overrides |
|---|---|---|
| OPNET_MNEMONIC | BIP-39 mnemonic phrase | Credentials file |
| OPNET_PRIVATE_KEY | Bitcoin WIF private key | Credentials file |
| OPNET_MLDSA_KEY | MLDSA private key (hex) | Credentials file |
| OPNET_MLDSA_LEVEL | MLDSA security level (44, 65, 87) | Credentials file |
| OPNET_NETWORK | Network (mainnet, testnet, regtest) | defaultNetwork config |
| OPNET_RPC_URL | RPC endpoint URL | rpcUrls.<network> config |
| OPNET_IPFS_GATEWAY | IPFS gateway URL | ipfsGateway config |
| OPNET_IPFS_PINNING_ENDPOINT | IPFS pinning service endpoint | ipfsPinningEndpoint config |
| OPNET_IPFS_PINNING_KEY | IPFS pinning API key | ipfsPinningApiKey config |
| OPNET_REGISTRY_ADDRESS | Registry contract address | registryAddresses.<network> config |
| OPNET_INDEXER_URL | Indexer API URL | indexerUrl config |
CI/CD example:
export OPNET_MNEMONIC="your mnemonic phrase here"
export OPNET_NETWORK="regtest"
opnet compile && opnet publish --yesCredential Storage
Credentials are stored in ~/.opnet/credentials.json with restricted permissions (0600 - owner read/write only).
Two authentication methods:
- BIP-39 Mnemonic (recommended) - Derives both Bitcoin (secp256k1) and MLDSA keys from a single phrase
- WIF + MLDSA key (advanced) - Separate Bitcoin private key and standalone MLDSA private key
Priority order: Environment variables > credentials file
MLDSA Security Levels
OPNet uses ML-DSA (Module-Lattice-Based Digital Signature Algorithm) for quantum-resistant signatures.
| Level | Public Key | Private Key | Signature | Security | |---|---|---|---|---| | MLDSA-44 | 1,312 bytes | 2,560 bytes | 2,420 bytes | ~128-bit | | MLDSA-65 | 1,952 bytes | 4,032 bytes | 3,309 bytes | ~192-bit | | MLDSA-87 | 2,592 bytes | 4,896 bytes | 4,627 bytes | ~256-bit |
Note: OPNet currently only supports MLDSA-44 on-chain.
.opnet Binary Format (OIP-0003)
The .opnet binary format consists of:
| Section | Size | Description |
|---|---|---|
| Magic bytes | 8 bytes | OPNETPLG |
| Format version | 4 bytes | uint32 LE |
| MLDSA level | 1 byte | 0=MLDSA-44, 1=MLDSA-65, 2=MLDSA-87 |
| Public key | Variable | Size depends on MLDSA level |
| Signature | Variable | Size depends on MLDSA level |
| Metadata length | 4 bytes | uint32 LE |
| Metadata | Variable | JSON-encoded plugin manifest |
| Bytecode length | 4 bytes | uint32 LE |
| Bytecode | Variable | V8 bytecode (bytenode compiled) |
| Proto length | 4 bytes | uint32 LE |
| Proto | Variable | Protobuf definitions (optional) |
| Checksum | 32 bytes | SHA-256 of metadata + bytecode + proto |
Plugin Manifest (plugin.json)
The plugin.json file defines your plugin's metadata, permissions, resource limits, and lifecycle configuration.
{
"name": "my-plugin",
"version": "1.0.0",
"opnetVersion": ">=0.0.1",
"main": "dist/index.jsc",
"target": "bytenode",
"type": "plugin",
"author": {
"name": "Developer Name",
"email": "[email protected]"
},
"description": "My OPNet plugin",
"pluginType": "standalone",
"permissions": {
"database": {
"enabled": false,
"collections": []
},
"blocks": {
"preProcess": false,
"postProcess": false,
"onChange": false
},
"epochs": {
"onChange": false,
"onFinalized": false
},
"mempool": {
"txFeed": false,
"txSubmit": false
},
"api": {
"addEndpoints": false,
"addWebsocket": false
},
"threading": {
"maxWorkers": 1,
"maxMemoryMB": 256
},
"filesystem": {
"configDir": false,
"tempDir": false
}
},
"resources": {
"memory": {
"maxHeapMB": 256,
"maxOldGenMB": 128,
"maxYoungGenMB": 64
},
"cpu": {
"maxThreads": 2,
"priority": "normal"
},
"timeout": {
"initMs": 30000,
"hookMs": 5000,
"shutdownMs": 10000
}
},
"lifecycle": {
"loadPriority": 100,
"enabledByDefault": true,
"requiresRestart": false
},
"dependencies": {}
}Permissions reference:
| Permission | Description |
|---|---|
| database.enabled | Access to plugin database |
| database.collections | Allowed collection names |
| blocks.preProcess | Hook before block processing |
| blocks.postProcess | Hook after block processing |
| blocks.onChange | Hook on block changes |
| epochs.onChange | Hook on epoch changes |
| epochs.onFinalized | Hook on epoch finalization |
| mempool.txFeed | Subscribe to mempool transactions |
| mempool.txSubmit | Submit transactions to mempool |
| api.addEndpoints | Register custom API endpoints |
| api.addWebsocket | Register WebSocket handlers |
| threading.maxWorkers | Maximum worker threads |
| threading.maxMemoryMB | Maximum memory per worker |
| filesystem.configDir | Access to config directory |
| filesystem.tempDir | Access to temp directory |
Workflow Examples
Publish a new plugin
# Create project
opnet init my-plugin
cd my-plugin
npm install
# Develop your plugin
# Edit src/index.ts
# Build and compile
npm run build
opnet compile
# Verify
opnet verify build/my-plugin.opnet
# Register scope (if using scoped name)
opnet scope:register myscope --network regtest
# Publish
opnet publish --network regtestRegister a domain and deploy a website
# Register a .btc domain
opnet domain register mysite --network regtest
# Option A: Deploy from a directory (upload + publish in one step)
opnet deploy mysite ./dist --network regtest
# Option B: Manual two-step process
# 1. Upload to IPFS separately, get the CID
# 2. Set the contenthash
opnet website mysite QmXyz... --network regtestTransfer ownership
# Current owner initiates transfer
opnet transfer @myscope bc1p...newowner
# New owner accepts
opnet accept @myscopeCI/CD pipeline
export OPNET_MNEMONIC="your mnemonic"
export OPNET_NETWORK="mainnet"
npm run build
opnet compile
opnet verify build/my-plugin.opnet --json
opnet publish --yesSecurity
- Credentials are stored with restricted permissions (
0600- owner read/write only) - All plugin binaries are signed with quantum-resistant MLDSA signatures
- SHA-256 checksums verify binary integrity
- IPFS CIDs provide content-addressed storage
- On-chain registry ensures immutable publishing records
- 72-hour mutability window for deprecation/undeprecation after publishing
License
Apache-2.0
