unified_uri 0.1.0

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

[![Crates.io](https://img.shields.io/crates/v/unified_uri.svg)](https://crates.io/crates/unified_uri)
[![Documentation](https://docs.rs/unified_uri/badge.svg)](https://docs.rs/unified_uri)

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

```toml
[dependencies]
unified_uri = "0.1"
```

### Basic Example

```rust
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

```rust
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

```rust
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](https://bitcoinqr.dev/). 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

- [`bip21`]https://crates.io/crates/bip21 - BIP21 URI parsing
- [`bitcoin`]https://crates.io/crates/bitcoin - Bitcoin address handling
- [`lightning-invoice`]https://crates.io/crates/lightning-invoice - BOLT11 invoice parsing
- [`url`]https://crates.io/crates/url - URL validation

## References

- [BIP21 - URI Scheme]https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
- [BOLT11 - Invoice Protocol]https://github.com/lightning/bolts/blob/master/11-payment-encoding.md
- [Unified QR Codes for Bitcoin]https://bitcoinqr.dev/
- [Payjoin Specification]https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki

## Contributing

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

## License

This project is licensed under the MIT License.