unified_uri

Crates.iounified_uri
lib.rsunified_uri
version0.1.0
created_at2025-10-27 09:34:07.458841+00
updated_at2025-10-27 09:34:07.458841+00
descriptionA Rust library for parsing unified Bitcoin URIs that support both Lightning Network invoices and Payjoin parameters, based on the BIP21 URI specification.
homepage
repositoryhttps://github.com/RCasatta/unified_uri
max_upload_size
id1902514
size40,910
Riccardo Casatta (RCasatta)

documentation

https://docs.rs/unified_uri

README

unified_uri

Crates.io Documentation

A Rust library for parsing unified Bitcoin URIs that support both Lightning Network invoices and Payjoin parameters, based on the BIP21 URI specification.

Overview

This crate extends the BIP21 URI standard to support unified QR codes that contain both on-chain Bitcoin addresses and Lightning Network payment information, as well as Payjoin parameters. This enables a single QR code to work with both on-chain and Lightning wallets, eliminating the need for separate payment interfaces.

Features

  • Lightning Network Support: Parse BOLT11 invoices from BIP21 URIs using the lightning parameter
  • Payjoin Integration: Support for payjoin endpoints (pj) and output substitution control (pjos)
  • Backwards Compatible: Works with standard BIP21 URIs (on-chain only)
  • Security: Validates payjoin endpoints to ensure they use secure protocols (HTTPS or .onion domains)

Usage

Add this to your Cargo.toml:

[dependencies]
unified_uri = "0.1"

Basic Example

use unified_uri::UnifiedUri;
use std::str::FromStr;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Parse a unified URI with Lightning and Payjoin support
    let uri_str = "bitcoin:BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U?pj=https://payjoin.example.com/payjoin&pjos=1";
    let uri = UnifiedUri::from_str(uri_str)?;

    // Access the Bitcoin address
    println!("Address: {}", uri.address);

    // Access Lightning invoice if present
    if let Some(invoice) = &uri.extras.lightning {
        println!("Lightning Invoice: {:?}", invoice);
    }

    // Access Payjoin parameters
    if let Some(pj_url) = &uri.extras.pj {
        println!("Payjoin endpoint: {}", pj_url);
    }

    // Check if output substitution is disabled
    if uri.extras.disable_output_substitution() {
        println!("Payjoin output substitution is disabled");
    }

    Ok(())
}

Building URIs

use unified_uri::UnifiedUriBuilder;
use bitcoin::{Address, Amount};
use std::str::FromStr;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a valid Bitcoin address
    let address: Address = "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlhfe2".parse()?;

    // Build a basic on-chain URI
    let basic_uri = UnifiedUriBuilder::new(address.clone()).build();
    println!("{}", basic_uri); // "bitcoin:bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlhfe2"

    // Build a full unified URI with all parameters
    let unified_uri = UnifiedUriBuilder::new(address)
        .amount(Amount::from_sat(100_000)?) // 0.001 BTC in satoshis
        .label("Payment for services")
        .message("Thank you for your business")
        .lightning("lnbc10u1p3pj257pp5yz...") // or use lightning_invoice(parsed_invoice)
        .payjoin_url("https://payjoin.example.com/payjoin")
        .disable_output_substitution(true)
        .build();

    println!("{}", unified_uri);
    // "bitcoin:bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlhfe2?amount=0.001&label=Payment%20for%20services&message=Thank%20you%20for%20your%20business&lightning=lnbc10u1p3pj257pp5yz...&pj=https://payjoin.example.com/payjoin&pjos=1"

    Ok(())
}

Parsing Different URI Types

use unified_uri::UnifiedUri;

// On-chain only (standard BIP21)
let onchain_uri = "bitcoin:1andreas3batLhQa2FawWjeyjCqyBzypd";

// With Lightning invoice
let lightning_uri = "bitcoin:BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U?lightning=LNBC10U1P3PJ257PP5YZTKWJCZ5FTL5LAXKAV23ZMZEKAW37ZK6KMV80PK4XAEV5QHTZ7QDPDWD3XGER9WD5KWM36YPRX7U3QD36KUCMGYP282ETNV3SHJCQZPGXQYZ5VQSP5USYC4LK9CHSFP53KVCNVQ456GANH60D89REYKDNGSMTJ6YW3NHVQ9QYYSSQJCEWM5CJWZ4A6RFJX77C490YCED6PEMK0UPKXHY89CMM7SCT66K8GNEANWYKZGDRWRFJE69H9U5U0W57RRCSYSAS7GADWMZXC8C6T0SPJAZUP6";

// With Payjoin parameters
let payjoin_uri = "bitcoin:BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U?pj=https://payjoin.example.com/payjoin&pjos=1";

// All three can be parsed with UnifiedUri
let unified_uri = UnifiedUri::from_str(lightning_uri)?;

BIP21 Parameters Supported

Parameter Type Description
lightning String BOLT11 Lightning invoice
pj String Payjoin endpoint URL
pjos String Payjoin output substitution (0 = enabled, 1 = disabled)

Unified QR Codes

This crate enables the creation and parsing of unified QR codes as described in the Unified QR Code specification. These QR codes allow a single payment request to work with:

  • On-chain only wallets: Ignore Lightning and Payjoin parameters
  • Lightning wallets: Use the Lightning invoice when present
  • Payjoin-compatible wallets: Use the Payjoin endpoint for enhanced privacy

API Reference

UnifiedUri<'a>

The main type for parsing unified BIP21 URIs. This is a type alias for Uri<'a, NetworkUnchecked, UnifiedExtras>.

UnifiedExtras

Contains the extra parameters parsed from the URI:

  • lightning: Option<Bolt11Invoice> - Lightning invoice if present
  • pj: Option<Url> - Payjoin endpoint URL if present
  • pjos: Option<bool> - Payjoin output substitution setting

Methods

  • disable_output_substitution() -> bool - Returns true if payjoin output substitution should be disabled

UnifiedUriBuilder

Builder for creating unified BIP21 URI strings with optional Lightning and Payjoin parameters.

Methods

  • new(address: Address) -> Self - Create a new builder instance with required Bitcoin address
  • amount(self, amount: Amount) -> Self - Set the payment amount in satoshis
  • label<S: Into<String>>(self, label: S) -> Self - Set the payment label
  • message<S: Into<String>>(self, message: S) -> Self - Set the payment message
  • lightning_invoice(self, invoice: Bolt11Invoice) -> Self - Set the Lightning invoice from Bolt11Invoice struct
  • lightning<S: Into<String>>(self, invoice: S) -> Self - Set the Lightning invoice from string
  • payjoin_url<S: Into<String>>(self, url: S) -> Self - Set the Payjoin endpoint URL from string
  • payjoin(self, url: Url) -> Self - Set the Payjoin endpoint URL from Url struct
  • disable_output_substitution(self, disable: bool) -> Self - Set whether to disable output substitution
  • build(self) -> String - Build the final URI string

Security Considerations

  • Payjoin endpoints are validated to ensure they use secure protocols (HTTPS or .onion domains)
  • Malformed Lightning invoices will result in parsing errors
  • Multiple parameters of the same type are not allowed and will cause errors

Dependencies

References

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License.

Commit count: 0

cargo fmt