parseBip21.ts raw

   1  export type Bip21Data = {
   2    address: string;
   3    amountSat?: number;
   4    label?: string;
   5    message?: string;
   6    lightning?: string; // BOLT11 invoice
   7  };
   8  
   9  export function parseBip21(uri: string): Bip21Data {
  10    // Strip the bitcoin: scheme (handles bitcoin:, bitcoin://, BITCOIN:, etc.)
  11    const withoutScheme = uri.replace(/^bitcoin:\/?\/?/i, "");
  12    if (!withoutScheme) {
  13      throw new Error("Invalid BIP21 URI: missing address");
  14    }
  15  
  16    const separatorIndex = withoutScheme.indexOf("?");
  17    const address =
  18      separatorIndex >= 0
  19        ? withoutScheme.slice(0, separatorIndex)
  20        : withoutScheme;
  21  
  22    const result: Bip21Data = { address };
  23  
  24    if (separatorIndex >= 0) {
  25      const rawParams = new URLSearchParams(
  26        withoutScheme.slice(separatorIndex + 1)
  27      );
  28      // BIP-21 query parameter keys are case-insensitive
  29      const params = new Map<string, string>();
  30      for (const [key, value] of rawParams) {
  31        const lower = key.toLowerCase();
  32        if (!params.has(lower)) {
  33          params.set(lower, value);
  34        }
  35      }
  36  
  37      const amountBtc = params.get("amount");
  38      if (amountBtc) {
  39        result.amountSat = Math.round(parseFloat(amountBtc) * 100_000_000);
  40      }
  41  
  42      const label = params.get("label");
  43      if (label) {
  44        result.label = label;
  45      }
  46  
  47      const message = params.get("message");
  48      if (message) {
  49        result.message = message;
  50      }
  51  
  52      const lightning = params.get("lightning");
  53      if (lightning) {
  54        result.lightning = lightning;
  55      }
  56    }
  57  
  58    return result;
  59  }
  60