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
Maintainers
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-321Or with npm:
npm install bip-321Quick 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 methodsValidation 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); // trueUsage 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 parseexpectedNetwork(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:
- ✅ URI must start with
bitcoin:(case-insensitive) - ✅ Address in URI path must be valid or empty
- ✅
amountmust be decimal BTC (no commas) - ✅
label,message, andamountcannot appear multiple times - ✅
popandreq-popcannot both be present - ✅ Required parameters (
req-*) must be understood or URI is invalid - ✅ Network-specific parameters (
bc,tb, etc.) must match address network - ✅
popURI scheme must not be forbidden (http, https, file, javascript, mailto) - ✅ If
expectedNetworkis 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
