JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 236
  • Score
    100M100P100Q97109F
  • License BSD-2-Clause

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

Package Exports

  • bip-321

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;
}
### 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