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 🙏

© 2025 – Pkg Stats / Ryan Hefner

bip-321

v0.0.8

Published

A TypeScript/JavaScript library for parsing BIP-321 Bitcoin URI scheme with support for multiple payment methods

Downloads

174

Readme

BIP-321 Parser

A TypeScript/JavaScript library for parsing BIP-321 Bitcoin URI scheme. This library validates and extracts payment information from Bitcoin URIs, supporting multiple payment methods including on-chain addresses, Lightning invoices, BOLT12 offers, and silent payments.

Features

  • Complete BIP-321 compliance - Implements the full BIP-321 specification
  • Multiple payment methods - Supports on-chain, Lightning (BOLT11), BOLT12 offers, silent payments, and Ark
  • Network detection - Automatically detects mainnet, testnet, regtest, and signet networks
  • Address validation - Validates Bitcoin addresses (P2PKH, P2SH, Segwit v0, Taproot)
  • Lightning invoice validation - Validates BOLT11 Lightning invoices
import { parseBIP321, type BIP321ParseResult, type PaymentMethod } from "bip-321";

const result: BIP321ParseResult = parseBIP321("bitcoin:...");

result.valid;           // boolean
result.network;         // "mainnet" | "testnet" | "regtest" | "signet" | undefined
result.paymentMethods;  // PaymentMethod[]
result.errors;          // string[]

result.paymentMethods.forEach((method: PaymentMethod) => {
  method.type;    // "onchain" | "lightning" | "offer" | "silent-payment" | "ark"
  method.network; // "mainnet" | "testnet" | "regtest" | "signet" | undefined
  method.valid;   // boolean
});

Installation

bun add bip-321

Or with npm:

npm install bip-321

Quick Start

import { parseBIP321 } from "bip-321";

// Parse a simple Bitcoin address
const result = parseBIP321("bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa");

console.log(result.valid); // true
console.log(result.network); // "mainnet"
console.log(result.address); // "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
console.log(result.paymentMethods); // Array of payment methods

Validation Functions

The library also exports standalone validation functions that can be used independently:

import {
  validateBitcoinAddress,
  validateLightningInvoice,
  validateBolt12Offer,
  validateSilentPaymentAddress,
  validateArkAddress,
  validatePopUri,
} from "bip-321";

// Validate a Bitcoin address
const btcResult = validateBitcoinAddress("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa");
console.log(btcResult.valid); // true
console.log(btcResult.network); // "mainnet"

// Validate a Lightning invoice
const lnResult = validateLightningInvoice("lnbc15u1p3xnhl2pp5...");
console.log(lnResult.valid); // true
console.log(lnResult.network); // "mainnet"

// Validate a BOLT12 offer
const offerResult = validateBolt12Offer("lno1qqqq02k20d");
console.log(offerResult.valid); // true

// Validate a Silent Payment address
const spResult = validateSilentPaymentAddress("sp1qq...");
console.log(spResult.valid); // true
console.log(spResult.network); // "mainnet"

// Validate an Ark address
const arkResult = validateArkAddress("ark1p...");
console.log(arkResult.valid); // true
console.log(arkResult.network); // "mainnet"

// Validate a pop URI
const popResult = validatePopUri("myapp://callback");
console.log(popResult.valid); // true

Usage Examples

Basic On-Chain Payment

import { parseBIP321 } from "bip-321";

const result = parseBIP321("bitcoin:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq");

if (result.valid) {
  console.log(`Network: ${result.network}`); // mainnet
  console.log(`Address: ${result.address}`);
  console.log(`Payment methods: ${result.paymentMethods.length}`);
}

Payment with Amount and Label

const result = parseBIP321(
  "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=0.5&label=Donation&message=Thank%20you"
);

console.log(`Amount: ${result.amount} BTC`); // 0.5 BTC
console.log(`Label: ${result.label}`); // "Donation"
console.log(`Message: ${result.message}`); // "Thank you"

Lightning Invoice with On-Chain Fallback

const result = parseBIP321(
  "bitcoin:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq?lightning=lnbc15u1p3xnhl2pp5..."
);

// Returns 2 payment methods: onchain and lightning
result.paymentMethods.forEach((method) => {
  console.log(`Type: ${method.type}, Network: ${method.network}, Valid: ${method.valid}`);
});

Lightning-Only Payment

const result = parseBIP321(
  "bitcoin:?lightning=lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3s..."
);

console.log(result.paymentMethods[0].type); // "lightning"
console.log(result.paymentMethods[0].network); // "mainnet"

Ark Payment

// Mainnet Ark address
const result = parseBIP321(
  "bitcoin:?ark=ark1pwh9vsmezqqpjy9akejayl2vvcse6he97rn40g84xrlvrlnhayuuyefrp9nse2yspqqjl5wpy"
);

console.log(result.paymentMethods[0].type); // "ark"
console.log(result.paymentMethods[0].network); // "mainnet"

// Testnet Ark address
const testnetResult = parseBIP321(
  "bitcoin:?ark=tark1pm6sr0fpzqqpnzzwxf209kju4qavs4gtumxk30yv2u5ncrvtp72z34axcvrydtdqpqq5838km"
);

console.log(testnetResult.paymentMethods[0].network); // "testnet"

Network Validation

// Ensure all payment methods are mainnet
const result = parseBIP321(
  "bitcoin:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq?lightning=lnbc...",
  "mainnet"
);

if (result.valid) {
  // All payment methods are guaranteed to be mainnet
  console.log("All payment methods are mainnet");
}

// Reject testnet addresses when expecting mainnet
const invalid = parseBIP321(
  "bitcoin:tb1qghfhmd4zh7ncpmxl3qzhmq566jk8ckq4gafnmg",
  "mainnet"
);

console.log(invalid.valid); // false
console.log(invalid.errors); // ["Payment method network mismatch..."]

Multiple Payment Methods

const result = parseBIP321(
  "bitcoin:?lightning=lnbc...&lno=lno1bogusoffer&sp=sp1qsilentpayment"
);

// Returns 3 payment methods: lightning, offer (BOLT12), and silent-payment
console.log(`Total payment methods: ${result.paymentMethods.length}`);

Network-Specific Parameters

// Mainnet and testnet addresses in one URI
const result = parseBIP321(
  "bitcoin:?bc=bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq&tb=tb1qghfhmd4zh7ncpmxl3qzhmq566jk8ckq4gafnmg"
);

const byNetwork = getPaymentMethodsByNetwork(result);
console.log(`Mainnet methods: ${byNetwork.mainnet.length}`);
console.log(`Testnet methods: ${byNetwork.testnet.length}`);

Error Handling

const result = parseBIP321("bitcoin:invalidaddress");

if (!result.valid) {
  console.log("Errors:");
  result.errors.forEach((error) => console.log(`  - ${error}`));
}

API Reference

parseBIP321(uri: string, expectedNetwork?: "mainnet" | "testnet" | "regtest" | "signet"): BIP321ParseResult

Parses a BIP-321 URI and returns detailed information about the payment request.

Parameters:

  • uri - The Bitcoin URI string to parse
  • expectedNetwork (optional) - Expected network for all payment methods. If specified, all payment methods must match this network or the URI will be marked invalid.

Returns: BIP321ParseResult object

Validation Functions

The library exports individual validation functions for each payment method type:

validateBitcoinAddress(address: string)

Validates a Bitcoin address and returns network information.

Returns:

{
  valid: boolean;
  network?: "mainnet" | "testnet" | "regtest" | "signet";
  error?: string;
}

validateLightningInvoice(invoice: string)

Validates a BOLT11 Lightning invoice and detects the network.

Returns:

{
  valid: boolean;
  network?: "mainnet" | "testnet" | "regtest" | "signet";
  error?: string;
}

validateBolt12Offer(offer: string)

Validates a BOLT12 offer. Note: BOLT12 offers are network-agnostic.

Returns:

{
  valid: boolean;
  error?: string;
}

validateSilentPaymentAddress(address: string)

Validates a BIP-352 Silent Payment address.

Returns:

{
  valid: boolean;
  network?: "mainnet" | "testnet";
  error?: string;
}

Note: For Silent Payments, testnet covers testnet, signet, and regtest.

validateArkAddress(address: string)

Validates an Ark address (BOAT-0001).

Returns:

{
  valid: boolean;
  network?: "mainnet" | "testnet";
  error?: string;
}

Note: For Ark, testnet covers testnet, signet, and regtest.

validatePopUri(uri: string)

Validates a proof-of-payment URI and checks for forbidden schemes.

Returns:

{
  valid: boolean;
  error?: string;
}

<old_text line=240>

BIP321ParseResult

The parseBIP321 function returns a BIP321ParseResult object containing:

interface BIP321ParseResult {
  // Basic information
  address?: string; // Main Bitcoin address from URI path
  network?: "mainnet" | "testnet" | "regtest" | "signet";
  amount?: number; // Amount in BTC
  label?: string; // Label for the recipient
  message?: string; // Message describing the transaction
  
  // Proof of payment
  pop?: string; // Proof of payment callback URI
  popRequired?: boolean; // Whether pop callback is required
  
  // Payment methods
  paymentMethods: PaymentMethod[]; // All available payment methods
  
  // Parameters
  requiredParams: string[]; // Unknown required parameters (req-*)
  optionalParams: Record<string, string[]>; // Unknown optional parameters
  
  // Validation
  valid: boolean; // Whether the URI is valid
  errors: string[]; // Array of error messages
}

PaymentMethod Interface

interface PaymentMethod {
  type: "onchain" | "lightning" | "offer" | "silent-payment" | "ark";
  value: string; // The actual address/invoice value
  network?: "mainnet" | "testnet" | "regtest" | "signet";
  valid: boolean; // Whether this payment method is valid
  error?: string; // Error message if invalid
}

Helper Functions

getPaymentMethodsByNetwork(result: BIP321ParseResult)

Groups payment methods by network.

const byNetwork = getPaymentMethodsByNetwork(result);
// Returns: { mainnet: [], testnet: [], regtest: [], signet: [], unknown: [] }

getValidPaymentMethods(result: BIP321ParseResult)

Returns only valid payment methods.

const valid = getValidPaymentMethods(result);
// Returns: PaymentMethod[]

formatPaymentMethodsSummary(result: BIP321ParseResult)

Generates a human-readable summary of the parsed URI.

const summary = formatPaymentMethodsSummary(result);
console.log(summary);
// Outputs:
// Valid: true
// Amount: 0.5 BTC
// Label: Donation
// Payment Methods: 2
//   ✓ onchain (mainnet)
//   ✓ lightning (mainnet)

Supported Payment Methods

| Method | Parameter Key | Description | |--------|--------------|-------------| | On-chain | address or bc/tb/bcrt/tbs | Bitcoin addresses (P2PKH, P2SH, Segwit, Taproot) | | Lightning | lightning | BOLT11 Lightning invoices | | BOLT12 Offer | lno | Lightning BOLT12 offers | | Silent Payments | sp | BIP352 Silent Payment addresses | | Ark | ark | Ark addresses (mainnet: ark1..., testnet: tark1...) |

Network Detection

The library automatically detects the network from:

Bitcoin Addresses

  • Mainnet: 1..., 3..., bc1...
  • Testnet: m..., n..., 2..., tb1...
  • Regtest: bcrt1...

Lightning Invoices

  • Mainnet: lnbc...
  • Testnet: lntb...
  • Regtest: lnbcrt...
  • Signet: lntbs...

Silent Payment Addresses

  • Mainnet: sp1q...
  • Testnet: tsp1q... (covers testnet, signet, and regtest)

Ark Addresses

  • Mainnet: ark1...
  • Testnet: tark1... (covers testnet, signet, and regtest)

BOLT12 Offers

  • Network-agnostic: lno... (no network-specific prefix)

Validation Rules

The parser enforces BIP-321 validation rules:

  1. ✅ URI must start with bitcoin: (case-insensitive)
  2. ✅ Address in URI path must be valid or empty
  3. amount must be decimal BTC (no commas)
  4. label, message, and amount cannot appear multiple times
  5. pop and req-pop cannot both be present
  6. ✅ Required parameters (req-*) must be understood or URI is invalid
  7. ✅ Network-specific parameters (bc, tb, etc.) must match address network
  8. pop URI scheme must not be forbidden (http, https, file, javascript, mailto)
  9. ✅ If expectedNetwork is specified, all payment methods must match that network

Browser Usage

<!DOCTYPE html>
<html>
<head>
  <script type="module">
    import { parseBIP321 } from './index.js';
    
    const uri = prompt("Enter Bitcoin URI:");
    const result = parseBIP321(uri);
    
    if (result.valid) {
      alert(`Valid payment request!\nNetwork: ${result.network}\nMethods: ${result.paymentMethods.length}`);
    } else {
      alert(`Invalid URI:\n${result.errors.join('\n')}`);
    }
  </script>
</head>
<body>
  <h1>BIP-321 Parser Demo</h1>
</body>
</html>

React Native Usage

import { parseBIP321 } from "bip-321";
import { Alert } from "react-native";

function parseQRCode(data: string) {
  const result = parseBIP321(data);
  
  if (result.valid) {
    Alert.alert(
      "Payment Request",
      `Network: ${result.network}\nAmount: ${result.amount || 'Not specified'} BTC`
    );
  } else {
    Alert.alert("Invalid QR Code", result.errors.join("\n"));
  }
}

License

MIT

Related