npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@btc-vision/cli

v1.1.0

Published

CLI for the OPNet plugin ecosystem - scaffolding, compilation, signing, and registry interaction

Readme

@btc-vision/cli

Bitcoin TypeScript NodeJS NPM

code style: prettier

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

npm install -g @btc-vision/cli

Or 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 publish

Commands

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 path

Options 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 --yes

opnet 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-key

Output 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.txt
opnet 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 --json
opnet keygen info

Display information about MLDSA key sizes across all security levels.

opnet keygen info

Output:

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 bytes

Plugin 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 --force

Generated 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:

  1. Loads and validates plugin.json
  2. Bundles TypeScript with esbuild (CJS format)
  3. Compiles to V8 bytecode via bytenode
  4. Optionally signs the binary with your MLDSA key
  5. Assembles the final .opnet binary

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-minify

Prerequisites:

  • plugin.json must exist in the project directory
  • src/index.ts must 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 sign to 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 --json

Exit codes:

  • 0 - Binary is valid and properly signed
  • 1 - 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 --json

For 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 --force

Behavior:

  • 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 --force is used
  • Requires wallet credentials (opnet login)

Registry - Publishing

opnet publish

Publish a plugin to the OPNet on-chain registry. This command:

  1. Parses and validates the .opnet binary
  2. Verifies checksum and signature
  3. Confirms your wallet matches the signer
  4. Checks scope registration (for scoped packages)
  5. Uploads the binary to IPFS
  6. Registers the package on-chain (if new)
  7. 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 regtest

Prerequisites:

  • Binary must be signed (run opnet compile or opnet 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" --yes

Note: 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 --yes

Registry - 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-verify

What happens:

  1. Resolves the package and version from the on-chain registry
  2. Downloads the binary from IPFS
  3. Verifies checksum integrity
  4. Verifies MLDSA signature (unless --skip-verify)
  5. Removes older versions of the same plugin from the output directory
  6. 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 regtest

opnet 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-plugins

Table 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 regtest

Output 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 --yes

opnet 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 --cancel

opnet 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 --yes

Domains & 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 --yes

Note: 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 regtest

For 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 k51 or 12D3 → 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 --yes

What happens:

  1. Validates domain ownership
  2. Uploads the directory/file to IPFS
  3. Sets the CIDv1 contenthash on-chain
  4. 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 --yes

Credential Storage

Credentials are stored in ~/.opnet/credentials.json with restricted permissions (0600 - owner read/write only).

Two authentication methods:

  1. BIP-39 Mnemonic (recommended) - Derives both Bitcoin (secp256k1) and MLDSA keys from a single phrase
  2. 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 regtest

Register 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 regtest

Transfer ownership

# Current owner initiates transfer
opnet transfer @myscope bc1p...newowner

# New owner accepts
opnet accept @myscope

CI/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 --yes

Security

  • 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

Links