🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP β†’
Sign In

@getalby/lightning-tools

Package Overview
Dependencies
Maintainers
4
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@getalby/lightning-tools - npm Package Compare versions

Comparing version
8.1.1
to
8.2.0
+115
dist/cjs/bip21.cjs
'use strict';
const BIP21_SCHEME = /^bitcoin:/i;
// BIP21 grammar: amountparam = "amount=" *digit [ "." *digit ]
// We require at least one digit on either side of the decimal point β€” this is
// slightly stricter than the spec ABNF but matches every real-world example
// (the spec shows "50", "50.00", "20.3"). Crucially this rejects scientific
// notation ("1e-3"), hex ("0x10"), commas, signs, and leading "+" / "-".
const BIP21_AMOUNT_RE = /^(\d+)(?:\.(\d+))?$/;
const SATS_PER_BTC = 100000000n;
const BTC_DECIMALS = 8;
/**
* Convert a BIP21-compliant decimal BTC string to an integer number of
* satoshis using exact decimal arithmetic (no floats). Fractional digits
* beyond 8 are rounded half-up to the nearest satoshi.
*
* Assumes the input has already been validated against BIP21_AMOUNT_RE.
*/
const btcStringToSats = (btc) => {
const match = BIP21_AMOUNT_RE.exec(btc);
if (!match) {
// Should be unreachable β€” caller pre-validates.
return Number.NaN;
}
const [, integerPart, rawFractional = ""] = match;
let fractionalSats;
if (rawFractional.length <= BTC_DECIMALS) {
fractionalSats = BigInt(rawFractional.padEnd(BTC_DECIMALS, "0"));
}
else {
// Round half-up at the satoshi boundary.
const truncated = rawFractional.slice(0, BTC_DECIMALS);
const roundDigit = rawFractional.charCodeAt(BTC_DECIMALS) - 48;
fractionalSats = BigInt(truncated);
if (roundDigit >= 5)
fractionalSats += 1n;
}
const totalSats = BigInt(integerPart) * SATS_PER_BTC + fractionalSats;
return Number(totalSats);
};
/**
* Parse a BIP21 (`bitcoin:`) URI. Returns `null` if the input doesn't have the
* `bitcoin:` scheme.
*
* The address is returned as-is (case preserved) β€” callers should validate it
* separately if they need to ensure it's a well-formed bitcoin address.
*
* Per BIP21, parameters prefixed with `req-` are required: if the client
* doesn't understand any of them, the payment MUST NOT be made. The unknown
* required params are surfaced via `unknownRequiredParams` so callers can
* decide how to fail.
*
* @example
* parseBip21("bitcoin:bc1q...?amount=0.001&lightning=lnbc...")
* // => { address: "bc1q...", amount: 0.001, amountSats: 100000, lightning: "lnbc...", ... }
*/
const parseBip21 = (uri) => {
if (typeof uri !== "string") {
return null;
}
const normalized = uri.trim();
if (!BIP21_SCHEME.test(normalized)) {
return null;
}
const withoutScheme = normalized.replace(BIP21_SCHEME, "");
const queryStart = withoutScheme.indexOf("?");
const address = queryStart === -1 ? withoutScheme : withoutScheme.slice(0, queryStart);
const query = queryStart === -1 ? "" : withoutScheme.slice(queryStart + 1);
const params = {};
const unknownRequiredParams = [];
if (query) {
// URLSearchParams handles decoding and repeated params; for BIP21 last-write-wins
// is fine since the spec doesn't allow repeats.
const search = new URLSearchParams(query);
search.forEach((value, key) => {
params[key] = value;
});
}
const knownParams = new Set([
"amount",
"label",
"message",
"lightning",
"lno",
]);
for (const key of Object.keys(params)) {
if (key.startsWith("req-") && !knownParams.has(key.slice(4))) {
unknownRequiredParams.push(key);
}
}
const result = {
address: address.trim(),
params,
unknownRequiredParams,
};
if (params.amount !== undefined && BIP21_AMOUNT_RE.test(params.amount)) {
result.amount = Number(params.amount);
result.amountSats = btcStringToSats(params.amount);
}
if (params.label !== undefined)
result.label = params.label;
if (params.message !== undefined)
result.message = params.message;
if (params.lightning !== undefined)
result.lightning = params.lightning;
if (params.lno !== undefined)
result.lno = params.lno;
return result;
};
/** Returns true if the input starts with the `bitcoin:` URI scheme. */
const isBip21 = (uri) => typeof uri === "string" && BIP21_SCHEME.test(uri.trim());
exports.isBip21 = isBip21;
exports.parseBip21 = parseBip21;
//# sourceMappingURL=bip21.cjs.map
{"version":3,"file":"bip21.cjs","sources":["../../src/bip21/utils.ts"],"sourcesContent":["import type { Bip21 } from \"./types\";\n\nconst BIP21_SCHEME = /^bitcoin:/i;\n\n// BIP21 grammar: amountparam = \"amount=\" *digit [ \".\" *digit ]\n// We require at least one digit on either side of the decimal point β€” this is\n// slightly stricter than the spec ABNF but matches every real-world example\n// (the spec shows \"50\", \"50.00\", \"20.3\"). Crucially this rejects scientific\n// notation (\"1e-3\"), hex (\"0x10\"), commas, signs, and leading \"+\" / \"-\".\nconst BIP21_AMOUNT_RE = /^(\\d+)(?:\\.(\\d+))?$/;\n\nconst SATS_PER_BTC = 100_000_000n;\nconst BTC_DECIMALS = 8;\n\n/**\n * Convert a BIP21-compliant decimal BTC string to an integer number of\n * satoshis using exact decimal arithmetic (no floats). Fractional digits\n * beyond 8 are rounded half-up to the nearest satoshi.\n *\n * Assumes the input has already been validated against BIP21_AMOUNT_RE.\n */\nconst btcStringToSats = (btc: string): number => {\n const match = BIP21_AMOUNT_RE.exec(btc);\n if (!match) {\n // Should be unreachable β€” caller pre-validates.\n return Number.NaN;\n }\n const [, integerPart, rawFractional = \"\"] = match;\n\n let fractionalSats: bigint;\n if (rawFractional.length <= BTC_DECIMALS) {\n fractionalSats = BigInt(rawFractional.padEnd(BTC_DECIMALS, \"0\"));\n } else {\n // Round half-up at the satoshi boundary.\n const truncated = rawFractional.slice(0, BTC_DECIMALS);\n const roundDigit = rawFractional.charCodeAt(BTC_DECIMALS) - 48;\n fractionalSats = BigInt(truncated);\n if (roundDigit >= 5) fractionalSats += 1n;\n }\n\n const totalSats = BigInt(integerPart) * SATS_PER_BTC + fractionalSats;\n return Number(totalSats);\n};\n\n/**\n * Parse a BIP21 (`bitcoin:`) URI. Returns `null` if the input doesn't have the\n * `bitcoin:` scheme.\n *\n * The address is returned as-is (case preserved) β€” callers should validate it\n * separately if they need to ensure it's a well-formed bitcoin address.\n *\n * Per BIP21, parameters prefixed with `req-` are required: if the client\n * doesn't understand any of them, the payment MUST NOT be made. The unknown\n * required params are surfaced via `unknownRequiredParams` so callers can\n * decide how to fail.\n *\n * @example\n * parseBip21(\"bitcoin:bc1q...?amount=0.001&lightning=lnbc...\")\n * // => { address: \"bc1q...\", amount: 0.001, amountSats: 100000, lightning: \"lnbc...\", ... }\n */\nexport const parseBip21 = (uri: string): Bip21 | null => {\n if (typeof uri !== \"string\") {\n return null;\n }\n const normalized = uri.trim();\n if (!BIP21_SCHEME.test(normalized)) {\n return null;\n }\n\n const withoutScheme = normalized.replace(BIP21_SCHEME, \"\");\n const queryStart = withoutScheme.indexOf(\"?\");\n const address =\n queryStart === -1 ? withoutScheme : withoutScheme.slice(0, queryStart);\n const query = queryStart === -1 ? \"\" : withoutScheme.slice(queryStart + 1);\n\n const params: Record<string, string> = {};\n const unknownRequiredParams: string[] = [];\n\n if (query) {\n // URLSearchParams handles decoding and repeated params; for BIP21 last-write-wins\n // is fine since the spec doesn't allow repeats.\n const search = new URLSearchParams(query);\n search.forEach((value, key) => {\n params[key] = value;\n });\n }\n\n const knownParams = new Set([\n \"amount\",\n \"label\",\n \"message\",\n \"lightning\",\n \"lno\",\n ]);\n\n for (const key of Object.keys(params)) {\n if (key.startsWith(\"req-\") && !knownParams.has(key.slice(4))) {\n unknownRequiredParams.push(key);\n }\n }\n\n const result: Bip21 = {\n address: address.trim(),\n params,\n unknownRequiredParams,\n };\n\n if (params.amount !== undefined && BIP21_AMOUNT_RE.test(params.amount)) {\n result.amount = Number(params.amount);\n result.amountSats = btcStringToSats(params.amount);\n }\n if (params.label !== undefined) result.label = params.label;\n if (params.message !== undefined) result.message = params.message;\n if (params.lightning !== undefined) result.lightning = params.lightning;\n if (params.lno !== undefined) result.lno = params.lno;\n\n return result;\n};\n\n/** Returns true if the input starts with the `bitcoin:` URI scheme. */\nexport const isBip21 = (uri: string): boolean =>\n typeof uri === \"string\" && BIP21_SCHEME.test(uri.trim());\n"],"names":[],"mappings":";;AAEA,MAAM,YAAY,GAAG,YAAY;AAEjC;AACA;AACA;AACA;AACA;AACA,MAAM,eAAe,GAAG,qBAAqB;AAE7C,MAAM,YAAY,GAAG,UAAY;AACjC,MAAM,YAAY,GAAG,CAAC;AAEtB;;;;;;AAMG;AACH,MAAM,eAAe,GAAG,CAAC,GAAW,KAAY;IAC9C,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC;IACvC,IAAI,CAAC,KAAK,EAAE;;QAEV,OAAO,MAAM,CAAC,GAAG;IACnB;IACA,MAAM,GAAG,WAAW,EAAE,aAAa,GAAG,EAAE,CAAC,GAAG,KAAK;AAEjD,IAAA,IAAI,cAAsB;AAC1B,IAAA,IAAI,aAAa,CAAC,MAAM,IAAI,YAAY,EAAE;AACxC,QAAA,cAAc,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;IAClE;SAAO;;QAEL,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC;QACtD,MAAM,UAAU,GAAG,aAAa,CAAC,UAAU,CAAC,YAAY,CAAC,GAAG,EAAE;AAC9D,QAAA,cAAc,GAAG,MAAM,CAAC,SAAS,CAAC;QAClC,IAAI,UAAU,IAAI,CAAC;YAAE,cAAc,IAAI,EAAE;IAC3C;IAEA,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,YAAY,GAAG,cAAc;AACrE,IAAA,OAAO,MAAM,CAAC,SAAS,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;;;;;AAeG;AACI,MAAM,UAAU,GAAG,CAAC,GAAW,KAAkB;AACtD,IAAA,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;AAC3B,QAAA,OAAO,IAAI;IACb;AACA,IAAA,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,EAAE;IAC7B,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE;AAClC,QAAA,OAAO,IAAI;IACb;IAEA,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;IAC1D,MAAM,UAAU,GAAG,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC;IAC7C,MAAM,OAAO,GACX,UAAU,KAAK,EAAE,GAAG,aAAa,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC;IACxE,MAAM,KAAK,GAAG,UAAU,KAAK,EAAE,GAAG,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC;IAE1E,MAAM,MAAM,GAA2B,EAAE;IACzC,MAAM,qBAAqB,GAAa,EAAE;IAE1C,IAAI,KAAK,EAAE;;;AAGT,QAAA,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,KAAK,CAAC;QACzC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,KAAI;AAC5B,YAAA,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK;AACrB,QAAA,CAAC,CAAC;IACJ;AAEA,IAAA,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;QAC1B,QAAQ;QACR,OAAO;QACP,SAAS;QACT,WAAW;QACX,KAAK;AACN,KAAA,CAAC;IAEF,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;QACrC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;AAC5D,YAAA,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC;QACjC;IACF;AAEA,IAAA,MAAM,MAAM,GAAU;AACpB,QAAA,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE;QACvB,MAAM;QACN,qBAAqB;KACtB;AAED,IAAA,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE;QACtE,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;QACrC,MAAM,CAAC,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC;IACpD;AACA,IAAA,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;AAAE,QAAA,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK;AAC3D,IAAA,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS;AAAE,QAAA,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO;AACjE,IAAA,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS;AAAE,QAAA,MAAM,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS;AACvE,IAAA,IAAI,MAAM,CAAC,GAAG,KAAK,SAAS;AAAE,QAAA,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG;AAErD,IAAA,OAAO,MAAM;AACf;AAEA;MACa,OAAO,GAAG,CAAC,GAAW,KACjC,OAAO,GAAG,KAAK,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;;;;;"}
const BIP21_SCHEME = /^bitcoin:/i;
// BIP21 grammar: amountparam = "amount=" *digit [ "." *digit ]
// We require at least one digit on either side of the decimal point β€” this is
// slightly stricter than the spec ABNF but matches every real-world example
// (the spec shows "50", "50.00", "20.3"). Crucially this rejects scientific
// notation ("1e-3"), hex ("0x10"), commas, signs, and leading "+" / "-".
const BIP21_AMOUNT_RE = /^(\d+)(?:\.(\d+))?$/;
const SATS_PER_BTC = 100000000n;
const BTC_DECIMALS = 8;
/**
* Convert a BIP21-compliant decimal BTC string to an integer number of
* satoshis using exact decimal arithmetic (no floats). Fractional digits
* beyond 8 are rounded half-up to the nearest satoshi.
*
* Assumes the input has already been validated against BIP21_AMOUNT_RE.
*/
const btcStringToSats = (btc) => {
const match = BIP21_AMOUNT_RE.exec(btc);
if (!match) {
// Should be unreachable β€” caller pre-validates.
return Number.NaN;
}
const [, integerPart, rawFractional = ""] = match;
let fractionalSats;
if (rawFractional.length <= BTC_DECIMALS) {
fractionalSats = BigInt(rawFractional.padEnd(BTC_DECIMALS, "0"));
}
else {
// Round half-up at the satoshi boundary.
const truncated = rawFractional.slice(0, BTC_DECIMALS);
const roundDigit = rawFractional.charCodeAt(BTC_DECIMALS) - 48;
fractionalSats = BigInt(truncated);
if (roundDigit >= 5)
fractionalSats += 1n;
}
const totalSats = BigInt(integerPart) * SATS_PER_BTC + fractionalSats;
return Number(totalSats);
};
/**
* Parse a BIP21 (`bitcoin:`) URI. Returns `null` if the input doesn't have the
* `bitcoin:` scheme.
*
* The address is returned as-is (case preserved) β€” callers should validate it
* separately if they need to ensure it's a well-formed bitcoin address.
*
* Per BIP21, parameters prefixed with `req-` are required: if the client
* doesn't understand any of them, the payment MUST NOT be made. The unknown
* required params are surfaced via `unknownRequiredParams` so callers can
* decide how to fail.
*
* @example
* parseBip21("bitcoin:bc1q...?amount=0.001&lightning=lnbc...")
* // => { address: "bc1q...", amount: 0.001, amountSats: 100000, lightning: "lnbc...", ... }
*/
const parseBip21 = (uri) => {
if (typeof uri !== "string") {
return null;
}
const normalized = uri.trim();
if (!BIP21_SCHEME.test(normalized)) {
return null;
}
const withoutScheme = normalized.replace(BIP21_SCHEME, "");
const queryStart = withoutScheme.indexOf("?");
const address = queryStart === -1 ? withoutScheme : withoutScheme.slice(0, queryStart);
const query = queryStart === -1 ? "" : withoutScheme.slice(queryStart + 1);
const params = {};
const unknownRequiredParams = [];
if (query) {
// URLSearchParams handles decoding and repeated params; for BIP21 last-write-wins
// is fine since the spec doesn't allow repeats.
const search = new URLSearchParams(query);
search.forEach((value, key) => {
params[key] = value;
});
}
const knownParams = new Set([
"amount",
"label",
"message",
"lightning",
"lno",
]);
for (const key of Object.keys(params)) {
if (key.startsWith("req-") && !knownParams.has(key.slice(4))) {
unknownRequiredParams.push(key);
}
}
const result = {
address: address.trim(),
params,
unknownRequiredParams,
};
if (params.amount !== undefined && BIP21_AMOUNT_RE.test(params.amount)) {
result.amount = Number(params.amount);
result.amountSats = btcStringToSats(params.amount);
}
if (params.label !== undefined)
result.label = params.label;
if (params.message !== undefined)
result.message = params.message;
if (params.lightning !== undefined)
result.lightning = params.lightning;
if (params.lno !== undefined)
result.lno = params.lno;
return result;
};
/** Returns true if the input starts with the `bitcoin:` URI scheme. */
const isBip21 = (uri) => typeof uri === "string" && BIP21_SCHEME.test(uri.trim());
export { isBip21, parseBip21 };
//# sourceMappingURL=bip21.js.map
{"version":3,"file":"bip21.js","sources":["../../src/bip21/utils.ts"],"sourcesContent":["import type { Bip21 } from \"./types\";\n\nconst BIP21_SCHEME = /^bitcoin:/i;\n\n// BIP21 grammar: amountparam = \"amount=\" *digit [ \".\" *digit ]\n// We require at least one digit on either side of the decimal point β€” this is\n// slightly stricter than the spec ABNF but matches every real-world example\n// (the spec shows \"50\", \"50.00\", \"20.3\"). Crucially this rejects scientific\n// notation (\"1e-3\"), hex (\"0x10\"), commas, signs, and leading \"+\" / \"-\".\nconst BIP21_AMOUNT_RE = /^(\\d+)(?:\\.(\\d+))?$/;\n\nconst SATS_PER_BTC = 100_000_000n;\nconst BTC_DECIMALS = 8;\n\n/**\n * Convert a BIP21-compliant decimal BTC string to an integer number of\n * satoshis using exact decimal arithmetic (no floats). Fractional digits\n * beyond 8 are rounded half-up to the nearest satoshi.\n *\n * Assumes the input has already been validated against BIP21_AMOUNT_RE.\n */\nconst btcStringToSats = (btc: string): number => {\n const match = BIP21_AMOUNT_RE.exec(btc);\n if (!match) {\n // Should be unreachable β€” caller pre-validates.\n return Number.NaN;\n }\n const [, integerPart, rawFractional = \"\"] = match;\n\n let fractionalSats: bigint;\n if (rawFractional.length <= BTC_DECIMALS) {\n fractionalSats = BigInt(rawFractional.padEnd(BTC_DECIMALS, \"0\"));\n } else {\n // Round half-up at the satoshi boundary.\n const truncated = rawFractional.slice(0, BTC_DECIMALS);\n const roundDigit = rawFractional.charCodeAt(BTC_DECIMALS) - 48;\n fractionalSats = BigInt(truncated);\n if (roundDigit >= 5) fractionalSats += 1n;\n }\n\n const totalSats = BigInt(integerPart) * SATS_PER_BTC + fractionalSats;\n return Number(totalSats);\n};\n\n/**\n * Parse a BIP21 (`bitcoin:`) URI. Returns `null` if the input doesn't have the\n * `bitcoin:` scheme.\n *\n * The address is returned as-is (case preserved) β€” callers should validate it\n * separately if they need to ensure it's a well-formed bitcoin address.\n *\n * Per BIP21, parameters prefixed with `req-` are required: if the client\n * doesn't understand any of them, the payment MUST NOT be made. The unknown\n * required params are surfaced via `unknownRequiredParams` so callers can\n * decide how to fail.\n *\n * @example\n * parseBip21(\"bitcoin:bc1q...?amount=0.001&lightning=lnbc...\")\n * // => { address: \"bc1q...\", amount: 0.001, amountSats: 100000, lightning: \"lnbc...\", ... }\n */\nexport const parseBip21 = (uri: string): Bip21 | null => {\n if (typeof uri !== \"string\") {\n return null;\n }\n const normalized = uri.trim();\n if (!BIP21_SCHEME.test(normalized)) {\n return null;\n }\n\n const withoutScheme = normalized.replace(BIP21_SCHEME, \"\");\n const queryStart = withoutScheme.indexOf(\"?\");\n const address =\n queryStart === -1 ? withoutScheme : withoutScheme.slice(0, queryStart);\n const query = queryStart === -1 ? \"\" : withoutScheme.slice(queryStart + 1);\n\n const params: Record<string, string> = {};\n const unknownRequiredParams: string[] = [];\n\n if (query) {\n // URLSearchParams handles decoding and repeated params; for BIP21 last-write-wins\n // is fine since the spec doesn't allow repeats.\n const search = new URLSearchParams(query);\n search.forEach((value, key) => {\n params[key] = value;\n });\n }\n\n const knownParams = new Set([\n \"amount\",\n \"label\",\n \"message\",\n \"lightning\",\n \"lno\",\n ]);\n\n for (const key of Object.keys(params)) {\n if (key.startsWith(\"req-\") && !knownParams.has(key.slice(4))) {\n unknownRequiredParams.push(key);\n }\n }\n\n const result: Bip21 = {\n address: address.trim(),\n params,\n unknownRequiredParams,\n };\n\n if (params.amount !== undefined && BIP21_AMOUNT_RE.test(params.amount)) {\n result.amount = Number(params.amount);\n result.amountSats = btcStringToSats(params.amount);\n }\n if (params.label !== undefined) result.label = params.label;\n if (params.message !== undefined) result.message = params.message;\n if (params.lightning !== undefined) result.lightning = params.lightning;\n if (params.lno !== undefined) result.lno = params.lno;\n\n return result;\n};\n\n/** Returns true if the input starts with the `bitcoin:` URI scheme. */\nexport const isBip21 = (uri: string): boolean =>\n typeof uri === \"string\" && BIP21_SCHEME.test(uri.trim());\n"],"names":[],"mappings":"AAEA,MAAM,YAAY,GAAG,YAAY;AAEjC;AACA;AACA;AACA;AACA;AACA,MAAM,eAAe,GAAG,qBAAqB;AAE7C,MAAM,YAAY,GAAG,UAAY;AACjC,MAAM,YAAY,GAAG,CAAC;AAEtB;;;;;;AAMG;AACH,MAAM,eAAe,GAAG,CAAC,GAAW,KAAY;IAC9C,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC;IACvC,IAAI,CAAC,KAAK,EAAE;;QAEV,OAAO,MAAM,CAAC,GAAG;IACnB;IACA,MAAM,GAAG,WAAW,EAAE,aAAa,GAAG,EAAE,CAAC,GAAG,KAAK;AAEjD,IAAA,IAAI,cAAsB;AAC1B,IAAA,IAAI,aAAa,CAAC,MAAM,IAAI,YAAY,EAAE;AACxC,QAAA,cAAc,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;IAClE;SAAO;;QAEL,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC;QACtD,MAAM,UAAU,GAAG,aAAa,CAAC,UAAU,CAAC,YAAY,CAAC,GAAG,EAAE;AAC9D,QAAA,cAAc,GAAG,MAAM,CAAC,SAAS,CAAC;QAClC,IAAI,UAAU,IAAI,CAAC;YAAE,cAAc,IAAI,EAAE;IAC3C;IAEA,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,YAAY,GAAG,cAAc;AACrE,IAAA,OAAO,MAAM,CAAC,SAAS,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;;;;;AAeG;AACI,MAAM,UAAU,GAAG,CAAC,GAAW,KAAkB;AACtD,IAAA,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;AAC3B,QAAA,OAAO,IAAI;IACb;AACA,IAAA,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,EAAE;IAC7B,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE;AAClC,QAAA,OAAO,IAAI;IACb;IAEA,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;IAC1D,MAAM,UAAU,GAAG,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC;IAC7C,MAAM,OAAO,GACX,UAAU,KAAK,EAAE,GAAG,aAAa,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC;IACxE,MAAM,KAAK,GAAG,UAAU,KAAK,EAAE,GAAG,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC;IAE1E,MAAM,MAAM,GAA2B,EAAE;IACzC,MAAM,qBAAqB,GAAa,EAAE;IAE1C,IAAI,KAAK,EAAE;;;AAGT,QAAA,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,KAAK,CAAC;QACzC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,KAAI;AAC5B,YAAA,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK;AACrB,QAAA,CAAC,CAAC;IACJ;AAEA,IAAA,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;QAC1B,QAAQ;QACR,OAAO;QACP,SAAS;QACT,WAAW;QACX,KAAK;AACN,KAAA,CAAC;IAEF,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;QACrC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;AAC5D,YAAA,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC;QACjC;IACF;AAEA,IAAA,MAAM,MAAM,GAAU;AACpB,QAAA,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE;QACvB,MAAM;QACN,qBAAqB;KACtB;AAED,IAAA,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE;QACtE,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;QACrC,MAAM,CAAC,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC;IACpD;AACA,IAAA,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;AAAE,QAAA,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK;AAC3D,IAAA,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS;AAAE,QAAA,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO;AACjE,IAAA,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS;AAAE,QAAA,MAAM,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS;AACvE,IAAA,IAAI,MAAM,CAAC,GAAG,KAAK,SAAS;AAAE,QAAA,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG;AAErD,IAAA,OAAO,MAAM;AACf;AAEA;MACa,OAAO,GAAG,CAAC,GAAW,KACjC,OAAO,GAAG,KAAK,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;;;;"}
type Bip21 = {
/** Bare on-chain bitcoin address (no scheme, no query string). */
address: string;
/** Requested amount in BTC, as defined by BIP21 (e.g. 0.01). */
amount?: number;
/** Requested amount converted to satoshis, for convenience. */
amountSats?: number;
/** Label for the recipient, decoded. */
label?: string;
/** Free-form message, decoded. */
message?: string;
/** BOLT11 invoice or LNURL, from the `lightning=` parameter (unified QR). */
lightning?: string;
/** BOLT12 offer, from the `lno=` parameter. */
lno?: string;
/**
* `req-*` parameters that the client doesn't know how to handle.
* Per BIP21, callers MUST reject the URI if this is non-empty.
*/
unknownRequiredParams: string[];
/** All query parameters, decoded, for advanced/custom use. */
params: Record<string, string>;
};
/**
* Parse a BIP21 (`bitcoin:`) URI. Returns `null` if the input doesn't have the
* `bitcoin:` scheme.
*
* The address is returned as-is (case preserved) β€” callers should validate it
* separately if they need to ensure it's a well-formed bitcoin address.
*
* Per BIP21, parameters prefixed with `req-` are required: if the client
* doesn't understand any of them, the payment MUST NOT be made. The unknown
* required params are surfaced via `unknownRequiredParams` so callers can
* decide how to fail.
*
* @example
* parseBip21("bitcoin:bc1q...?amount=0.001&lightning=lnbc...")
* // => { address: "bc1q...", amount: 0.001, amountSats: 100000, lightning: "lnbc...", ... }
*/
declare const parseBip21: (uri: string) => Bip21 | null;
/** Returns true if the input starts with the `bitcoin:` URI scheme. */
declare const isBip21: (uri: string) => boolean;
export { isBip21, parseBip21 };
export type { Bip21 };
+1304
-2
'use strict';
function bytes(b, ...lengths) {
if (!(b instanceof Uint8Array))
throw new Error('Expected Uint8Array');
if (lengths.length > 0 && !lengths.includes(b.length))
throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`);
}
function exists(instance, checkFinished = true) {
if (instance.destroyed)
throw new Error('Hash instance has been destroyed');
if (checkFinished && instance.finished)
throw new Error('Hash#digest() has already been called');
}
function output(out, instance) {
bytes(out);
const min = instance.outputLen;
if (out.length < min) {
throw new Error(`digestInto() expects output buffer of length at least ${min}`);
}
}
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
// node.js versions earlier than v19 don't declare it in global scope.
// For node.js, package.json#exports field mapping rewrites import
// from `crypto` to `cryptoNode`, which imports native module.
// Makes the utils un-importable in browsers without a bundler.
// Once node.js 18 is deprecated, we can just drop the import.
const u8a = (a) => a instanceof Uint8Array;
// Cast array to view
const createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
// The rotate right (circular right shift) operation for uint32
const rotr = (word, shift) => (word << (32 - shift)) | (word >>> shift);
// big-endian hardware is rare. Just in case someone still decides to run hashes:
// early-throw an error because we don't support BE yet.
const isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44;
if (!isLE)
throw new Error('Non little-endian hardware is not supported');
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
/**
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
*/
function bytesToHex(bytes) {
if (!u8a(bytes))
throw new Error('Uint8Array expected');
// pre-caching improves the speed 6x
let hex = '';
for (let i = 0; i < bytes.length; i++) {
hex += hexes[bytes[i]];
}
return hex;
}
/**
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
*/
function utf8ToBytes(str) {
if (typeof str !== 'string')
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
}
/**
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
* Warning: when Uint8Array is passed, it would NOT get copied.
* Keep in mind for future mutable operations.
*/
function toBytes(data) {
if (typeof data === 'string')
data = utf8ToBytes(data);
if (!u8a(data))
throw new Error(`expected Uint8Array, got ${typeof data}`);
return data;
}
// For runtime check if class implements interface
class Hash {
// Safe version that clones internal state
clone() {
return this._cloneInto();
}
}
function wrapConstructor(hashCons) {
const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
const tmp = hashCons();
hashC.outputLen = tmp.outputLen;
hashC.blockLen = tmp.blockLen;
hashC.create = () => hashCons();
return hashC;
}
// Polyfill for Safari 14
function setBigUint64(view, byteOffset, value, isLE) {
if (typeof view.setBigUint64 === 'function')
return view.setBigUint64(byteOffset, value, isLE);
const _32n = BigInt(32);
const _u32_max = BigInt(0xffffffff);
const wh = Number((value >> _32n) & _u32_max);
const wl = Number(value & _u32_max);
const h = isLE ? 4 : 0;
const l = isLE ? 0 : 4;
view.setUint32(byteOffset + h, wh, isLE);
view.setUint32(byteOffset + l, wl, isLE);
}
// Base SHA2 class (RFC 6234)
class SHA2 extends Hash {
constructor(blockLen, outputLen, padOffset, isLE) {
super();
this.blockLen = blockLen;
this.outputLen = outputLen;
this.padOffset = padOffset;
this.isLE = isLE;
this.finished = false;
this.length = 0;
this.pos = 0;
this.destroyed = false;
this.buffer = new Uint8Array(blockLen);
this.view = createView(this.buffer);
}
update(data) {
exists(this);
const { view, buffer, blockLen } = this;
data = toBytes(data);
const len = data.length;
for (let pos = 0; pos < len;) {
const take = Math.min(blockLen - this.pos, len - pos);
// Fast path: we have at least one block in input, cast it to view and process
if (take === blockLen) {
const dataView = createView(data);
for (; blockLen <= len - pos; pos += blockLen)
this.process(dataView, pos);
continue;
}
buffer.set(data.subarray(pos, pos + take), this.pos);
this.pos += take;
pos += take;
if (this.pos === blockLen) {
this.process(view, 0);
this.pos = 0;
}
}
this.length += data.length;
this.roundClean();
return this;
}
digestInto(out) {
exists(this);
output(out, this);
this.finished = true;
// Padding
// We can avoid allocation of buffer for padding completely if it
// was previously not allocated here. But it won't change performance.
const { buffer, view, blockLen, isLE } = this;
let { pos } = this;
// append the bit '1' to the message
buffer[pos++] = 0b10000000;
this.buffer.subarray(pos).fill(0);
// we have less than padOffset left in buffer, so we cannot put length in current block, need process it and pad again
if (this.padOffset > blockLen - pos) {
this.process(view, 0);
pos = 0;
}
// Pad until full block byte with zeros
for (let i = pos; i < blockLen; i++)
buffer[i] = 0;
// Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that
// You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.
// So we just write lowest 64 bits of that value.
setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
this.process(view, 0);
const oview = createView(out);
const len = this.outputLen;
// NOTE: we do division by 4 later, which should be fused in single op with modulo by JIT
if (len % 4)
throw new Error('_sha2: outputLen should be aligned to 32bit');
const outLen = len / 4;
const state = this.get();
if (outLen > state.length)
throw new Error('_sha2: outputLen bigger than state');
for (let i = 0; i < outLen; i++)
oview.setUint32(4 * i, state[i], isLE);
}
digest() {
const { buffer, outputLen } = this;
this.digestInto(buffer);
const res = buffer.slice(0, outputLen);
this.destroy();
return res;
}
_cloneInto(to) {
to || (to = new this.constructor());
to.set(...this.get());
const { blockLen, buffer, length, finished, destroyed, pos } = this;
to.length = length;
to.pos = pos;
to.finished = finished;
to.destroyed = destroyed;
if (length % blockLen)
to.buffer.set(buffer);
return to;
}
}
// SHA2-256 need to try 2^128 hashes to execute birthday attack.
// BTC network is doing 2^67 hashes/sec as per early 2023.
// Choice: a ? b : c
const Chi = (a, b, c) => (a & b) ^ (~a & c);
// Majority function, true if any two inpust is true
const Maj = (a, b, c) => (a & b) ^ (a & c) ^ (b & c);
// Round constants:
// first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311)
// prettier-ignore
const SHA256_K = /* @__PURE__ */ new Uint32Array([
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
]);
// Initial state (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):
// prettier-ignore
const IV = /* @__PURE__ */ new Uint32Array([
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
]);
// Temporary buffer, not used to store anything between runs
// Named this way because it matches specification.
const SHA256_W = /* @__PURE__ */ new Uint32Array(64);
class SHA256 extends SHA2 {
constructor() {
super(64, 32, 8, false);
// We cannot use array here since array allows indexing by variable
// which means optimizer/compiler cannot use registers.
this.A = IV[0] | 0;
this.B = IV[1] | 0;
this.C = IV[2] | 0;
this.D = IV[3] | 0;
this.E = IV[4] | 0;
this.F = IV[5] | 0;
this.G = IV[6] | 0;
this.H = IV[7] | 0;
}
get() {
const { A, B, C, D, E, F, G, H } = this;
return [A, B, C, D, E, F, G, H];
}
// prettier-ignore
set(A, B, C, D, E, F, G, H) {
this.A = A | 0;
this.B = B | 0;
this.C = C | 0;
this.D = D | 0;
this.E = E | 0;
this.F = F | 0;
this.G = G | 0;
this.H = H | 0;
}
process(view, offset) {
// Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array
for (let i = 0; i < 16; i++, offset += 4)
SHA256_W[i] = view.getUint32(offset, false);
for (let i = 16; i < 64; i++) {
const W15 = SHA256_W[i - 15];
const W2 = SHA256_W[i - 2];
const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ (W15 >>> 3);
const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ (W2 >>> 10);
SHA256_W[i] = (s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16]) | 0;
}
// Compression function main loop, 64 rounds
let { A, B, C, D, E, F, G, H } = this;
for (let i = 0; i < 64; i++) {
const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0;
const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
const T2 = (sigma0 + Maj(A, B, C)) | 0;
H = G;
G = F;
F = E;
E = (D + T1) | 0;
D = C;
C = B;
B = A;
A = (T1 + T2) | 0;
}
// Add the compressed chunk to the current hash value
A = (A + this.A) | 0;
B = (B + this.B) | 0;
C = (C + this.C) | 0;
D = (D + this.D) | 0;
E = (E + this.E) | 0;
F = (F + this.F) | 0;
G = (G + this.G) | 0;
H = (H + this.H) | 0;
this.set(A, B, C, D, E, F, G, H);
}
roundClean() {
SHA256_W.fill(0);
}
destroy() {
this.set(0, 0, 0, 0, 0, 0, 0, 0);
this.buffer.fill(0);
}
}
/**
* SHA2-256 hash function
* @param message - data that would be hashed
*/
const sha256 = /* @__PURE__ */ wrapConstructor(() => new SHA256());
var lib = {};
var hasRequiredLib;
function requireLib () {
if (hasRequiredLib) return lib;
hasRequiredLib = 1;
(function (exports) {
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
Object.defineProperty(exports, "__esModule", { value: true });
exports.bytes = exports.stringToBytes = exports.str = exports.bytesToString = exports.hex = exports.utf8 = exports.bech32m = exports.bech32 = exports.base58check = exports.base58xmr = exports.base58xrp = exports.base58flickr = exports.base58 = exports.base64url = exports.base64 = exports.base32crockford = exports.base32hex = exports.base32 = exports.base16 = exports.utils = exports.assertNumber = void 0;
function assertNumber(n) {
if (!Number.isSafeInteger(n))
throw new Error(`Wrong integer: ${n}`);
}
exports.assertNumber = assertNumber;
function chain(...args) {
const wrap = (a, b) => (c) => a(b(c));
const encode = Array.from(args)
.reverse()
.reduce((acc, i) => (acc ? wrap(acc, i.encode) : i.encode), undefined);
const decode = args.reduce((acc, i) => (acc ? wrap(acc, i.decode) : i.decode), undefined);
return { encode, decode };
}
function alphabet(alphabet) {
return {
encode: (digits) => {
if (!Array.isArray(digits) || (digits.length && typeof digits[0] !== 'number'))
throw new Error('alphabet.encode input should be an array of numbers');
return digits.map((i) => {
assertNumber(i);
if (i < 0 || i >= alphabet.length)
throw new Error(`Digit index outside alphabet: ${i} (alphabet: ${alphabet.length})`);
return alphabet[i];
});
},
decode: (input) => {
if (!Array.isArray(input) || (input.length && typeof input[0] !== 'string'))
throw new Error('alphabet.decode input should be array of strings');
return input.map((letter) => {
if (typeof letter !== 'string')
throw new Error(`alphabet.decode: not string element=${letter}`);
const index = alphabet.indexOf(letter);
if (index === -1)
throw new Error(`Unknown letter: "${letter}". Allowed: ${alphabet}`);
return index;
});
},
};
}
function join(separator = '') {
if (typeof separator !== 'string')
throw new Error('join separator should be string');
return {
encode: (from) => {
if (!Array.isArray(from) || (from.length && typeof from[0] !== 'string'))
throw new Error('join.encode input should be array of strings');
for (let i of from)
if (typeof i !== 'string')
throw new Error(`join.encode: non-string input=${i}`);
return from.join(separator);
},
decode: (to) => {
if (typeof to !== 'string')
throw new Error('join.decode input should be string');
return to.split(separator);
},
};
}
function padding(bits, chr = '=') {
assertNumber(bits);
if (typeof chr !== 'string')
throw new Error('padding chr should be string');
return {
encode(data) {
if (!Array.isArray(data) || (data.length && typeof data[0] !== 'string'))
throw new Error('padding.encode input should be array of strings');
for (let i of data)
if (typeof i !== 'string')
throw new Error(`padding.encode: non-string input=${i}`);
while ((data.length * bits) % 8)
data.push(chr);
return data;
},
decode(input) {
if (!Array.isArray(input) || (input.length && typeof input[0] !== 'string'))
throw new Error('padding.encode input should be array of strings');
for (let i of input)
if (typeof i !== 'string')
throw new Error(`padding.decode: non-string input=${i}`);
let end = input.length;
if ((end * bits) % 8)
throw new Error('Invalid padding: string should have whole number of bytes');
for (; end > 0 && input[end - 1] === chr; end--) {
if (!(((end - 1) * bits) % 8))
throw new Error('Invalid padding: string has too much padding');
}
return input.slice(0, end);
},
};
}
function normalize(fn) {
if (typeof fn !== 'function')
throw new Error('normalize fn should be function');
return { encode: (from) => from, decode: (to) => fn(to) };
}
function convertRadix(data, from, to) {
if (from < 2)
throw new Error(`convertRadix: wrong from=${from}, base cannot be less than 2`);
if (to < 2)
throw new Error(`convertRadix: wrong to=${to}, base cannot be less than 2`);
if (!Array.isArray(data))
throw new Error('convertRadix: data should be array');
if (!data.length)
return [];
let pos = 0;
const res = [];
const digits = Array.from(data);
digits.forEach((d) => {
assertNumber(d);
if (d < 0 || d >= from)
throw new Error(`Wrong integer: ${d}`);
});
while (true) {
let carry = 0;
let done = true;
for (let i = pos; i < digits.length; i++) {
const digit = digits[i];
const digitBase = from * carry + digit;
if (!Number.isSafeInteger(digitBase) ||
(from * carry) / from !== carry ||
digitBase - digit !== from * carry) {
throw new Error('convertRadix: carry overflow');
}
carry = digitBase % to;
digits[i] = Math.floor(digitBase / to);
if (!Number.isSafeInteger(digits[i]) || digits[i] * to + carry !== digitBase)
throw new Error('convertRadix: carry overflow');
if (!done)
continue;
else if (!digits[i])
pos = i;
else
done = false;
}
res.push(carry);
if (done)
break;
}
for (let i = 0; i < data.length - 1 && data[i] === 0; i++)
res.push(0);
return res.reverse();
}
const gcd = (a, b) => (!b ? a : gcd(b, a % b));
const radix2carry = (from, to) => from + (to - gcd(from, to));
function convertRadix2(data, from, to, padding) {
if (!Array.isArray(data))
throw new Error('convertRadix2: data should be array');
if (from <= 0 || from > 32)
throw new Error(`convertRadix2: wrong from=${from}`);
if (to <= 0 || to > 32)
throw new Error(`convertRadix2: wrong to=${to}`);
if (radix2carry(from, to) > 32) {
throw new Error(`convertRadix2: carry overflow from=${from} to=${to} carryBits=${radix2carry(from, to)}`);
}
let carry = 0;
let pos = 0;
const mask = 2 ** to - 1;
const res = [];
for (const n of data) {
assertNumber(n);
if (n >= 2 ** from)
throw new Error(`convertRadix2: invalid data word=${n} from=${from}`);
carry = (carry << from) | n;
if (pos + from > 32)
throw new Error(`convertRadix2: carry overflow pos=${pos} from=${from}`);
pos += from;
for (; pos >= to; pos -= to)
res.push(((carry >> (pos - to)) & mask) >>> 0);
carry &= 2 ** pos - 1;
}
carry = (carry << (to - pos)) & mask;
if (!padding && pos >= from)
throw new Error('Excess padding');
if (!padding && carry)
throw new Error(`Non-zero padding: ${carry}`);
if (padding && pos > 0)
res.push(carry >>> 0);
return res;
}
function radix(num) {
assertNumber(num);
return {
encode: (bytes) => {
if (!(bytes instanceof Uint8Array))
throw new Error('radix.encode input should be Uint8Array');
return convertRadix(Array.from(bytes), 2 ** 8, num);
},
decode: (digits) => {
if (!Array.isArray(digits) || (digits.length && typeof digits[0] !== 'number'))
throw new Error('radix.decode input should be array of strings');
return Uint8Array.from(convertRadix(digits, num, 2 ** 8));
},
};
}
function radix2(bits, revPadding = false) {
assertNumber(bits);
if (bits <= 0 || bits > 32)
throw new Error('radix2: bits should be in (0..32]');
if (radix2carry(8, bits) > 32 || radix2carry(bits, 8) > 32)
throw new Error('radix2: carry overflow');
return {
encode: (bytes) => {
if (!(bytes instanceof Uint8Array))
throw new Error('radix2.encode input should be Uint8Array');
return convertRadix2(Array.from(bytes), 8, bits, !revPadding);
},
decode: (digits) => {
if (!Array.isArray(digits) || (digits.length && typeof digits[0] !== 'number'))
throw new Error('radix2.decode input should be array of strings');
return Uint8Array.from(convertRadix2(digits, bits, 8, revPadding));
},
};
}
function unsafeWrapper(fn) {
if (typeof fn !== 'function')
throw new Error('unsafeWrapper fn should be function');
return function (...args) {
try {
return fn.apply(null, args);
}
catch (e) { }
};
}
function checksum(len, fn) {
assertNumber(len);
if (typeof fn !== 'function')
throw new Error('checksum fn should be function');
return {
encode(data) {
if (!(data instanceof Uint8Array))
throw new Error('checksum.encode: input should be Uint8Array');
const checksum = fn(data).slice(0, len);
const res = new Uint8Array(data.length + len);
res.set(data);
res.set(checksum, data.length);
return res;
},
decode(data) {
if (!(data instanceof Uint8Array))
throw new Error('checksum.decode: input should be Uint8Array');
const payload = data.slice(0, -len);
const newChecksum = fn(payload).slice(0, len);
const oldChecksum = data.slice(-len);
for (let i = 0; i < len; i++)
if (newChecksum[i] !== oldChecksum[i])
throw new Error('Invalid checksum');
return payload;
},
};
}
exports.utils = { alphabet, chain, checksum, radix, radix2, join, padding };
exports.base16 = chain(radix2(4), alphabet('0123456789ABCDEF'), join(''));
exports.base32 = chain(radix2(5), alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'), padding(5), join(''));
exports.base32hex = chain(radix2(5), alphabet('0123456789ABCDEFGHIJKLMNOPQRSTUV'), padding(5), join(''));
exports.base32crockford = chain(radix2(5), alphabet('0123456789ABCDEFGHJKMNPQRSTVWXYZ'), join(''), normalize((s) => s.toUpperCase().replace(/O/g, '0').replace(/[IL]/g, '1')));
exports.base64 = chain(radix2(6), alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'), padding(6), join(''));
exports.base64url = chain(radix2(6), alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'), padding(6), join(''));
const genBase58 = (abc) => chain(radix(58), alphabet(abc), join(''));
exports.base58 = genBase58('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
exports.base58flickr = genBase58('123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ');
exports.base58xrp = genBase58('rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz');
const XMR_BLOCK_LEN = [0, 2, 3, 5, 6, 7, 9, 10, 11];
exports.base58xmr = {
encode(data) {
let res = '';
for (let i = 0; i < data.length; i += 8) {
const block = data.subarray(i, i + 8);
res += exports.base58.encode(block).padStart(XMR_BLOCK_LEN[block.length], '1');
}
return res;
},
decode(str) {
let res = [];
for (let i = 0; i < str.length; i += 11) {
const slice = str.slice(i, i + 11);
const blockLen = XMR_BLOCK_LEN.indexOf(slice.length);
const block = exports.base58.decode(slice);
for (let j = 0; j < block.length - blockLen; j++) {
if (block[j] !== 0)
throw new Error('base58xmr: wrong padding');
}
res = res.concat(Array.from(block.slice(block.length - blockLen)));
}
return Uint8Array.from(res);
},
};
const base58check = (sha256) => chain(checksum(4, (data) => sha256(sha256(data))), exports.base58);
exports.base58check = base58check;
const BECH_ALPHABET = chain(alphabet('qpzry9x8gf2tvdw0s3jn54khce6mua7l'), join(''));
const POLYMOD_GENERATORS = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
function bech32Polymod(pre) {
const b = pre >> 25;
let chk = (pre & 0x1ffffff) << 5;
for (let i = 0; i < POLYMOD_GENERATORS.length; i++) {
if (((b >> i) & 1) === 1)
chk ^= POLYMOD_GENERATORS[i];
}
return chk;
}
function bechChecksum(prefix, words, encodingConst = 1) {
const len = prefix.length;
let chk = 1;
for (let i = 0; i < len; i++) {
const c = prefix.charCodeAt(i);
if (c < 33 || c > 126)
throw new Error(`Invalid prefix (${prefix})`);
chk = bech32Polymod(chk) ^ (c >> 5);
}
chk = bech32Polymod(chk);
for (let i = 0; i < len; i++)
chk = bech32Polymod(chk) ^ (prefix.charCodeAt(i) & 0x1f);
for (let v of words)
chk = bech32Polymod(chk) ^ v;
for (let i = 0; i < 6; i++)
chk = bech32Polymod(chk);
chk ^= encodingConst;
return BECH_ALPHABET.encode(convertRadix2([chk % 2 ** 30], 30, 5, false));
}
function genBech32(encoding) {
const ENCODING_CONST = encoding === 'bech32' ? 1 : 0x2bc830a3;
const _words = radix2(5);
const fromWords = _words.decode;
const toWords = _words.encode;
const fromWordsUnsafe = unsafeWrapper(fromWords);
function encode(prefix, words, limit = 90) {
if (typeof prefix !== 'string')
throw new Error(`bech32.encode prefix should be string, not ${typeof prefix}`);
if (!Array.isArray(words) || (words.length && typeof words[0] !== 'number'))
throw new Error(`bech32.encode words should be array of numbers, not ${typeof words}`);
const actualLength = prefix.length + 7 + words.length;
if (limit !== false && actualLength > limit)
throw new TypeError(`Length ${actualLength} exceeds limit ${limit}`);
prefix = prefix.toLowerCase();
return `${prefix}1${BECH_ALPHABET.encode(words)}${bechChecksum(prefix, words, ENCODING_CONST)}`;
}
function decode(str, limit = 90) {
if (typeof str !== 'string')
throw new Error(`bech32.decode input should be string, not ${typeof str}`);
if (str.length < 8 || (limit !== false && str.length > limit))
throw new TypeError(`Wrong string length: ${str.length} (${str}). Expected (8..${limit})`);
const lowered = str.toLowerCase();
if (str !== lowered && str !== str.toUpperCase())
throw new Error(`String must be lowercase or uppercase`);
str = lowered;
const sepIndex = str.lastIndexOf('1');
if (sepIndex === 0 || sepIndex === -1)
throw new Error(`Letter "1" must be present between prefix and data only`);
const prefix = str.slice(0, sepIndex);
const _words = str.slice(sepIndex + 1);
if (_words.length < 6)
throw new Error('Data must be at least 6 characters long');
const words = BECH_ALPHABET.decode(_words).slice(0, -6);
const sum = bechChecksum(prefix, words, ENCODING_CONST);
if (!_words.endsWith(sum))
throw new Error(`Invalid checksum in ${str}: expected "${sum}"`);
return { prefix, words };
}
const decodeUnsafe = unsafeWrapper(decode);
function decodeToBytes(str) {
const { prefix, words } = decode(str, false);
return { prefix, words, bytes: fromWords(words) };
}
return { encode, decode, decodeToBytes, decodeUnsafe, fromWords, fromWordsUnsafe, toWords };
}
exports.bech32 = genBech32('bech32');
exports.bech32m = genBech32('bech32m');
exports.utf8 = {
encode: (data) => new TextDecoder().decode(data),
decode: (str) => new TextEncoder().encode(str),
};
exports.hex = chain(radix2(4), alphabet('0123456789abcdef'), join(''), normalize((s) => {
if (typeof s !== 'string' || s.length % 2)
throw new TypeError(`hex.decode: expected string, got ${typeof s} with length ${s.length}`);
return s.toLowerCase();
}));
const CODERS = {
utf8: exports.utf8, hex: exports.hex, base16: exports.base16, base32: exports.base32, base64: exports.base64, base64url: exports.base64url, base58: exports.base58, base58xmr: exports.base58xmr
};
const coderTypeError = `Invalid encoding type. Available types: ${Object.keys(CODERS).join(', ')}`;
const bytesToString = (type, bytes) => {
if (typeof type !== 'string' || !CODERS.hasOwnProperty(type))
throw new TypeError(coderTypeError);
if (!(bytes instanceof Uint8Array))
throw new TypeError('bytesToString() expects Uint8Array');
return CODERS[type].encode(bytes);
};
exports.bytesToString = bytesToString;
exports.str = exports.bytesToString;
const stringToBytes = (type, str) => {
if (!CODERS.hasOwnProperty(type))
throw new TypeError(coderTypeError);
if (typeof str !== 'string')
throw new TypeError('stringToBytes() expects string');
return CODERS[type].decode(str);
};
exports.stringToBytes = stringToBytes;
exports.bytes = exports.stringToBytes;
} (lib));
return lib;
}
var bolt11;
var hasRequiredBolt11;
function requireBolt11 () {
if (hasRequiredBolt11) return bolt11;
hasRequiredBolt11 = 1;
const {bech32, hex, utf8} = requireLib();
// defaults for encode; default timestamp is current time at call
const DEFAULTNETWORK = {
// default network is bitcoin
bech32: 'bc',
pubKeyHash: 0x00,
scriptHash: 0x05,
validWitnessVersions: [0]
};
const TESTNETWORK = {
bech32: 'tb',
pubKeyHash: 0x6f,
scriptHash: 0xc4,
validWitnessVersions: [0]
};
const SIGNETNETWORK = {
bech32: 'tbs',
pubKeyHash: 0x6f,
scriptHash: 0xc4,
validWitnessVersions: [0]
};
const REGTESTNETWORK = {
bech32: 'bcrt',
pubKeyHash: 0x6f,
scriptHash: 0xc4,
validWitnessVersions: [0]
};
const SIMNETWORK = {
bech32: 'sb',
pubKeyHash: 0x3f,
scriptHash: 0x7b,
validWitnessVersions: [0]
};
const FEATUREBIT_ORDER = [
'option_data_loss_protect',
'initial_routing_sync',
'option_upfront_shutdown_script',
'gossip_queries',
'var_onion_optin',
'gossip_queries_ex',
'option_static_remotekey',
'payment_secret',
'basic_mpp',
'option_support_large_channel'
];
const DIVISORS = {
m: BigInt(1e3),
u: BigInt(1e6),
n: BigInt(1e9),
p: BigInt(1e12)
};
const MAX_MILLISATS = BigInt('2100000000000000000');
const MILLISATS_PER_BTC = BigInt(1e11);
const TAGCODES = {
payment_hash: 1,
payment_secret: 16,
description: 13,
payee: 19,
description_hash: 23, // commit to longer descriptions (used by lnurl-pay)
expiry: 6, // default: 3600 (1 hour)
min_final_cltv_expiry: 24, // default: 9
fallback_address: 9,
route_hint: 3, // for extra routing info (private etc.)
feature_bits: 5,
metadata: 27
};
// reverse the keys and values of TAGCODES and insert into TAGNAMES
const TAGNAMES = {};
for (let i = 0, keys = Object.keys(TAGCODES); i < keys.length; i++) {
const currentName = keys[i];
const currentCode = TAGCODES[keys[i]].toString();
TAGNAMES[currentCode] = currentName;
}
const TAGPARSERS = {
1: words => hex.encode(bech32.fromWordsUnsafe(words)), // 256 bits
16: words => hex.encode(bech32.fromWordsUnsafe(words)), // 256 bits
13: words => utf8.encode(bech32.fromWordsUnsafe(words)), // string variable length
19: words => hex.encode(bech32.fromWordsUnsafe(words)), // 264 bits
23: words => hex.encode(bech32.fromWordsUnsafe(words)), // 256 bits
27: words => hex.encode(bech32.fromWordsUnsafe(words)), // variable
6: wordsToIntBE, // default: 3600 (1 hour)
24: wordsToIntBE, // default: 9
3: routingInfoParser, // for extra routing info (private etc.)
5: featureBitsParser // keep feature bits as array of 5 bit words
};
function getUnknownParser(tagCode) {
return words => ({
tagCode: parseInt(tagCode),
words: bech32.encode('unknown', words, Number.MAX_SAFE_INTEGER)
})
}
function wordsToIntBE(words) {
return words.reverse().reduce((total, item, index) => {
return total + item * Math.pow(32, index)
}, 0)
}
// first convert from words to buffer, trimming padding where necessary
// parse in 51 byte chunks. See encoder for details.
function routingInfoParser(words) {
const routes = [];
let pubkey,
shortChannelId,
feeBaseMSats,
feeProportionalMillionths,
cltvExpiryDelta;
let routesBuffer = bech32.fromWordsUnsafe(words);
while (routesBuffer.length > 0) {
pubkey = hex.encode(routesBuffer.slice(0, 33)); // 33 bytes
shortChannelId = hex.encode(routesBuffer.slice(33, 41)); // 8 bytes
feeBaseMSats = parseInt(hex.encode(routesBuffer.slice(41, 45)), 16); // 4 bytes
feeProportionalMillionths = parseInt(
hex.encode(routesBuffer.slice(45, 49)),
16
); // 4 bytes
cltvExpiryDelta = parseInt(hex.encode(routesBuffer.slice(49, 51)), 16); // 2 bytes
routesBuffer = routesBuffer.slice(51);
routes.push({
pubkey,
short_channel_id: shortChannelId,
fee_base_msat: feeBaseMSats,
fee_proportional_millionths: feeProportionalMillionths,
cltv_expiry_delta: cltvExpiryDelta
});
}
return routes
}
function featureBitsParser(words) {
const bools = words
.slice()
.reverse()
.map(word => [
!!(word & 0b1),
!!(word & 0b10),
!!(word & 0b100),
!!(word & 0b1000),
!!(word & 0b10000)
])
.reduce((finalArr, itemArr) => finalArr.concat(itemArr), []);
while (bools.length < FEATUREBIT_ORDER.length * 2) {
bools.push(false);
}
const featureBits = {};
FEATUREBIT_ORDER.forEach((featureName, index) => {
let status;
if (bools[index * 2]) {
status = 'required';
} else if (bools[index * 2 + 1]) {
status = 'supported';
} else {
status = 'unsupported';
}
featureBits[featureName] = status;
});
const extraBits = bools.slice(FEATUREBIT_ORDER.length * 2);
featureBits.extra_bits = {
start_bit: FEATUREBIT_ORDER.length * 2,
bits: extraBits,
has_required: extraBits.reduce(
(result, bit, index) =>
index % 2 !== 0 ? result || false : result || bit,
false
)
};
return featureBits
}
function hrpToMillisat(hrpString, outputString) {
let divisor, value;
if (hrpString.slice(-1).match(/^[munp]$/)) {
divisor = hrpString.slice(-1);
value = hrpString.slice(0, -1);
} else if (hrpString.slice(-1).match(/^[^munp0-9]$/)) {
throw new Error('Not a valid multiplier for the amount')
} else {
value = hrpString;
}
if (!value.match(/^\d+$/))
throw new Error('Not a valid human readable amount')
const valueBN = BigInt(value);
const millisatoshisBN = divisor
? (valueBN * MILLISATS_PER_BTC) / DIVISORS[divisor]
: valueBN * MILLISATS_PER_BTC;
if (
(divisor === 'p' && !(valueBN % BigInt(10) === BigInt(0))) ||
millisatoshisBN > MAX_MILLISATS
) {
throw new Error('Amount is outside of valid range')
}
return outputString ? millisatoshisBN.toString() : millisatoshisBN
}
// decode will only have extra comments that aren't covered in encode comments.
// also if anything is hard to read I'll comment.
function decode(paymentRequest, network) {
if (typeof paymentRequest !== 'string')
throw new Error('Lightning Payment Request must be string')
if (paymentRequest.slice(0, 2).toLowerCase() !== 'ln')
throw new Error('Not a proper lightning payment request')
const sections = [];
const decoded = bech32.decode(paymentRequest, Number.MAX_SAFE_INTEGER);
paymentRequest = paymentRequest.toLowerCase();
const prefix = decoded.prefix;
let words = decoded.words;
let letters = paymentRequest.slice(prefix.length + 1);
let sigWords = words.slice(-104);
words = words.slice(0, -104);
// Without reverse lookups, can't say that the multipier at the end must
// have a number before it, so instead we parse, and if the second group
// doesn't have anything, there's a good chance the last letter of the
// coin type got captured by the third group, so just re-regex without
// the number.
let prefixMatches = prefix.match(/^ln(\S+?)(\d*)([a-zA-Z]?)$/);
if (prefixMatches && !prefixMatches[2])
prefixMatches = prefix.match(/^ln(\S+)$/);
if (!prefixMatches) {
throw new Error('Not a proper lightning payment request')
}
// "ln" section
sections.push({
name: 'lightning_network',
letters: 'ln'
});
// "bc" section
const bech32Prefix = prefixMatches[1];
let coinNetwork;
if (!network) {
switch (bech32Prefix) {
case DEFAULTNETWORK.bech32:
coinNetwork = DEFAULTNETWORK;
break
case TESTNETWORK.bech32:
coinNetwork = TESTNETWORK;
break
case SIGNETNETWORK.bech32:
coinNetwork = SIGNETNETWORK;
break
case REGTESTNETWORK.bech32:
coinNetwork = REGTESTNETWORK;
break
case SIMNETWORK.bech32:
coinNetwork = SIMNETWORK;
break
}
} else {
if (
network.bech32 === undefined ||
network.pubKeyHash === undefined ||
network.scriptHash === undefined ||
!Array.isArray(network.validWitnessVersions)
)
throw new Error('Invalid network')
coinNetwork = network;
}
if (!coinNetwork || coinNetwork.bech32 !== bech32Prefix) {
throw new Error('Unknown coin bech32 prefix')
}
sections.push({
name: 'coin_network',
letters: bech32Prefix,
value: coinNetwork
});
// amount section
const value = prefixMatches[2];
let millisatoshis;
if (value) {
const divisor = prefixMatches[3];
millisatoshis = hrpToMillisat(value + divisor, true);
sections.push({
name: 'amount',
letters: prefixMatches[2] + prefixMatches[3],
value: millisatoshis
});
} else {
millisatoshis = null;
}
// "1" separator
sections.push({
name: 'separator',
letters: '1'
});
// timestamp
const timestamp = wordsToIntBE(words.slice(0, 7));
words = words.slice(7); // trim off the left 7 words
sections.push({
name: 'timestamp',
letters: letters.slice(0, 7),
value: timestamp
});
letters = letters.slice(7);
let tagName, parser, tagLength, tagWords;
// we have no tag count to go on, so just keep hacking off words
// until we have none.
while (words.length > 0) {
const tagCode = words[0].toString();
tagName = TAGNAMES[tagCode] || 'unknown_tag';
parser = TAGPARSERS[tagCode] || getUnknownParser(tagCode);
words = words.slice(1);
tagLength = wordsToIntBE(words.slice(0, 2));
words = words.slice(2);
tagWords = words.slice(0, tagLength);
words = words.slice(tagLength);
sections.push({
name: tagName,
tag: letters[0],
letters: letters.slice(0, 1 + 2 + tagLength),
value: parser(tagWords) // see: parsers for more comments
});
letters = letters.slice(1 + 2 + tagLength);
}
// signature
sections.push({
name: 'signature',
letters: letters.slice(0, 104),
value: hex.encode(bech32.fromWordsUnsafe(sigWords))
});
letters = letters.slice(104);
// checksum
sections.push({
name: 'checksum',
letters: letters
});
let result = {
paymentRequest,
sections,
get expiry() {
let exp = sections.find(s => s.name === 'expiry');
if (exp) return getValue('timestamp') + exp.value
},
get route_hints() {
return sections.filter(s => s.name === 'route_hint').map(s => s.value)
}
};
for (let name in TAGCODES) {
if (name === 'route_hint') {
// route hints can be multiple, so this won't work for them
continue
}
Object.defineProperty(result, name, {
get() {
return getValue(name)
}
});
}
return result
function getValue(name) {
let section = sections.find(s => s.name === name);
return section ? section.value : undefined
}
}
bolt11 = {
decode,
hrpToMillisat
};
return bolt11;
}
var bolt11Exports = requireBolt11();
// from https://stackoverflow.com/a/50868276
const fromHexString = (hexString) => Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
const decodeInvoice = (paymentRequest) => {
if (!paymentRequest)
return null;
try {
const decoded = bolt11Exports.decode(paymentRequest);
if (!decoded || !decoded.sections)
return null;
const hashTag = decoded.sections.find((value) => value.name === "payment_hash");
if (hashTag?.name !== "payment_hash" || !hashTag.value)
return null;
const paymentHash = hashTag.value;
let satoshi = 0;
let millisatoshi = 0;
let amountRaw = "0";
const amountTag = decoded.sections.find((value) => value.name === "amount");
if (amountTag?.name === "amount" && amountTag.value) {
amountRaw = amountTag.value;
millisatoshi = parseInt(amountTag.value);
satoshi = parseInt(amountTag.value) / 1000; // millisats
}
const timestampTag = decoded.sections.find((value) => value.name === "timestamp");
if (timestampTag?.name !== "timestamp" || !timestampTag.value)
return null;
const timestamp = timestampTag.value;
let expiry;
const expiryTag = decoded.sections.find((value) => value.name === "expiry");
if (expiryTag?.name === "expiry") {
expiry = expiryTag.value;
}
const descriptionTag = decoded.sections.find((value) => value.name === "description");
const description = descriptionTag?.name === "description"
? descriptionTag?.value
: undefined;
return {
paymentHash,
satoshi,
millisatoshi,
amountRaw,
timestamp,
expiry,
description,
};
}
catch {
return null;
}
};
function validatePreimage(preimage, paymentHash) {
try {
if (!/^[0-9a-fA-F]{64}$/.test(preimage))
return false;
if (!/^[0-9a-fA-F]{64}$/.test(paymentHash))
return false;
const preimageHash = bytesToHex(sha256(fromHexString(preimage)));
return paymentHash === preimageHash;
}
catch {
return false;
}
}
class Invoice {
constructor(args) {
this.paymentRequest = args.pr;
if (!this.paymentRequest) {
throw new Error("Invalid payment request");
}
const decodedInvoice = decodeInvoice(this.paymentRequest);
if (!decodedInvoice) {
throw new Error("Failed to decode payment request");
}
this.paymentHash = decodedInvoice.paymentHash;
this.satoshi = decodedInvoice.satoshi;
this.millisatoshi = decodedInvoice.millisatoshi;
this.amountRaw = decodedInvoice.amountRaw;
this.timestamp = decodedInvoice.timestamp;
this.expiry = decodedInvoice.expiry;
this.createdDate = new Date(this.timestamp * 1000);
this.expiryDate = this.expiry
? new Date((this.timestamp + this.expiry) * 1000)
: undefined;
this.description = decodedInvoice.description ?? null;
this.verify = args.verify ?? null;
this.preimage = args.preimage ?? null;
this.successAction = args.successAction ?? null;
}
async isPaid() {
if (this.preimage)
return this.validatePreimage(this.preimage);
else if (this.verify) {
return await this.verifyPayment();
}
else {
throw new Error("Could not verify payment");
}
}
validatePreimage(preimage) {
if (!preimage || !this.paymentHash)
return false;
return validatePreimage(preimage, this.paymentHash);
}
async verifyPayment() {
try {
if (!this.verify) {
throw new Error("LNURL verify not available");
}
const response = await fetch(this.verify);
if (!response.ok) {
throw new Error(`Verification request failed: ${response.status} ${response.statusText}`);
}
const json = await response.json();
if (json.preimage) {
this.preimage = json.preimage;
}
return json.settled;
}
catch (error) {
console.error("Failed to check LNURL-verify", error);
return false;
}
}
hasExpired() {
const { expiryDate } = this;
if (expiryDate) {
return expiryDate.getTime() < Date.now();
}
return false;
}
}
/** Apply a previously-obtained credential to the outgoing request headers. */
const applyCredentials = (headers, credentials) => {
headers.set(credentials.header, credentials.value);
};
/** Attach payment metadata to a response and return it (typed). */
const attachPayment = (response, payment) => {
if (payment) {
response.payment = payment;
}
return response;
};
/** Payment metadata describing a request authorized with a reused credential. */
const reusedCredentialPayment = (credentials) => credentials ? { paid: false, amount: 0, credentials } : undefined;
/** Satoshi amount of a BOLT11 invoice (0 when it cannot be decoded). */
const getInvoiceAmount = (invoice) => {
try {
return new Invoice({ pr: invoice }).satoshi;
}
catch (_) {
return 0;
}
};
/**
* Client: parse "www-authenticate" header from server response

@@ -42,2 +1324,5 @@ * @param input

const invoice = details.invoice;
// Preserve the scheme the server challenged with (L402 or LSAT) so the
// retry's Authorization header matches what the server expects.
const scheme = /^\s*LSAT\b/i.test(l402Header) ? "LSAT" : "L402";
if (!token) {

@@ -50,4 +1335,12 @@ throw new Error("L402: missing token/macaroon in WWW-Authenticate header");

const invResp = await wallet.payInvoice({ invoice });
headers.set("Authorization", `L402 ${token}:${invResp.preimage}`);
return fetch(url, fetchArgs);
const value = `${scheme} ${token}:${invResp.preimage}`;
headers.set("Authorization", value);
const response = await fetch(url, fetchArgs);
return attachPayment(response, {
paid: true,
amount: getInvoiceAmount(invoice),
feesPaid: invResp.fees_paid,
preimage: invResp.preimage,
credentials: { header: "Authorization", value },
});
};

@@ -66,2 +1359,11 @@ const fetchWithL402 = async (url, fetchArgs, options) => {

fetchArgs.headers = headers;
// If the caller supplied a credential, we MUST use it and never pay again β€”
// even if the server still responds with a 402. Re-paying here is the exact
// double-charge this API exists to prevent; the caller decides what to do
// with a rejected credential (retry after settlement, top up, etc.).
if (options.credentials) {
applyCredentials(headers, options.credentials);
const reusedResp = await fetch(url, fetchArgs);
return attachPayment(reusedResp, reusedCredentialPayment(options.credentials));
}
const initResp = await fetch(url, fetchArgs);

@@ -68,0 +1370,0 @@ const header = initResp.headers.get("www-authenticate");

'use strict';
function bytes(b, ...lengths) {
if (!(b instanceof Uint8Array))
throw new Error('Expected Uint8Array');
if (lengths.length > 0 && !lengths.includes(b.length))
throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`);
}
function exists(instance, checkFinished = true) {
if (instance.destroyed)
throw new Error('Hash instance has been destroyed');
if (checkFinished && instance.finished)
throw new Error('Hash#digest() has already been called');
}
function output(out, instance) {
bytes(out);
const min = instance.outputLen;
if (out.length < min) {
throw new Error(`digestInto() expects output buffer of length at least ${min}`);
}
}
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
// node.js versions earlier than v19 don't declare it in global scope.
// For node.js, package.json#exports field mapping rewrites import
// from `crypto` to `cryptoNode`, which imports native module.
// Makes the utils un-importable in browsers without a bundler.
// Once node.js 18 is deprecated, we can just drop the import.
const u8a = (a) => a instanceof Uint8Array;
// Cast array to view
const createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
// The rotate right (circular right shift) operation for uint32
const rotr = (word, shift) => (word << (32 - shift)) | (word >>> shift);
// big-endian hardware is rare. Just in case someone still decides to run hashes:
// early-throw an error because we don't support BE yet.
const isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44;
if (!isLE)
throw new Error('Non little-endian hardware is not supported');
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
/**
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
*/
function bytesToHex(bytes) {
if (!u8a(bytes))
throw new Error('Uint8Array expected');
// pre-caching improves the speed 6x
let hex = '';
for (let i = 0; i < bytes.length; i++) {
hex += hexes[bytes[i]];
}
return hex;
}
/**
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
*/
function utf8ToBytes(str) {
if (typeof str !== 'string')
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
}
/**
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
* Warning: when Uint8Array is passed, it would NOT get copied.
* Keep in mind for future mutable operations.
*/
function toBytes(data) {
if (typeof data === 'string')
data = utf8ToBytes(data);
if (!u8a(data))
throw new Error(`expected Uint8Array, got ${typeof data}`);
return data;
}
// For runtime check if class implements interface
class Hash {
// Safe version that clones internal state
clone() {
return this._cloneInto();
}
}
function wrapConstructor(hashCons) {
const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
const tmp = hashCons();
hashC.outputLen = tmp.outputLen;
hashC.blockLen = tmp.blockLen;
hashC.create = () => hashCons();
return hashC;
}
// Polyfill for Safari 14
function setBigUint64(view, byteOffset, value, isLE) {
if (typeof view.setBigUint64 === 'function')
return view.setBigUint64(byteOffset, value, isLE);
const _32n = BigInt(32);
const _u32_max = BigInt(0xffffffff);
const wh = Number((value >> _32n) & _u32_max);
const wl = Number(value & _u32_max);
const h = isLE ? 4 : 0;
const l = isLE ? 0 : 4;
view.setUint32(byteOffset + h, wh, isLE);
view.setUint32(byteOffset + l, wl, isLE);
}
// Base SHA2 class (RFC 6234)
class SHA2 extends Hash {
constructor(blockLen, outputLen, padOffset, isLE) {
super();
this.blockLen = blockLen;
this.outputLen = outputLen;
this.padOffset = padOffset;
this.isLE = isLE;
this.finished = false;
this.length = 0;
this.pos = 0;
this.destroyed = false;
this.buffer = new Uint8Array(blockLen);
this.view = createView(this.buffer);
}
update(data) {
exists(this);
const { view, buffer, blockLen } = this;
data = toBytes(data);
const len = data.length;
for (let pos = 0; pos < len;) {
const take = Math.min(blockLen - this.pos, len - pos);
// Fast path: we have at least one block in input, cast it to view and process
if (take === blockLen) {
const dataView = createView(data);
for (; blockLen <= len - pos; pos += blockLen)
this.process(dataView, pos);
continue;
}
buffer.set(data.subarray(pos, pos + take), this.pos);
this.pos += take;
pos += take;
if (this.pos === blockLen) {
this.process(view, 0);
this.pos = 0;
}
}
this.length += data.length;
this.roundClean();
return this;
}
digestInto(out) {
exists(this);
output(out, this);
this.finished = true;
// Padding
// We can avoid allocation of buffer for padding completely if it
// was previously not allocated here. But it won't change performance.
const { buffer, view, blockLen, isLE } = this;
let { pos } = this;
// append the bit '1' to the message
buffer[pos++] = 0b10000000;
this.buffer.subarray(pos).fill(0);
// we have less than padOffset left in buffer, so we cannot put length in current block, need process it and pad again
if (this.padOffset > blockLen - pos) {
this.process(view, 0);
pos = 0;
}
// Pad until full block byte with zeros
for (let i = pos; i < blockLen; i++)
buffer[i] = 0;
// Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that
// You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.
// So we just write lowest 64 bits of that value.
setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
this.process(view, 0);
const oview = createView(out);
const len = this.outputLen;
// NOTE: we do division by 4 later, which should be fused in single op with modulo by JIT
if (len % 4)
throw new Error('_sha2: outputLen should be aligned to 32bit');
const outLen = len / 4;
const state = this.get();
if (outLen > state.length)
throw new Error('_sha2: outputLen bigger than state');
for (let i = 0; i < outLen; i++)
oview.setUint32(4 * i, state[i], isLE);
}
digest() {
const { buffer, outputLen } = this;
this.digestInto(buffer);
const res = buffer.slice(0, outputLen);
this.destroy();
return res;
}
_cloneInto(to) {
to || (to = new this.constructor());
to.set(...this.get());
const { blockLen, buffer, length, finished, destroyed, pos } = this;
to.length = length;
to.pos = pos;
to.finished = finished;
to.destroyed = destroyed;
if (length % blockLen)
to.buffer.set(buffer);
return to;
}
}
// SHA2-256 need to try 2^128 hashes to execute birthday attack.
// BTC network is doing 2^67 hashes/sec as per early 2023.
// Choice: a ? b : c
const Chi = (a, b, c) => (a & b) ^ (~a & c);
// Majority function, true if any two inpust is true
const Maj = (a, b, c) => (a & b) ^ (a & c) ^ (b & c);
// Round constants:
// first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311)
// prettier-ignore
const SHA256_K = /* @__PURE__ */ new Uint32Array([
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
]);
// Initial state (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):
// prettier-ignore
const IV = /* @__PURE__ */ new Uint32Array([
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
]);
// Temporary buffer, not used to store anything between runs
// Named this way because it matches specification.
const SHA256_W = /* @__PURE__ */ new Uint32Array(64);
class SHA256 extends SHA2 {
constructor() {
super(64, 32, 8, false);
// We cannot use array here since array allows indexing by variable
// which means optimizer/compiler cannot use registers.
this.A = IV[0] | 0;
this.B = IV[1] | 0;
this.C = IV[2] | 0;
this.D = IV[3] | 0;
this.E = IV[4] | 0;
this.F = IV[5] | 0;
this.G = IV[6] | 0;
this.H = IV[7] | 0;
}
get() {
const { A, B, C, D, E, F, G, H } = this;
return [A, B, C, D, E, F, G, H];
}
// prettier-ignore
set(A, B, C, D, E, F, G, H) {
this.A = A | 0;
this.B = B | 0;
this.C = C | 0;
this.D = D | 0;
this.E = E | 0;
this.F = F | 0;
this.G = G | 0;
this.H = H | 0;
}
process(view, offset) {
// Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array
for (let i = 0; i < 16; i++, offset += 4)
SHA256_W[i] = view.getUint32(offset, false);
for (let i = 16; i < 64; i++) {
const W15 = SHA256_W[i - 15];
const W2 = SHA256_W[i - 2];
const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ (W15 >>> 3);
const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ (W2 >>> 10);
SHA256_W[i] = (s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16]) | 0;
}
// Compression function main loop, 64 rounds
let { A, B, C, D, E, F, G, H } = this;
for (let i = 0; i < 64; i++) {
const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0;
const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
const T2 = (sigma0 + Maj(A, B, C)) | 0;
H = G;
G = F;
F = E;
E = (D + T1) | 0;
D = C;
C = B;
B = A;
A = (T1 + T2) | 0;
}
// Add the compressed chunk to the current hash value
A = (A + this.A) | 0;
B = (B + this.B) | 0;
C = (C + this.C) | 0;
D = (D + this.D) | 0;
E = (E + this.E) | 0;
F = (F + this.F) | 0;
G = (G + this.G) | 0;
H = (H + this.H) | 0;
this.set(A, B, C, D, E, F, G, H);
}
roundClean() {
SHA256_W.fill(0);
}
destroy() {
this.set(0, 0, 0, 0, 0, 0, 0, 0);
this.buffer.fill(0);
}
}
/**
* SHA2-256 hash function
* @param message - data that would be hashed
*/
const sha256 = /* @__PURE__ */ wrapConstructor(() => new SHA256());
var lib = {};
var hasRequiredLib;
function requireLib () {
if (hasRequiredLib) return lib;
hasRequiredLib = 1;
(function (exports) {
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
Object.defineProperty(exports, "__esModule", { value: true });
exports.bytes = exports.stringToBytes = exports.str = exports.bytesToString = exports.hex = exports.utf8 = exports.bech32m = exports.bech32 = exports.base58check = exports.base58xmr = exports.base58xrp = exports.base58flickr = exports.base58 = exports.base64url = exports.base64 = exports.base32crockford = exports.base32hex = exports.base32 = exports.base16 = exports.utils = exports.assertNumber = void 0;
function assertNumber(n) {
if (!Number.isSafeInteger(n))
throw new Error(`Wrong integer: ${n}`);
}
exports.assertNumber = assertNumber;
function chain(...args) {
const wrap = (a, b) => (c) => a(b(c));
const encode = Array.from(args)
.reverse()
.reduce((acc, i) => (acc ? wrap(acc, i.encode) : i.encode), undefined);
const decode = args.reduce((acc, i) => (acc ? wrap(acc, i.decode) : i.decode), undefined);
return { encode, decode };
}
function alphabet(alphabet) {
return {
encode: (digits) => {
if (!Array.isArray(digits) || (digits.length && typeof digits[0] !== 'number'))
throw new Error('alphabet.encode input should be an array of numbers');
return digits.map((i) => {
assertNumber(i);
if (i < 0 || i >= alphabet.length)
throw new Error(`Digit index outside alphabet: ${i} (alphabet: ${alphabet.length})`);
return alphabet[i];
});
},
decode: (input) => {
if (!Array.isArray(input) || (input.length && typeof input[0] !== 'string'))
throw new Error('alphabet.decode input should be array of strings');
return input.map((letter) => {
if (typeof letter !== 'string')
throw new Error(`alphabet.decode: not string element=${letter}`);
const index = alphabet.indexOf(letter);
if (index === -1)
throw new Error(`Unknown letter: "${letter}". Allowed: ${alphabet}`);
return index;
});
},
};
}
function join(separator = '') {
if (typeof separator !== 'string')
throw new Error('join separator should be string');
return {
encode: (from) => {
if (!Array.isArray(from) || (from.length && typeof from[0] !== 'string'))
throw new Error('join.encode input should be array of strings');
for (let i of from)
if (typeof i !== 'string')
throw new Error(`join.encode: non-string input=${i}`);
return from.join(separator);
},
decode: (to) => {
if (typeof to !== 'string')
throw new Error('join.decode input should be string');
return to.split(separator);
},
};
}
function padding(bits, chr = '=') {
assertNumber(bits);
if (typeof chr !== 'string')
throw new Error('padding chr should be string');
return {
encode(data) {
if (!Array.isArray(data) || (data.length && typeof data[0] !== 'string'))
throw new Error('padding.encode input should be array of strings');
for (let i of data)
if (typeof i !== 'string')
throw new Error(`padding.encode: non-string input=${i}`);
while ((data.length * bits) % 8)
data.push(chr);
return data;
},
decode(input) {
if (!Array.isArray(input) || (input.length && typeof input[0] !== 'string'))
throw new Error('padding.encode input should be array of strings');
for (let i of input)
if (typeof i !== 'string')
throw new Error(`padding.decode: non-string input=${i}`);
let end = input.length;
if ((end * bits) % 8)
throw new Error('Invalid padding: string should have whole number of bytes');
for (; end > 0 && input[end - 1] === chr; end--) {
if (!(((end - 1) * bits) % 8))
throw new Error('Invalid padding: string has too much padding');
}
return input.slice(0, end);
},
};
}
function normalize(fn) {
if (typeof fn !== 'function')
throw new Error('normalize fn should be function');
return { encode: (from) => from, decode: (to) => fn(to) };
}
function convertRadix(data, from, to) {
if (from < 2)
throw new Error(`convertRadix: wrong from=${from}, base cannot be less than 2`);
if (to < 2)
throw new Error(`convertRadix: wrong to=${to}, base cannot be less than 2`);
if (!Array.isArray(data))
throw new Error('convertRadix: data should be array');
if (!data.length)
return [];
let pos = 0;
const res = [];
const digits = Array.from(data);
digits.forEach((d) => {
assertNumber(d);
if (d < 0 || d >= from)
throw new Error(`Wrong integer: ${d}`);
});
while (true) {
let carry = 0;
let done = true;
for (let i = pos; i < digits.length; i++) {
const digit = digits[i];
const digitBase = from * carry + digit;
if (!Number.isSafeInteger(digitBase) ||
(from * carry) / from !== carry ||
digitBase - digit !== from * carry) {
throw new Error('convertRadix: carry overflow');
}
carry = digitBase % to;
digits[i] = Math.floor(digitBase / to);
if (!Number.isSafeInteger(digits[i]) || digits[i] * to + carry !== digitBase)
throw new Error('convertRadix: carry overflow');
if (!done)
continue;
else if (!digits[i])
pos = i;
else
done = false;
}
res.push(carry);
if (done)
break;
}
for (let i = 0; i < data.length - 1 && data[i] === 0; i++)
res.push(0);
return res.reverse();
}
const gcd = (a, b) => (!b ? a : gcd(b, a % b));
const radix2carry = (from, to) => from + (to - gcd(from, to));
function convertRadix2(data, from, to, padding) {
if (!Array.isArray(data))
throw new Error('convertRadix2: data should be array');
if (from <= 0 || from > 32)
throw new Error(`convertRadix2: wrong from=${from}`);
if (to <= 0 || to > 32)
throw new Error(`convertRadix2: wrong to=${to}`);
if (radix2carry(from, to) > 32) {
throw new Error(`convertRadix2: carry overflow from=${from} to=${to} carryBits=${radix2carry(from, to)}`);
}
let carry = 0;
let pos = 0;
const mask = 2 ** to - 1;
const res = [];
for (const n of data) {
assertNumber(n);
if (n >= 2 ** from)
throw new Error(`convertRadix2: invalid data word=${n} from=${from}`);
carry = (carry << from) | n;
if (pos + from > 32)
throw new Error(`convertRadix2: carry overflow pos=${pos} from=${from}`);
pos += from;
for (; pos >= to; pos -= to)
res.push(((carry >> (pos - to)) & mask) >>> 0);
carry &= 2 ** pos - 1;
}
carry = (carry << (to - pos)) & mask;
if (!padding && pos >= from)
throw new Error('Excess padding');
if (!padding && carry)
throw new Error(`Non-zero padding: ${carry}`);
if (padding && pos > 0)
res.push(carry >>> 0);
return res;
}
function radix(num) {
assertNumber(num);
return {
encode: (bytes) => {
if (!(bytes instanceof Uint8Array))
throw new Error('radix.encode input should be Uint8Array');
return convertRadix(Array.from(bytes), 2 ** 8, num);
},
decode: (digits) => {
if (!Array.isArray(digits) || (digits.length && typeof digits[0] !== 'number'))
throw new Error('radix.decode input should be array of strings');
return Uint8Array.from(convertRadix(digits, num, 2 ** 8));
},
};
}
function radix2(bits, revPadding = false) {
assertNumber(bits);
if (bits <= 0 || bits > 32)
throw new Error('radix2: bits should be in (0..32]');
if (radix2carry(8, bits) > 32 || radix2carry(bits, 8) > 32)
throw new Error('radix2: carry overflow');
return {
encode: (bytes) => {
if (!(bytes instanceof Uint8Array))
throw new Error('radix2.encode input should be Uint8Array');
return convertRadix2(Array.from(bytes), 8, bits, !revPadding);
},
decode: (digits) => {
if (!Array.isArray(digits) || (digits.length && typeof digits[0] !== 'number'))
throw new Error('radix2.decode input should be array of strings');
return Uint8Array.from(convertRadix2(digits, bits, 8, revPadding));
},
};
}
function unsafeWrapper(fn) {
if (typeof fn !== 'function')
throw new Error('unsafeWrapper fn should be function');
return function (...args) {
try {
return fn.apply(null, args);
}
catch (e) { }
};
}
function checksum(len, fn) {
assertNumber(len);
if (typeof fn !== 'function')
throw new Error('checksum fn should be function');
return {
encode(data) {
if (!(data instanceof Uint8Array))
throw new Error('checksum.encode: input should be Uint8Array');
const checksum = fn(data).slice(0, len);
const res = new Uint8Array(data.length + len);
res.set(data);
res.set(checksum, data.length);
return res;
},
decode(data) {
if (!(data instanceof Uint8Array))
throw new Error('checksum.decode: input should be Uint8Array');
const payload = data.slice(0, -len);
const newChecksum = fn(payload).slice(0, len);
const oldChecksum = data.slice(-len);
for (let i = 0; i < len; i++)
if (newChecksum[i] !== oldChecksum[i])
throw new Error('Invalid checksum');
return payload;
},
};
}
exports.utils = { alphabet, chain, checksum, radix, radix2, join, padding };
exports.base16 = chain(radix2(4), alphabet('0123456789ABCDEF'), join(''));
exports.base32 = chain(radix2(5), alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'), padding(5), join(''));
exports.base32hex = chain(radix2(5), alphabet('0123456789ABCDEFGHIJKLMNOPQRSTUV'), padding(5), join(''));
exports.base32crockford = chain(radix2(5), alphabet('0123456789ABCDEFGHJKMNPQRSTVWXYZ'), join(''), normalize((s) => s.toUpperCase().replace(/O/g, '0').replace(/[IL]/g, '1')));
exports.base64 = chain(radix2(6), alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'), padding(6), join(''));
exports.base64url = chain(radix2(6), alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'), padding(6), join(''));
const genBase58 = (abc) => chain(radix(58), alphabet(abc), join(''));
exports.base58 = genBase58('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
exports.base58flickr = genBase58('123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ');
exports.base58xrp = genBase58('rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz');
const XMR_BLOCK_LEN = [0, 2, 3, 5, 6, 7, 9, 10, 11];
exports.base58xmr = {
encode(data) {
let res = '';
for (let i = 0; i < data.length; i += 8) {
const block = data.subarray(i, i + 8);
res += exports.base58.encode(block).padStart(XMR_BLOCK_LEN[block.length], '1');
}
return res;
},
decode(str) {
let res = [];
for (let i = 0; i < str.length; i += 11) {
const slice = str.slice(i, i + 11);
const blockLen = XMR_BLOCK_LEN.indexOf(slice.length);
const block = exports.base58.decode(slice);
for (let j = 0; j < block.length - blockLen; j++) {
if (block[j] !== 0)
throw new Error('base58xmr: wrong padding');
}
res = res.concat(Array.from(block.slice(block.length - blockLen)));
}
return Uint8Array.from(res);
},
};
const base58check = (sha256) => chain(checksum(4, (data) => sha256(sha256(data))), exports.base58);
exports.base58check = base58check;
const BECH_ALPHABET = chain(alphabet('qpzry9x8gf2tvdw0s3jn54khce6mua7l'), join(''));
const POLYMOD_GENERATORS = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
function bech32Polymod(pre) {
const b = pre >> 25;
let chk = (pre & 0x1ffffff) << 5;
for (let i = 0; i < POLYMOD_GENERATORS.length; i++) {
if (((b >> i) & 1) === 1)
chk ^= POLYMOD_GENERATORS[i];
}
return chk;
}
function bechChecksum(prefix, words, encodingConst = 1) {
const len = prefix.length;
let chk = 1;
for (let i = 0; i < len; i++) {
const c = prefix.charCodeAt(i);
if (c < 33 || c > 126)
throw new Error(`Invalid prefix (${prefix})`);
chk = bech32Polymod(chk) ^ (c >> 5);
}
chk = bech32Polymod(chk);
for (let i = 0; i < len; i++)
chk = bech32Polymod(chk) ^ (prefix.charCodeAt(i) & 0x1f);
for (let v of words)
chk = bech32Polymod(chk) ^ v;
for (let i = 0; i < 6; i++)
chk = bech32Polymod(chk);
chk ^= encodingConst;
return BECH_ALPHABET.encode(convertRadix2([chk % 2 ** 30], 30, 5, false));
}
function genBech32(encoding) {
const ENCODING_CONST = encoding === 'bech32' ? 1 : 0x2bc830a3;
const _words = radix2(5);
const fromWords = _words.decode;
const toWords = _words.encode;
const fromWordsUnsafe = unsafeWrapper(fromWords);
function encode(prefix, words, limit = 90) {
if (typeof prefix !== 'string')
throw new Error(`bech32.encode prefix should be string, not ${typeof prefix}`);
if (!Array.isArray(words) || (words.length && typeof words[0] !== 'number'))
throw new Error(`bech32.encode words should be array of numbers, not ${typeof words}`);
const actualLength = prefix.length + 7 + words.length;
if (limit !== false && actualLength > limit)
throw new TypeError(`Length ${actualLength} exceeds limit ${limit}`);
prefix = prefix.toLowerCase();
return `${prefix}1${BECH_ALPHABET.encode(words)}${bechChecksum(prefix, words, ENCODING_CONST)}`;
}
function decode(str, limit = 90) {
if (typeof str !== 'string')
throw new Error(`bech32.decode input should be string, not ${typeof str}`);
if (str.length < 8 || (limit !== false && str.length > limit))
throw new TypeError(`Wrong string length: ${str.length} (${str}). Expected (8..${limit})`);
const lowered = str.toLowerCase();
if (str !== lowered && str !== str.toUpperCase())
throw new Error(`String must be lowercase or uppercase`);
str = lowered;
const sepIndex = str.lastIndexOf('1');
if (sepIndex === 0 || sepIndex === -1)
throw new Error(`Letter "1" must be present between prefix and data only`);
const prefix = str.slice(0, sepIndex);
const _words = str.slice(sepIndex + 1);
if (_words.length < 6)
throw new Error('Data must be at least 6 characters long');
const words = BECH_ALPHABET.decode(_words).slice(0, -6);
const sum = bechChecksum(prefix, words, ENCODING_CONST);
if (!_words.endsWith(sum))
throw new Error(`Invalid checksum in ${str}: expected "${sum}"`);
return { prefix, words };
}
const decodeUnsafe = unsafeWrapper(decode);
function decodeToBytes(str) {
const { prefix, words } = decode(str, false);
return { prefix, words, bytes: fromWords(words) };
}
return { encode, decode, decodeToBytes, decodeUnsafe, fromWords, fromWordsUnsafe, toWords };
}
exports.bech32 = genBech32('bech32');
exports.bech32m = genBech32('bech32m');
exports.utf8 = {
encode: (data) => new TextDecoder().decode(data),
decode: (str) => new TextEncoder().encode(str),
};
exports.hex = chain(radix2(4), alphabet('0123456789abcdef'), join(''), normalize((s) => {
if (typeof s !== 'string' || s.length % 2)
throw new TypeError(`hex.decode: expected string, got ${typeof s} with length ${s.length}`);
return s.toLowerCase();
}));
const CODERS = {
utf8: exports.utf8, hex: exports.hex, base16: exports.base16, base32: exports.base32, base64: exports.base64, base64url: exports.base64url, base58: exports.base58, base58xmr: exports.base58xmr
};
const coderTypeError = `Invalid encoding type. Available types: ${Object.keys(CODERS).join(', ')}`;
const bytesToString = (type, bytes) => {
if (typeof type !== 'string' || !CODERS.hasOwnProperty(type))
throw new TypeError(coderTypeError);
if (!(bytes instanceof Uint8Array))
throw new TypeError('bytesToString() expects Uint8Array');
return CODERS[type].encode(bytes);
};
exports.bytesToString = bytesToString;
exports.str = exports.bytesToString;
const stringToBytes = (type, str) => {
if (!CODERS.hasOwnProperty(type))
throw new TypeError(coderTypeError);
if (typeof str !== 'string')
throw new TypeError('stringToBytes() expects string');
return CODERS[type].decode(str);
};
exports.stringToBytes = stringToBytes;
exports.bytes = exports.stringToBytes;
} (lib));
return lib;
}
var bolt11;
var hasRequiredBolt11;
function requireBolt11 () {
if (hasRequiredBolt11) return bolt11;
hasRequiredBolt11 = 1;
const {bech32, hex, utf8} = requireLib();
// defaults for encode; default timestamp is current time at call
const DEFAULTNETWORK = {
// default network is bitcoin
bech32: 'bc',
pubKeyHash: 0x00,
scriptHash: 0x05,
validWitnessVersions: [0]
};
const TESTNETWORK = {
bech32: 'tb',
pubKeyHash: 0x6f,
scriptHash: 0xc4,
validWitnessVersions: [0]
};
const SIGNETNETWORK = {
bech32: 'tbs',
pubKeyHash: 0x6f,
scriptHash: 0xc4,
validWitnessVersions: [0]
};
const REGTESTNETWORK = {
bech32: 'bcrt',
pubKeyHash: 0x6f,
scriptHash: 0xc4,
validWitnessVersions: [0]
};
const SIMNETWORK = {
bech32: 'sb',
pubKeyHash: 0x3f,
scriptHash: 0x7b,
validWitnessVersions: [0]
};
const FEATUREBIT_ORDER = [
'option_data_loss_protect',
'initial_routing_sync',
'option_upfront_shutdown_script',
'gossip_queries',
'var_onion_optin',
'gossip_queries_ex',
'option_static_remotekey',
'payment_secret',
'basic_mpp',
'option_support_large_channel'
];
const DIVISORS = {
m: BigInt(1e3),
u: BigInt(1e6),
n: BigInt(1e9),
p: BigInt(1e12)
};
const MAX_MILLISATS = BigInt('2100000000000000000');
const MILLISATS_PER_BTC = BigInt(1e11);
const TAGCODES = {
payment_hash: 1,
payment_secret: 16,
description: 13,
payee: 19,
description_hash: 23, // commit to longer descriptions (used by lnurl-pay)
expiry: 6, // default: 3600 (1 hour)
min_final_cltv_expiry: 24, // default: 9
fallback_address: 9,
route_hint: 3, // for extra routing info (private etc.)
feature_bits: 5,
metadata: 27
};
// reverse the keys and values of TAGCODES and insert into TAGNAMES
const TAGNAMES = {};
for (let i = 0, keys = Object.keys(TAGCODES); i < keys.length; i++) {
const currentName = keys[i];
const currentCode = TAGCODES[keys[i]].toString();
TAGNAMES[currentCode] = currentName;
}
const TAGPARSERS = {
1: words => hex.encode(bech32.fromWordsUnsafe(words)), // 256 bits
16: words => hex.encode(bech32.fromWordsUnsafe(words)), // 256 bits
13: words => utf8.encode(bech32.fromWordsUnsafe(words)), // string variable length
19: words => hex.encode(bech32.fromWordsUnsafe(words)), // 264 bits
23: words => hex.encode(bech32.fromWordsUnsafe(words)), // 256 bits
27: words => hex.encode(bech32.fromWordsUnsafe(words)), // variable
6: wordsToIntBE, // default: 3600 (1 hour)
24: wordsToIntBE, // default: 9
3: routingInfoParser, // for extra routing info (private etc.)
5: featureBitsParser // keep feature bits as array of 5 bit words
};
function getUnknownParser(tagCode) {
return words => ({
tagCode: parseInt(tagCode),
words: bech32.encode('unknown', words, Number.MAX_SAFE_INTEGER)
})
}
function wordsToIntBE(words) {
return words.reverse().reduce((total, item, index) => {
return total + item * Math.pow(32, index)
}, 0)
}
// first convert from words to buffer, trimming padding where necessary
// parse in 51 byte chunks. See encoder for details.
function routingInfoParser(words) {
const routes = [];
let pubkey,
shortChannelId,
feeBaseMSats,
feeProportionalMillionths,
cltvExpiryDelta;
let routesBuffer = bech32.fromWordsUnsafe(words);
while (routesBuffer.length > 0) {
pubkey = hex.encode(routesBuffer.slice(0, 33)); // 33 bytes
shortChannelId = hex.encode(routesBuffer.slice(33, 41)); // 8 bytes
feeBaseMSats = parseInt(hex.encode(routesBuffer.slice(41, 45)), 16); // 4 bytes
feeProportionalMillionths = parseInt(
hex.encode(routesBuffer.slice(45, 49)),
16
); // 4 bytes
cltvExpiryDelta = parseInt(hex.encode(routesBuffer.slice(49, 51)), 16); // 2 bytes
routesBuffer = routesBuffer.slice(51);
routes.push({
pubkey,
short_channel_id: shortChannelId,
fee_base_msat: feeBaseMSats,
fee_proportional_millionths: feeProportionalMillionths,
cltv_expiry_delta: cltvExpiryDelta
});
}
return routes
}
function featureBitsParser(words) {
const bools = words
.slice()
.reverse()
.map(word => [
!!(word & 0b1),
!!(word & 0b10),
!!(word & 0b100),
!!(word & 0b1000),
!!(word & 0b10000)
])
.reduce((finalArr, itemArr) => finalArr.concat(itemArr), []);
while (bools.length < FEATUREBIT_ORDER.length * 2) {
bools.push(false);
}
const featureBits = {};
FEATUREBIT_ORDER.forEach((featureName, index) => {
let status;
if (bools[index * 2]) {
status = 'required';
} else if (bools[index * 2 + 1]) {
status = 'supported';
} else {
status = 'unsupported';
}
featureBits[featureName] = status;
});
const extraBits = bools.slice(FEATUREBIT_ORDER.length * 2);
featureBits.extra_bits = {
start_bit: FEATUREBIT_ORDER.length * 2,
bits: extraBits,
has_required: extraBits.reduce(
(result, bit, index) =>
index % 2 !== 0 ? result || false : result || bit,
false
)
};
return featureBits
}
function hrpToMillisat(hrpString, outputString) {
let divisor, value;
if (hrpString.slice(-1).match(/^[munp]$/)) {
divisor = hrpString.slice(-1);
value = hrpString.slice(0, -1);
} else if (hrpString.slice(-1).match(/^[^munp0-9]$/)) {
throw new Error('Not a valid multiplier for the amount')
} else {
value = hrpString;
}
if (!value.match(/^\d+$/))
throw new Error('Not a valid human readable amount')
const valueBN = BigInt(value);
const millisatoshisBN = divisor
? (valueBN * MILLISATS_PER_BTC) / DIVISORS[divisor]
: valueBN * MILLISATS_PER_BTC;
if (
(divisor === 'p' && !(valueBN % BigInt(10) === BigInt(0))) ||
millisatoshisBN > MAX_MILLISATS
) {
throw new Error('Amount is outside of valid range')
}
return outputString ? millisatoshisBN.toString() : millisatoshisBN
}
// decode will only have extra comments that aren't covered in encode comments.
// also if anything is hard to read I'll comment.
function decode(paymentRequest, network) {
if (typeof paymentRequest !== 'string')
throw new Error('Lightning Payment Request must be string')
if (paymentRequest.slice(0, 2).toLowerCase() !== 'ln')
throw new Error('Not a proper lightning payment request')
const sections = [];
const decoded = bech32.decode(paymentRequest, Number.MAX_SAFE_INTEGER);
paymentRequest = paymentRequest.toLowerCase();
const prefix = decoded.prefix;
let words = decoded.words;
let letters = paymentRequest.slice(prefix.length + 1);
let sigWords = words.slice(-104);
words = words.slice(0, -104);
// Without reverse lookups, can't say that the multipier at the end must
// have a number before it, so instead we parse, and if the second group
// doesn't have anything, there's a good chance the last letter of the
// coin type got captured by the third group, so just re-regex without
// the number.
let prefixMatches = prefix.match(/^ln(\S+?)(\d*)([a-zA-Z]?)$/);
if (prefixMatches && !prefixMatches[2])
prefixMatches = prefix.match(/^ln(\S+)$/);
if (!prefixMatches) {
throw new Error('Not a proper lightning payment request')
}
// "ln" section
sections.push({
name: 'lightning_network',
letters: 'ln'
});
// "bc" section
const bech32Prefix = prefixMatches[1];
let coinNetwork;
if (!network) {
switch (bech32Prefix) {
case DEFAULTNETWORK.bech32:
coinNetwork = DEFAULTNETWORK;
break
case TESTNETWORK.bech32:
coinNetwork = TESTNETWORK;
break
case SIGNETNETWORK.bech32:
coinNetwork = SIGNETNETWORK;
break
case REGTESTNETWORK.bech32:
coinNetwork = REGTESTNETWORK;
break
case SIMNETWORK.bech32:
coinNetwork = SIMNETWORK;
break
}
} else {
if (
network.bech32 === undefined ||
network.pubKeyHash === undefined ||
network.scriptHash === undefined ||
!Array.isArray(network.validWitnessVersions)
)
throw new Error('Invalid network')
coinNetwork = network;
}
if (!coinNetwork || coinNetwork.bech32 !== bech32Prefix) {
throw new Error('Unknown coin bech32 prefix')
}
sections.push({
name: 'coin_network',
letters: bech32Prefix,
value: coinNetwork
});
// amount section
const value = prefixMatches[2];
let millisatoshis;
if (value) {
const divisor = prefixMatches[3];
millisatoshis = hrpToMillisat(value + divisor, true);
sections.push({
name: 'amount',
letters: prefixMatches[2] + prefixMatches[3],
value: millisatoshis
});
} else {
millisatoshis = null;
}
// "1" separator
sections.push({
name: 'separator',
letters: '1'
});
// timestamp
const timestamp = wordsToIntBE(words.slice(0, 7));
words = words.slice(7); // trim off the left 7 words
sections.push({
name: 'timestamp',
letters: letters.slice(0, 7),
value: timestamp
});
letters = letters.slice(7);
let tagName, parser, tagLength, tagWords;
// we have no tag count to go on, so just keep hacking off words
// until we have none.
while (words.length > 0) {
const tagCode = words[0].toString();
tagName = TAGNAMES[tagCode] || 'unknown_tag';
parser = TAGPARSERS[tagCode] || getUnknownParser(tagCode);
words = words.slice(1);
tagLength = wordsToIntBE(words.slice(0, 2));
words = words.slice(2);
tagWords = words.slice(0, tagLength);
words = words.slice(tagLength);
sections.push({
name: tagName,
tag: letters[0],
letters: letters.slice(0, 1 + 2 + tagLength),
value: parser(tagWords) // see: parsers for more comments
});
letters = letters.slice(1 + 2 + tagLength);
}
// signature
sections.push({
name: 'signature',
letters: letters.slice(0, 104),
value: hex.encode(bech32.fromWordsUnsafe(sigWords))
});
letters = letters.slice(104);
// checksum
sections.push({
name: 'checksum',
letters: letters
});
let result = {
paymentRequest,
sections,
get expiry() {
let exp = sections.find(s => s.name === 'expiry');
if (exp) return getValue('timestamp') + exp.value
},
get route_hints() {
return sections.filter(s => s.name === 'route_hint').map(s => s.value)
}
};
for (let name in TAGCODES) {
if (name === 'route_hint') {
// route hints can be multiple, so this won't work for them
continue
}
Object.defineProperty(result, name, {
get() {
return getValue(name)
}
});
}
return result
function getValue(name) {
let section = sections.find(s => s.name === name);
return section ? section.value : undefined
}
}
bolt11 = {
decode,
hrpToMillisat
};
return bolt11;
}
var bolt11Exports = requireBolt11();
// from https://stackoverflow.com/a/50868276
const fromHexString = (hexString) => Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
const decodeInvoice = (paymentRequest) => {
if (!paymentRequest)
return null;
try {
const decoded = bolt11Exports.decode(paymentRequest);
if (!decoded || !decoded.sections)
return null;
const hashTag = decoded.sections.find((value) => value.name === "payment_hash");
if (hashTag?.name !== "payment_hash" || !hashTag.value)
return null;
const paymentHash = hashTag.value;
let satoshi = 0;
let millisatoshi = 0;
let amountRaw = "0";
const amountTag = decoded.sections.find((value) => value.name === "amount");
if (amountTag?.name === "amount" && amountTag.value) {
amountRaw = amountTag.value;
millisatoshi = parseInt(amountTag.value);
satoshi = parseInt(amountTag.value) / 1000; // millisats
}
const timestampTag = decoded.sections.find((value) => value.name === "timestamp");
if (timestampTag?.name !== "timestamp" || !timestampTag.value)
return null;
const timestamp = timestampTag.value;
let expiry;
const expiryTag = decoded.sections.find((value) => value.name === "expiry");
if (expiryTag?.name === "expiry") {
expiry = expiryTag.value;
}
const descriptionTag = decoded.sections.find((value) => value.name === "description");
const description = descriptionTag?.name === "description"
? descriptionTag?.value
: undefined;
return {
paymentHash,
satoshi,
millisatoshi,
amountRaw,
timestamp,
expiry,
description,
};
}
catch {
return null;
}
};
function validatePreimage(preimage, paymentHash) {
try {
if (!/^[0-9a-fA-F]{64}$/.test(preimage))
return false;
if (!/^[0-9a-fA-F]{64}$/.test(paymentHash))
return false;
const preimageHash = bytesToHex(sha256(fromHexString(preimage)));
return paymentHash === preimageHash;
}
catch {
return false;
}
}
class Invoice {
constructor(args) {
this.paymentRequest = args.pr;
if (!this.paymentRequest) {
throw new Error("Invalid payment request");
}
const decodedInvoice = decodeInvoice(this.paymentRequest);
if (!decodedInvoice) {
throw new Error("Failed to decode payment request");
}
this.paymentHash = decodedInvoice.paymentHash;
this.satoshi = decodedInvoice.satoshi;
this.millisatoshi = decodedInvoice.millisatoshi;
this.amountRaw = decodedInvoice.amountRaw;
this.timestamp = decodedInvoice.timestamp;
this.expiry = decodedInvoice.expiry;
this.createdDate = new Date(this.timestamp * 1000);
this.expiryDate = this.expiry
? new Date((this.timestamp + this.expiry) * 1000)
: undefined;
this.description = decodedInvoice.description ?? null;
this.verify = args.verify ?? null;
this.preimage = args.preimage ?? null;
this.successAction = args.successAction ?? null;
}
async isPaid() {
if (this.preimage)
return this.validatePreimage(this.preimage);
else if (this.verify) {
return await this.verifyPayment();
}
else {
throw new Error("Could not verify payment");
}
}
validatePreimage(preimage) {
if (!preimage || !this.paymentHash)
return false;
return validatePreimage(preimage, this.paymentHash);
}
async verifyPayment() {
try {
if (!this.verify) {
throw new Error("LNURL verify not available");
}
const response = await fetch(this.verify);
if (!response.ok) {
throw new Error(`Verification request failed: ${response.status} ${response.statusText}`);
}
const json = await response.json();
if (json.preimage) {
this.preimage = json.preimage;
}
return json.settled;
}
catch (error) {
console.error("Failed to check LNURL-verify", error);
return false;
}
}
hasExpired() {
const { expiryDate } = this;
if (expiryDate) {
return expiryDate.getTime() < Date.now();
}
return false;
}
}
/** Apply a previously-obtained credential to the outgoing request headers. */
const applyCredentials = (headers, credentials) => {
headers.set(credentials.header, credentials.value);
};
/** Attach payment metadata to a response and return it (typed). */
const attachPayment = (response, payment) => {
if (payment) {
response.payment = payment;
}
return response;
};
/** Payment metadata describing a request authorized with a reused credential. */
const reusedCredentialPayment = (credentials) => credentials ? { paid: false, amount: 0, credentials } : undefined;
/** Satoshi amount of a BOLT11 invoice (0 when it cannot be decoded). */
const getInvoiceAmount = (invoice) => {
try {
return new Invoice({ pr: invoice }).satoshi;
}
catch (_) {
return 0;
}
};
/**
* Parse a `WWW-Authenticate: Payment …` header produced by a

@@ -141,4 +1423,12 @@ * draft-lightning-charge-00 server. Expected format:

const credential = buildMppCredential(challenge, invResp.preimage);
headers.set("Authorization", `Payment ${credential}`);
return fetch(url, fetchArgs);
const value = `Payment ${credential}`;
headers.set("Authorization", value);
const response = await fetch(url, fetchArgs);
return attachPayment(response, {
paid: true,
amount: getInvoiceAmount(invoice),
feesPaid: invResp.fees_paid,
preimage: invResp.preimage,
credentials: { header: "Authorization", value },
});
};

@@ -154,5 +1444,7 @@ /**

*
* Note: lightning-charge uses consume-once challenge semantics – each
* challenge embeds a fresh invoice, so paid credentials cannot be reused.
* The `store` option is accepted for API consistency but is not used.
* Pass a previous credential via `options.credentials` to reuse it (e.g. when
* polling); the credential is applied and the function NEVER pays again, even
* if the server still responds with a 402 (that response is returned as-is).
* Note: lightning-charge typically uses consume-once challenge semantics, so a
* reused credential is only accepted by servers that explicitly support it.
*/

@@ -171,2 +1463,11 @@ const fetchWithMpp = async (url, fetchArgs, options) => {

fetchArgs.headers = headers;
// If the caller supplied a credential, we MUST use it and never pay again β€”
// even if the server still responds with a 402. Re-paying here is the exact
// double-charge this API exists to prevent; the caller decides what to do
// with a rejected credential (retry after settlement, top up, etc.).
if (options.credentials) {
applyCredentials(headers, options.credentials);
const reusedResp = await fetch(url, fetchArgs);
return attachPayment(reusedResp, reusedCredentialPayment(options.credentials));
}
const initResp = await fetch(url, fetchArgs);

@@ -173,0 +1474,0 @@ const wwwAuthHeader = initResp.headers.get("www-authenticate");

+46
-15
'use strict';
const buildX402PaymentSignature = (scheme, network, invoice, requirements) => {
const json = JSON.stringify({
x402Version: 2,
scheme,
network,
payload: { invoice },
accepted: requirements,
});
// btoa only handles latin1; encode via UTF-8 to be safe
return btoa(unescape(encodeURIComponent(json)));
};
function bytes(b, ...lengths) {

@@ -1274,2 +1262,28 @@ if (!(b instanceof Uint8Array))

/** Apply a previously-obtained credential to the outgoing request headers. */
const applyCredentials = (headers, credentials) => {
headers.set(credentials.header, credentials.value);
};
/** Attach payment metadata to a response and return it (typed). */
const attachPayment = (response, payment) => {
if (payment) {
response.payment = payment;
}
return response;
};
/** Payment metadata describing a request authorized with a reused credential. */
const reusedCredentialPayment = (credentials) => credentials ? { paid: false, amount: 0, credentials } : undefined;
const buildX402PaymentSignature = (scheme, network, invoice, requirements) => {
const json = JSON.stringify({
x402Version: 2,
scheme,
network,
payload: { invoice },
accepted: requirements,
});
// btoa only handles latin1; encode via UTF-8 to be safe
return btoa(unescape(encodeURIComponent(json)));
};
const decodeX402Header = (x402Header) => {

@@ -1321,5 +1335,13 @@ let parsed;

}
await wallet.payInvoice({ invoice: invoice.paymentRequest });
headers.set("payment-signature", buildX402PaymentSignature(requirements.scheme, requirements.network, invoice.paymentRequest, requirements));
return fetch(url, fetchArgs);
const invResp = await wallet.payInvoice({ invoice: invoice.paymentRequest });
const value = buildX402PaymentSignature(requirements.scheme, requirements.network, invoice.paymentRequest, requirements);
headers.set("payment-signature", value);
const response = await fetch(url, fetchArgs);
return attachPayment(response, {
paid: true,
amount: invoice.satoshi,
feesPaid: invResp.fees_paid,
preimage: invResp.preimage,
credentials: { header: "payment-signature", value },
});
};

@@ -1335,2 +1357,11 @@ const fetchWithX402 = async (url, fetchArgs, options) => {

fetchArgs.headers = headers;
// If the caller supplied a credential, we MUST use it and never pay again β€”
// even if the server still responds with a 402. Re-paying here is the exact
// double-charge this API exists to prevent; the caller decides what to do
// with a rejected credential (retry after settlement, top up, etc.).
if (options.credentials) {
applyCredentials(headers, options.credentials);
const reusedResp = await fetch(url, fetchArgs);
return attachPayment(reusedResp, reusedCredentialPayment(options.credentials));
}
const initResp = await fetch(url, fetchArgs);

@@ -1337,0 +1368,0 @@ const header = initResp.headers.get("PAYMENT-REQUIRED");

@@ -0,2 +1,1284 @@

function bytes(b, ...lengths) {
if (!(b instanceof Uint8Array))
throw new Error('Expected Uint8Array');
if (lengths.length > 0 && !lengths.includes(b.length))
throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`);
}
function exists(instance, checkFinished = true) {
if (instance.destroyed)
throw new Error('Hash instance has been destroyed');
if (checkFinished && instance.finished)
throw new Error('Hash#digest() has already been called');
}
function output(out, instance) {
bytes(out);
const min = instance.outputLen;
if (out.length < min) {
throw new Error(`digestInto() expects output buffer of length at least ${min}`);
}
}
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
// node.js versions earlier than v19 don't declare it in global scope.
// For node.js, package.json#exports field mapping rewrites import
// from `crypto` to `cryptoNode`, which imports native module.
// Makes the utils un-importable in browsers without a bundler.
// Once node.js 18 is deprecated, we can just drop the import.
const u8a = (a) => a instanceof Uint8Array;
// Cast array to view
const createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
// The rotate right (circular right shift) operation for uint32
const rotr = (word, shift) => (word << (32 - shift)) | (word >>> shift);
// big-endian hardware is rare. Just in case someone still decides to run hashes:
// early-throw an error because we don't support BE yet.
const isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44;
if (!isLE)
throw new Error('Non little-endian hardware is not supported');
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
/**
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
*/
function bytesToHex(bytes) {
if (!u8a(bytes))
throw new Error('Uint8Array expected');
// pre-caching improves the speed 6x
let hex = '';
for (let i = 0; i < bytes.length; i++) {
hex += hexes[bytes[i]];
}
return hex;
}
/**
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
*/
function utf8ToBytes(str) {
if (typeof str !== 'string')
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
}
/**
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
* Warning: when Uint8Array is passed, it would NOT get copied.
* Keep in mind for future mutable operations.
*/
function toBytes(data) {
if (typeof data === 'string')
data = utf8ToBytes(data);
if (!u8a(data))
throw new Error(`expected Uint8Array, got ${typeof data}`);
return data;
}
// For runtime check if class implements interface
class Hash {
// Safe version that clones internal state
clone() {
return this._cloneInto();
}
}
function wrapConstructor(hashCons) {
const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
const tmp = hashCons();
hashC.outputLen = tmp.outputLen;
hashC.blockLen = tmp.blockLen;
hashC.create = () => hashCons();
return hashC;
}
// Polyfill for Safari 14
function setBigUint64(view, byteOffset, value, isLE) {
if (typeof view.setBigUint64 === 'function')
return view.setBigUint64(byteOffset, value, isLE);
const _32n = BigInt(32);
const _u32_max = BigInt(0xffffffff);
const wh = Number((value >> _32n) & _u32_max);
const wl = Number(value & _u32_max);
const h = isLE ? 4 : 0;
const l = isLE ? 0 : 4;
view.setUint32(byteOffset + h, wh, isLE);
view.setUint32(byteOffset + l, wl, isLE);
}
// Base SHA2 class (RFC 6234)
class SHA2 extends Hash {
constructor(blockLen, outputLen, padOffset, isLE) {
super();
this.blockLen = blockLen;
this.outputLen = outputLen;
this.padOffset = padOffset;
this.isLE = isLE;
this.finished = false;
this.length = 0;
this.pos = 0;
this.destroyed = false;
this.buffer = new Uint8Array(blockLen);
this.view = createView(this.buffer);
}
update(data) {
exists(this);
const { view, buffer, blockLen } = this;
data = toBytes(data);
const len = data.length;
for (let pos = 0; pos < len;) {
const take = Math.min(blockLen - this.pos, len - pos);
// Fast path: we have at least one block in input, cast it to view and process
if (take === blockLen) {
const dataView = createView(data);
for (; blockLen <= len - pos; pos += blockLen)
this.process(dataView, pos);
continue;
}
buffer.set(data.subarray(pos, pos + take), this.pos);
this.pos += take;
pos += take;
if (this.pos === blockLen) {
this.process(view, 0);
this.pos = 0;
}
}
this.length += data.length;
this.roundClean();
return this;
}
digestInto(out) {
exists(this);
output(out, this);
this.finished = true;
// Padding
// We can avoid allocation of buffer for padding completely if it
// was previously not allocated here. But it won't change performance.
const { buffer, view, blockLen, isLE } = this;
let { pos } = this;
// append the bit '1' to the message
buffer[pos++] = 0b10000000;
this.buffer.subarray(pos).fill(0);
// we have less than padOffset left in buffer, so we cannot put length in current block, need process it and pad again
if (this.padOffset > blockLen - pos) {
this.process(view, 0);
pos = 0;
}
// Pad until full block byte with zeros
for (let i = pos; i < blockLen; i++)
buffer[i] = 0;
// Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that
// You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.
// So we just write lowest 64 bits of that value.
setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
this.process(view, 0);
const oview = createView(out);
const len = this.outputLen;
// NOTE: we do division by 4 later, which should be fused in single op with modulo by JIT
if (len % 4)
throw new Error('_sha2: outputLen should be aligned to 32bit');
const outLen = len / 4;
const state = this.get();
if (outLen > state.length)
throw new Error('_sha2: outputLen bigger than state');
for (let i = 0; i < outLen; i++)
oview.setUint32(4 * i, state[i], isLE);
}
digest() {
const { buffer, outputLen } = this;
this.digestInto(buffer);
const res = buffer.slice(0, outputLen);
this.destroy();
return res;
}
_cloneInto(to) {
to || (to = new this.constructor());
to.set(...this.get());
const { blockLen, buffer, length, finished, destroyed, pos } = this;
to.length = length;
to.pos = pos;
to.finished = finished;
to.destroyed = destroyed;
if (length % blockLen)
to.buffer.set(buffer);
return to;
}
}
// SHA2-256 need to try 2^128 hashes to execute birthday attack.
// BTC network is doing 2^67 hashes/sec as per early 2023.
// Choice: a ? b : c
const Chi = (a, b, c) => (a & b) ^ (~a & c);
// Majority function, true if any two inpust is true
const Maj = (a, b, c) => (a & b) ^ (a & c) ^ (b & c);
// Round constants:
// first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311)
// prettier-ignore
const SHA256_K = /* @__PURE__ */ new Uint32Array([
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
]);
// Initial state (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):
// prettier-ignore
const IV = /* @__PURE__ */ new Uint32Array([
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
]);
// Temporary buffer, not used to store anything between runs
// Named this way because it matches specification.
const SHA256_W = /* @__PURE__ */ new Uint32Array(64);
class SHA256 extends SHA2 {
constructor() {
super(64, 32, 8, false);
// We cannot use array here since array allows indexing by variable
// which means optimizer/compiler cannot use registers.
this.A = IV[0] | 0;
this.B = IV[1] | 0;
this.C = IV[2] | 0;
this.D = IV[3] | 0;
this.E = IV[4] | 0;
this.F = IV[5] | 0;
this.G = IV[6] | 0;
this.H = IV[7] | 0;
}
get() {
const { A, B, C, D, E, F, G, H } = this;
return [A, B, C, D, E, F, G, H];
}
// prettier-ignore
set(A, B, C, D, E, F, G, H) {
this.A = A | 0;
this.B = B | 0;
this.C = C | 0;
this.D = D | 0;
this.E = E | 0;
this.F = F | 0;
this.G = G | 0;
this.H = H | 0;
}
process(view, offset) {
// Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array
for (let i = 0; i < 16; i++, offset += 4)
SHA256_W[i] = view.getUint32(offset, false);
for (let i = 16; i < 64; i++) {
const W15 = SHA256_W[i - 15];
const W2 = SHA256_W[i - 2];
const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ (W15 >>> 3);
const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ (W2 >>> 10);
SHA256_W[i] = (s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16]) | 0;
}
// Compression function main loop, 64 rounds
let { A, B, C, D, E, F, G, H } = this;
for (let i = 0; i < 64; i++) {
const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0;
const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
const T2 = (sigma0 + Maj(A, B, C)) | 0;
H = G;
G = F;
F = E;
E = (D + T1) | 0;
D = C;
C = B;
B = A;
A = (T1 + T2) | 0;
}
// Add the compressed chunk to the current hash value
A = (A + this.A) | 0;
B = (B + this.B) | 0;
C = (C + this.C) | 0;
D = (D + this.D) | 0;
E = (E + this.E) | 0;
F = (F + this.F) | 0;
G = (G + this.G) | 0;
H = (H + this.H) | 0;
this.set(A, B, C, D, E, F, G, H);
}
roundClean() {
SHA256_W.fill(0);
}
destroy() {
this.set(0, 0, 0, 0, 0, 0, 0, 0);
this.buffer.fill(0);
}
}
/**
* SHA2-256 hash function
* @param message - data that would be hashed
*/
const sha256 = /* @__PURE__ */ wrapConstructor(() => new SHA256());
var lib = {};
var hasRequiredLib;
function requireLib () {
if (hasRequiredLib) return lib;
hasRequiredLib = 1;
(function (exports) {
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
Object.defineProperty(exports, "__esModule", { value: true });
exports.bytes = exports.stringToBytes = exports.str = exports.bytesToString = exports.hex = exports.utf8 = exports.bech32m = exports.bech32 = exports.base58check = exports.base58xmr = exports.base58xrp = exports.base58flickr = exports.base58 = exports.base64url = exports.base64 = exports.base32crockford = exports.base32hex = exports.base32 = exports.base16 = exports.utils = exports.assertNumber = void 0;
function assertNumber(n) {
if (!Number.isSafeInteger(n))
throw new Error(`Wrong integer: ${n}`);
}
exports.assertNumber = assertNumber;
function chain(...args) {
const wrap = (a, b) => (c) => a(b(c));
const encode = Array.from(args)
.reverse()
.reduce((acc, i) => (acc ? wrap(acc, i.encode) : i.encode), undefined);
const decode = args.reduce((acc, i) => (acc ? wrap(acc, i.decode) : i.decode), undefined);
return { encode, decode };
}
function alphabet(alphabet) {
return {
encode: (digits) => {
if (!Array.isArray(digits) || (digits.length && typeof digits[0] !== 'number'))
throw new Error('alphabet.encode input should be an array of numbers');
return digits.map((i) => {
assertNumber(i);
if (i < 0 || i >= alphabet.length)
throw new Error(`Digit index outside alphabet: ${i} (alphabet: ${alphabet.length})`);
return alphabet[i];
});
},
decode: (input) => {
if (!Array.isArray(input) || (input.length && typeof input[0] !== 'string'))
throw new Error('alphabet.decode input should be array of strings');
return input.map((letter) => {
if (typeof letter !== 'string')
throw new Error(`alphabet.decode: not string element=${letter}`);
const index = alphabet.indexOf(letter);
if (index === -1)
throw new Error(`Unknown letter: "${letter}". Allowed: ${alphabet}`);
return index;
});
},
};
}
function join(separator = '') {
if (typeof separator !== 'string')
throw new Error('join separator should be string');
return {
encode: (from) => {
if (!Array.isArray(from) || (from.length && typeof from[0] !== 'string'))
throw new Error('join.encode input should be array of strings');
for (let i of from)
if (typeof i !== 'string')
throw new Error(`join.encode: non-string input=${i}`);
return from.join(separator);
},
decode: (to) => {
if (typeof to !== 'string')
throw new Error('join.decode input should be string');
return to.split(separator);
},
};
}
function padding(bits, chr = '=') {
assertNumber(bits);
if (typeof chr !== 'string')
throw new Error('padding chr should be string');
return {
encode(data) {
if (!Array.isArray(data) || (data.length && typeof data[0] !== 'string'))
throw new Error('padding.encode input should be array of strings');
for (let i of data)
if (typeof i !== 'string')
throw new Error(`padding.encode: non-string input=${i}`);
while ((data.length * bits) % 8)
data.push(chr);
return data;
},
decode(input) {
if (!Array.isArray(input) || (input.length && typeof input[0] !== 'string'))
throw new Error('padding.encode input should be array of strings');
for (let i of input)
if (typeof i !== 'string')
throw new Error(`padding.decode: non-string input=${i}`);
let end = input.length;
if ((end * bits) % 8)
throw new Error('Invalid padding: string should have whole number of bytes');
for (; end > 0 && input[end - 1] === chr; end--) {
if (!(((end - 1) * bits) % 8))
throw new Error('Invalid padding: string has too much padding');
}
return input.slice(0, end);
},
};
}
function normalize(fn) {
if (typeof fn !== 'function')
throw new Error('normalize fn should be function');
return { encode: (from) => from, decode: (to) => fn(to) };
}
function convertRadix(data, from, to) {
if (from < 2)
throw new Error(`convertRadix: wrong from=${from}, base cannot be less than 2`);
if (to < 2)
throw new Error(`convertRadix: wrong to=${to}, base cannot be less than 2`);
if (!Array.isArray(data))
throw new Error('convertRadix: data should be array');
if (!data.length)
return [];
let pos = 0;
const res = [];
const digits = Array.from(data);
digits.forEach((d) => {
assertNumber(d);
if (d < 0 || d >= from)
throw new Error(`Wrong integer: ${d}`);
});
while (true) {
let carry = 0;
let done = true;
for (let i = pos; i < digits.length; i++) {
const digit = digits[i];
const digitBase = from * carry + digit;
if (!Number.isSafeInteger(digitBase) ||
(from * carry) / from !== carry ||
digitBase - digit !== from * carry) {
throw new Error('convertRadix: carry overflow');
}
carry = digitBase % to;
digits[i] = Math.floor(digitBase / to);
if (!Number.isSafeInteger(digits[i]) || digits[i] * to + carry !== digitBase)
throw new Error('convertRadix: carry overflow');
if (!done)
continue;
else if (!digits[i])
pos = i;
else
done = false;
}
res.push(carry);
if (done)
break;
}
for (let i = 0; i < data.length - 1 && data[i] === 0; i++)
res.push(0);
return res.reverse();
}
const gcd = (a, b) => (!b ? a : gcd(b, a % b));
const radix2carry = (from, to) => from + (to - gcd(from, to));
function convertRadix2(data, from, to, padding) {
if (!Array.isArray(data))
throw new Error('convertRadix2: data should be array');
if (from <= 0 || from > 32)
throw new Error(`convertRadix2: wrong from=${from}`);
if (to <= 0 || to > 32)
throw new Error(`convertRadix2: wrong to=${to}`);
if (radix2carry(from, to) > 32) {
throw new Error(`convertRadix2: carry overflow from=${from} to=${to} carryBits=${radix2carry(from, to)}`);
}
let carry = 0;
let pos = 0;
const mask = 2 ** to - 1;
const res = [];
for (const n of data) {
assertNumber(n);
if (n >= 2 ** from)
throw new Error(`convertRadix2: invalid data word=${n} from=${from}`);
carry = (carry << from) | n;
if (pos + from > 32)
throw new Error(`convertRadix2: carry overflow pos=${pos} from=${from}`);
pos += from;
for (; pos >= to; pos -= to)
res.push(((carry >> (pos - to)) & mask) >>> 0);
carry &= 2 ** pos - 1;
}
carry = (carry << (to - pos)) & mask;
if (!padding && pos >= from)
throw new Error('Excess padding');
if (!padding && carry)
throw new Error(`Non-zero padding: ${carry}`);
if (padding && pos > 0)
res.push(carry >>> 0);
return res;
}
function radix(num) {
assertNumber(num);
return {
encode: (bytes) => {
if (!(bytes instanceof Uint8Array))
throw new Error('radix.encode input should be Uint8Array');
return convertRadix(Array.from(bytes), 2 ** 8, num);
},
decode: (digits) => {
if (!Array.isArray(digits) || (digits.length && typeof digits[0] !== 'number'))
throw new Error('radix.decode input should be array of strings');
return Uint8Array.from(convertRadix(digits, num, 2 ** 8));
},
};
}
function radix2(bits, revPadding = false) {
assertNumber(bits);
if (bits <= 0 || bits > 32)
throw new Error('radix2: bits should be in (0..32]');
if (radix2carry(8, bits) > 32 || radix2carry(bits, 8) > 32)
throw new Error('radix2: carry overflow');
return {
encode: (bytes) => {
if (!(bytes instanceof Uint8Array))
throw new Error('radix2.encode input should be Uint8Array');
return convertRadix2(Array.from(bytes), 8, bits, !revPadding);
},
decode: (digits) => {
if (!Array.isArray(digits) || (digits.length && typeof digits[0] !== 'number'))
throw new Error('radix2.decode input should be array of strings');
return Uint8Array.from(convertRadix2(digits, bits, 8, revPadding));
},
};
}
function unsafeWrapper(fn) {
if (typeof fn !== 'function')
throw new Error('unsafeWrapper fn should be function');
return function (...args) {
try {
return fn.apply(null, args);
}
catch (e) { }
};
}
function checksum(len, fn) {
assertNumber(len);
if (typeof fn !== 'function')
throw new Error('checksum fn should be function');
return {
encode(data) {
if (!(data instanceof Uint8Array))
throw new Error('checksum.encode: input should be Uint8Array');
const checksum = fn(data).slice(0, len);
const res = new Uint8Array(data.length + len);
res.set(data);
res.set(checksum, data.length);
return res;
},
decode(data) {
if (!(data instanceof Uint8Array))
throw new Error('checksum.decode: input should be Uint8Array');
const payload = data.slice(0, -len);
const newChecksum = fn(payload).slice(0, len);
const oldChecksum = data.slice(-len);
for (let i = 0; i < len; i++)
if (newChecksum[i] !== oldChecksum[i])
throw new Error('Invalid checksum');
return payload;
},
};
}
exports.utils = { alphabet, chain, checksum, radix, radix2, join, padding };
exports.base16 = chain(radix2(4), alphabet('0123456789ABCDEF'), join(''));
exports.base32 = chain(radix2(5), alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'), padding(5), join(''));
exports.base32hex = chain(radix2(5), alphabet('0123456789ABCDEFGHIJKLMNOPQRSTUV'), padding(5), join(''));
exports.base32crockford = chain(radix2(5), alphabet('0123456789ABCDEFGHJKMNPQRSTVWXYZ'), join(''), normalize((s) => s.toUpperCase().replace(/O/g, '0').replace(/[IL]/g, '1')));
exports.base64 = chain(radix2(6), alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'), padding(6), join(''));
exports.base64url = chain(radix2(6), alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'), padding(6), join(''));
const genBase58 = (abc) => chain(radix(58), alphabet(abc), join(''));
exports.base58 = genBase58('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
exports.base58flickr = genBase58('123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ');
exports.base58xrp = genBase58('rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz');
const XMR_BLOCK_LEN = [0, 2, 3, 5, 6, 7, 9, 10, 11];
exports.base58xmr = {
encode(data) {
let res = '';
for (let i = 0; i < data.length; i += 8) {
const block = data.subarray(i, i + 8);
res += exports.base58.encode(block).padStart(XMR_BLOCK_LEN[block.length], '1');
}
return res;
},
decode(str) {
let res = [];
for (let i = 0; i < str.length; i += 11) {
const slice = str.slice(i, i + 11);
const blockLen = XMR_BLOCK_LEN.indexOf(slice.length);
const block = exports.base58.decode(slice);
for (let j = 0; j < block.length - blockLen; j++) {
if (block[j] !== 0)
throw new Error('base58xmr: wrong padding');
}
res = res.concat(Array.from(block.slice(block.length - blockLen)));
}
return Uint8Array.from(res);
},
};
const base58check = (sha256) => chain(checksum(4, (data) => sha256(sha256(data))), exports.base58);
exports.base58check = base58check;
const BECH_ALPHABET = chain(alphabet('qpzry9x8gf2tvdw0s3jn54khce6mua7l'), join(''));
const POLYMOD_GENERATORS = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
function bech32Polymod(pre) {
const b = pre >> 25;
let chk = (pre & 0x1ffffff) << 5;
for (let i = 0; i < POLYMOD_GENERATORS.length; i++) {
if (((b >> i) & 1) === 1)
chk ^= POLYMOD_GENERATORS[i];
}
return chk;
}
function bechChecksum(prefix, words, encodingConst = 1) {
const len = prefix.length;
let chk = 1;
for (let i = 0; i < len; i++) {
const c = prefix.charCodeAt(i);
if (c < 33 || c > 126)
throw new Error(`Invalid prefix (${prefix})`);
chk = bech32Polymod(chk) ^ (c >> 5);
}
chk = bech32Polymod(chk);
for (let i = 0; i < len; i++)
chk = bech32Polymod(chk) ^ (prefix.charCodeAt(i) & 0x1f);
for (let v of words)
chk = bech32Polymod(chk) ^ v;
for (let i = 0; i < 6; i++)
chk = bech32Polymod(chk);
chk ^= encodingConst;
return BECH_ALPHABET.encode(convertRadix2([chk % 2 ** 30], 30, 5, false));
}
function genBech32(encoding) {
const ENCODING_CONST = encoding === 'bech32' ? 1 : 0x2bc830a3;
const _words = radix2(5);
const fromWords = _words.decode;
const toWords = _words.encode;
const fromWordsUnsafe = unsafeWrapper(fromWords);
function encode(prefix, words, limit = 90) {
if (typeof prefix !== 'string')
throw new Error(`bech32.encode prefix should be string, not ${typeof prefix}`);
if (!Array.isArray(words) || (words.length && typeof words[0] !== 'number'))
throw new Error(`bech32.encode words should be array of numbers, not ${typeof words}`);
const actualLength = prefix.length + 7 + words.length;
if (limit !== false && actualLength > limit)
throw new TypeError(`Length ${actualLength} exceeds limit ${limit}`);
prefix = prefix.toLowerCase();
return `${prefix}1${BECH_ALPHABET.encode(words)}${bechChecksum(prefix, words, ENCODING_CONST)}`;
}
function decode(str, limit = 90) {
if (typeof str !== 'string')
throw new Error(`bech32.decode input should be string, not ${typeof str}`);
if (str.length < 8 || (limit !== false && str.length > limit))
throw new TypeError(`Wrong string length: ${str.length} (${str}). Expected (8..${limit})`);
const lowered = str.toLowerCase();
if (str !== lowered && str !== str.toUpperCase())
throw new Error(`String must be lowercase or uppercase`);
str = lowered;
const sepIndex = str.lastIndexOf('1');
if (sepIndex === 0 || sepIndex === -1)
throw new Error(`Letter "1" must be present between prefix and data only`);
const prefix = str.slice(0, sepIndex);
const _words = str.slice(sepIndex + 1);
if (_words.length < 6)
throw new Error('Data must be at least 6 characters long');
const words = BECH_ALPHABET.decode(_words).slice(0, -6);
const sum = bechChecksum(prefix, words, ENCODING_CONST);
if (!_words.endsWith(sum))
throw new Error(`Invalid checksum in ${str}: expected "${sum}"`);
return { prefix, words };
}
const decodeUnsafe = unsafeWrapper(decode);
function decodeToBytes(str) {
const { prefix, words } = decode(str, false);
return { prefix, words, bytes: fromWords(words) };
}
return { encode, decode, decodeToBytes, decodeUnsafe, fromWords, fromWordsUnsafe, toWords };
}
exports.bech32 = genBech32('bech32');
exports.bech32m = genBech32('bech32m');
exports.utf8 = {
encode: (data) => new TextDecoder().decode(data),
decode: (str) => new TextEncoder().encode(str),
};
exports.hex = chain(radix2(4), alphabet('0123456789abcdef'), join(''), normalize((s) => {
if (typeof s !== 'string' || s.length % 2)
throw new TypeError(`hex.decode: expected string, got ${typeof s} with length ${s.length}`);
return s.toLowerCase();
}));
const CODERS = {
utf8: exports.utf8, hex: exports.hex, base16: exports.base16, base32: exports.base32, base64: exports.base64, base64url: exports.base64url, base58: exports.base58, base58xmr: exports.base58xmr
};
const coderTypeError = `Invalid encoding type. Available types: ${Object.keys(CODERS).join(', ')}`;
const bytesToString = (type, bytes) => {
if (typeof type !== 'string' || !CODERS.hasOwnProperty(type))
throw new TypeError(coderTypeError);
if (!(bytes instanceof Uint8Array))
throw new TypeError('bytesToString() expects Uint8Array');
return CODERS[type].encode(bytes);
};
exports.bytesToString = bytesToString;
exports.str = exports.bytesToString;
const stringToBytes = (type, str) => {
if (!CODERS.hasOwnProperty(type))
throw new TypeError(coderTypeError);
if (typeof str !== 'string')
throw new TypeError('stringToBytes() expects string');
return CODERS[type].decode(str);
};
exports.stringToBytes = stringToBytes;
exports.bytes = exports.stringToBytes;
} (lib));
return lib;
}
var bolt11;
var hasRequiredBolt11;
function requireBolt11 () {
if (hasRequiredBolt11) return bolt11;
hasRequiredBolt11 = 1;
const {bech32, hex, utf8} = requireLib();
// defaults for encode; default timestamp is current time at call
const DEFAULTNETWORK = {
// default network is bitcoin
bech32: 'bc',
pubKeyHash: 0x00,
scriptHash: 0x05,
validWitnessVersions: [0]
};
const TESTNETWORK = {
bech32: 'tb',
pubKeyHash: 0x6f,
scriptHash: 0xc4,
validWitnessVersions: [0]
};
const SIGNETNETWORK = {
bech32: 'tbs',
pubKeyHash: 0x6f,
scriptHash: 0xc4,
validWitnessVersions: [0]
};
const REGTESTNETWORK = {
bech32: 'bcrt',
pubKeyHash: 0x6f,
scriptHash: 0xc4,
validWitnessVersions: [0]
};
const SIMNETWORK = {
bech32: 'sb',
pubKeyHash: 0x3f,
scriptHash: 0x7b,
validWitnessVersions: [0]
};
const FEATUREBIT_ORDER = [
'option_data_loss_protect',
'initial_routing_sync',
'option_upfront_shutdown_script',
'gossip_queries',
'var_onion_optin',
'gossip_queries_ex',
'option_static_remotekey',
'payment_secret',
'basic_mpp',
'option_support_large_channel'
];
const DIVISORS = {
m: BigInt(1e3),
u: BigInt(1e6),
n: BigInt(1e9),
p: BigInt(1e12)
};
const MAX_MILLISATS = BigInt('2100000000000000000');
const MILLISATS_PER_BTC = BigInt(1e11);
const TAGCODES = {
payment_hash: 1,
payment_secret: 16,
description: 13,
payee: 19,
description_hash: 23, // commit to longer descriptions (used by lnurl-pay)
expiry: 6, // default: 3600 (1 hour)
min_final_cltv_expiry: 24, // default: 9
fallback_address: 9,
route_hint: 3, // for extra routing info (private etc.)
feature_bits: 5,
metadata: 27
};
// reverse the keys and values of TAGCODES and insert into TAGNAMES
const TAGNAMES = {};
for (let i = 0, keys = Object.keys(TAGCODES); i < keys.length; i++) {
const currentName = keys[i];
const currentCode = TAGCODES[keys[i]].toString();
TAGNAMES[currentCode] = currentName;
}
const TAGPARSERS = {
1: words => hex.encode(bech32.fromWordsUnsafe(words)), // 256 bits
16: words => hex.encode(bech32.fromWordsUnsafe(words)), // 256 bits
13: words => utf8.encode(bech32.fromWordsUnsafe(words)), // string variable length
19: words => hex.encode(bech32.fromWordsUnsafe(words)), // 264 bits
23: words => hex.encode(bech32.fromWordsUnsafe(words)), // 256 bits
27: words => hex.encode(bech32.fromWordsUnsafe(words)), // variable
6: wordsToIntBE, // default: 3600 (1 hour)
24: wordsToIntBE, // default: 9
3: routingInfoParser, // for extra routing info (private etc.)
5: featureBitsParser // keep feature bits as array of 5 bit words
};
function getUnknownParser(tagCode) {
return words => ({
tagCode: parseInt(tagCode),
words: bech32.encode('unknown', words, Number.MAX_SAFE_INTEGER)
})
}
function wordsToIntBE(words) {
return words.reverse().reduce((total, item, index) => {
return total + item * Math.pow(32, index)
}, 0)
}
// first convert from words to buffer, trimming padding where necessary
// parse in 51 byte chunks. See encoder for details.
function routingInfoParser(words) {
const routes = [];
let pubkey,
shortChannelId,
feeBaseMSats,
feeProportionalMillionths,
cltvExpiryDelta;
let routesBuffer = bech32.fromWordsUnsafe(words);
while (routesBuffer.length > 0) {
pubkey = hex.encode(routesBuffer.slice(0, 33)); // 33 bytes
shortChannelId = hex.encode(routesBuffer.slice(33, 41)); // 8 bytes
feeBaseMSats = parseInt(hex.encode(routesBuffer.slice(41, 45)), 16); // 4 bytes
feeProportionalMillionths = parseInt(
hex.encode(routesBuffer.slice(45, 49)),
16
); // 4 bytes
cltvExpiryDelta = parseInt(hex.encode(routesBuffer.slice(49, 51)), 16); // 2 bytes
routesBuffer = routesBuffer.slice(51);
routes.push({
pubkey,
short_channel_id: shortChannelId,
fee_base_msat: feeBaseMSats,
fee_proportional_millionths: feeProportionalMillionths,
cltv_expiry_delta: cltvExpiryDelta
});
}
return routes
}
function featureBitsParser(words) {
const bools = words
.slice()
.reverse()
.map(word => [
!!(word & 0b1),
!!(word & 0b10),
!!(word & 0b100),
!!(word & 0b1000),
!!(word & 0b10000)
])
.reduce((finalArr, itemArr) => finalArr.concat(itemArr), []);
while (bools.length < FEATUREBIT_ORDER.length * 2) {
bools.push(false);
}
const featureBits = {};
FEATUREBIT_ORDER.forEach((featureName, index) => {
let status;
if (bools[index * 2]) {
status = 'required';
} else if (bools[index * 2 + 1]) {
status = 'supported';
} else {
status = 'unsupported';
}
featureBits[featureName] = status;
});
const extraBits = bools.slice(FEATUREBIT_ORDER.length * 2);
featureBits.extra_bits = {
start_bit: FEATUREBIT_ORDER.length * 2,
bits: extraBits,
has_required: extraBits.reduce(
(result, bit, index) =>
index % 2 !== 0 ? result || false : result || bit,
false
)
};
return featureBits
}
function hrpToMillisat(hrpString, outputString) {
let divisor, value;
if (hrpString.slice(-1).match(/^[munp]$/)) {
divisor = hrpString.slice(-1);
value = hrpString.slice(0, -1);
} else if (hrpString.slice(-1).match(/^[^munp0-9]$/)) {
throw new Error('Not a valid multiplier for the amount')
} else {
value = hrpString;
}
if (!value.match(/^\d+$/))
throw new Error('Not a valid human readable amount')
const valueBN = BigInt(value);
const millisatoshisBN = divisor
? (valueBN * MILLISATS_PER_BTC) / DIVISORS[divisor]
: valueBN * MILLISATS_PER_BTC;
if (
(divisor === 'p' && !(valueBN % BigInt(10) === BigInt(0))) ||
millisatoshisBN > MAX_MILLISATS
) {
throw new Error('Amount is outside of valid range')
}
return outputString ? millisatoshisBN.toString() : millisatoshisBN
}
// decode will only have extra comments that aren't covered in encode comments.
// also if anything is hard to read I'll comment.
function decode(paymentRequest, network) {
if (typeof paymentRequest !== 'string')
throw new Error('Lightning Payment Request must be string')
if (paymentRequest.slice(0, 2).toLowerCase() !== 'ln')
throw new Error('Not a proper lightning payment request')
const sections = [];
const decoded = bech32.decode(paymentRequest, Number.MAX_SAFE_INTEGER);
paymentRequest = paymentRequest.toLowerCase();
const prefix = decoded.prefix;
let words = decoded.words;
let letters = paymentRequest.slice(prefix.length + 1);
let sigWords = words.slice(-104);
words = words.slice(0, -104);
// Without reverse lookups, can't say that the multipier at the end must
// have a number before it, so instead we parse, and if the second group
// doesn't have anything, there's a good chance the last letter of the
// coin type got captured by the third group, so just re-regex without
// the number.
let prefixMatches = prefix.match(/^ln(\S+?)(\d*)([a-zA-Z]?)$/);
if (prefixMatches && !prefixMatches[2])
prefixMatches = prefix.match(/^ln(\S+)$/);
if (!prefixMatches) {
throw new Error('Not a proper lightning payment request')
}
// "ln" section
sections.push({
name: 'lightning_network',
letters: 'ln'
});
// "bc" section
const bech32Prefix = prefixMatches[1];
let coinNetwork;
if (!network) {
switch (bech32Prefix) {
case DEFAULTNETWORK.bech32:
coinNetwork = DEFAULTNETWORK;
break
case TESTNETWORK.bech32:
coinNetwork = TESTNETWORK;
break
case SIGNETNETWORK.bech32:
coinNetwork = SIGNETNETWORK;
break
case REGTESTNETWORK.bech32:
coinNetwork = REGTESTNETWORK;
break
case SIMNETWORK.bech32:
coinNetwork = SIMNETWORK;
break
}
} else {
if (
network.bech32 === undefined ||
network.pubKeyHash === undefined ||
network.scriptHash === undefined ||
!Array.isArray(network.validWitnessVersions)
)
throw new Error('Invalid network')
coinNetwork = network;
}
if (!coinNetwork || coinNetwork.bech32 !== bech32Prefix) {
throw new Error('Unknown coin bech32 prefix')
}
sections.push({
name: 'coin_network',
letters: bech32Prefix,
value: coinNetwork
});
// amount section
const value = prefixMatches[2];
let millisatoshis;
if (value) {
const divisor = prefixMatches[3];
millisatoshis = hrpToMillisat(value + divisor, true);
sections.push({
name: 'amount',
letters: prefixMatches[2] + prefixMatches[3],
value: millisatoshis
});
} else {
millisatoshis = null;
}
// "1" separator
sections.push({
name: 'separator',
letters: '1'
});
// timestamp
const timestamp = wordsToIntBE(words.slice(0, 7));
words = words.slice(7); // trim off the left 7 words
sections.push({
name: 'timestamp',
letters: letters.slice(0, 7),
value: timestamp
});
letters = letters.slice(7);
let tagName, parser, tagLength, tagWords;
// we have no tag count to go on, so just keep hacking off words
// until we have none.
while (words.length > 0) {
const tagCode = words[0].toString();
tagName = TAGNAMES[tagCode] || 'unknown_tag';
parser = TAGPARSERS[tagCode] || getUnknownParser(tagCode);
words = words.slice(1);
tagLength = wordsToIntBE(words.slice(0, 2));
words = words.slice(2);
tagWords = words.slice(0, tagLength);
words = words.slice(tagLength);
sections.push({
name: tagName,
tag: letters[0],
letters: letters.slice(0, 1 + 2 + tagLength),
value: parser(tagWords) // see: parsers for more comments
});
letters = letters.slice(1 + 2 + tagLength);
}
// signature
sections.push({
name: 'signature',
letters: letters.slice(0, 104),
value: hex.encode(bech32.fromWordsUnsafe(sigWords))
});
letters = letters.slice(104);
// checksum
sections.push({
name: 'checksum',
letters: letters
});
let result = {
paymentRequest,
sections,
get expiry() {
let exp = sections.find(s => s.name === 'expiry');
if (exp) return getValue('timestamp') + exp.value
},
get route_hints() {
return sections.filter(s => s.name === 'route_hint').map(s => s.value)
}
};
for (let name in TAGCODES) {
if (name === 'route_hint') {
// route hints can be multiple, so this won't work for them
continue
}
Object.defineProperty(result, name, {
get() {
return getValue(name)
}
});
}
return result
function getValue(name) {
let section = sections.find(s => s.name === name);
return section ? section.value : undefined
}
}
bolt11 = {
decode,
hrpToMillisat
};
return bolt11;
}
var bolt11Exports = requireBolt11();
// from https://stackoverflow.com/a/50868276
const fromHexString = (hexString) => Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
const decodeInvoice = (paymentRequest) => {
if (!paymentRequest)
return null;
try {
const decoded = bolt11Exports.decode(paymentRequest);
if (!decoded || !decoded.sections)
return null;
const hashTag = decoded.sections.find((value) => value.name === "payment_hash");
if (hashTag?.name !== "payment_hash" || !hashTag.value)
return null;
const paymentHash = hashTag.value;
let satoshi = 0;
let millisatoshi = 0;
let amountRaw = "0";
const amountTag = decoded.sections.find((value) => value.name === "amount");
if (amountTag?.name === "amount" && amountTag.value) {
amountRaw = amountTag.value;
millisatoshi = parseInt(amountTag.value);
satoshi = parseInt(amountTag.value) / 1000; // millisats
}
const timestampTag = decoded.sections.find((value) => value.name === "timestamp");
if (timestampTag?.name !== "timestamp" || !timestampTag.value)
return null;
const timestamp = timestampTag.value;
let expiry;
const expiryTag = decoded.sections.find((value) => value.name === "expiry");
if (expiryTag?.name === "expiry") {
expiry = expiryTag.value;
}
const descriptionTag = decoded.sections.find((value) => value.name === "description");
const description = descriptionTag?.name === "description"
? descriptionTag?.value
: undefined;
return {
paymentHash,
satoshi,
millisatoshi,
amountRaw,
timestamp,
expiry,
description,
};
}
catch {
return null;
}
};
function validatePreimage(preimage, paymentHash) {
try {
if (!/^[0-9a-fA-F]{64}$/.test(preimage))
return false;
if (!/^[0-9a-fA-F]{64}$/.test(paymentHash))
return false;
const preimageHash = bytesToHex(sha256(fromHexString(preimage)));
return paymentHash === preimageHash;
}
catch {
return false;
}
}
class Invoice {
constructor(args) {
this.paymentRequest = args.pr;
if (!this.paymentRequest) {
throw new Error("Invalid payment request");
}
const decodedInvoice = decodeInvoice(this.paymentRequest);
if (!decodedInvoice) {
throw new Error("Failed to decode payment request");
}
this.paymentHash = decodedInvoice.paymentHash;
this.satoshi = decodedInvoice.satoshi;
this.millisatoshi = decodedInvoice.millisatoshi;
this.amountRaw = decodedInvoice.amountRaw;
this.timestamp = decodedInvoice.timestamp;
this.expiry = decodedInvoice.expiry;
this.createdDate = new Date(this.timestamp * 1000);
this.expiryDate = this.expiry
? new Date((this.timestamp + this.expiry) * 1000)
: undefined;
this.description = decodedInvoice.description ?? null;
this.verify = args.verify ?? null;
this.preimage = args.preimage ?? null;
this.successAction = args.successAction ?? null;
}
async isPaid() {
if (this.preimage)
return this.validatePreimage(this.preimage);
else if (this.verify) {
return await this.verifyPayment();
}
else {
throw new Error("Could not verify payment");
}
}
validatePreimage(preimage) {
if (!preimage || !this.paymentHash)
return false;
return validatePreimage(preimage, this.paymentHash);
}
async verifyPayment() {
try {
if (!this.verify) {
throw new Error("LNURL verify not available");
}
const response = await fetch(this.verify);
if (!response.ok) {
throw new Error(`Verification request failed: ${response.status} ${response.statusText}`);
}
const json = await response.json();
if (json.preimage) {
this.preimage = json.preimage;
}
return json.settled;
}
catch (error) {
console.error("Failed to check LNURL-verify", error);
return false;
}
}
hasExpired() {
const { expiryDate } = this;
if (expiryDate) {
return expiryDate.getTime() < Date.now();
}
return false;
}
}
/** Apply a previously-obtained credential to the outgoing request headers. */
const applyCredentials = (headers, credentials) => {
headers.set(credentials.header, credentials.value);
};
/** Attach payment metadata to a response and return it (typed). */
const attachPayment = (response, payment) => {
if (payment) {
response.payment = payment;
}
return response;
};
/** Payment metadata describing a request authorized with a reused credential. */
const reusedCredentialPayment = (credentials) => credentials ? { paid: false, amount: 0, credentials } : undefined;
/** Satoshi amount of a BOLT11 invoice (0 when it cannot be decoded). */
const getInvoiceAmount = (invoice) => {
try {
return new Invoice({ pr: invoice }).satoshi;
}
catch (_) {
return 0;
}
};
/**
* Client: parse "www-authenticate" header from server response

@@ -40,2 +1322,5 @@ * @param input

const invoice = details.invoice;
// Preserve the scheme the server challenged with (L402 or LSAT) so the
// retry's Authorization header matches what the server expects.
const scheme = /^\s*LSAT\b/i.test(l402Header) ? "LSAT" : "L402";
if (!token) {

@@ -48,4 +1333,12 @@ throw new Error("L402: missing token/macaroon in WWW-Authenticate header");

const invResp = await wallet.payInvoice({ invoice });
headers.set("Authorization", `L402 ${token}:${invResp.preimage}`);
return fetch(url, fetchArgs);
const value = `${scheme} ${token}:${invResp.preimage}`;
headers.set("Authorization", value);
const response = await fetch(url, fetchArgs);
return attachPayment(response, {
paid: true,
amount: getInvoiceAmount(invoice),
feesPaid: invResp.fees_paid,
preimage: invResp.preimage,
credentials: { header: "Authorization", value },
});
};

@@ -64,2 +1357,11 @@ const fetchWithL402 = async (url, fetchArgs, options) => {

fetchArgs.headers = headers;
// If the caller supplied a credential, we MUST use it and never pay again β€”
// even if the server still responds with a 402. Re-paying here is the exact
// double-charge this API exists to prevent; the caller decides what to do
// with a rejected credential (retry after settlement, top up, etc.).
if (options.credentials) {
applyCredentials(headers, options.credentials);
const reusedResp = await fetch(url, fetchArgs);
return attachPayment(reusedResp, reusedCredentialPayment(options.credentials));
}
const initResp = await fetch(url, fetchArgs);

@@ -66,0 +1368,0 @@ const header = initResp.headers.get("www-authenticate");

@@ -0,2 +1,1284 @@

function bytes(b, ...lengths) {
if (!(b instanceof Uint8Array))
throw new Error('Expected Uint8Array');
if (lengths.length > 0 && !lengths.includes(b.length))
throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`);
}
function exists(instance, checkFinished = true) {
if (instance.destroyed)
throw new Error('Hash instance has been destroyed');
if (checkFinished && instance.finished)
throw new Error('Hash#digest() has already been called');
}
function output(out, instance) {
bytes(out);
const min = instance.outputLen;
if (out.length < min) {
throw new Error(`digestInto() expects output buffer of length at least ${min}`);
}
}
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
// node.js versions earlier than v19 don't declare it in global scope.
// For node.js, package.json#exports field mapping rewrites import
// from `crypto` to `cryptoNode`, which imports native module.
// Makes the utils un-importable in browsers without a bundler.
// Once node.js 18 is deprecated, we can just drop the import.
const u8a = (a) => a instanceof Uint8Array;
// Cast array to view
const createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
// The rotate right (circular right shift) operation for uint32
const rotr = (word, shift) => (word << (32 - shift)) | (word >>> shift);
// big-endian hardware is rare. Just in case someone still decides to run hashes:
// early-throw an error because we don't support BE yet.
const isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44;
if (!isLE)
throw new Error('Non little-endian hardware is not supported');
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
/**
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
*/
function bytesToHex(bytes) {
if (!u8a(bytes))
throw new Error('Uint8Array expected');
// pre-caching improves the speed 6x
let hex = '';
for (let i = 0; i < bytes.length; i++) {
hex += hexes[bytes[i]];
}
return hex;
}
/**
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
*/
function utf8ToBytes(str) {
if (typeof str !== 'string')
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
}
/**
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
* Warning: when Uint8Array is passed, it would NOT get copied.
* Keep in mind for future mutable operations.
*/
function toBytes(data) {
if (typeof data === 'string')
data = utf8ToBytes(data);
if (!u8a(data))
throw new Error(`expected Uint8Array, got ${typeof data}`);
return data;
}
// For runtime check if class implements interface
class Hash {
// Safe version that clones internal state
clone() {
return this._cloneInto();
}
}
function wrapConstructor(hashCons) {
const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
const tmp = hashCons();
hashC.outputLen = tmp.outputLen;
hashC.blockLen = tmp.blockLen;
hashC.create = () => hashCons();
return hashC;
}
// Polyfill for Safari 14
function setBigUint64(view, byteOffset, value, isLE) {
if (typeof view.setBigUint64 === 'function')
return view.setBigUint64(byteOffset, value, isLE);
const _32n = BigInt(32);
const _u32_max = BigInt(0xffffffff);
const wh = Number((value >> _32n) & _u32_max);
const wl = Number(value & _u32_max);
const h = isLE ? 4 : 0;
const l = isLE ? 0 : 4;
view.setUint32(byteOffset + h, wh, isLE);
view.setUint32(byteOffset + l, wl, isLE);
}
// Base SHA2 class (RFC 6234)
class SHA2 extends Hash {
constructor(blockLen, outputLen, padOffset, isLE) {
super();
this.blockLen = blockLen;
this.outputLen = outputLen;
this.padOffset = padOffset;
this.isLE = isLE;
this.finished = false;
this.length = 0;
this.pos = 0;
this.destroyed = false;
this.buffer = new Uint8Array(blockLen);
this.view = createView(this.buffer);
}
update(data) {
exists(this);
const { view, buffer, blockLen } = this;
data = toBytes(data);
const len = data.length;
for (let pos = 0; pos < len;) {
const take = Math.min(blockLen - this.pos, len - pos);
// Fast path: we have at least one block in input, cast it to view and process
if (take === blockLen) {
const dataView = createView(data);
for (; blockLen <= len - pos; pos += blockLen)
this.process(dataView, pos);
continue;
}
buffer.set(data.subarray(pos, pos + take), this.pos);
this.pos += take;
pos += take;
if (this.pos === blockLen) {
this.process(view, 0);
this.pos = 0;
}
}
this.length += data.length;
this.roundClean();
return this;
}
digestInto(out) {
exists(this);
output(out, this);
this.finished = true;
// Padding
// We can avoid allocation of buffer for padding completely if it
// was previously not allocated here. But it won't change performance.
const { buffer, view, blockLen, isLE } = this;
let { pos } = this;
// append the bit '1' to the message
buffer[pos++] = 0b10000000;
this.buffer.subarray(pos).fill(0);
// we have less than padOffset left in buffer, so we cannot put length in current block, need process it and pad again
if (this.padOffset > blockLen - pos) {
this.process(view, 0);
pos = 0;
}
// Pad until full block byte with zeros
for (let i = pos; i < blockLen; i++)
buffer[i] = 0;
// Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that
// You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.
// So we just write lowest 64 bits of that value.
setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
this.process(view, 0);
const oview = createView(out);
const len = this.outputLen;
// NOTE: we do division by 4 later, which should be fused in single op with modulo by JIT
if (len % 4)
throw new Error('_sha2: outputLen should be aligned to 32bit');
const outLen = len / 4;
const state = this.get();
if (outLen > state.length)
throw new Error('_sha2: outputLen bigger than state');
for (let i = 0; i < outLen; i++)
oview.setUint32(4 * i, state[i], isLE);
}
digest() {
const { buffer, outputLen } = this;
this.digestInto(buffer);
const res = buffer.slice(0, outputLen);
this.destroy();
return res;
}
_cloneInto(to) {
to || (to = new this.constructor());
to.set(...this.get());
const { blockLen, buffer, length, finished, destroyed, pos } = this;
to.length = length;
to.pos = pos;
to.finished = finished;
to.destroyed = destroyed;
if (length % blockLen)
to.buffer.set(buffer);
return to;
}
}
// SHA2-256 need to try 2^128 hashes to execute birthday attack.
// BTC network is doing 2^67 hashes/sec as per early 2023.
// Choice: a ? b : c
const Chi = (a, b, c) => (a & b) ^ (~a & c);
// Majority function, true if any two inpust is true
const Maj = (a, b, c) => (a & b) ^ (a & c) ^ (b & c);
// Round constants:
// first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311)
// prettier-ignore
const SHA256_K = /* @__PURE__ */ new Uint32Array([
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
]);
// Initial state (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):
// prettier-ignore
const IV = /* @__PURE__ */ new Uint32Array([
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
]);
// Temporary buffer, not used to store anything between runs
// Named this way because it matches specification.
const SHA256_W = /* @__PURE__ */ new Uint32Array(64);
class SHA256 extends SHA2 {
constructor() {
super(64, 32, 8, false);
// We cannot use array here since array allows indexing by variable
// which means optimizer/compiler cannot use registers.
this.A = IV[0] | 0;
this.B = IV[1] | 0;
this.C = IV[2] | 0;
this.D = IV[3] | 0;
this.E = IV[4] | 0;
this.F = IV[5] | 0;
this.G = IV[6] | 0;
this.H = IV[7] | 0;
}
get() {
const { A, B, C, D, E, F, G, H } = this;
return [A, B, C, D, E, F, G, H];
}
// prettier-ignore
set(A, B, C, D, E, F, G, H) {
this.A = A | 0;
this.B = B | 0;
this.C = C | 0;
this.D = D | 0;
this.E = E | 0;
this.F = F | 0;
this.G = G | 0;
this.H = H | 0;
}
process(view, offset) {
// Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array
for (let i = 0; i < 16; i++, offset += 4)
SHA256_W[i] = view.getUint32(offset, false);
for (let i = 16; i < 64; i++) {
const W15 = SHA256_W[i - 15];
const W2 = SHA256_W[i - 2];
const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ (W15 >>> 3);
const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ (W2 >>> 10);
SHA256_W[i] = (s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16]) | 0;
}
// Compression function main loop, 64 rounds
let { A, B, C, D, E, F, G, H } = this;
for (let i = 0; i < 64; i++) {
const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0;
const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
const T2 = (sigma0 + Maj(A, B, C)) | 0;
H = G;
G = F;
F = E;
E = (D + T1) | 0;
D = C;
C = B;
B = A;
A = (T1 + T2) | 0;
}
// Add the compressed chunk to the current hash value
A = (A + this.A) | 0;
B = (B + this.B) | 0;
C = (C + this.C) | 0;
D = (D + this.D) | 0;
E = (E + this.E) | 0;
F = (F + this.F) | 0;
G = (G + this.G) | 0;
H = (H + this.H) | 0;
this.set(A, B, C, D, E, F, G, H);
}
roundClean() {
SHA256_W.fill(0);
}
destroy() {
this.set(0, 0, 0, 0, 0, 0, 0, 0);
this.buffer.fill(0);
}
}
/**
* SHA2-256 hash function
* @param message - data that would be hashed
*/
const sha256 = /* @__PURE__ */ wrapConstructor(() => new SHA256());
var lib = {};
var hasRequiredLib;
function requireLib () {
if (hasRequiredLib) return lib;
hasRequiredLib = 1;
(function (exports) {
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
Object.defineProperty(exports, "__esModule", { value: true });
exports.bytes = exports.stringToBytes = exports.str = exports.bytesToString = exports.hex = exports.utf8 = exports.bech32m = exports.bech32 = exports.base58check = exports.base58xmr = exports.base58xrp = exports.base58flickr = exports.base58 = exports.base64url = exports.base64 = exports.base32crockford = exports.base32hex = exports.base32 = exports.base16 = exports.utils = exports.assertNumber = void 0;
function assertNumber(n) {
if (!Number.isSafeInteger(n))
throw new Error(`Wrong integer: ${n}`);
}
exports.assertNumber = assertNumber;
function chain(...args) {
const wrap = (a, b) => (c) => a(b(c));
const encode = Array.from(args)
.reverse()
.reduce((acc, i) => (acc ? wrap(acc, i.encode) : i.encode), undefined);
const decode = args.reduce((acc, i) => (acc ? wrap(acc, i.decode) : i.decode), undefined);
return { encode, decode };
}
function alphabet(alphabet) {
return {
encode: (digits) => {
if (!Array.isArray(digits) || (digits.length && typeof digits[0] !== 'number'))
throw new Error('alphabet.encode input should be an array of numbers');
return digits.map((i) => {
assertNumber(i);
if (i < 0 || i >= alphabet.length)
throw new Error(`Digit index outside alphabet: ${i} (alphabet: ${alphabet.length})`);
return alphabet[i];
});
},
decode: (input) => {
if (!Array.isArray(input) || (input.length && typeof input[0] !== 'string'))
throw new Error('alphabet.decode input should be array of strings');
return input.map((letter) => {
if (typeof letter !== 'string')
throw new Error(`alphabet.decode: not string element=${letter}`);
const index = alphabet.indexOf(letter);
if (index === -1)
throw new Error(`Unknown letter: "${letter}". Allowed: ${alphabet}`);
return index;
});
},
};
}
function join(separator = '') {
if (typeof separator !== 'string')
throw new Error('join separator should be string');
return {
encode: (from) => {
if (!Array.isArray(from) || (from.length && typeof from[0] !== 'string'))
throw new Error('join.encode input should be array of strings');
for (let i of from)
if (typeof i !== 'string')
throw new Error(`join.encode: non-string input=${i}`);
return from.join(separator);
},
decode: (to) => {
if (typeof to !== 'string')
throw new Error('join.decode input should be string');
return to.split(separator);
},
};
}
function padding(bits, chr = '=') {
assertNumber(bits);
if (typeof chr !== 'string')
throw new Error('padding chr should be string');
return {
encode(data) {
if (!Array.isArray(data) || (data.length && typeof data[0] !== 'string'))
throw new Error('padding.encode input should be array of strings');
for (let i of data)
if (typeof i !== 'string')
throw new Error(`padding.encode: non-string input=${i}`);
while ((data.length * bits) % 8)
data.push(chr);
return data;
},
decode(input) {
if (!Array.isArray(input) || (input.length && typeof input[0] !== 'string'))
throw new Error('padding.encode input should be array of strings');
for (let i of input)
if (typeof i !== 'string')
throw new Error(`padding.decode: non-string input=${i}`);
let end = input.length;
if ((end * bits) % 8)
throw new Error('Invalid padding: string should have whole number of bytes');
for (; end > 0 && input[end - 1] === chr; end--) {
if (!(((end - 1) * bits) % 8))
throw new Error('Invalid padding: string has too much padding');
}
return input.slice(0, end);
},
};
}
function normalize(fn) {
if (typeof fn !== 'function')
throw new Error('normalize fn should be function');
return { encode: (from) => from, decode: (to) => fn(to) };
}
function convertRadix(data, from, to) {
if (from < 2)
throw new Error(`convertRadix: wrong from=${from}, base cannot be less than 2`);
if (to < 2)
throw new Error(`convertRadix: wrong to=${to}, base cannot be less than 2`);
if (!Array.isArray(data))
throw new Error('convertRadix: data should be array');
if (!data.length)
return [];
let pos = 0;
const res = [];
const digits = Array.from(data);
digits.forEach((d) => {
assertNumber(d);
if (d < 0 || d >= from)
throw new Error(`Wrong integer: ${d}`);
});
while (true) {
let carry = 0;
let done = true;
for (let i = pos; i < digits.length; i++) {
const digit = digits[i];
const digitBase = from * carry + digit;
if (!Number.isSafeInteger(digitBase) ||
(from * carry) / from !== carry ||
digitBase - digit !== from * carry) {
throw new Error('convertRadix: carry overflow');
}
carry = digitBase % to;
digits[i] = Math.floor(digitBase / to);
if (!Number.isSafeInteger(digits[i]) || digits[i] * to + carry !== digitBase)
throw new Error('convertRadix: carry overflow');
if (!done)
continue;
else if (!digits[i])
pos = i;
else
done = false;
}
res.push(carry);
if (done)
break;
}
for (let i = 0; i < data.length - 1 && data[i] === 0; i++)
res.push(0);
return res.reverse();
}
const gcd = (a, b) => (!b ? a : gcd(b, a % b));
const radix2carry = (from, to) => from + (to - gcd(from, to));
function convertRadix2(data, from, to, padding) {
if (!Array.isArray(data))
throw new Error('convertRadix2: data should be array');
if (from <= 0 || from > 32)
throw new Error(`convertRadix2: wrong from=${from}`);
if (to <= 0 || to > 32)
throw new Error(`convertRadix2: wrong to=${to}`);
if (radix2carry(from, to) > 32) {
throw new Error(`convertRadix2: carry overflow from=${from} to=${to} carryBits=${radix2carry(from, to)}`);
}
let carry = 0;
let pos = 0;
const mask = 2 ** to - 1;
const res = [];
for (const n of data) {
assertNumber(n);
if (n >= 2 ** from)
throw new Error(`convertRadix2: invalid data word=${n} from=${from}`);
carry = (carry << from) | n;
if (pos + from > 32)
throw new Error(`convertRadix2: carry overflow pos=${pos} from=${from}`);
pos += from;
for (; pos >= to; pos -= to)
res.push(((carry >> (pos - to)) & mask) >>> 0);
carry &= 2 ** pos - 1;
}
carry = (carry << (to - pos)) & mask;
if (!padding && pos >= from)
throw new Error('Excess padding');
if (!padding && carry)
throw new Error(`Non-zero padding: ${carry}`);
if (padding && pos > 0)
res.push(carry >>> 0);
return res;
}
function radix(num) {
assertNumber(num);
return {
encode: (bytes) => {
if (!(bytes instanceof Uint8Array))
throw new Error('radix.encode input should be Uint8Array');
return convertRadix(Array.from(bytes), 2 ** 8, num);
},
decode: (digits) => {
if (!Array.isArray(digits) || (digits.length && typeof digits[0] !== 'number'))
throw new Error('radix.decode input should be array of strings');
return Uint8Array.from(convertRadix(digits, num, 2 ** 8));
},
};
}
function radix2(bits, revPadding = false) {
assertNumber(bits);
if (bits <= 0 || bits > 32)
throw new Error('radix2: bits should be in (0..32]');
if (radix2carry(8, bits) > 32 || radix2carry(bits, 8) > 32)
throw new Error('radix2: carry overflow');
return {
encode: (bytes) => {
if (!(bytes instanceof Uint8Array))
throw new Error('radix2.encode input should be Uint8Array');
return convertRadix2(Array.from(bytes), 8, bits, !revPadding);
},
decode: (digits) => {
if (!Array.isArray(digits) || (digits.length && typeof digits[0] !== 'number'))
throw new Error('radix2.decode input should be array of strings');
return Uint8Array.from(convertRadix2(digits, bits, 8, revPadding));
},
};
}
function unsafeWrapper(fn) {
if (typeof fn !== 'function')
throw new Error('unsafeWrapper fn should be function');
return function (...args) {
try {
return fn.apply(null, args);
}
catch (e) { }
};
}
function checksum(len, fn) {
assertNumber(len);
if (typeof fn !== 'function')
throw new Error('checksum fn should be function');
return {
encode(data) {
if (!(data instanceof Uint8Array))
throw new Error('checksum.encode: input should be Uint8Array');
const checksum = fn(data).slice(0, len);
const res = new Uint8Array(data.length + len);
res.set(data);
res.set(checksum, data.length);
return res;
},
decode(data) {
if (!(data instanceof Uint8Array))
throw new Error('checksum.decode: input should be Uint8Array');
const payload = data.slice(0, -len);
const newChecksum = fn(payload).slice(0, len);
const oldChecksum = data.slice(-len);
for (let i = 0; i < len; i++)
if (newChecksum[i] !== oldChecksum[i])
throw new Error('Invalid checksum');
return payload;
},
};
}
exports.utils = { alphabet, chain, checksum, radix, radix2, join, padding };
exports.base16 = chain(radix2(4), alphabet('0123456789ABCDEF'), join(''));
exports.base32 = chain(radix2(5), alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'), padding(5), join(''));
exports.base32hex = chain(radix2(5), alphabet('0123456789ABCDEFGHIJKLMNOPQRSTUV'), padding(5), join(''));
exports.base32crockford = chain(radix2(5), alphabet('0123456789ABCDEFGHJKMNPQRSTVWXYZ'), join(''), normalize((s) => s.toUpperCase().replace(/O/g, '0').replace(/[IL]/g, '1')));
exports.base64 = chain(radix2(6), alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'), padding(6), join(''));
exports.base64url = chain(radix2(6), alphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'), padding(6), join(''));
const genBase58 = (abc) => chain(radix(58), alphabet(abc), join(''));
exports.base58 = genBase58('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
exports.base58flickr = genBase58('123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ');
exports.base58xrp = genBase58('rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz');
const XMR_BLOCK_LEN = [0, 2, 3, 5, 6, 7, 9, 10, 11];
exports.base58xmr = {
encode(data) {
let res = '';
for (let i = 0; i < data.length; i += 8) {
const block = data.subarray(i, i + 8);
res += exports.base58.encode(block).padStart(XMR_BLOCK_LEN[block.length], '1');
}
return res;
},
decode(str) {
let res = [];
for (let i = 0; i < str.length; i += 11) {
const slice = str.slice(i, i + 11);
const blockLen = XMR_BLOCK_LEN.indexOf(slice.length);
const block = exports.base58.decode(slice);
for (let j = 0; j < block.length - blockLen; j++) {
if (block[j] !== 0)
throw new Error('base58xmr: wrong padding');
}
res = res.concat(Array.from(block.slice(block.length - blockLen)));
}
return Uint8Array.from(res);
},
};
const base58check = (sha256) => chain(checksum(4, (data) => sha256(sha256(data))), exports.base58);
exports.base58check = base58check;
const BECH_ALPHABET = chain(alphabet('qpzry9x8gf2tvdw0s3jn54khce6mua7l'), join(''));
const POLYMOD_GENERATORS = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
function bech32Polymod(pre) {
const b = pre >> 25;
let chk = (pre & 0x1ffffff) << 5;
for (let i = 0; i < POLYMOD_GENERATORS.length; i++) {
if (((b >> i) & 1) === 1)
chk ^= POLYMOD_GENERATORS[i];
}
return chk;
}
function bechChecksum(prefix, words, encodingConst = 1) {
const len = prefix.length;
let chk = 1;
for (let i = 0; i < len; i++) {
const c = prefix.charCodeAt(i);
if (c < 33 || c > 126)
throw new Error(`Invalid prefix (${prefix})`);
chk = bech32Polymod(chk) ^ (c >> 5);
}
chk = bech32Polymod(chk);
for (let i = 0; i < len; i++)
chk = bech32Polymod(chk) ^ (prefix.charCodeAt(i) & 0x1f);
for (let v of words)
chk = bech32Polymod(chk) ^ v;
for (let i = 0; i < 6; i++)
chk = bech32Polymod(chk);
chk ^= encodingConst;
return BECH_ALPHABET.encode(convertRadix2([chk % 2 ** 30], 30, 5, false));
}
function genBech32(encoding) {
const ENCODING_CONST = encoding === 'bech32' ? 1 : 0x2bc830a3;
const _words = radix2(5);
const fromWords = _words.decode;
const toWords = _words.encode;
const fromWordsUnsafe = unsafeWrapper(fromWords);
function encode(prefix, words, limit = 90) {
if (typeof prefix !== 'string')
throw new Error(`bech32.encode prefix should be string, not ${typeof prefix}`);
if (!Array.isArray(words) || (words.length && typeof words[0] !== 'number'))
throw new Error(`bech32.encode words should be array of numbers, not ${typeof words}`);
const actualLength = prefix.length + 7 + words.length;
if (limit !== false && actualLength > limit)
throw new TypeError(`Length ${actualLength} exceeds limit ${limit}`);
prefix = prefix.toLowerCase();
return `${prefix}1${BECH_ALPHABET.encode(words)}${bechChecksum(prefix, words, ENCODING_CONST)}`;
}
function decode(str, limit = 90) {
if (typeof str !== 'string')
throw new Error(`bech32.decode input should be string, not ${typeof str}`);
if (str.length < 8 || (limit !== false && str.length > limit))
throw new TypeError(`Wrong string length: ${str.length} (${str}). Expected (8..${limit})`);
const lowered = str.toLowerCase();
if (str !== lowered && str !== str.toUpperCase())
throw new Error(`String must be lowercase or uppercase`);
str = lowered;
const sepIndex = str.lastIndexOf('1');
if (sepIndex === 0 || sepIndex === -1)
throw new Error(`Letter "1" must be present between prefix and data only`);
const prefix = str.slice(0, sepIndex);
const _words = str.slice(sepIndex + 1);
if (_words.length < 6)
throw new Error('Data must be at least 6 characters long');
const words = BECH_ALPHABET.decode(_words).slice(0, -6);
const sum = bechChecksum(prefix, words, ENCODING_CONST);
if (!_words.endsWith(sum))
throw new Error(`Invalid checksum in ${str}: expected "${sum}"`);
return { prefix, words };
}
const decodeUnsafe = unsafeWrapper(decode);
function decodeToBytes(str) {
const { prefix, words } = decode(str, false);
return { prefix, words, bytes: fromWords(words) };
}
return { encode, decode, decodeToBytes, decodeUnsafe, fromWords, fromWordsUnsafe, toWords };
}
exports.bech32 = genBech32('bech32');
exports.bech32m = genBech32('bech32m');
exports.utf8 = {
encode: (data) => new TextDecoder().decode(data),
decode: (str) => new TextEncoder().encode(str),
};
exports.hex = chain(radix2(4), alphabet('0123456789abcdef'), join(''), normalize((s) => {
if (typeof s !== 'string' || s.length % 2)
throw new TypeError(`hex.decode: expected string, got ${typeof s} with length ${s.length}`);
return s.toLowerCase();
}));
const CODERS = {
utf8: exports.utf8, hex: exports.hex, base16: exports.base16, base32: exports.base32, base64: exports.base64, base64url: exports.base64url, base58: exports.base58, base58xmr: exports.base58xmr
};
const coderTypeError = `Invalid encoding type. Available types: ${Object.keys(CODERS).join(', ')}`;
const bytesToString = (type, bytes) => {
if (typeof type !== 'string' || !CODERS.hasOwnProperty(type))
throw new TypeError(coderTypeError);
if (!(bytes instanceof Uint8Array))
throw new TypeError('bytesToString() expects Uint8Array');
return CODERS[type].encode(bytes);
};
exports.bytesToString = bytesToString;
exports.str = exports.bytesToString;
const stringToBytes = (type, str) => {
if (!CODERS.hasOwnProperty(type))
throw new TypeError(coderTypeError);
if (typeof str !== 'string')
throw new TypeError('stringToBytes() expects string');
return CODERS[type].decode(str);
};
exports.stringToBytes = stringToBytes;
exports.bytes = exports.stringToBytes;
} (lib));
return lib;
}
var bolt11;
var hasRequiredBolt11;
function requireBolt11 () {
if (hasRequiredBolt11) return bolt11;
hasRequiredBolt11 = 1;
const {bech32, hex, utf8} = requireLib();
// defaults for encode; default timestamp is current time at call
const DEFAULTNETWORK = {
// default network is bitcoin
bech32: 'bc',
pubKeyHash: 0x00,
scriptHash: 0x05,
validWitnessVersions: [0]
};
const TESTNETWORK = {
bech32: 'tb',
pubKeyHash: 0x6f,
scriptHash: 0xc4,
validWitnessVersions: [0]
};
const SIGNETNETWORK = {
bech32: 'tbs',
pubKeyHash: 0x6f,
scriptHash: 0xc4,
validWitnessVersions: [0]
};
const REGTESTNETWORK = {
bech32: 'bcrt',
pubKeyHash: 0x6f,
scriptHash: 0xc4,
validWitnessVersions: [0]
};
const SIMNETWORK = {
bech32: 'sb',
pubKeyHash: 0x3f,
scriptHash: 0x7b,
validWitnessVersions: [0]
};
const FEATUREBIT_ORDER = [
'option_data_loss_protect',
'initial_routing_sync',
'option_upfront_shutdown_script',
'gossip_queries',
'var_onion_optin',
'gossip_queries_ex',
'option_static_remotekey',
'payment_secret',
'basic_mpp',
'option_support_large_channel'
];
const DIVISORS = {
m: BigInt(1e3),
u: BigInt(1e6),
n: BigInt(1e9),
p: BigInt(1e12)
};
const MAX_MILLISATS = BigInt('2100000000000000000');
const MILLISATS_PER_BTC = BigInt(1e11);
const TAGCODES = {
payment_hash: 1,
payment_secret: 16,
description: 13,
payee: 19,
description_hash: 23, // commit to longer descriptions (used by lnurl-pay)
expiry: 6, // default: 3600 (1 hour)
min_final_cltv_expiry: 24, // default: 9
fallback_address: 9,
route_hint: 3, // for extra routing info (private etc.)
feature_bits: 5,
metadata: 27
};
// reverse the keys and values of TAGCODES and insert into TAGNAMES
const TAGNAMES = {};
for (let i = 0, keys = Object.keys(TAGCODES); i < keys.length; i++) {
const currentName = keys[i];
const currentCode = TAGCODES[keys[i]].toString();
TAGNAMES[currentCode] = currentName;
}
const TAGPARSERS = {
1: words => hex.encode(bech32.fromWordsUnsafe(words)), // 256 bits
16: words => hex.encode(bech32.fromWordsUnsafe(words)), // 256 bits
13: words => utf8.encode(bech32.fromWordsUnsafe(words)), // string variable length
19: words => hex.encode(bech32.fromWordsUnsafe(words)), // 264 bits
23: words => hex.encode(bech32.fromWordsUnsafe(words)), // 256 bits
27: words => hex.encode(bech32.fromWordsUnsafe(words)), // variable
6: wordsToIntBE, // default: 3600 (1 hour)
24: wordsToIntBE, // default: 9
3: routingInfoParser, // for extra routing info (private etc.)
5: featureBitsParser // keep feature bits as array of 5 bit words
};
function getUnknownParser(tagCode) {
return words => ({
tagCode: parseInt(tagCode),
words: bech32.encode('unknown', words, Number.MAX_SAFE_INTEGER)
})
}
function wordsToIntBE(words) {
return words.reverse().reduce((total, item, index) => {
return total + item * Math.pow(32, index)
}, 0)
}
// first convert from words to buffer, trimming padding where necessary
// parse in 51 byte chunks. See encoder for details.
function routingInfoParser(words) {
const routes = [];
let pubkey,
shortChannelId,
feeBaseMSats,
feeProportionalMillionths,
cltvExpiryDelta;
let routesBuffer = bech32.fromWordsUnsafe(words);
while (routesBuffer.length > 0) {
pubkey = hex.encode(routesBuffer.slice(0, 33)); // 33 bytes
shortChannelId = hex.encode(routesBuffer.slice(33, 41)); // 8 bytes
feeBaseMSats = parseInt(hex.encode(routesBuffer.slice(41, 45)), 16); // 4 bytes
feeProportionalMillionths = parseInt(
hex.encode(routesBuffer.slice(45, 49)),
16
); // 4 bytes
cltvExpiryDelta = parseInt(hex.encode(routesBuffer.slice(49, 51)), 16); // 2 bytes
routesBuffer = routesBuffer.slice(51);
routes.push({
pubkey,
short_channel_id: shortChannelId,
fee_base_msat: feeBaseMSats,
fee_proportional_millionths: feeProportionalMillionths,
cltv_expiry_delta: cltvExpiryDelta
});
}
return routes
}
function featureBitsParser(words) {
const bools = words
.slice()
.reverse()
.map(word => [
!!(word & 0b1),
!!(word & 0b10),
!!(word & 0b100),
!!(word & 0b1000),
!!(word & 0b10000)
])
.reduce((finalArr, itemArr) => finalArr.concat(itemArr), []);
while (bools.length < FEATUREBIT_ORDER.length * 2) {
bools.push(false);
}
const featureBits = {};
FEATUREBIT_ORDER.forEach((featureName, index) => {
let status;
if (bools[index * 2]) {
status = 'required';
} else if (bools[index * 2 + 1]) {
status = 'supported';
} else {
status = 'unsupported';
}
featureBits[featureName] = status;
});
const extraBits = bools.slice(FEATUREBIT_ORDER.length * 2);
featureBits.extra_bits = {
start_bit: FEATUREBIT_ORDER.length * 2,
bits: extraBits,
has_required: extraBits.reduce(
(result, bit, index) =>
index % 2 !== 0 ? result || false : result || bit,
false
)
};
return featureBits
}
function hrpToMillisat(hrpString, outputString) {
let divisor, value;
if (hrpString.slice(-1).match(/^[munp]$/)) {
divisor = hrpString.slice(-1);
value = hrpString.slice(0, -1);
} else if (hrpString.slice(-1).match(/^[^munp0-9]$/)) {
throw new Error('Not a valid multiplier for the amount')
} else {
value = hrpString;
}
if (!value.match(/^\d+$/))
throw new Error('Not a valid human readable amount')
const valueBN = BigInt(value);
const millisatoshisBN = divisor
? (valueBN * MILLISATS_PER_BTC) / DIVISORS[divisor]
: valueBN * MILLISATS_PER_BTC;
if (
(divisor === 'p' && !(valueBN % BigInt(10) === BigInt(0))) ||
millisatoshisBN > MAX_MILLISATS
) {
throw new Error('Amount is outside of valid range')
}
return outputString ? millisatoshisBN.toString() : millisatoshisBN
}
// decode will only have extra comments that aren't covered in encode comments.
// also if anything is hard to read I'll comment.
function decode(paymentRequest, network) {
if (typeof paymentRequest !== 'string')
throw new Error('Lightning Payment Request must be string')
if (paymentRequest.slice(0, 2).toLowerCase() !== 'ln')
throw new Error('Not a proper lightning payment request')
const sections = [];
const decoded = bech32.decode(paymentRequest, Number.MAX_SAFE_INTEGER);
paymentRequest = paymentRequest.toLowerCase();
const prefix = decoded.prefix;
let words = decoded.words;
let letters = paymentRequest.slice(prefix.length + 1);
let sigWords = words.slice(-104);
words = words.slice(0, -104);
// Without reverse lookups, can't say that the multipier at the end must
// have a number before it, so instead we parse, and if the second group
// doesn't have anything, there's a good chance the last letter of the
// coin type got captured by the third group, so just re-regex without
// the number.
let prefixMatches = prefix.match(/^ln(\S+?)(\d*)([a-zA-Z]?)$/);
if (prefixMatches && !prefixMatches[2])
prefixMatches = prefix.match(/^ln(\S+)$/);
if (!prefixMatches) {
throw new Error('Not a proper lightning payment request')
}
// "ln" section
sections.push({
name: 'lightning_network',
letters: 'ln'
});
// "bc" section
const bech32Prefix = prefixMatches[1];
let coinNetwork;
if (!network) {
switch (bech32Prefix) {
case DEFAULTNETWORK.bech32:
coinNetwork = DEFAULTNETWORK;
break
case TESTNETWORK.bech32:
coinNetwork = TESTNETWORK;
break
case SIGNETNETWORK.bech32:
coinNetwork = SIGNETNETWORK;
break
case REGTESTNETWORK.bech32:
coinNetwork = REGTESTNETWORK;
break
case SIMNETWORK.bech32:
coinNetwork = SIMNETWORK;
break
}
} else {
if (
network.bech32 === undefined ||
network.pubKeyHash === undefined ||
network.scriptHash === undefined ||
!Array.isArray(network.validWitnessVersions)
)
throw new Error('Invalid network')
coinNetwork = network;
}
if (!coinNetwork || coinNetwork.bech32 !== bech32Prefix) {
throw new Error('Unknown coin bech32 prefix')
}
sections.push({
name: 'coin_network',
letters: bech32Prefix,
value: coinNetwork
});
// amount section
const value = prefixMatches[2];
let millisatoshis;
if (value) {
const divisor = prefixMatches[3];
millisatoshis = hrpToMillisat(value + divisor, true);
sections.push({
name: 'amount',
letters: prefixMatches[2] + prefixMatches[3],
value: millisatoshis
});
} else {
millisatoshis = null;
}
// "1" separator
sections.push({
name: 'separator',
letters: '1'
});
// timestamp
const timestamp = wordsToIntBE(words.slice(0, 7));
words = words.slice(7); // trim off the left 7 words
sections.push({
name: 'timestamp',
letters: letters.slice(0, 7),
value: timestamp
});
letters = letters.slice(7);
let tagName, parser, tagLength, tagWords;
// we have no tag count to go on, so just keep hacking off words
// until we have none.
while (words.length > 0) {
const tagCode = words[0].toString();
tagName = TAGNAMES[tagCode] || 'unknown_tag';
parser = TAGPARSERS[tagCode] || getUnknownParser(tagCode);
words = words.slice(1);
tagLength = wordsToIntBE(words.slice(0, 2));
words = words.slice(2);
tagWords = words.slice(0, tagLength);
words = words.slice(tagLength);
sections.push({
name: tagName,
tag: letters[0],
letters: letters.slice(0, 1 + 2 + tagLength),
value: parser(tagWords) // see: parsers for more comments
});
letters = letters.slice(1 + 2 + tagLength);
}
// signature
sections.push({
name: 'signature',
letters: letters.slice(0, 104),
value: hex.encode(bech32.fromWordsUnsafe(sigWords))
});
letters = letters.slice(104);
// checksum
sections.push({
name: 'checksum',
letters: letters
});
let result = {
paymentRequest,
sections,
get expiry() {
let exp = sections.find(s => s.name === 'expiry');
if (exp) return getValue('timestamp') + exp.value
},
get route_hints() {
return sections.filter(s => s.name === 'route_hint').map(s => s.value)
}
};
for (let name in TAGCODES) {
if (name === 'route_hint') {
// route hints can be multiple, so this won't work for them
continue
}
Object.defineProperty(result, name, {
get() {
return getValue(name)
}
});
}
return result
function getValue(name) {
let section = sections.find(s => s.name === name);
return section ? section.value : undefined
}
}
bolt11 = {
decode,
hrpToMillisat
};
return bolt11;
}
var bolt11Exports = requireBolt11();
// from https://stackoverflow.com/a/50868276
const fromHexString = (hexString) => Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
const decodeInvoice = (paymentRequest) => {
if (!paymentRequest)
return null;
try {
const decoded = bolt11Exports.decode(paymentRequest);
if (!decoded || !decoded.sections)
return null;
const hashTag = decoded.sections.find((value) => value.name === "payment_hash");
if (hashTag?.name !== "payment_hash" || !hashTag.value)
return null;
const paymentHash = hashTag.value;
let satoshi = 0;
let millisatoshi = 0;
let amountRaw = "0";
const amountTag = decoded.sections.find((value) => value.name === "amount");
if (amountTag?.name === "amount" && amountTag.value) {
amountRaw = amountTag.value;
millisatoshi = parseInt(amountTag.value);
satoshi = parseInt(amountTag.value) / 1000; // millisats
}
const timestampTag = decoded.sections.find((value) => value.name === "timestamp");
if (timestampTag?.name !== "timestamp" || !timestampTag.value)
return null;
const timestamp = timestampTag.value;
let expiry;
const expiryTag = decoded.sections.find((value) => value.name === "expiry");
if (expiryTag?.name === "expiry") {
expiry = expiryTag.value;
}
const descriptionTag = decoded.sections.find((value) => value.name === "description");
const description = descriptionTag?.name === "description"
? descriptionTag?.value
: undefined;
return {
paymentHash,
satoshi,
millisatoshi,
amountRaw,
timestamp,
expiry,
description,
};
}
catch {
return null;
}
};
function validatePreimage(preimage, paymentHash) {
try {
if (!/^[0-9a-fA-F]{64}$/.test(preimage))
return false;
if (!/^[0-9a-fA-F]{64}$/.test(paymentHash))
return false;
const preimageHash = bytesToHex(sha256(fromHexString(preimage)));
return paymentHash === preimageHash;
}
catch {
return false;
}
}
class Invoice {
constructor(args) {
this.paymentRequest = args.pr;
if (!this.paymentRequest) {
throw new Error("Invalid payment request");
}
const decodedInvoice = decodeInvoice(this.paymentRequest);
if (!decodedInvoice) {
throw new Error("Failed to decode payment request");
}
this.paymentHash = decodedInvoice.paymentHash;
this.satoshi = decodedInvoice.satoshi;
this.millisatoshi = decodedInvoice.millisatoshi;
this.amountRaw = decodedInvoice.amountRaw;
this.timestamp = decodedInvoice.timestamp;
this.expiry = decodedInvoice.expiry;
this.createdDate = new Date(this.timestamp * 1000);
this.expiryDate = this.expiry
? new Date((this.timestamp + this.expiry) * 1000)
: undefined;
this.description = decodedInvoice.description ?? null;
this.verify = args.verify ?? null;
this.preimage = args.preimage ?? null;
this.successAction = args.successAction ?? null;
}
async isPaid() {
if (this.preimage)
return this.validatePreimage(this.preimage);
else if (this.verify) {
return await this.verifyPayment();
}
else {
throw new Error("Could not verify payment");
}
}
validatePreimage(preimage) {
if (!preimage || !this.paymentHash)
return false;
return validatePreimage(preimage, this.paymentHash);
}
async verifyPayment() {
try {
if (!this.verify) {
throw new Error("LNURL verify not available");
}
const response = await fetch(this.verify);
if (!response.ok) {
throw new Error(`Verification request failed: ${response.status} ${response.statusText}`);
}
const json = await response.json();
if (json.preimage) {
this.preimage = json.preimage;
}
return json.settled;
}
catch (error) {
console.error("Failed to check LNURL-verify", error);
return false;
}
}
hasExpired() {
const { expiryDate } = this;
if (expiryDate) {
return expiryDate.getTime() < Date.now();
}
return false;
}
}
/** Apply a previously-obtained credential to the outgoing request headers. */
const applyCredentials = (headers, credentials) => {
headers.set(credentials.header, credentials.value);
};
/** Attach payment metadata to a response and return it (typed). */
const attachPayment = (response, payment) => {
if (payment) {
response.payment = payment;
}
return response;
};
/** Payment metadata describing a request authorized with a reused credential. */
const reusedCredentialPayment = (credentials) => credentials ? { paid: false, amount: 0, credentials } : undefined;
/** Satoshi amount of a BOLT11 invoice (0 when it cannot be decoded). */
const getInvoiceAmount = (invoice) => {
try {
return new Invoice({ pr: invoice }).satoshi;
}
catch (_) {
return 0;
}
};
/**
* Parse a `WWW-Authenticate: Payment …` header produced by a

@@ -139,4 +1421,12 @@ * draft-lightning-charge-00 server. Expected format:

const credential = buildMppCredential(challenge, invResp.preimage);
headers.set("Authorization", `Payment ${credential}`);
return fetch(url, fetchArgs);
const value = `Payment ${credential}`;
headers.set("Authorization", value);
const response = await fetch(url, fetchArgs);
return attachPayment(response, {
paid: true,
amount: getInvoiceAmount(invoice),
feesPaid: invResp.fees_paid,
preimage: invResp.preimage,
credentials: { header: "Authorization", value },
});
};

@@ -152,5 +1442,7 @@ /**

*
* Note: lightning-charge uses consume-once challenge semantics – each
* challenge embeds a fresh invoice, so paid credentials cannot be reused.
* The `store` option is accepted for API consistency but is not used.
* Pass a previous credential via `options.credentials` to reuse it (e.g. when
* polling); the credential is applied and the function NEVER pays again, even
* if the server still responds with a 402 (that response is returned as-is).
* Note: lightning-charge typically uses consume-once challenge semantics, so a
* reused credential is only accepted by servers that explicitly support it.
*/

@@ -169,2 +1461,11 @@ const fetchWithMpp = async (url, fetchArgs, options) => {

fetchArgs.headers = headers;
// If the caller supplied a credential, we MUST use it and never pay again β€”
// even if the server still responds with a 402. Re-paying here is the exact
// double-charge this API exists to prevent; the caller decides what to do
// with a rejected credential (retry after settlement, top up, etc.).
if (options.credentials) {
applyCredentials(headers, options.credentials);
const reusedResp = await fetch(url, fetchArgs);
return attachPayment(reusedResp, reusedCredentialPayment(options.credentials));
}
const initResp = await fetch(url, fetchArgs);

@@ -171,0 +1472,0 @@ const wwwAuthHeader = initResp.headers.get("www-authenticate");

@@ -1,13 +0,1 @@

const buildX402PaymentSignature = (scheme, network, invoice, requirements) => {
const json = JSON.stringify({
x402Version: 2,
scheme,
network,
payload: { invoice },
accepted: requirements,
});
// btoa only handles latin1; encode via UTF-8 to be safe
return btoa(unescape(encodeURIComponent(json)));
};
function bytes(b, ...lengths) {

@@ -1272,2 +1260,28 @@ if (!(b instanceof Uint8Array))

/** Apply a previously-obtained credential to the outgoing request headers. */
const applyCredentials = (headers, credentials) => {
headers.set(credentials.header, credentials.value);
};
/** Attach payment metadata to a response and return it (typed). */
const attachPayment = (response, payment) => {
if (payment) {
response.payment = payment;
}
return response;
};
/** Payment metadata describing a request authorized with a reused credential. */
const reusedCredentialPayment = (credentials) => credentials ? { paid: false, amount: 0, credentials } : undefined;
const buildX402PaymentSignature = (scheme, network, invoice, requirements) => {
const json = JSON.stringify({
x402Version: 2,
scheme,
network,
payload: { invoice },
accepted: requirements,
});
// btoa only handles latin1; encode via UTF-8 to be safe
return btoa(unescape(encodeURIComponent(json)));
};
const decodeX402Header = (x402Header) => {

@@ -1319,5 +1333,13 @@ let parsed;

}
await wallet.payInvoice({ invoice: invoice.paymentRequest });
headers.set("payment-signature", buildX402PaymentSignature(requirements.scheme, requirements.network, invoice.paymentRequest, requirements));
return fetch(url, fetchArgs);
const invResp = await wallet.payInvoice({ invoice: invoice.paymentRequest });
const value = buildX402PaymentSignature(requirements.scheme, requirements.network, invoice.paymentRequest, requirements);
headers.set("payment-signature", value);
const response = await fetch(url, fetchArgs);
return attachPayment(response, {
paid: true,
amount: invoice.satoshi,
feesPaid: invResp.fees_paid,
preimage: invResp.preimage,
credentials: { header: "payment-signature", value },
});
};

@@ -1333,2 +1355,11 @@ const fetchWithX402 = async (url, fetchArgs, options) => {

fetchArgs.headers = headers;
// If the caller supplied a credential, we MUST use it and never pay again β€”
// even if the server still responds with a 402. Re-paying here is the exact
// double-charge this API exists to prevent; the caller decides what to do
// with a rejected credential (retry after settlement, top up, etc.).
if (options.credentials) {
applyCredentials(headers, options.credentials);
const reusedResp = await fetch(url, fetchArgs);
return attachPayment(reusedResp, reusedCredentialPayment(options.credentials));
}
const initResp = await fetch(url, fetchArgs);

@@ -1335,0 +1366,0 @@ const header = initResp.headers.get("PAYMENT-REQUIRED");

!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).LightningTools={})}(this,function(e){"use strict";function t(e,t=!0){if(e.destroyed)throw new Error("Hash instance has been destroyed");if(t&&e.finished)throw new Error("Hash#digest() has already been called")}function r(e,t){!function(e,...t){if(!(e instanceof Uint8Array))throw new Error("Expected Uint8Array");if(t.length>0&&!t.includes(e.length))throw new Error(`Expected Uint8Array of length ${t}, not of length=${e.length}`)}(e);const r=t.outputLen;if(e.length<r)throw new Error(`digestInto() expects output buffer of length at least ${r}`)}
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */const n=e=>e instanceof Uint8Array,o=e=>new DataView(e.buffer,e.byteOffset,e.byteLength),s=(e,t)=>e<<32-t|e>>>t;if(!(68===new Uint8Array(new Uint32Array([287454020]).buffer)[0]))throw new Error("Non little-endian hardware is not supported");const i=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0"));function a(e){if(!n(e))throw new Error("Uint8Array expected");let t="";for(let r=0;r<e.length;r++)t+=i[e[r]];return t}function c(e){if("string"==typeof e&&(e=function(e){if("string"!=typeof e)throw new Error("utf8ToBytes expected string, got "+typeof e);return new Uint8Array((new TextEncoder).encode(e))}(e)),!n(e))throw new Error("expected Uint8Array, got "+typeof e);return e}class h{clone(){return this._cloneInto()}}function l(e){const t=t=>e().update(c(t)).digest(),r=e();return t.outputLen=r.outputLen,t.blockLen=r.blockLen,t.create=()=>e(),t}class u extends h{constructor(e,t,r,n){super(),this.blockLen=e,this.outputLen=t,this.padOffset=r,this.isLE=n,this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.buffer=new Uint8Array(e),this.view=o(this.buffer)}update(e){t(this);const{view:r,buffer:n,blockLen:s}=this,i=(e=c(e)).length;for(let t=0;t<i;){const a=Math.min(s-this.pos,i-t);if(a===s){const r=o(e);for(;s<=i-t;t+=s)this.process(r,t);continue}n.set(e.subarray(t,t+a),this.pos),this.pos+=a,t+=a,this.pos===s&&(this.process(r,0),this.pos=0)}return this.length+=e.length,this.roundClean(),this}digestInto(e){t(this),r(e,this),this.finished=!0;const{buffer:n,view:s,blockLen:i,isLE:a}=this;let{pos:c}=this;n[c++]=128,this.buffer.subarray(c).fill(0),this.padOffset>i-c&&(this.process(s,0),c=0);for(let e=c;e<i;e++)n[e]=0;!function(e,t,r,n){if("function"==typeof e.setBigUint64)return e.setBigUint64(t,r,n);const o=BigInt(32),s=BigInt(4294967295),i=Number(r>>o&s),a=Number(r&s),c=n?4:0,h=n?0:4;e.setUint32(t+c,i,n),e.setUint32(t+h,a,n)}(s,i-8,BigInt(8*this.length),a),this.process(s,0);const h=o(e),l=this.outputLen;if(l%4)throw new Error("_sha2: outputLen should be aligned to 32bit");const u=l/4,f=this.get();if(u>f.length)throw new Error("_sha2: outputLen bigger than state");for(let e=0;e<u;e++)h.setUint32(4*e,f[e],a)}digest(){const{buffer:e,outputLen:t}=this;this.digestInto(e);const r=e.slice(0,t);return this.destroy(),r}_cloneInto(e){e||(e=new this.constructor),e.set(...this.get());const{blockLen:t,buffer:r,length:n,finished:o,destroyed:s,pos:i}=this;return e.length=n,e.pos=i,e.finished=o,e.destroyed=s,n%t&&e.buffer.set(r),e}}const f=(e,t,r)=>e&t^~e&r,d=(e,t,r)=>e&t^e&r^t&r,p=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),w=new Uint32Array([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),m=new Uint32Array(64);class y extends u{constructor(){super(64,32,8,!1),this.A=0|w[0],this.B=0|w[1],this.C=0|w[2],this.D=0|w[3],this.E=0|w[4],this.F=0|w[5],this.G=0|w[6],this.H=0|w[7]}get(){const{A:e,B:t,C:r,D:n,E:o,F:s,G:i,H:a}=this;return[e,t,r,n,o,s,i,a]}set(e,t,r,n,o,s,i,a){this.A=0|e,this.B=0|t,this.C=0|r,this.D=0|n,this.E=0|o,this.F=0|s,this.G=0|i,this.H=0|a}process(e,t){for(let r=0;r<16;r++,t+=4)m[r]=e.getUint32(t,!1);for(let e=16;e<64;e++){const t=m[e-15],r=m[e-2],n=s(t,7)^s(t,18)^t>>>3,o=s(r,17)^s(r,19)^r>>>10;m[e]=o+m[e-7]+n+m[e-16]|0}let{A:r,B:n,C:o,D:i,E:a,F:c,G:h,H:l}=this;for(let e=0;e<64;e++){const t=l+(s(a,6)^s(a,11)^s(a,25))+f(a,c,h)+p[e]+m[e]|0,u=(s(r,2)^s(r,13)^s(r,22))+d(r,n,o)|0;l=h,h=c,c=a,a=i+t|0,i=o,o=n,n=r,r=t+u|0}r=r+this.A|0,n=n+this.B|0,o=o+this.C|0,i=i+this.D|0,a=a+this.E|0,c=c+this.F|0,h=h+this.G|0,l=l+this.H|0,this.set(r,n,o,i,a,c,h,l)}roundClean(){m.fill(0)}destroy(){this.set(0,0,0,0,0,0,0,0),this.buffer.fill(0)}}const g=l(()=>new y);var b,v,E,x={};var A=function(){if(E)return v;E=1;const{bech32:e,hex:t,utf8:r}=(b||(b=1,function(e){function t(e){if(!Number.isSafeInteger(e))throw new Error(`Wrong integer: ${e}`)}function r(...e){const t=(e,t)=>r=>e(t(r));return{encode:Array.from(e).reverse().reduce((e,r)=>e?t(e,r.encode):r.encode,void 0),decode:e.reduce((e,r)=>e?t(e,r.decode):r.decode,void 0)}}function n(e){return{encode:r=>{if(!Array.isArray(r)||r.length&&"number"!=typeof r[0])throw new Error("alphabet.encode input should be an array of numbers");return r.map(r=>{if(t(r),r<0||r>=e.length)throw new Error(`Digit index outside alphabet: ${r} (alphabet: ${e.length})`);return e[r]})},decode:t=>{if(!Array.isArray(t)||t.length&&"string"!=typeof t[0])throw new Error("alphabet.decode input should be array of strings");return t.map(t=>{if("string"!=typeof t)throw new Error(`alphabet.decode: not string element=${t}`);const r=e.indexOf(t);if(-1===r)throw new Error(`Unknown letter: "${t}". Allowed: ${e}`);return r})}}}function o(e=""){if("string"!=typeof e)throw new Error("join separator should be string");return{encode:t=>{if(!Array.isArray(t)||t.length&&"string"!=typeof t[0])throw new Error("join.encode input should be array of strings");for(let e of t)if("string"!=typeof e)throw new Error(`join.encode: non-string input=${e}`);return t.join(e)},decode:t=>{if("string"!=typeof t)throw new Error("join.decode input should be string");return t.split(e)}}}function s(e,r="="){if(t(e),"string"!=typeof r)throw new Error("padding chr should be string");return{encode(t){if(!Array.isArray(t)||t.length&&"string"!=typeof t[0])throw new Error("padding.encode input should be array of strings");for(let e of t)if("string"!=typeof e)throw new Error(`padding.encode: non-string input=${e}`);for(;t.length*e%8;)t.push(r);return t},decode(t){if(!Array.isArray(t)||t.length&&"string"!=typeof t[0])throw new Error("padding.encode input should be array of strings");for(let e of t)if("string"!=typeof e)throw new Error(`padding.decode: non-string input=${e}`);let n=t.length;if(n*e%8)throw new Error("Invalid padding: string should have whole number of bytes");for(;n>0&&t[n-1]===r;n--)if(!((n-1)*e%8))throw new Error("Invalid padding: string has too much padding");return t.slice(0,n)}}}function i(e){if("function"!=typeof e)throw new Error("normalize fn should be function");return{encode:e=>e,decode:t=>e(t)}}function a(e,r,n){if(r<2)throw new Error(`convertRadix: wrong from=${r}, base cannot be less than 2`);if(n<2)throw new Error(`convertRadix: wrong to=${n}, base cannot be less than 2`);if(!Array.isArray(e))throw new Error("convertRadix: data should be array");if(!e.length)return[];let o=0;const s=[],i=Array.from(e);for(i.forEach(e=>{if(t(e),e<0||e>=r)throw new Error(`Wrong integer: ${e}`)});;){let e=0,t=!0;for(let s=o;s<i.length;s++){const a=i[s],c=r*e+a;if(!Number.isSafeInteger(c)||r*e/r!==e||c-a!==r*e)throw new Error("convertRadix: carry overflow");if(e=c%n,i[s]=Math.floor(c/n),!Number.isSafeInteger(i[s])||i[s]*n+e!==c)throw new Error("convertRadix: carry overflow");t&&(i[s]?t=!1:o=s)}if(s.push(e),t)break}for(let t=0;t<e.length-1&&0===e[t];t++)s.push(0);return s.reverse()}
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */const n=e=>e instanceof Uint8Array,o=e=>new DataView(e.buffer,e.byteOffset,e.byteLength),s=(e,t)=>e<<32-t|e>>>t;if(!(68===new Uint8Array(new Uint32Array([287454020]).buffer)[0]))throw new Error("Non little-endian hardware is not supported");const i=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0"));function a(e){if(!n(e))throw new Error("Uint8Array expected");let t="";for(let r=0;r<e.length;r++)t+=i[e[r]];return t}function c(e){if("string"==typeof e&&(e=function(e){if("string"!=typeof e)throw new Error("utf8ToBytes expected string, got "+typeof e);return new Uint8Array((new TextEncoder).encode(e))}(e)),!n(e))throw new Error("expected Uint8Array, got "+typeof e);return e}class h{clone(){return this._cloneInto()}}function l(e){const t=t=>e().update(c(t)).digest(),r=e();return t.outputLen=r.outputLen,t.blockLen=r.blockLen,t.create=()=>e(),t}class u extends h{constructor(e,t,r,n){super(),this.blockLen=e,this.outputLen=t,this.padOffset=r,this.isLE=n,this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.buffer=new Uint8Array(e),this.view=o(this.buffer)}update(e){t(this);const{view:r,buffer:n,blockLen:s}=this,i=(e=c(e)).length;for(let t=0;t<i;){const a=Math.min(s-this.pos,i-t);if(a===s){const r=o(e);for(;s<=i-t;t+=s)this.process(r,t);continue}n.set(e.subarray(t,t+a),this.pos),this.pos+=a,t+=a,this.pos===s&&(this.process(r,0),this.pos=0)}return this.length+=e.length,this.roundClean(),this}digestInto(e){t(this),r(e,this),this.finished=!0;const{buffer:n,view:s,blockLen:i,isLE:a}=this;let{pos:c}=this;n[c++]=128,this.buffer.subarray(c).fill(0),this.padOffset>i-c&&(this.process(s,0),c=0);for(let e=c;e<i;e++)n[e]=0;!function(e,t,r,n){if("function"==typeof e.setBigUint64)return e.setBigUint64(t,r,n);const o=BigInt(32),s=BigInt(4294967295),i=Number(r>>o&s),a=Number(r&s),c=n?4:0,h=n?0:4;e.setUint32(t+c,i,n),e.setUint32(t+h,a,n)}(s,i-8,BigInt(8*this.length),a),this.process(s,0);const h=o(e),l=this.outputLen;if(l%4)throw new Error("_sha2: outputLen should be aligned to 32bit");const u=l/4,d=this.get();if(u>d.length)throw new Error("_sha2: outputLen bigger than state");for(let e=0;e<u;e++)h.setUint32(4*e,d[e],a)}digest(){const{buffer:e,outputLen:t}=this;this.digestInto(e);const r=e.slice(0,t);return this.destroy(),r}_cloneInto(e){e||(e=new this.constructor),e.set(...this.get());const{blockLen:t,buffer:r,length:n,finished:o,destroyed:s,pos:i}=this;return e.length=n,e.pos=i,e.finished=o,e.destroyed=s,n%t&&e.buffer.set(r),e}}const d=(e,t,r)=>e&t^~e&r,f=(e,t,r)=>e&t^e&r^t&r,p=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),w=new Uint32Array([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),m=new Uint32Array(64);class y extends u{constructor(){super(64,32,8,!1),this.A=0|w[0],this.B=0|w[1],this.C=0|w[2],this.D=0|w[3],this.E=0|w[4],this.F=0|w[5],this.G=0|w[6],this.H=0|w[7]}get(){const{A:e,B:t,C:r,D:n,E:o,F:s,G:i,H:a}=this;return[e,t,r,n,o,s,i,a]}set(e,t,r,n,o,s,i,a){this.A=0|e,this.B=0|t,this.C=0|r,this.D=0|n,this.E=0|o,this.F=0|s,this.G=0|i,this.H=0|a}process(e,t){for(let r=0;r<16;r++,t+=4)m[r]=e.getUint32(t,!1);for(let e=16;e<64;e++){const t=m[e-15],r=m[e-2],n=s(t,7)^s(t,18)^t>>>3,o=s(r,17)^s(r,19)^r>>>10;m[e]=o+m[e-7]+n+m[e-16]|0}let{A:r,B:n,C:o,D:i,E:a,F:c,G:h,H:l}=this;for(let e=0;e<64;e++){const t=l+(s(a,6)^s(a,11)^s(a,25))+d(a,c,h)+p[e]+m[e]|0,u=(s(r,2)^s(r,13)^s(r,22))+f(r,n,o)|0;l=h,h=c,c=a,a=i+t|0,i=o,o=n,n=r,r=t+u|0}r=r+this.A|0,n=n+this.B|0,o=o+this.C|0,i=i+this.D|0,a=a+this.E|0,c=c+this.F|0,h=h+this.G|0,l=l+this.H|0,this.set(r,n,o,i,a,c,h,l)}roundClean(){m.fill(0)}destroy(){this.set(0,0,0,0,0,0,0,0),this.buffer.fill(0)}}const g=l(()=>new y);var b,v,E,x={};var A=function(){if(E)return v;E=1;const{bech32:e,hex:t,utf8:r}=(b||(b=1,function(e){function t(e){if(!Number.isSafeInteger(e))throw new Error(`Wrong integer: ${e}`)}function r(...e){const t=(e,t)=>r=>e(t(r));return{encode:Array.from(e).reverse().reduce((e,r)=>e?t(e,r.encode):r.encode,void 0),decode:e.reduce((e,r)=>e?t(e,r.decode):r.decode,void 0)}}function n(e){return{encode:r=>{if(!Array.isArray(r)||r.length&&"number"!=typeof r[0])throw new Error("alphabet.encode input should be an array of numbers");return r.map(r=>{if(t(r),r<0||r>=e.length)throw new Error(`Digit index outside alphabet: ${r} (alphabet: ${e.length})`);return e[r]})},decode:t=>{if(!Array.isArray(t)||t.length&&"string"!=typeof t[0])throw new Error("alphabet.decode input should be array of strings");return t.map(t=>{if("string"!=typeof t)throw new Error(`alphabet.decode: not string element=${t}`);const r=e.indexOf(t);if(-1===r)throw new Error(`Unknown letter: "${t}". Allowed: ${e}`);return r})}}}function o(e=""){if("string"!=typeof e)throw new Error("join separator should be string");return{encode:t=>{if(!Array.isArray(t)||t.length&&"string"!=typeof t[0])throw new Error("join.encode input should be array of strings");for(let e of t)if("string"!=typeof e)throw new Error(`join.encode: non-string input=${e}`);return t.join(e)},decode:t=>{if("string"!=typeof t)throw new Error("join.decode input should be string");return t.split(e)}}}function s(e,r="="){if(t(e),"string"!=typeof r)throw new Error("padding chr should be string");return{encode(t){if(!Array.isArray(t)||t.length&&"string"!=typeof t[0])throw new Error("padding.encode input should be array of strings");for(let e of t)if("string"!=typeof e)throw new Error(`padding.encode: non-string input=${e}`);for(;t.length*e%8;)t.push(r);return t},decode(t){if(!Array.isArray(t)||t.length&&"string"!=typeof t[0])throw new Error("padding.encode input should be array of strings");for(let e of t)if("string"!=typeof e)throw new Error(`padding.decode: non-string input=${e}`);let n=t.length;if(n*e%8)throw new Error("Invalid padding: string should have whole number of bytes");for(;n>0&&t[n-1]===r;n--)if(!((n-1)*e%8))throw new Error("Invalid padding: string has too much padding");return t.slice(0,n)}}}function i(e){if("function"!=typeof e)throw new Error("normalize fn should be function");return{encode:e=>e,decode:t=>e(t)}}function a(e,r,n){if(r<2)throw new Error(`convertRadix: wrong from=${r}, base cannot be less than 2`);if(n<2)throw new Error(`convertRadix: wrong to=${n}, base cannot be less than 2`);if(!Array.isArray(e))throw new Error("convertRadix: data should be array");if(!e.length)return[];let o=0;const s=[],i=Array.from(e);for(i.forEach(e=>{if(t(e),e<0||e>=r)throw new Error(`Wrong integer: ${e}`)});;){let e=0,t=!0;for(let s=o;s<i.length;s++){const a=i[s],c=r*e+a;if(!Number.isSafeInteger(c)||r*e/r!==e||c-a!==r*e)throw new Error("convertRadix: carry overflow");if(e=c%n,i[s]=Math.floor(c/n),!Number.isSafeInteger(i[s])||i[s]*n+e!==c)throw new Error("convertRadix: carry overflow");t&&(i[s]?t=!1:o=s)}if(s.push(e),t)break}for(let t=0;t<e.length-1&&0===e[t];t++)s.push(0);return s.reverse()}
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
Object.defineProperty(e,"__esModule",{value:!0}),e.bytes=e.stringToBytes=e.str=e.bytesToString=e.hex=e.utf8=e.bech32m=e.bech32=e.base58check=e.base58xmr=e.base58xrp=e.base58flickr=e.base58=e.base64url=e.base64=e.base32crockford=e.base32hex=e.base32=e.base16=e.utils=e.assertNumber=void 0,e.assertNumber=t;const c=(e,t)=>t?c(t,e%t):e,h=(e,t)=>e+(t-c(e,t));function l(e,r,n,o){if(!Array.isArray(e))throw new Error("convertRadix2: data should be array");if(r<=0||r>32)throw new Error(`convertRadix2: wrong from=${r}`);if(n<=0||n>32)throw new Error(`convertRadix2: wrong to=${n}`);if(h(r,n)>32)throw new Error(`convertRadix2: carry overflow from=${r} to=${n} carryBits=${h(r,n)}`);let s=0,i=0;const a=2**n-1,c=[];for(const o of e){if(t(o),o>=2**r)throw new Error(`convertRadix2: invalid data word=${o} from=${r}`);if(s=s<<r|o,i+r>32)throw new Error(`convertRadix2: carry overflow pos=${i} from=${r}`);for(i+=r;i>=n;i-=n)c.push((s>>i-n&a)>>>0);s&=2**i-1}if(s=s<<n-i&a,!o&&i>=r)throw new Error("Excess padding");if(!o&&s)throw new Error(`Non-zero padding: ${s}`);return o&&i>0&&c.push(s>>>0),c}function u(e){return t(e),{encode:t=>{if(!(t instanceof Uint8Array))throw new Error("radix.encode input should be Uint8Array");return a(Array.from(t),256,e)},decode:t=>{if(!Array.isArray(t)||t.length&&"number"!=typeof t[0])throw new Error("radix.decode input should be array of strings");return Uint8Array.from(a(t,e,256))}}}function f(e,r=!1){if(t(e),e<=0||e>32)throw new Error("radix2: bits should be in (0..32]");if(h(8,e)>32||h(e,8)>32)throw new Error("radix2: carry overflow");return{encode:t=>{if(!(t instanceof Uint8Array))throw new Error("radix2.encode input should be Uint8Array");return l(Array.from(t),8,e,!r)},decode:t=>{if(!Array.isArray(t)||t.length&&"number"!=typeof t[0])throw new Error("radix2.decode input should be array of strings");return Uint8Array.from(l(t,e,8,r))}}}function d(e){if("function"!=typeof e)throw new Error("unsafeWrapper fn should be function");return function(...t){try{return e.apply(null,t)}catch(e){}}}function p(e,r){if(t(e),"function"!=typeof r)throw new Error("checksum fn should be function");return{encode(t){if(!(t instanceof Uint8Array))throw new Error("checksum.encode: input should be Uint8Array");const n=r(t).slice(0,e),o=new Uint8Array(t.length+e);return o.set(t),o.set(n,t.length),o},decode(t){if(!(t instanceof Uint8Array))throw new Error("checksum.decode: input should be Uint8Array");const n=t.slice(0,-e),o=r(n).slice(0,e),s=t.slice(-e);for(let t=0;t<e;t++)if(o[t]!==s[t])throw new Error("Invalid checksum");return n}}}e.utils={alphabet:n,chain:r,checksum:p,radix:u,radix2:f,join:o,padding:s},e.base16=r(f(4),n("0123456789ABCDEF"),o("")),e.base32=r(f(5),n("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"),s(5),o("")),e.base32hex=r(f(5),n("0123456789ABCDEFGHIJKLMNOPQRSTUV"),s(5),o("")),e.base32crockford=r(f(5),n("0123456789ABCDEFGHJKMNPQRSTVWXYZ"),o(""),i(e=>e.toUpperCase().replace(/O/g,"0").replace(/[IL]/g,"1"))),e.base64=r(f(6),n("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"),s(6),o("")),e.base64url=r(f(6),n("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"),s(6),o(""));const w=e=>r(u(58),n(e),o(""));e.base58=w("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"),e.base58flickr=w("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"),e.base58xrp=w("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz");const m=[0,2,3,5,6,7,9,10,11];e.base58xmr={encode(t){let r="";for(let n=0;n<t.length;n+=8){const o=t.subarray(n,n+8);r+=e.base58.encode(o).padStart(m[o.length],"1")}return r},decode(t){let r=[];for(let n=0;n<t.length;n+=11){const o=t.slice(n,n+11),s=m.indexOf(o.length),i=e.base58.decode(o);for(let e=0;e<i.length-s;e++)if(0!==i[e])throw new Error("base58xmr: wrong padding");r=r.concat(Array.from(i.slice(i.length-s)))}return Uint8Array.from(r)}},e.base58check=t=>r(p(4,e=>t(t(e))),e.base58);const y=r(n("qpzry9x8gf2tvdw0s3jn54khce6mua7l"),o("")),g=[996825010,642813549,513874426,1027748829,705979059];function b(e){const t=e>>25;let r=(33554431&e)<<5;for(let e=0;e<g.length;e++)1==(t>>e&1)&&(r^=g[e]);return r}function v(e,t,r=1){const n=e.length;let o=1;for(let t=0;t<n;t++){const r=e.charCodeAt(t);if(r<33||r>126)throw new Error(`Invalid prefix (${e})`);o=b(o)^r>>5}o=b(o);for(let t=0;t<n;t++)o=b(o)^31&e.charCodeAt(t);for(let e of t)o=b(o)^e;for(let e=0;e<6;e++)o=b(o);return o^=r,y.encode(l([o%2**30],30,5,!1))}function E(e){const t="bech32"===e?1:734539939,r=f(5),n=r.decode,o=r.encode,s=d(n);function i(e,r=90){if("string"!=typeof e)throw new Error("bech32.decode input should be string, not "+typeof e);if(e.length<8||!1!==r&&e.length>r)throw new TypeError(`Wrong string length: ${e.length} (${e}). Expected (8..${r})`);const n=e.toLowerCase();if(e!==n&&e!==e.toUpperCase())throw new Error("String must be lowercase or uppercase");const o=(e=n).lastIndexOf("1");if(0===o||-1===o)throw new Error('Letter "1" must be present between prefix and data only');const s=e.slice(0,o),i=e.slice(o+1);if(i.length<6)throw new Error("Data must be at least 6 characters long");const a=y.decode(i).slice(0,-6),c=v(s,a,t);if(!i.endsWith(c))throw new Error(`Invalid checksum in ${e}: expected "${c}"`);return{prefix:s,words:a}}return{encode:function(e,r,n=90){if("string"!=typeof e)throw new Error("bech32.encode prefix should be string, not "+typeof e);if(!Array.isArray(r)||r.length&&"number"!=typeof r[0])throw new Error("bech32.encode words should be array of numbers, not "+typeof r);const o=e.length+7+r.length;if(!1!==n&&o>n)throw new TypeError(`Length ${o} exceeds limit ${n}`);return`${e=e.toLowerCase()}1${y.encode(r)}${v(e,r,t)}`},decode:i,decodeToBytes:function(e){const{prefix:t,words:r}=i(e,!1);return{prefix:t,words:r,bytes:n(r)}},decodeUnsafe:d(i),fromWords:n,fromWordsUnsafe:s,toWords:o}}e.bech32=E("bech32"),e.bech32m=E("bech32m"),e.utf8={encode:e=>(new TextDecoder).decode(e),decode:e=>(new TextEncoder).encode(e)},e.hex=r(f(4),n("0123456789abcdef"),o(""),i(e=>{if("string"!=typeof e||e.length%2)throw new TypeError(`hex.decode: expected string, got ${typeof e} with length ${e.length}`);return e.toLowerCase()}));const x={utf8:e.utf8,hex:e.hex,base16:e.base16,base32:e.base32,base64:e.base64,base64url:e.base64url,base58:e.base58,base58xmr:e.base58xmr},A=`Invalid encoding type. Available types: ${Object.keys(x).join(", ")}`;e.bytesToString=(e,t)=>{if("string"!=typeof e||!x.hasOwnProperty(e))throw new TypeError(A);if(!(t instanceof Uint8Array))throw new TypeError("bytesToString() expects Uint8Array");return x[e].encode(t)},e.str=e.bytesToString,e.stringToBytes=(e,t)=>{if(!x.hasOwnProperty(e))throw new TypeError(A);if("string"!=typeof t)throw new TypeError("stringToBytes() expects string");return x[e].decode(t)},e.bytes=e.stringToBytes}(x)),x),n={bech32:"bc",pubKeyHash:0,scriptHash:5,validWitnessVersions:[0]},o={bech32:"tb",pubKeyHash:111,scriptHash:196,validWitnessVersions:[0]},s={bech32:"tbs",pubKeyHash:111,scriptHash:196,validWitnessVersions:[0]},i={bech32:"bcrt",pubKeyHash:111,scriptHash:196,validWitnessVersions:[0]},a={bech32:"sb",pubKeyHash:63,scriptHash:123,validWitnessVersions:[0]},c=["option_data_loss_protect","initial_routing_sync","option_upfront_shutdown_script","gossip_queries","var_onion_optin","gossip_queries_ex","option_static_remotekey","payment_secret","basic_mpp","option_support_large_channel"],h={m:BigInt(1e3),u:BigInt(1e6),n:BigInt(1e9),p:BigInt(1e12)},l=BigInt("2100000000000000000"),u=BigInt(1e11),f={payment_hash:1,payment_secret:16,description:13,payee:19,description_hash:23,expiry:6,min_final_cltv_expiry:24,fallback_address:9,route_hint:3,feature_bits:5,metadata:27},d={};for(let e=0,t=Object.keys(f);e<t.length;e++){const r=t[e],n=f[t[e]].toString();d[n]=r}const p={1:r=>t.encode(e.fromWordsUnsafe(r)),16:r=>t.encode(e.fromWordsUnsafe(r)),13:t=>r.encode(e.fromWordsUnsafe(t)),19:r=>t.encode(e.fromWordsUnsafe(r)),23:r=>t.encode(e.fromWordsUnsafe(r)),27:r=>t.encode(e.fromWordsUnsafe(r)),6:m,24:m,3:function(r){const n=[];let o,s,i,a,c,h=e.fromWordsUnsafe(r);for(;h.length>0;)o=t.encode(h.slice(0,33)),s=t.encode(h.slice(33,41)),i=parseInt(t.encode(h.slice(41,45)),16),a=parseInt(t.encode(h.slice(45,49)),16),c=parseInt(t.encode(h.slice(49,51)),16),h=h.slice(51),n.push({pubkey:o,short_channel_id:s,fee_base_msat:i,fee_proportional_millionths:a,cltv_expiry_delta:c});return n},5:function(e){const t=e.slice().reverse().map(e=>[!!(1&e),!!(2&e),!!(4&e),!!(8&e),!!(16&e)]).reduce((e,t)=>e.concat(t),[]);for(;t.length<2*c.length;)t.push(!1);const r={};c.forEach((e,n)=>{let o;o=t[2*n]?"required":t[2*n+1]?"supported":"unsupported",r[e]=o});const n=t.slice(2*c.length);return r.extra_bits={start_bit:2*c.length,bits:n,has_required:n.reduce((e,t,r)=>r%2!=0?e||!1:e||t,!1)},r}};function w(t){return r=>({tagCode:parseInt(t),words:e.encode("unknown",r,Number.MAX_SAFE_INTEGER)})}function m(e){return e.reverse().reduce((e,t,r)=>e+t*Math.pow(32,r),0)}function y(e,t){let r,n;if(e.slice(-1).match(/^[munp]$/))r=e.slice(-1),n=e.slice(0,-1);else{if(e.slice(-1).match(/^[^munp0-9]$/))throw new Error("Not a valid multiplier for the amount");n=e}if(!n.match(/^\d+$/))throw new Error("Not a valid human readable amount");const o=BigInt(n),s=r?o*u/h[r]:o*u;if("p"===r&&o%BigInt(10)!==BigInt(0)||s>l)throw new Error("Amount is outside of valid range");return t?s.toString():s}return v={decode:function(r,c){if("string"!=typeof r)throw new Error("Lightning Payment Request must be string");if("ln"!==r.slice(0,2).toLowerCase())throw new Error("Not a proper lightning payment request");const h=[],l=e.decode(r,Number.MAX_SAFE_INTEGER);r=r.toLowerCase();const u=l.prefix;let g=l.words,b=r.slice(u.length+1),v=g.slice(-104);g=g.slice(0,-104);let E=u.match(/^ln(\S+?)(\d*)([a-zA-Z]?)$/);if(E&&!E[2]&&(E=u.match(/^ln(\S+)$/)),!E)throw new Error("Not a proper lightning payment request");h.push({name:"lightning_network",letters:"ln"});const x=E[1];let A;if(c){if(void 0===c.bech32||void 0===c.pubKeyHash||void 0===c.scriptHash||!Array.isArray(c.validWitnessVersions))throw new Error("Invalid network");A=c}else switch(x){case n.bech32:A=n;break;case o.bech32:A=o;break;case s.bech32:A=s;break;case i.bech32:A=i;break;case a.bech32:A=a}if(!A||A.bech32!==x)throw new Error("Unknown coin bech32 prefix");h.push({name:"coin_network",letters:x,value:A});const k=E[2];let U;if(k){U=y(k+E[3],!0),h.push({name:"amount",letters:E[2]+E[3],value:U})}else U=null;h.push({name:"separator",letters:"1"});const $=m(g.slice(0,7));let L,I,N,R;for(g=g.slice(7),h.push({name:"timestamp",letters:b.slice(0,7),value:$}),b=b.slice(7);g.length>0;){const e=g[0].toString();L=d[e]||"unknown_tag",I=p[e]||w(e),g=g.slice(1),N=m(g.slice(0,2)),g=g.slice(2),R=g.slice(0,N),g=g.slice(N),h.push({name:L,tag:b[0],letters:b.slice(0,3+N),value:I(R)}),b=b.slice(3+N)}h.push({name:"signature",letters:b.slice(0,104),value:t.encode(e.fromWordsUnsafe(v))}),b=b.slice(104),h.push({name:"checksum",letters:b});let _={paymentRequest:r,sections:h,get expiry(){let e=h.find(e=>"expiry"===e.name);if(e)return S("timestamp")+e.value},get route_hints(){return h.filter(e=>"route_hint"===e.name).map(e=>e.value)}};for(let e in f)"route_hint"!==e&&Object.defineProperty(_,e,{get:()=>S(e)});return _;function S(e){let t=h.find(t=>t.name===e);return t?t.value:void 0}},hrpToMillisat:y}}();const k=e=>Uint8Array.from(e.match(/.{1,2}/g).map(e=>parseInt(e,16))),U=e=>{if(!e)return null;try{const t=A.decode(e);if(!t||!t.sections)return null;const r=t.sections.find(e=>"payment_hash"===e.name);if("payment_hash"!==r?.name||!r.value)return null;const n=r.value;let o=0,s=0,i="0";const a=t.sections.find(e=>"amount"===e.name);"amount"===a?.name&&a.value&&(i=a.value,s=parseInt(a.value),o=parseInt(a.value)/1e3);const c=t.sections.find(e=>"timestamp"===e.name);if("timestamp"!==c?.name||!c.value)return null;const h=c.value;let l;const u=t.sections.find(e=>"expiry"===e.name);"expiry"===u?.name&&(l=u.value);const f=t.sections.find(e=>"description"===e.name);return{paymentHash:n,satoshi:o,millisatoshi:s,amountRaw:i,timestamp:h,expiry:l,description:"description"===f?.name?f?.value:void 0}}catch{return null}};function $(e,t){try{if(!/^[0-9a-fA-F]{64}$/.test(e))return!1;if(!/^[0-9a-fA-F]{64}$/.test(t))return!1;return t===a(g(k(e)))}catch{return!1}}class L{constructor(e){if(this.paymentRequest=e.pr,!this.paymentRequest)throw new Error("Invalid payment request");const t=U(this.paymentRequest);if(!t)throw new Error("Failed to decode payment request");this.paymentHash=t.paymentHash,this.satoshi=t.satoshi,this.millisatoshi=t.millisatoshi,this.amountRaw=t.amountRaw,this.timestamp=t.timestamp,this.expiry=t.expiry,this.createdDate=new Date(1e3*this.timestamp),this.expiryDate=this.expiry?new Date(1e3*(this.timestamp+this.expiry)):void 0,this.description=t.description??null,this.verify=e.verify??null,this.preimage=e.preimage??null,this.successAction=e.successAction??null}async isPaid(){if(this.preimage)return this.validatePreimage(this.preimage);if(this.verify)return await this.verifyPayment();throw new Error("Could not verify payment")}validatePreimage(e){return!(!e||!this.paymentHash)&&$(e,this.paymentHash)}async verifyPayment(){try{if(!this.verify)throw new Error("LNURL verify not available");const e=await fetch(this.verify);if(!e.ok)throw new Error(`Verification request failed: ${e.status} ${e.statusText}`);const t=await e.json();return t.preimage&&(this.preimage=t.preimage),t.settled}catch(e){return console.error("Failed to check LNURL-verify",e),!1}}hasExpired(){const{expiryDate:e}=this;return!!e&&e.getTime()<Date.now()}}const I=e=>{if("keysend"!==e.tag)throw new Error("Invalid keysend params");if("OK"!==e.status)throw new Error("Keysend status not OK");if(!e.pubkey)throw new Error("Pubkey does not exist");const t=e.pubkey;let r,n;return e.customData&&e.customData[0]&&(r=e.customData[0].customKey,n=e.customData[0].customValue),{destination:t,customKey:r,customValue:n}};async function N({satoshi:e,comment:t,p:r,e:n,relays:o},s={}){const i=s.nostr||globalThis.nostr;if(!i)throw new Error("nostr option or window.nostr is not available");const a=[["relays",...o],["amount",e.toString()]];r&&a.push(["p",r]),n&&a.push(["e",n]);const c={pubkey:await i.getPublicKey(),created_at:Math.floor(Date.now()/1e3),kind:9734,tags:a,content:t??""};return c.id=S(c),await i.signEvent(c)}function R(e){if("string"!=typeof e.content)return!1;if("number"!=typeof e.created_at)return!1;if(!Array.isArray(e.tags))return!1;for(let t=0;t<e.tags.length;t++){const r=e.tags[t];if(!Array.isArray(r))return!1;for(let e=0;e<r.length;e++)if("object"==typeof r[e])return!1}return!0}function _(e){if(!R(e))throw new Error("can't serialize event with wrong or missing properties");return JSON.stringify([0,e.pubkey,e.created_at,e.kind,e.tags,e.content])}function S(e){return a(g(_(e)))}function D(e,t){let r,n;return t&&e&&(r=e.names?.[t],n=r?e.relays?.[r]:void 0),[e,r,n]}const W=/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/,T=e=>!!e&&W.test(e),P=({amount:e,min:t,max:r})=>e>0&&e>=t&&e<=r,H=async e=>{if("payRequest"!==e.tag)throw new Error("Invalid pay service params");const t=(e.callback+"").trim();if(!T(t))throw new Error("Callback must be a valid url");const r=Math.ceil(Number(e.minSendable||0)),n=Math.floor(Number(e.maxSendable));if(!r||!n||r>n)throw new Error("Invalid pay service params");let o,s;try{o=JSON.parse(e.metadata+""),s=a(g(e.metadata+""))}catch{o=[],s=a(g("[]"))}let i="",c="",h="",l="";for(let e=0;e<o.length;e++){const[t,r]=o[e];switch(t){case"text/plain":h=r;break;case"text/identifier":l=r;break;case"text/email":i=r;break;case"image/png;base64":case"image/jpeg;base64":c="data:"+t+","+r}}const u=e.payerData;let f;try{f=new URL(t).hostname}catch{}return{callback:t,fixed:r===n,min:r,max:n,domain:f,metadata:o,metadataHash:s,identifier:l,email:i,description:h,image:c,payerData:u,commentAllowed:Number(e.commentAllowed)||0,rawData:e,allowsNostr:e.allowsNostr||!1}},C=async(e,t)=>{const{boost:r}=e;t||(t={});const n=t.webln||globalThis.webln;if(!n)throw new Error("WebLN not available");if(!n.keysend)throw new Error("Keysend not available in current WebLN provider");const o=e.amount||Math.floor(r.value_msat/1e3),s={destination:e.destination,amount:o,customRecords:{7629169:JSON.stringify(r)}};e.customKey&&e.customValue&&(s.customRecords[e.customKey]=e.customValue),await n.enable();return await n.keysend(s)},B=/^((?:[^<>()[\]\\.,;:\s@"]+(?:\.[^<>()[\]\\.,;:\s@"]+)*)|(?:".+"))@((?:\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(?:(?:[a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,O="https://api.getalby.com/lnurl";function j(e,t){return{payInvoice:async r=>{const n=new L({pr:r.invoice});if(n.satoshi>t)throw new Error(`Invoice amount (${n.satoshi} sats) exceeds maxAmount (${t} sats)`);return e.payInvoice(r)}}}const q=e=>{const t=e.replace("L402","").replace("LSAT","").trim(),r={},n=/(\w+)=("([^"]*)"|'([^']*)'|([^,]*))/g;let o;for(;null!==(o=n.exec(t));)r[o[1]]=o[3]||o[4]||o[5];if(!r.token&&r.macaroon&&(r.token=r.macaroon,delete r.macaroon),!("token"in r)||"string"!=typeof r.token)throw new Error("No macaroon or token found in www-authenticate header");if(!("invoice"in r)||"string"!=typeof r.invoice)throw new Error("No invoice found in www-authenticate header");return r},F=async(e,t,r,n,o)=>{const s=q(e),i=s.token||s.macaroon,a=s.invoice;if(!i)throw new Error("L402: missing token/macaroon in WWW-Authenticate header");if(!a)throw new Error("L402: missing invoice in WWW-Authenticate header");const c=await o.payInvoice({invoice:a});return n.set("Authorization",`L402 ${i}:${c.preimage}`),fetch(t,r)},K=e=>{let t;try{t=JSON.parse(decodeURIComponent(escape(atob(e))))}catch(e){throw new Error("x402: invalid PAYMENT-REQUIRED header (not valid base64-encoded JSON)")}if(!Array.isArray(t.accepts)||0===t.accepts.length)throw new Error("x402: PAYMENT-REQUIRED header contains no payment options");return{accepts:t.accepts}},M=e=>{let t;try{({accepts:t}=K(e))}catch(e){return null}const r=t.find(e=>"lightning"===e?.extra?.paymentMethod);return r?.extra?.invoice?r:null},V=async(e,t,r,n,o)=>{const{accepts:s}=K(e),i=s.find(e=>"lightning"===e?.extra?.paymentMethod);if(!i)throw new Error("x402: unsupported x402 network, only Bitcoin lightning network is supported.");if(!i.extra?.invoice)throw new Error("x402: payment requirements missing lightning invoice");const a=new L({pr:i.extra.invoice});if(a.amountRaw!=i.amount)throw new Error(`Invalid invoice amount: ${a.amountRaw}. expected ${i.amount}`);return await o.payInvoice({invoice:a.paymentRequest}),n.set("payment-signature",((e,t,r,n)=>{const o=JSON.stringify({x402Version:2,scheme:e,network:t,payload:{invoice:r},accepted:n});return btoa(unescape(encodeURIComponent(o)))})(i.scheme,i.network,a.paymentRequest,i)),fetch(t,r)},z=e=>{if(!e.trimStart().toLowerCase().startsWith("payment"))return null;const t=e.slice(e.toLowerCase().indexOf("payment")+7).trim(),r={},n=/(\w+)=("([^"]*)"|'([^']*)'|([^,\s]*))/g;let o;for(;null!==(o=n.exec(t));)r[o[1]]=o[3]??o[4]??o[5]??"";return"lightning"===r.method&&"charge"===r.intent&&r.id&&r.realm&&r.request?{id:r.id,realm:r.realm,method:r.method,intent:r.intent,request:r.request,...r.expires?{expires:r.expires}:{}}:null},J=e=>{if(null===e||"object"!=typeof e)return JSON.stringify(e);if(Array.isArray(e))return"["+e.map(J).join(",")+"]";return"{"+Object.keys(e).sort().map(t=>JSON.stringify(t)+":"+J(e[t])).join(",")+"}"},G=(e,t,r)=>{const n={id:e.id,intent:e.intent,method:e.method,realm:e.realm,request:e.request};e.expires&&(n.expires=e.expires);return(e=>{const t=(new TextEncoder).encode(e);let r="";for(let e=0;e<t.length;e++)r+=String.fromCharCode(t[e]);return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")})(J({challenge:n,payload:{preimage:t}}))},Z=async(e,t,r,n,o)=>{const s=z(e);if(!s)throw new Error("mpp: invalid or unsupported WWW-Authenticate challenge (expected Payment method=lightning intent=charge)");let i;try{i=JSON.parse((e=>{const t=e.replace(/-/g,"+").replace(/_/g,"/"),r=atob(t),n=new Uint8Array(r.length);for(let e=0;e<r.length;e++)n[e]=r.charCodeAt(e);return new TextDecoder("utf-8").decode(n)})(s.request))}catch(e){throw new Error("mpp: invalid request auth-param (not valid base64url-encoded JSON)")}const a=i.methodDetails?.invoice;if(!a)throw new Error("mpp: missing invoice in charge request");const c=await o.payInvoice({invoice:a}),h=G(s,c.preimage);return n.set("Authorization",`Payment ${h}`),fetch(t,r)};async function X(e,t){const{createHmac:r}=await import("crypto");return r("sha256",e).update(t).digest("hex")}const Y=async e=>{const t="https://getalby.com/api/rates/"+e.toLowerCase()+".json",r=await fetch(t);if(!r.ok)throw new Error(`Failed to fetch rate: ${r.status} ${r.statusText}`);return(await r.json()).rate_float/1e8},Q=async({satoshi:e,currency:t})=>{const r=await Y(t);return Number(e)*r};e.DEFAULT_PROXY=O,e.Invoice=L,e.LN_ADDRESS_REGEX=B,e.LightningAddress=class{constructor(e,t){this.address=e,this.options={proxy:O},this.options=Object.assign(this.options,t),this.parse(),this.webln=this.options.webln}parse(){const e=B.exec(this.address.toLowerCase());e&&(this.username=e[1],this.domain=e[2])}getWebLN(){return this.webln||globalThis.webln}async fetch(){return this.options.proxy?this.fetchWithProxy():this.fetchWithoutProxy()}async fetchWithProxy(){const e=await fetch(`${this.options.proxy}/lightning-address-details?${new URLSearchParams({ln:this.address}).toString()}`);if(!e.ok)throw new Error(`Failed to fetch lnurl info: ${e.status} ${e.statusText}`);const t=await e.json();await this.parseLnUrlPayResponse(t.lnurlp),this.parseKeysendResponse(t.keysend),this.parseNostrResponse(t.nostr)}async fetchWithoutProxy(){this.domain&&this.username&&await Promise.all([this.fetchLnurlData(),this.fetchKeysendData(),this.fetchNostrData()])}async fetchLnurlData(){const e=await fetch(this.lnurlpUrl());if(e.ok){const t=await e.json();await this.parseLnUrlPayResponse(t)}}async fetchKeysendData(){const e=await fetch(this.keysendUrl());if(e.ok){const t=await e.json();this.parseKeysendResponse(t)}}async fetchNostrData(){const e=await fetch(this.nostrUrl());if(e.ok){const t=await e.json();this.parseNostrResponse(t)}}lnurlpUrl(){return`https://${this.domain}/.well-known/lnurlp/${this.username}`}keysendUrl(){return`https://${this.domain}/.well-known/keysend/${this.username}`}nostrUrl(){return`https://${this.domain}/.well-known/nostr.json?name=${this.username}`}async generateInvoice(e){let t;if(this.options.proxy){const r=await fetch(`${this.options.proxy}/generate-invoice?${new URLSearchParams({ln:this.address,...e}).toString()}`);if(!r.ok)throw new Error(`Failed to generate invoice: ${r.status} ${r.statusText}`);t=(await r.json()).invoice}else{if(!this.lnurlpData)throw new Error("No lnurlpData available. Please call fetch() first.");if(!this.lnurlpData.callback||!T(this.lnurlpData.callback))throw new Error("Valid callback does not exist in lnurlpData");const r=new URL(this.lnurlpData.callback);r.search=new URLSearchParams(e).toString();const n=await fetch(r.toString());if(!n.ok)throw new Error(`Failed to generate invoice: ${n.status} ${n.statusText}`);t=await n.json()}const r=t&&t.pr&&t.pr.toString();if(!r)throw new Error("Invalid pay service invoice");const n={pr:r};if(t&&t.verify&&(n.verify=t.verify.toString()),t&&t.successAction&&"object"==typeof t.successAction){const{tag:e,message:r,description:o,url:s}=t.successAction;"message"===e?n.successAction={tag:e,message:r}:"url"===e&&(n.successAction={tag:e,description:o,url:s})}return new L(n)}async requestInvoice(e){if(!this.lnurlpData)throw new Error("No lnurlpData available. Please call fetch() first.");const t=1e3*e.satoshi,{commentAllowed:r,min:n,max:o}=this.lnurlpData;if(!P({amount:t,min:n,max:o}))throw new Error("Invalid amount");if(e.comment&&r&&r>0&&e.comment.length>r)throw new Error(`The comment length must be ${r} characters or fewer`);const s={amount:t.toString()};return e.comment&&(s.comment=e.comment),e.payerdata&&(s.payerdata=JSON.stringify(e.payerdata)),this.generateInvoice(s)}async boost(e,t=0){if(!this.keysendData)throw new Error("No keysendData available. Please call fetch() first.");const{destination:r,customKey:n,customValue:o}=this.keysendData,s=this.getWebLN();if(!s)throw new Error("WebLN not available");return C({destination:r,customKey:n,customValue:o,amount:t,boost:e},{webln:s})}async zapInvoice({satoshi:e,comment:t,relays:r,e:n},o={}){if(!this.lnurlpData)throw new Error("No lnurlpData available. Please call fetch() first.");if(!this.nostrPubkey)throw new Error("Nostr Pubkey is missing");const s=this.nostrPubkey,i=1e3*e,{allowsNostr:a,min:c,max:h}=this.lnurlpData;if(!P({amount:i,min:c,max:h}))throw new Error("Invalid amount");if(!a)throw new Error("Your provider does not support zaps");const l=await N({satoshi:i,comment:t,p:s,e:n,relays:r},o),u={amount:i.toString(),nostr:JSON.stringify(l)};return await this.generateInvoice(u)}async zap(e,t={}){const r=this.zapInvoice(e,t),n=this.getWebLN();if(!n)throw new Error("WebLN not available");await n.enable();return n.sendPayment((await r).paymentRequest)}async parseLnUrlPayResponse(e){e&&(this.lnurlpData=await H(e))}parseKeysendResponse(e){e&&(this.keysendData=I(e))}parseNostrResponse(e){e&&([this.nostrData,this.nostrPubkey,this.nostrRelays]=D(e,this.username))}},e.createGuardedWallet=j,e.decodeInvoice=U,e.fetch402=async(e,t,r)=>{const n=r.maxAmount?j(r.wallet,r.maxAmount):r.wallet;t||(t={}),t.cache="no-store",t.mode="cors";const o=new Headers(t.headers??void 0);t.headers=o;const s=await fetch(e,t),i=s.headers.get("www-authenticate");if(i){const r=i.trimStart().toLowerCase();if(r.startsWith("l402")||r.startsWith("lsat"))return F(i,e,t,o,n)}if(i&&z(i))return Z(i,e,t,o,n);const a=s.headers.get("PAYMENT-REQUIRED");return a&&M(a)?V(a,e,t,o,n):s},e.fetchWithL402=async(e,t,r)=>{const n=r.wallet;if(!n)throw new Error("wallet is missing");t||(t={}),t.cache="no-store",t.mode="cors";const o=new Headers(t.headers??void 0);t.headers=o;const s=await fetch(e,t),i=s.headers.get("www-authenticate");return i?F(i,e,t,o,n):s},e.fetchWithMpp=async(e,t,r)=>{const n=r.wallet;if(!n)throw new Error("wallet is missing");t||(t={}),t.cache="no-store",t.mode="cors";const o=new Headers(t.headers??void 0);t.headers=o;const s=await fetch(e,t),i=s.headers.get("www-authenticate");return i&&i.trimStart().toLowerCase().startsWith("payment")?Z(i,e,t,o,n):s},e.fetchWithX402=async(e,t,r)=>{const n=r.wallet;t||(t={}),t.cache="no-store",t.mode="cors";const o=new Headers(t.headers??void 0);t.headers=o;const s=await fetch(e,t),i=s.headers.get("PAYMENT-REQUIRED");return i?V(i,e,t,o,n):s},e.findX402LightningRequirements=M,e.fromHexString=k,e.generateZapEvent=N,e.getEventHash=S,e.getFiatBtcRate=Y,e.getFiatCurrencies=async()=>{const e=await fetch("https://getalby.com/api/rates");if(!e.ok)throw new Error(`Failed to fetch currencies: ${e.status} ${e.statusText}`);const t=await e.json();return Object.entries(t).filter(([e])=>"BTC"!==e.toUpperCase()).map(([e,t])=>({code:e.toUpperCase(),name:t.name,priority:t.priority,symbol:t.symbol})).sort((e,t)=>e.name.localeCompare(t.name)).sort((e,t)=>e.priority-t.priority)},e.getFiatValue=Q,e.getFormattedFiatValue=async({satoshi:e,currency:t,locale:r})=>{r||(r="en");return(await Q({satoshi:e,currency:t})).toLocaleString(r,{style:"currency",currency:t})},e.getSatoshiValue=async({amount:e,currency:t})=>{const r=await Y(t);return Math.floor(Number(e)/r)},e.isUrl=T,e.isValidAmount=P,e.issueL402Macaroon=async function(e,t,r){if(void 0!==r&&Object.prototype.hasOwnProperty.call(r,"paymentHash"))throw new Error("paymentHash is reserved");const n={...r,paymentHash:t},o=Buffer.from(JSON.stringify(n)).toString("base64url");return`${o}.${await X(e,o)}`},e.makeL402AuthenticateHeader=e=>{if(!e.token)throw new Error("token must be provided");return`L402 version="0" token="${e.token}", invoice="${e.invoice}"`},e.parseKeysendResponse=I,e.parseL402=q,e.parseL402Authorization=function(e){const t=e.replace(/^LSAT /,"L402 "),r="L402 ";if(!t.startsWith(r))return null;const n=t.slice(5),o=n.indexOf(":");if(-1===o)throw new Error("Invalid authorization header value");return{token:n.slice(0,o),preimage:n.slice(o+1)}},e.parseLnUrlPayResponse=H,e.parseNostrResponse=D,e.sendBoostagram=C,e.serializeEvent=_,e.validateEvent=R,e.validatePreimage=$,e.verifyL402Macaroon=async function(e,t){const{timingSafeEqual:r}=await import("crypto"),n=t.lastIndexOf(".");if(-1===n)throw new Error("Invalid macaroon token");const o=t.slice(0,n),s=t.slice(n+1),i=await X(e,o);try{if(!r(Buffer.from(s,"hex"),Buffer.from(i,"hex")))throw new Error("Invalid macaroon token")}catch(e){throw new Error("Invalid macaroon token")}try{const e=JSON.parse(Buffer.from(o,"base64url").toString("utf8"));if(null===e||"object"!=typeof e||Array.isArray(e)||"string"!=typeof e.paymentHash)throw new Error("Invalid macaroon payload");return e}catch{throw new Error("Invalid macaroon token")}}});
Object.defineProperty(e,"__esModule",{value:!0}),e.bytes=e.stringToBytes=e.str=e.bytesToString=e.hex=e.utf8=e.bech32m=e.bech32=e.base58check=e.base58xmr=e.base58xrp=e.base58flickr=e.base58=e.base64url=e.base64=e.base32crockford=e.base32hex=e.base32=e.base16=e.utils=e.assertNumber=void 0,e.assertNumber=t;const c=(e,t)=>t?c(t,e%t):e,h=(e,t)=>e+(t-c(e,t));function l(e,r,n,o){if(!Array.isArray(e))throw new Error("convertRadix2: data should be array");if(r<=0||r>32)throw new Error(`convertRadix2: wrong from=${r}`);if(n<=0||n>32)throw new Error(`convertRadix2: wrong to=${n}`);if(h(r,n)>32)throw new Error(`convertRadix2: carry overflow from=${r} to=${n} carryBits=${h(r,n)}`);let s=0,i=0;const a=2**n-1,c=[];for(const o of e){if(t(o),o>=2**r)throw new Error(`convertRadix2: invalid data word=${o} from=${r}`);if(s=s<<r|o,i+r>32)throw new Error(`convertRadix2: carry overflow pos=${i} from=${r}`);for(i+=r;i>=n;i-=n)c.push((s>>i-n&a)>>>0);s&=2**i-1}if(s=s<<n-i&a,!o&&i>=r)throw new Error("Excess padding");if(!o&&s)throw new Error(`Non-zero padding: ${s}`);return o&&i>0&&c.push(s>>>0),c}function u(e){return t(e),{encode:t=>{if(!(t instanceof Uint8Array))throw new Error("radix.encode input should be Uint8Array");return a(Array.from(t),256,e)},decode:t=>{if(!Array.isArray(t)||t.length&&"number"!=typeof t[0])throw new Error("radix.decode input should be array of strings");return Uint8Array.from(a(t,e,256))}}}function d(e,r=!1){if(t(e),e<=0||e>32)throw new Error("radix2: bits should be in (0..32]");if(h(8,e)>32||h(e,8)>32)throw new Error("radix2: carry overflow");return{encode:t=>{if(!(t instanceof Uint8Array))throw new Error("radix2.encode input should be Uint8Array");return l(Array.from(t),8,e,!r)},decode:t=>{if(!Array.isArray(t)||t.length&&"number"!=typeof t[0])throw new Error("radix2.decode input should be array of strings");return Uint8Array.from(l(t,e,8,r))}}}function f(e){if("function"!=typeof e)throw new Error("unsafeWrapper fn should be function");return function(...t){try{return e.apply(null,t)}catch(e){}}}function p(e,r){if(t(e),"function"!=typeof r)throw new Error("checksum fn should be function");return{encode(t){if(!(t instanceof Uint8Array))throw new Error("checksum.encode: input should be Uint8Array");const n=r(t).slice(0,e),o=new Uint8Array(t.length+e);return o.set(t),o.set(n,t.length),o},decode(t){if(!(t instanceof Uint8Array))throw new Error("checksum.decode: input should be Uint8Array");const n=t.slice(0,-e),o=r(n).slice(0,e),s=t.slice(-e);for(let t=0;t<e;t++)if(o[t]!==s[t])throw new Error("Invalid checksum");return n}}}e.utils={alphabet:n,chain:r,checksum:p,radix:u,radix2:d,join:o,padding:s},e.base16=r(d(4),n("0123456789ABCDEF"),o("")),e.base32=r(d(5),n("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"),s(5),o("")),e.base32hex=r(d(5),n("0123456789ABCDEFGHIJKLMNOPQRSTUV"),s(5),o("")),e.base32crockford=r(d(5),n("0123456789ABCDEFGHJKMNPQRSTVWXYZ"),o(""),i(e=>e.toUpperCase().replace(/O/g,"0").replace(/[IL]/g,"1"))),e.base64=r(d(6),n("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"),s(6),o("")),e.base64url=r(d(6),n("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"),s(6),o(""));const w=e=>r(u(58),n(e),o(""));e.base58=w("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"),e.base58flickr=w("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"),e.base58xrp=w("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz");const m=[0,2,3,5,6,7,9,10,11];e.base58xmr={encode(t){let r="";for(let n=0;n<t.length;n+=8){const o=t.subarray(n,n+8);r+=e.base58.encode(o).padStart(m[o.length],"1")}return r},decode(t){let r=[];for(let n=0;n<t.length;n+=11){const o=t.slice(n,n+11),s=m.indexOf(o.length),i=e.base58.decode(o);for(let e=0;e<i.length-s;e++)if(0!==i[e])throw new Error("base58xmr: wrong padding");r=r.concat(Array.from(i.slice(i.length-s)))}return Uint8Array.from(r)}},e.base58check=t=>r(p(4,e=>t(t(e))),e.base58);const y=r(n("qpzry9x8gf2tvdw0s3jn54khce6mua7l"),o("")),g=[996825010,642813549,513874426,1027748829,705979059];function b(e){const t=e>>25;let r=(33554431&e)<<5;for(let e=0;e<g.length;e++)1==(t>>e&1)&&(r^=g[e]);return r}function v(e,t,r=1){const n=e.length;let o=1;for(let t=0;t<n;t++){const r=e.charCodeAt(t);if(r<33||r>126)throw new Error(`Invalid prefix (${e})`);o=b(o)^r>>5}o=b(o);for(let t=0;t<n;t++)o=b(o)^31&e.charCodeAt(t);for(let e of t)o=b(o)^e;for(let e=0;e<6;e++)o=b(o);return o^=r,y.encode(l([o%2**30],30,5,!1))}function E(e){const t="bech32"===e?1:734539939,r=d(5),n=r.decode,o=r.encode,s=f(n);function i(e,r=90){if("string"!=typeof e)throw new Error("bech32.decode input should be string, not "+typeof e);if(e.length<8||!1!==r&&e.length>r)throw new TypeError(`Wrong string length: ${e.length} (${e}). Expected (8..${r})`);const n=e.toLowerCase();if(e!==n&&e!==e.toUpperCase())throw new Error("String must be lowercase or uppercase");const o=(e=n).lastIndexOf("1");if(0===o||-1===o)throw new Error('Letter "1" must be present between prefix and data only');const s=e.slice(0,o),i=e.slice(o+1);if(i.length<6)throw new Error("Data must be at least 6 characters long");const a=y.decode(i).slice(0,-6),c=v(s,a,t);if(!i.endsWith(c))throw new Error(`Invalid checksum in ${e}: expected "${c}"`);return{prefix:s,words:a}}return{encode:function(e,r,n=90){if("string"!=typeof e)throw new Error("bech32.encode prefix should be string, not "+typeof e);if(!Array.isArray(r)||r.length&&"number"!=typeof r[0])throw new Error("bech32.encode words should be array of numbers, not "+typeof r);const o=e.length+7+r.length;if(!1!==n&&o>n)throw new TypeError(`Length ${o} exceeds limit ${n}`);return`${e=e.toLowerCase()}1${y.encode(r)}${v(e,r,t)}`},decode:i,decodeToBytes:function(e){const{prefix:t,words:r}=i(e,!1);return{prefix:t,words:r,bytes:n(r)}},decodeUnsafe:f(i),fromWords:n,fromWordsUnsafe:s,toWords:o}}e.bech32=E("bech32"),e.bech32m=E("bech32m"),e.utf8={encode:e=>(new TextDecoder).decode(e),decode:e=>(new TextEncoder).encode(e)},e.hex=r(d(4),n("0123456789abcdef"),o(""),i(e=>{if("string"!=typeof e||e.length%2)throw new TypeError(`hex.decode: expected string, got ${typeof e} with length ${e.length}`);return e.toLowerCase()}));const x={utf8:e.utf8,hex:e.hex,base16:e.base16,base32:e.base32,base64:e.base64,base64url:e.base64url,base58:e.base58,base58xmr:e.base58xmr},A=`Invalid encoding type. Available types: ${Object.keys(x).join(", ")}`;e.bytesToString=(e,t)=>{if("string"!=typeof e||!x.hasOwnProperty(e))throw new TypeError(A);if(!(t instanceof Uint8Array))throw new TypeError("bytesToString() expects Uint8Array");return x[e].encode(t)},e.str=e.bytesToString,e.stringToBytes=(e,t)=>{if(!x.hasOwnProperty(e))throw new TypeError(A);if("string"!=typeof t)throw new TypeError("stringToBytes() expects string");return x[e].decode(t)},e.bytes=e.stringToBytes}(x)),x),n={bech32:"bc",pubKeyHash:0,scriptHash:5,validWitnessVersions:[0]},o={bech32:"tb",pubKeyHash:111,scriptHash:196,validWitnessVersions:[0]},s={bech32:"tbs",pubKeyHash:111,scriptHash:196,validWitnessVersions:[0]},i={bech32:"bcrt",pubKeyHash:111,scriptHash:196,validWitnessVersions:[0]},a={bech32:"sb",pubKeyHash:63,scriptHash:123,validWitnessVersions:[0]},c=["option_data_loss_protect","initial_routing_sync","option_upfront_shutdown_script","gossip_queries","var_onion_optin","gossip_queries_ex","option_static_remotekey","payment_secret","basic_mpp","option_support_large_channel"],h={m:BigInt(1e3),u:BigInt(1e6),n:BigInt(1e9),p:BigInt(1e12)},l=BigInt("2100000000000000000"),u=BigInt(1e11),d={payment_hash:1,payment_secret:16,description:13,payee:19,description_hash:23,expiry:6,min_final_cltv_expiry:24,fallback_address:9,route_hint:3,feature_bits:5,metadata:27},f={};for(let e=0,t=Object.keys(d);e<t.length;e++){const r=t[e],n=d[t[e]].toString();f[n]=r}const p={1:r=>t.encode(e.fromWordsUnsafe(r)),16:r=>t.encode(e.fromWordsUnsafe(r)),13:t=>r.encode(e.fromWordsUnsafe(t)),19:r=>t.encode(e.fromWordsUnsafe(r)),23:r=>t.encode(e.fromWordsUnsafe(r)),27:r=>t.encode(e.fromWordsUnsafe(r)),6:m,24:m,3:function(r){const n=[];let o,s,i,a,c,h=e.fromWordsUnsafe(r);for(;h.length>0;)o=t.encode(h.slice(0,33)),s=t.encode(h.slice(33,41)),i=parseInt(t.encode(h.slice(41,45)),16),a=parseInt(t.encode(h.slice(45,49)),16),c=parseInt(t.encode(h.slice(49,51)),16),h=h.slice(51),n.push({pubkey:o,short_channel_id:s,fee_base_msat:i,fee_proportional_millionths:a,cltv_expiry_delta:c});return n},5:function(e){const t=e.slice().reverse().map(e=>[!!(1&e),!!(2&e),!!(4&e),!!(8&e),!!(16&e)]).reduce((e,t)=>e.concat(t),[]);for(;t.length<2*c.length;)t.push(!1);const r={};c.forEach((e,n)=>{let o;o=t[2*n]?"required":t[2*n+1]?"supported":"unsupported",r[e]=o});const n=t.slice(2*c.length);return r.extra_bits={start_bit:2*c.length,bits:n,has_required:n.reduce((e,t,r)=>r%2!=0?e||!1:e||t,!1)},r}};function w(t){return r=>({tagCode:parseInt(t),words:e.encode("unknown",r,Number.MAX_SAFE_INTEGER)})}function m(e){return e.reverse().reduce((e,t,r)=>e+t*Math.pow(32,r),0)}function y(e,t){let r,n;if(e.slice(-1).match(/^[munp]$/))r=e.slice(-1),n=e.slice(0,-1);else{if(e.slice(-1).match(/^[^munp0-9]$/))throw new Error("Not a valid multiplier for the amount");n=e}if(!n.match(/^\d+$/))throw new Error("Not a valid human readable amount");const o=BigInt(n),s=r?o*u/h[r]:o*u;if("p"===r&&o%BigInt(10)!==BigInt(0)||s>l)throw new Error("Amount is outside of valid range");return t?s.toString():s}return v={decode:function(r,c){if("string"!=typeof r)throw new Error("Lightning Payment Request must be string");if("ln"!==r.slice(0,2).toLowerCase())throw new Error("Not a proper lightning payment request");const h=[],l=e.decode(r,Number.MAX_SAFE_INTEGER);r=r.toLowerCase();const u=l.prefix;let g=l.words,b=r.slice(u.length+1),v=g.slice(-104);g=g.slice(0,-104);let E=u.match(/^ln(\S+?)(\d*)([a-zA-Z]?)$/);if(E&&!E[2]&&(E=u.match(/^ln(\S+)$/)),!E)throw new Error("Not a proper lightning payment request");h.push({name:"lightning_network",letters:"ln"});const x=E[1];let A;if(c){if(void 0===c.bech32||void 0===c.pubKeyHash||void 0===c.scriptHash||!Array.isArray(c.validWitnessVersions))throw new Error("Invalid network");A=c}else switch(x){case n.bech32:A=n;break;case o.bech32:A=o;break;case s.bech32:A=s;break;case i.bech32:A=i;break;case a.bech32:A=a}if(!A||A.bech32!==x)throw new Error("Unknown coin bech32 prefix");h.push({name:"coin_network",letters:x,value:A});const k=E[2];let L;if(k){L=y(k+E[3],!0),h.push({name:"amount",letters:E[2]+E[3],value:L})}else L=null;h.push({name:"separator",letters:"1"});const $=m(g.slice(0,7));let U,I,N,R;for(g=g.slice(7),h.push({name:"timestamp",letters:b.slice(0,7),value:$}),b=b.slice(7);g.length>0;){const e=g[0].toString();U=f[e]||"unknown_tag",I=p[e]||w(e),g=g.slice(1),N=m(g.slice(0,2)),g=g.slice(2),R=g.slice(0,N),g=g.slice(N),h.push({name:U,tag:b[0],letters:b.slice(0,3+N),value:I(R)}),b=b.slice(3+N)}h.push({name:"signature",letters:b.slice(0,104),value:t.encode(e.fromWordsUnsafe(v))}),b=b.slice(104),h.push({name:"checksum",letters:b});let _={paymentRequest:r,sections:h,get expiry(){let e=h.find(e=>"expiry"===e.name);if(e)return S("timestamp")+e.value},get route_hints(){return h.filter(e=>"route_hint"===e.name).map(e=>e.value)}};for(let e in d)"route_hint"!==e&&Object.defineProperty(_,e,{get:()=>S(e)});return _;function S(e){let t=h.find(t=>t.name===e);return t?t.value:void 0}},hrpToMillisat:y}}();const k=e=>Uint8Array.from(e.match(/.{1,2}/g).map(e=>parseInt(e,16))),L=e=>{if(!e)return null;try{const t=A.decode(e);if(!t||!t.sections)return null;const r=t.sections.find(e=>"payment_hash"===e.name);if("payment_hash"!==r?.name||!r.value)return null;const n=r.value;let o=0,s=0,i="0";const a=t.sections.find(e=>"amount"===e.name);"amount"===a?.name&&a.value&&(i=a.value,s=parseInt(a.value),o=parseInt(a.value)/1e3);const c=t.sections.find(e=>"timestamp"===e.name);if("timestamp"!==c?.name||!c.value)return null;const h=c.value;let l;const u=t.sections.find(e=>"expiry"===e.name);"expiry"===u?.name&&(l=u.value);const d=t.sections.find(e=>"description"===e.name);return{paymentHash:n,satoshi:o,millisatoshi:s,amountRaw:i,timestamp:h,expiry:l,description:"description"===d?.name?d?.value:void 0}}catch{return null}};function $(e,t){try{if(!/^[0-9a-fA-F]{64}$/.test(e))return!1;if(!/^[0-9a-fA-F]{64}$/.test(t))return!1;return t===a(g(k(e)))}catch{return!1}}class U{constructor(e){if(this.paymentRequest=e.pr,!this.paymentRequest)throw new Error("Invalid payment request");const t=L(this.paymentRequest);if(!t)throw new Error("Failed to decode payment request");this.paymentHash=t.paymentHash,this.satoshi=t.satoshi,this.millisatoshi=t.millisatoshi,this.amountRaw=t.amountRaw,this.timestamp=t.timestamp,this.expiry=t.expiry,this.createdDate=new Date(1e3*this.timestamp),this.expiryDate=this.expiry?new Date(1e3*(this.timestamp+this.expiry)):void 0,this.description=t.description??null,this.verify=e.verify??null,this.preimage=e.preimage??null,this.successAction=e.successAction??null}async isPaid(){if(this.preimage)return this.validatePreimage(this.preimage);if(this.verify)return await this.verifyPayment();throw new Error("Could not verify payment")}validatePreimage(e){return!(!e||!this.paymentHash)&&$(e,this.paymentHash)}async verifyPayment(){try{if(!this.verify)throw new Error("LNURL verify not available");const e=await fetch(this.verify);if(!e.ok)throw new Error(`Verification request failed: ${e.status} ${e.statusText}`);const t=await e.json();return t.preimage&&(this.preimage=t.preimage),t.settled}catch(e){return console.error("Failed to check LNURL-verify",e),!1}}hasExpired(){const{expiryDate:e}=this;return!!e&&e.getTime()<Date.now()}}const I=e=>{if("keysend"!==e.tag)throw new Error("Invalid keysend params");if("OK"!==e.status)throw new Error("Keysend status not OK");if(!e.pubkey)throw new Error("Pubkey does not exist");const t=e.pubkey;let r,n;return e.customData&&e.customData[0]&&(r=e.customData[0].customKey,n=e.customData[0].customValue),{destination:t,customKey:r,customValue:n}};async function N({satoshi:e,comment:t,p:r,e:n,relays:o},s={}){const i=s.nostr||globalThis.nostr;if(!i)throw new Error("nostr option or window.nostr is not available");const a=[["relays",...o],["amount",e.toString()]];r&&a.push(["p",r]),n&&a.push(["e",n]);const c={pubkey:await i.getPublicKey(),created_at:Math.floor(Date.now()/1e3),kind:9734,tags:a,content:t??""};return c.id=S(c),await i.signEvent(c)}function R(e){if("string"!=typeof e.content)return!1;if("number"!=typeof e.created_at)return!1;if(!Array.isArray(e.tags))return!1;for(let t=0;t<e.tags.length;t++){const r=e.tags[t];if(!Array.isArray(r))return!1;for(let e=0;e<r.length;e++)if("object"==typeof r[e])return!1}return!0}function _(e){if(!R(e))throw new Error("can't serialize event with wrong or missing properties");return JSON.stringify([0,e.pubkey,e.created_at,e.kind,e.tags,e.content])}function S(e){return a(g(_(e)))}function D(e,t){let r,n;return t&&e&&(r=e.names?.[t],n=r?e.relays?.[r]:void 0),[e,r,n]}const W=/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/,P=e=>!!e&&W.test(e),T=({amount:e,min:t,max:r})=>e>0&&e>=t&&e<=r,B=async e=>{if("payRequest"!==e.tag)throw new Error("Invalid pay service params");const t=(e.callback+"").trim();if(!P(t))throw new Error("Callback must be a valid url");const r=Math.ceil(Number(e.minSendable||0)),n=Math.floor(Number(e.maxSendable));if(!r||!n||r>n)throw new Error("Invalid pay service params");let o,s;try{o=JSON.parse(e.metadata+""),s=a(g(e.metadata+""))}catch{o=[],s=a(g("[]"))}let i="",c="",h="",l="";for(let e=0;e<o.length;e++){const[t,r]=o[e];switch(t){case"text/plain":h=r;break;case"text/identifier":l=r;break;case"text/email":i=r;break;case"image/png;base64":case"image/jpeg;base64":c="data:"+t+","+r}}const u=e.payerData;let d;try{d=new URL(t).hostname}catch{}return{callback:t,fixed:r===n,min:r,max:n,domain:d,metadata:o,metadataHash:s,identifier:l,email:i,description:h,image:c,payerData:u,commentAllowed:Number(e.commentAllowed)||0,rawData:e,allowsNostr:e.allowsNostr||!1}},C=async(e,t)=>{const{boost:r}=e;t||(t={});const n=t.webln||globalThis.webln;if(!n)throw new Error("WebLN not available");if(!n.keysend)throw new Error("Keysend not available in current WebLN provider");const o=e.amount||Math.floor(r.value_msat/1e3),s={destination:e.destination,amount:o,customRecords:{7629169:JSON.stringify(r)}};e.customKey&&e.customValue&&(s.customRecords[e.customKey]=e.customValue),await n.enable();return await n.keysend(s)},H=/^((?:[^<>()[\]\\.,;:\s@"]+(?:\.[^<>()[\]\\.,;:\s@"]+)*)|(?:".+"))@((?:\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(?:(?:[a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,O="https://api.getalby.com/lnurl";const j=(e,t)=>{e.set(t.header,t.value)},q=(e,t)=>(t&&(e.payment=t),e),F=e=>e?{paid:!1,amount:0,credentials:e}:void 0,K=e=>{try{return new U({pr:e}).satoshi}catch(e){return 0}};function M(e,t){return{payInvoice:async r=>{const n=new U({pr:r.invoice});if(n.satoshi>t)throw new Error(`Invoice amount (${n.satoshi} sats) exceeds maxAmount (${t} sats)`);return e.payInvoice(r)}}}const V=e=>{const t=e.replace("L402","").replace("LSAT","").trim(),r={},n=/(\w+)=("([^"]*)"|'([^']*)'|([^,]*))/g;let o;for(;null!==(o=n.exec(t));)r[o[1]]=o[3]||o[4]||o[5];if(!r.token&&r.macaroon&&(r.token=r.macaroon,delete r.macaroon),!("token"in r)||"string"!=typeof r.token)throw new Error("No macaroon or token found in www-authenticate header");if(!("invoice"in r)||"string"!=typeof r.invoice)throw new Error("No invoice found in www-authenticate header");return r},z=async(e,t,r,n,o)=>{const s=V(e),i=s.token||s.macaroon,a=s.invoice,c=/^\s*LSAT\b/i.test(e)?"LSAT":"L402";if(!i)throw new Error("L402: missing token/macaroon in WWW-Authenticate header");if(!a)throw new Error("L402: missing invoice in WWW-Authenticate header");const h=await o.payInvoice({invoice:a}),l=`${c} ${i}:${h.preimage}`;n.set("Authorization",l);const u=await fetch(t,r);return q(u,{paid:!0,amount:K(a),feesPaid:h.fees_paid,preimage:h.preimage,credentials:{header:"Authorization",value:l}})},J=e=>{let t;try{t=JSON.parse(decodeURIComponent(escape(atob(e))))}catch(e){throw new Error("x402: invalid PAYMENT-REQUIRED header (not valid base64-encoded JSON)")}if(!Array.isArray(t.accepts)||0===t.accepts.length)throw new Error("x402: PAYMENT-REQUIRED header contains no payment options");return{accepts:t.accepts}},G=e=>{let t;try{({accepts:t}=J(e))}catch(e){return null}const r=t.find(e=>"lightning"===e?.extra?.paymentMethod);return r?.extra?.invoice?r:null},Z=async(e,t,r,n,o)=>{const{accepts:s}=J(e),i=s.find(e=>"lightning"===e?.extra?.paymentMethod);if(!i)throw new Error("x402: unsupported x402 network, only Bitcoin lightning network is supported.");if(!i.extra?.invoice)throw new Error("x402: payment requirements missing lightning invoice");const a=new U({pr:i.extra.invoice});if(a.amountRaw!=i.amount)throw new Error(`Invalid invoice amount: ${a.amountRaw}. expected ${i.amount}`);const c=await o.payInvoice({invoice:a.paymentRequest}),h=((e,t,r,n)=>{const o=JSON.stringify({x402Version:2,scheme:e,network:t,payload:{invoice:r},accepted:n});return btoa(unescape(encodeURIComponent(o)))})(i.scheme,i.network,a.paymentRequest,i);n.set("payment-signature",h);const l=await fetch(t,r);return q(l,{paid:!0,amount:a.satoshi,feesPaid:c.fees_paid,preimage:c.preimage,credentials:{header:"payment-signature",value:h}})},X=e=>{if(!e.trimStart().toLowerCase().startsWith("payment"))return null;const t=e.slice(e.toLowerCase().indexOf("payment")+7).trim(),r={},n=/(\w+)=("([^"]*)"|'([^']*)'|([^,\s]*))/g;let o;for(;null!==(o=n.exec(t));)r[o[1]]=o[3]??o[4]??o[5]??"";return"lightning"===r.method&&"charge"===r.intent&&r.id&&r.realm&&r.request?{id:r.id,realm:r.realm,method:r.method,intent:r.intent,request:r.request,...r.expires?{expires:r.expires}:{}}:null},Y=e=>{if(null===e||"object"!=typeof e)return JSON.stringify(e);if(Array.isArray(e))return"["+e.map(Y).join(",")+"]";return"{"+Object.keys(e).sort().map(t=>JSON.stringify(t)+":"+Y(e[t])).join(",")+"}"},Q=(e,t,r)=>{const n={id:e.id,intent:e.intent,method:e.method,realm:e.realm,request:e.request};e.expires&&(n.expires=e.expires);return(e=>{const t=(new TextEncoder).encode(e);let r="";for(let e=0;e<t.length;e++)r+=String.fromCharCode(t[e]);return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")})(Y({challenge:n,payload:{preimage:t}}))},ee=async(e,t,r,n,o)=>{const s=X(e);if(!s)throw new Error("mpp: invalid or unsupported WWW-Authenticate challenge (expected Payment method=lightning intent=charge)");let i;try{i=JSON.parse((e=>{const t=e.replace(/-/g,"+").replace(/_/g,"/"),r=atob(t),n=new Uint8Array(r.length);for(let e=0;e<r.length;e++)n[e]=r.charCodeAt(e);return new TextDecoder("utf-8").decode(n)})(s.request))}catch(e){throw new Error("mpp: invalid request auth-param (not valid base64url-encoded JSON)")}const a=i.methodDetails?.invoice;if(!a)throw new Error("mpp: missing invoice in charge request");const c=await o.payInvoice({invoice:a}),h=`Payment ${Q(s,c.preimage)}`;n.set("Authorization",h);const l=await fetch(t,r);return q(l,{paid:!0,amount:K(a),feesPaid:c.fees_paid,preimage:c.preimage,credentials:{header:"Authorization",value:h}})};async function te(e,t){const{createHmac:r}=await import("crypto");return r("sha256",e).update(t).digest("hex")}const re=async e=>{const t="https://getalby.com/api/rates/"+e.toLowerCase()+".json",r=await fetch(t);if(!r.ok)throw new Error(`Failed to fetch rate: ${r.status} ${r.statusText}`);return(await r.json()).rate_float/1e8},ne=async({satoshi:e,currency:t})=>{const r=await re(t);return Number(e)*r},oe=/^bitcoin:/i,se=/^(\d+)(?:\.(\d+))?$/;e.DEFAULT_PROXY=O,e.Invoice=U,e.LN_ADDRESS_REGEX=H,e.LightningAddress=class{constructor(e,t){this.address=e,this.options={proxy:O},this.options=Object.assign(this.options,t),this.parse(),this.webln=this.options.webln}parse(){const e=H.exec(this.address.toLowerCase());e&&(this.username=e[1],this.domain=e[2])}getWebLN(){return this.webln||globalThis.webln}async fetch(){return this.options.proxy?this.fetchWithProxy():this.fetchWithoutProxy()}async fetchWithProxy(){const e=await fetch(`${this.options.proxy}/lightning-address-details?${new URLSearchParams({ln:this.address}).toString()}`);if(!e.ok)throw new Error(`Failed to fetch lnurl info: ${e.status} ${e.statusText}`);const t=await e.json();await this.parseLnUrlPayResponse(t.lnurlp),this.parseKeysendResponse(t.keysend),this.parseNostrResponse(t.nostr)}async fetchWithoutProxy(){this.domain&&this.username&&await Promise.all([this.fetchLnurlData(),this.fetchKeysendData(),this.fetchNostrData()])}async fetchLnurlData(){const e=await fetch(this.lnurlpUrl());if(e.ok){const t=await e.json();await this.parseLnUrlPayResponse(t)}}async fetchKeysendData(){const e=await fetch(this.keysendUrl());if(e.ok){const t=await e.json();this.parseKeysendResponse(t)}}async fetchNostrData(){const e=await fetch(this.nostrUrl());if(e.ok){const t=await e.json();this.parseNostrResponse(t)}}lnurlpUrl(){return`https://${this.domain}/.well-known/lnurlp/${this.username}`}keysendUrl(){return`https://${this.domain}/.well-known/keysend/${this.username}`}nostrUrl(){return`https://${this.domain}/.well-known/nostr.json?name=${this.username}`}async generateInvoice(e){let t;if(this.options.proxy){const r=await fetch(`${this.options.proxy}/generate-invoice?${new URLSearchParams({ln:this.address,...e}).toString()}`);if(!r.ok)throw new Error(`Failed to generate invoice: ${r.status} ${r.statusText}`);t=(await r.json()).invoice}else{if(!this.lnurlpData)throw new Error("No lnurlpData available. Please call fetch() first.");if(!this.lnurlpData.callback||!P(this.lnurlpData.callback))throw new Error("Valid callback does not exist in lnurlpData");const r=new URL(this.lnurlpData.callback);r.search=new URLSearchParams(e).toString();const n=await fetch(r.toString());if(!n.ok)throw new Error(`Failed to generate invoice: ${n.status} ${n.statusText}`);t=await n.json()}const r=t&&t.pr&&t.pr.toString();if(!r)throw new Error("Invalid pay service invoice");const n={pr:r};if(t&&t.verify&&(n.verify=t.verify.toString()),t&&t.successAction&&"object"==typeof t.successAction){const{tag:e,message:r,description:o,url:s}=t.successAction;"message"===e?n.successAction={tag:e,message:r}:"url"===e&&(n.successAction={tag:e,description:o,url:s})}return new U(n)}async requestInvoice(e){if(!this.lnurlpData)throw new Error("No lnurlpData available. Please call fetch() first.");const t=1e3*e.satoshi,{commentAllowed:r,min:n,max:o}=this.lnurlpData;if(!T({amount:t,min:n,max:o}))throw new Error("Invalid amount");if(e.comment&&r&&r>0&&e.comment.length>r)throw new Error(`The comment length must be ${r} characters or fewer`);const s={amount:t.toString()};return e.comment&&(s.comment=e.comment),e.payerdata&&(s.payerdata=JSON.stringify(e.payerdata)),this.generateInvoice(s)}async boost(e,t=0){if(!this.keysendData)throw new Error("No keysendData available. Please call fetch() first.");const{destination:r,customKey:n,customValue:o}=this.keysendData,s=this.getWebLN();if(!s)throw new Error("WebLN not available");return C({destination:r,customKey:n,customValue:o,amount:t,boost:e},{webln:s})}async zapInvoice({satoshi:e,comment:t,relays:r,e:n},o={}){if(!this.lnurlpData)throw new Error("No lnurlpData available. Please call fetch() first.");if(!this.nostrPubkey)throw new Error("Nostr Pubkey is missing");const s=this.nostrPubkey,i=1e3*e,{allowsNostr:a,min:c,max:h}=this.lnurlpData;if(!T({amount:i,min:c,max:h}))throw new Error("Invalid amount");if(!a)throw new Error("Your provider does not support zaps");const l=await N({satoshi:i,comment:t,p:s,e:n,relays:r},o),u={amount:i.toString(),nostr:JSON.stringify(l)};return await this.generateInvoice(u)}async zap(e,t={}){const r=this.zapInvoice(e,t),n=this.getWebLN();if(!n)throw new Error("WebLN not available");await n.enable();return n.sendPayment((await r).paymentRequest)}async parseLnUrlPayResponse(e){e&&(this.lnurlpData=await B(e))}parseKeysendResponse(e){e&&(this.keysendData=I(e))}parseNostrResponse(e){e&&([this.nostrData,this.nostrPubkey,this.nostrRelays]=D(e,this.username))}},e.applyCredentials=j,e.attachPayment=q,e.createGuardedWallet=M,e.decodeInvoice=L,e.fetch402=async(e,t,r)=>{const n=r.maxAmount?M(r.wallet,r.maxAmount):r.wallet;t||(t={}),t.cache="no-store",t.mode="cors";const o=new Headers(t.headers??void 0);if(t.headers=o,r.credentials){j(o,r.credentials);const n=await fetch(e,t);return q(n,F(r.credentials))}const s=await fetch(e,t),i=s.headers.get("www-authenticate");if(i){const r=i.trimStart().toLowerCase();if(r.startsWith("l402")||r.startsWith("lsat"))return z(i,e,t,o,n)}if(i&&X(i))return ee(i,e,t,o,n);const a=s.headers.get("PAYMENT-REQUIRED");return a&&G(a)?Z(a,e,t,o,n):s},e.fetchWithL402=async(e,t,r)=>{const n=r.wallet;if(!n)throw new Error("wallet is missing");t||(t={}),t.cache="no-store",t.mode="cors";const o=new Headers(t.headers??void 0);if(t.headers=o,r.credentials){j(o,r.credentials);const n=await fetch(e,t);return q(n,F(r.credentials))}const s=await fetch(e,t),i=s.headers.get("www-authenticate");return i?z(i,e,t,o,n):s},e.fetchWithMpp=async(e,t,r)=>{const n=r.wallet;if(!n)throw new Error("wallet is missing");t||(t={}),t.cache="no-store",t.mode="cors";const o=new Headers(t.headers??void 0);if(t.headers=o,r.credentials){j(o,r.credentials);const n=await fetch(e,t);return q(n,F(r.credentials))}const s=await fetch(e,t),i=s.headers.get("www-authenticate");return i&&i.trimStart().toLowerCase().startsWith("payment")?ee(i,e,t,o,n):s},e.fetchWithX402=async(e,t,r)=>{const n=r.wallet;t||(t={}),t.cache="no-store",t.mode="cors";const o=new Headers(t.headers??void 0);if(t.headers=o,r.credentials){j(o,r.credentials);const n=await fetch(e,t);return q(n,F(r.credentials))}const s=await fetch(e,t),i=s.headers.get("PAYMENT-REQUIRED");return i?Z(i,e,t,o,n):s},e.findX402LightningRequirements=G,e.fromHexString=k,e.generateZapEvent=N,e.getEventHash=S,e.getFiatBtcRate=re,e.getFiatCurrencies=async()=>{const e=await fetch("https://getalby.com/api/rates");if(!e.ok)throw new Error(`Failed to fetch currencies: ${e.status} ${e.statusText}`);const t=await e.json();return Object.entries(t).filter(([e])=>"BTC"!==e.toUpperCase()).map(([e,t])=>({code:e.toUpperCase(),name:t.name,priority:t.priority,symbol:t.symbol})).sort((e,t)=>e.name.localeCompare(t.name)).sort((e,t)=>e.priority-t.priority)},e.getFiatValue=ne,e.getFormattedFiatValue=async({satoshi:e,currency:t,locale:r})=>{r||(r="en");return(await ne({satoshi:e,currency:t})).toLocaleString(r,{style:"currency",currency:t})},e.getInvoiceAmount=K,e.getSatoshiValue=async({amount:e,currency:t})=>{const r=await re(t);return Math.floor(Number(e)/r)},e.isBip21=e=>"string"==typeof e&&oe.test(e.trim()),e.isUrl=P,e.isValidAmount=T,e.issueL402Macaroon=async function(e,t,r){if(void 0!==r&&Object.prototype.hasOwnProperty.call(r,"paymentHash"))throw new Error("paymentHash is reserved");const n={...r,paymentHash:t},o=Buffer.from(JSON.stringify(n)).toString("base64url");return`${o}.${await te(e,o)}`},e.makeL402AuthenticateHeader=e=>{if(!e.token)throw new Error("token must be provided");return`L402 version="0" token="${e.token}", invoice="${e.invoice}"`},e.parseBip21=e=>{if("string"!=typeof e)return null;const t=e.trim();if(!oe.test(t))return null;const r=t.replace(oe,""),n=r.indexOf("?"),o=-1===n?r:r.slice(0,n),s=-1===n?"":r.slice(n+1),i={},a=[];if(s){new URLSearchParams(s).forEach((e,t)=>{i[t]=e})}const c=new Set(["amount","label","message","lightning","lno"]);for(const e of Object.keys(i))e.startsWith("req-")&&!c.has(e.slice(4))&&a.push(e);const h={address:o.trim(),params:i,unknownRequiredParams:a};return void 0!==i.amount&&se.test(i.amount)&&(h.amount=Number(i.amount),h.amountSats=(e=>{const t=se.exec(e);if(!t)return Number.NaN;const[,r,n=""]=t;let o;if(n.length<=8)o=BigInt(n.padEnd(8,"0"));else{const e=n.slice(0,8),t=n.charCodeAt(8)-48;o=BigInt(e),t>=5&&(o+=1n)}const s=100000000n*BigInt(r)+o;return Number(s)})(i.amount)),void 0!==i.label&&(h.label=i.label),void 0!==i.message&&(h.message=i.message),void 0!==i.lightning&&(h.lightning=i.lightning),void 0!==i.lno&&(h.lno=i.lno),h},e.parseKeysendResponse=I,e.parseL402=V,e.parseL402Authorization=function(e){const t=e.replace(/^LSAT /,"L402 "),r="L402 ";if(!t.startsWith(r))return null;const n=t.slice(5),o=n.indexOf(":");if(-1===o)throw new Error("Invalid authorization header value");return{token:n.slice(0,o),preimage:n.slice(o+1)}},e.parseLnUrlPayResponse=B,e.parseNostrResponse=D,e.reusedCredentialPayment=F,e.sendBoostagram=C,e.serializeEvent=_,e.validateEvent=R,e.validatePreimage=$,e.verifyL402Macaroon=async function(e,t){const{timingSafeEqual:r}=await import("crypto"),n=t.lastIndexOf(".");if(-1===n)throw new Error("Invalid macaroon token");const o=t.slice(0,n),s=t.slice(n+1),i=await te(e,o);try{if(!r(Buffer.from(s,"hex"),Buffer.from(i,"hex")))throw new Error("Invalid macaroon token")}catch(e){throw new Error("Invalid macaroon token")}try{const e=JSON.parse(Buffer.from(o,"base64url").toString("utf8"));if(null===e||"object"!=typeof e||Array.isArray(e)||"string"!=typeof e.paymentHash)throw new Error("Invalid macaroon payload");return e}catch{throw new Error("Invalid macaroon token")}}});
//# sourceMappingURL=lightning-tools.umd.js.map

@@ -6,14 +6,67 @@ interface Wallet {

preimage: string;
fees_paid?: number;
}>;
}
/**
* A reusable payment credential. After a successful paid request the credential
* is attached to the response (see {@link PaymentInfo}). Pass it back via
* `options.credentials` on a follow-up request (e.g. polling a long-running
* job) to authorize the request without paying again.
*
* `header` is the HTTP header the credential is sent in (`Authorization` for
* L402/MPP, `payment-signature` for x402) and `value` is the header value.
*/
interface PaymentCredentials {
header: string;
value: string;
}
/**
* Payment metadata attached (as `response.payment`) to responses returned by
* the 402 fetch helpers.
*/
interface PaymentInfo {
/** Whether a lightning payment was made to obtain this response. */
paid: boolean;
/** Amount of the paid invoice, in satoshis (0 when `paid` is false). */
amount: number;
/** Routing fees paid, in millisatoshis, when reported by the wallet. */
feesPaid?: number;
/** Payment preimage, present when a payment was made. */
preimage?: string;
/**
* Reusable credential. Pass it back via `options.credentials` on a follow-up
* request (e.g. polling) to authorize it without paying again.
*/
credentials: PaymentCredentials;
}
/** A `Response` with optional payment metadata attached by the 402 helpers. */
type PaidResponse = Response & {
payment?: PaymentInfo;
};
/** Options accepted by the 402 fetch helpers. */
interface Fetch402Options {
wallet: Wallet;
/**
* A credential returned by a previous paid request. When provided it is
* applied to the request and the helper NEVER pays again β€” even if the server
* still responds with a 402 (that response is returned to the caller as-is).
* Use it to authorize follow-up requests (e.g. polling) without re-paying.
*/
credentials?: PaymentCredentials;
}
/** Apply a previously-obtained credential to the outgoing request headers. */
declare const applyCredentials: (headers: Headers, credentials: PaymentCredentials) => void;
/** Attach payment metadata to a response and return it (typed). */
declare const attachPayment: (response: Response, payment: PaymentInfo | undefined) => PaidResponse;
/** Payment metadata describing a request authorized with a reused credential. */
declare const reusedCredentialPayment: (credentials: PaymentCredentials | undefined) => PaymentInfo | undefined;
/** Satoshi amount of a BOLT11 invoice (0 when it cannot be decoded). */
declare const getInvoiceAmount: (invoice: string) => number;
declare function createGuardedWallet(wallet: Wallet, maxAmountSats: number): Wallet;
declare const fetch402: (url: string, fetchArgs: RequestInit, options: {
wallet: Wallet;
declare const fetch402: (url: string, fetchArgs: RequestInit, options: Fetch402Options & {
maxAmount?: number;
}) => Promise<Response>;
}) => Promise<PaidResponse>;
declare const fetchWithL402: (url: string, fetchArgs: RequestInit, options: {
wallet: Wallet;
}) => Promise<Response>;
declare const fetchWithL402: (url: string, fetchArgs: RequestInit, options: Fetch402Options) => Promise<PaidResponse>;

@@ -76,5 +129,3 @@ interface WwwAuthenticatePayload {

declare const findX402LightningRequirements: (x402Header: string) => X402Requirements | null;
declare const fetchWithX402: (url: string, fetchArgs: RequestInit, options: {
wallet: Wallet;
}) => Promise<Response>;
declare const fetchWithX402: (url: string, fetchArgs: RequestInit, options: Fetch402Options) => Promise<PaidResponse>;

@@ -90,11 +141,11 @@ /**

*
* Note: lightning-charge uses consume-once challenge semantics – each
* challenge embeds a fresh invoice, so paid credentials cannot be reused.
* The `store` option is accepted for API consistency but is not used.
* Pass a previous credential via `options.credentials` to reuse it (e.g. when
* polling); the credential is applied and the function NEVER pays again, even
* if the server still responds with a 402 (that response is returned as-is).
* Note: lightning-charge typically uses consume-once challenge semantics, so a
* reused credential is only accepted by servers that explicitly support it.
*/
declare const fetchWithMpp: (url: string, fetchArgs: RequestInit, options: {
wallet: Wallet;
}) => Promise<Response>;
declare const fetchWithMpp: (url: string, fetchArgs: RequestInit, options: Fetch402Options) => Promise<PaidResponse>;
export { createGuardedWallet, fetch402, fetchWithL402, fetchWithMpp, fetchWithX402, findX402LightningRequirements, issueL402Macaroon, makeL402AuthenticateHeader, parseL402, parseL402Authorization, verifyL402Macaroon };
export type { MacaroonPayload, Wallet };
export { applyCredentials, attachPayment, createGuardedWallet, fetch402, fetchWithL402, fetchWithMpp, fetchWithX402, findX402LightningRequirements, getInvoiceAmount, issueL402Macaroon, makeL402AuthenticateHeader, parseL402, parseL402Authorization, reusedCredentialPayment, verifyL402Macaroon };
export type { Fetch402Options, MacaroonPayload, PaidResponse, PaymentCredentials, PaymentInfo, Wallet };

@@ -6,9 +6,55 @@ interface Wallet {

preimage: string;
fees_paid?: number;
}>;
}
declare const fetchWithL402: (url: string, fetchArgs: RequestInit, options: {
/**
* A reusable payment credential. After a successful paid request the credential
* is attached to the response (see {@link PaymentInfo}). Pass it back via
* `options.credentials` on a follow-up request (e.g. polling a long-running
* job) to authorize the request without paying again.
*
* `header` is the HTTP header the credential is sent in (`Authorization` for
* L402/MPP, `payment-signature` for x402) and `value` is the header value.
*/
interface PaymentCredentials {
header: string;
value: string;
}
/**
* Payment metadata attached (as `response.payment`) to responses returned by
* the 402 fetch helpers.
*/
interface PaymentInfo {
/** Whether a lightning payment was made to obtain this response. */
paid: boolean;
/** Amount of the paid invoice, in satoshis (0 when `paid` is false). */
amount: number;
/** Routing fees paid, in millisatoshis, when reported by the wallet. */
feesPaid?: number;
/** Payment preimage, present when a payment was made. */
preimage?: string;
/**
* Reusable credential. Pass it back via `options.credentials` on a follow-up
* request (e.g. polling) to authorize it without paying again.
*/
credentials: PaymentCredentials;
}
/** A `Response` with optional payment metadata attached by the 402 helpers. */
type PaidResponse = Response & {
payment?: PaymentInfo;
};
/** Options accepted by the 402 fetch helpers. */
interface Fetch402Options {
wallet: Wallet;
}) => Promise<Response>;
/**
* A credential returned by a previous paid request. When provided it is
* applied to the request and the helper NEVER pays again β€” even if the server
* still responds with a 402 (that response is returned to the caller as-is).
* Use it to authorize follow-up requests (e.g. polling) without re-paying.
*/
credentials?: PaymentCredentials;
}
declare const fetchWithL402: (url: string, fetchArgs: RequestInit, options: Fetch402Options) => Promise<PaidResponse>;
interface WwwAuthenticatePayload {

@@ -15,0 +61,0 @@ token: string;

@@ -6,4 +6,52 @@ interface Wallet {

preimage: string;
fees_paid?: number;
}>;
}
/**
* A reusable payment credential. After a successful paid request the credential
* is attached to the response (see {@link PaymentInfo}). Pass it back via
* `options.credentials` on a follow-up request (e.g. polling a long-running
* job) to authorize the request without paying again.
*
* `header` is the HTTP header the credential is sent in (`Authorization` for
* L402/MPP, `payment-signature` for x402) and `value` is the header value.
*/
interface PaymentCredentials {
header: string;
value: string;
}
/**
* Payment metadata attached (as `response.payment`) to responses returned by
* the 402 fetch helpers.
*/
interface PaymentInfo {
/** Whether a lightning payment was made to obtain this response. */
paid: boolean;
/** Amount of the paid invoice, in satoshis (0 when `paid` is false). */
amount: number;
/** Routing fees paid, in millisatoshis, when reported by the wallet. */
feesPaid?: number;
/** Payment preimage, present when a payment was made. */
preimage?: string;
/**
* Reusable credential. Pass it back via `options.credentials` on a follow-up
* request (e.g. polling) to authorize it without paying again.
*/
credentials: PaymentCredentials;
}
/** A `Response` with optional payment metadata attached by the 402 helpers. */
type PaidResponse = Response & {
payment?: PaymentInfo;
};
/** Options accepted by the 402 fetch helpers. */
interface Fetch402Options {
wallet: Wallet;
/**
* A credential returned by a previous paid request. When provided it is
* applied to the request and the helper NEVER pays again β€” even if the server
* still responds with a 402 (that response is returned to the caller as-is).
* Use it to authorize follow-up requests (e.g. polling) without re-paying.
*/
credentials?: PaymentCredentials;
}

@@ -19,10 +67,10 @@ /**

*
* Note: lightning-charge uses consume-once challenge semantics – each
* challenge embeds a fresh invoice, so paid credentials cannot be reused.
* The `store` option is accepted for API consistency but is not used.
* Pass a previous credential via `options.credentials` to reuse it (e.g. when
* polling); the credential is applied and the function NEVER pays again, even
* if the server still responds with a 402 (that response is returned as-is).
* Note: lightning-charge typically uses consume-once challenge semantics, so a
* reused credential is only accepted by servers that explicitly support it.
*/
declare const fetchWithMpp: (url: string, fetchArgs: RequestInit, options: {
wallet: Wallet;
}) => Promise<Response>;
declare const fetchWithMpp: (url: string, fetchArgs: RequestInit, options: Fetch402Options) => Promise<PaidResponse>;
export { fetchWithMpp };

@@ -6,4 +6,52 @@ interface Wallet {

preimage: string;
fees_paid?: number;
}>;
}
/**
* A reusable payment credential. After a successful paid request the credential
* is attached to the response (see {@link PaymentInfo}). Pass it back via
* `options.credentials` on a follow-up request (e.g. polling a long-running
* job) to authorize the request without paying again.
*
* `header` is the HTTP header the credential is sent in (`Authorization` for
* L402/MPP, `payment-signature` for x402) and `value` is the header value.
*/
interface PaymentCredentials {
header: string;
value: string;
}
/**
* Payment metadata attached (as `response.payment`) to responses returned by
* the 402 fetch helpers.
*/
interface PaymentInfo {
/** Whether a lightning payment was made to obtain this response. */
paid: boolean;
/** Amount of the paid invoice, in satoshis (0 when `paid` is false). */
amount: number;
/** Routing fees paid, in millisatoshis, when reported by the wallet. */
feesPaid?: number;
/** Payment preimage, present when a payment was made. */
preimage?: string;
/**
* Reusable credential. Pass it back via `options.credentials` on a follow-up
* request (e.g. polling) to authorize it without paying again.
*/
credentials: PaymentCredentials;
}
/** A `Response` with optional payment metadata attached by the 402 helpers. */
type PaidResponse = Response & {
payment?: PaymentInfo;
};
/** Options accepted by the 402 fetch helpers. */
interface Fetch402Options {
wallet: Wallet;
/**
* A credential returned by a previous paid request. When provided it is
* applied to the request and the helper NEVER pays again β€” even if the server
* still responds with a 402 (that response is returned to the caller as-is).
* Use it to authorize follow-up requests (e.g. polling) without re-paying.
*/
credentials?: PaymentCredentials;
}

@@ -29,6 +77,4 @@ interface X402Requirements {

declare const findX402LightningRequirements: (x402Header: string) => X402Requirements | null;
declare const fetchWithX402: (url: string, fetchArgs: RequestInit, options: {
wallet: Wallet;
}) => Promise<Response>;
declare const fetchWithX402: (url: string, fetchArgs: RequestInit, options: Fetch402Options) => Promise<PaidResponse>;
export { fetchWithX402, findX402LightningRequirements };

@@ -248,14 +248,67 @@ import * as _webbtc_webln_types from '@webbtc/webln-types';

preimage: string;
fees_paid?: number;
}>;
}
/**
* A reusable payment credential. After a successful paid request the credential
* is attached to the response (see {@link PaymentInfo}). Pass it back via
* `options.credentials` on a follow-up request (e.g. polling a long-running
* job) to authorize the request without paying again.
*
* `header` is the HTTP header the credential is sent in (`Authorization` for
* L402/MPP, `payment-signature` for x402) and `value` is the header value.
*/
interface PaymentCredentials {
header: string;
value: string;
}
/**
* Payment metadata attached (as `response.payment`) to responses returned by
* the 402 fetch helpers.
*/
interface PaymentInfo {
/** Whether a lightning payment was made to obtain this response. */
paid: boolean;
/** Amount of the paid invoice, in satoshis (0 when `paid` is false). */
amount: number;
/** Routing fees paid, in millisatoshis, when reported by the wallet. */
feesPaid?: number;
/** Payment preimage, present when a payment was made. */
preimage?: string;
/**
* Reusable credential. Pass it back via `options.credentials` on a follow-up
* request (e.g. polling) to authorize it without paying again.
*/
credentials: PaymentCredentials;
}
/** A `Response` with optional payment metadata attached by the 402 helpers. */
type PaidResponse = Response & {
payment?: PaymentInfo;
};
/** Options accepted by the 402 fetch helpers. */
interface Fetch402Options {
wallet: Wallet;
/**
* A credential returned by a previous paid request. When provided it is
* applied to the request and the helper NEVER pays again β€” even if the server
* still responds with a 402 (that response is returned to the caller as-is).
* Use it to authorize follow-up requests (e.g. polling) without re-paying.
*/
credentials?: PaymentCredentials;
}
/** Apply a previously-obtained credential to the outgoing request headers. */
declare const applyCredentials: (headers: Headers, credentials: PaymentCredentials) => void;
/** Attach payment metadata to a response and return it (typed). */
declare const attachPayment: (response: Response, payment: PaymentInfo | undefined) => PaidResponse;
/** Payment metadata describing a request authorized with a reused credential. */
declare const reusedCredentialPayment: (credentials: PaymentCredentials | undefined) => PaymentInfo | undefined;
/** Satoshi amount of a BOLT11 invoice (0 when it cannot be decoded). */
declare const getInvoiceAmount: (invoice: string) => number;
declare function createGuardedWallet(wallet: Wallet, maxAmountSats: number): Wallet;
declare const fetch402: (url: string, fetchArgs: RequestInit, options: {
wallet: Wallet;
declare const fetch402: (url: string, fetchArgs: RequestInit, options: Fetch402Options & {
maxAmount?: number;
}) => Promise<Response>;
}) => Promise<PaidResponse>;
declare const fetchWithL402: (url: string, fetchArgs: RequestInit, options: {
wallet: Wallet;
}) => Promise<Response>;
declare const fetchWithL402: (url: string, fetchArgs: RequestInit, options: Fetch402Options) => Promise<PaidResponse>;

@@ -318,5 +371,3 @@ interface WwwAuthenticatePayload {

declare const findX402LightningRequirements: (x402Header: string) => X402Requirements | null;
declare const fetchWithX402: (url: string, fetchArgs: RequestInit, options: {
wallet: Wallet;
}) => Promise<Response>;
declare const fetchWithX402: (url: string, fetchArgs: RequestInit, options: Fetch402Options) => Promise<PaidResponse>;

@@ -332,9 +383,9 @@ /**

*
* Note: lightning-charge uses consume-once challenge semantics – each
* challenge embeds a fresh invoice, so paid credentials cannot be reused.
* The `store` option is accepted for API consistency but is not used.
* Pass a previous credential via `options.credentials` to reuse it (e.g. when
* polling); the credential is applied and the function NEVER pays again, even
* if the server still responds with a 402 (that response is returned as-is).
* Note: lightning-charge typically uses consume-once challenge semantics, so a
* reused credential is only accepted by servers that explicitly support it.
*/
declare const fetchWithMpp: (url: string, fetchArgs: RequestInit, options: {
wallet: Wallet;
}) => Promise<Response>;
declare const fetchWithMpp: (url: string, fetchArgs: RequestInit, options: Fetch402Options) => Promise<PaidResponse>;

@@ -363,3 +414,47 @@ interface FiatCurrency {

export { DEFAULT_PROXY, Invoice, LN_ADDRESS_REGEX, LightningAddress, createGuardedWallet, decodeInvoice, fetch402, fetchWithL402, fetchWithMpp, fetchWithX402, findX402LightningRequirements, fromHexString, generateZapEvent, getEventHash, getFiatBtcRate, getFiatCurrencies, getFiatValue, getFormattedFiatValue, getSatoshiValue, isUrl, isValidAmount, issueL402Macaroon, makeL402AuthenticateHeader, parseKeysendResponse, parseL402, parseL402Authorization, parseLnUrlPayResponse, parseNostrResponse, sendBoostagram, serializeEvent, validateEvent, validatePreimage, verifyL402Macaroon };
export type { Boost, BoostArguments, BoostOptions, Event, FiatCurrency, InvoiceArgs, KeySendRawData, KeysendResponse, LUD18PayerData, LUD18ServicePayerData, LnUrlPayResponse, LnUrlRawData, MacaroonPayload, NostrProvider, NostrResponse, RequestInvoiceArgs, SuccessAction, Wallet, WeblnBoostParams, ZapArgs, ZapOptions };
type Bip21 = {
/** Bare on-chain bitcoin address (no scheme, no query string). */
address: string;
/** Requested amount in BTC, as defined by BIP21 (e.g. 0.01). */
amount?: number;
/** Requested amount converted to satoshis, for convenience. */
amountSats?: number;
/** Label for the recipient, decoded. */
label?: string;
/** Free-form message, decoded. */
message?: string;
/** BOLT11 invoice or LNURL, from the `lightning=` parameter (unified QR). */
lightning?: string;
/** BOLT12 offer, from the `lno=` parameter. */
lno?: string;
/**
* `req-*` parameters that the client doesn't know how to handle.
* Per BIP21, callers MUST reject the URI if this is non-empty.
*/
unknownRequiredParams: string[];
/** All query parameters, decoded, for advanced/custom use. */
params: Record<string, string>;
};
/**
* Parse a BIP21 (`bitcoin:`) URI. Returns `null` if the input doesn't have the
* `bitcoin:` scheme.
*
* The address is returned as-is (case preserved) β€” callers should validate it
* separately if they need to ensure it's a well-formed bitcoin address.
*
* Per BIP21, parameters prefixed with `req-` are required: if the client
* doesn't understand any of them, the payment MUST NOT be made. The unknown
* required params are surfaced via `unknownRequiredParams` so callers can
* decide how to fail.
*
* @example
* parseBip21("bitcoin:bc1q...?amount=0.001&lightning=lnbc...")
* // => { address: "bc1q...", amount: 0.001, amountSats: 100000, lightning: "lnbc...", ... }
*/
declare const parseBip21: (uri: string) => Bip21 | null;
/** Returns true if the input starts with the `bitcoin:` URI scheme. */
declare const isBip21: (uri: string) => boolean;
export { DEFAULT_PROXY, Invoice, LN_ADDRESS_REGEX, LightningAddress, applyCredentials, attachPayment, createGuardedWallet, decodeInvoice, fetch402, fetchWithL402, fetchWithMpp, fetchWithX402, findX402LightningRequirements, fromHexString, generateZapEvent, getEventHash, getFiatBtcRate, getFiatCurrencies, getFiatValue, getFormattedFiatValue, getInvoiceAmount, getSatoshiValue, isBip21, isUrl, isValidAmount, issueL402Macaroon, makeL402AuthenticateHeader, parseBip21, parseKeysendResponse, parseL402, parseL402Authorization, parseLnUrlPayResponse, parseNostrResponse, reusedCredentialPayment, sendBoostagram, serializeEvent, validateEvent, validatePreimage, verifyL402Macaroon };
export type { Bip21, Boost, BoostArguments, BoostOptions, Event, Fetch402Options, FiatCurrency, InvoiceArgs, KeySendRawData, KeysendResponse, LUD18PayerData, LUD18ServicePayerData, LnUrlPayResponse, LnUrlRawData, MacaroonPayload, NostrProvider, NostrResponse, PaidResponse, PaymentCredentials, PaymentInfo, RequestInvoiceArgs, SuccessAction, Wallet, WeblnBoostParams, ZapArgs, ZapOptions };
{
"name": "@getalby/lightning-tools",
"version": "8.1.1",
"version": "8.2.0",
"description": "Collection of helpful building blocks and tools to develop Bitcoin Lightning web apps",

@@ -71,2 +71,7 @@ "type": "module",

"types": "./dist/types/podcasting2.d.ts"
},
"./bip21": {
"import": "./dist/esm/bip21.js",
"require": "./dist/cjs/bip21.cjs",
"types": "./dist/types/bip21.d.ts"
}

@@ -73,0 +78,0 @@ },

+78
-13

@@ -13,3 +13,3 @@ <p align="center">

Skip the rest of this README and use the [Alby Bitcoin Builder Skill](https://github.com/getAlby/builder-skill) instead. It will handle the rest!
Skip the rest of this README and use the [Alby Bitcoin Builder Skill](https://github.com/getAlby/builder-skill) to build a bitcoin app or the [Alby Bitcoin Payment Skill](https://github.com/getAlby/payments-skill) to give your agent payment capabilities. It will handle the rest!

@@ -188,3 +188,4 @@ ## Manual Installation

- options:
- wallet: any object that implements `payInvoice({ invoice })` and returns `{ preimage }`. Used to pay L402, X402 and MPP invoices.
- wallet: any object that implements `payInvoice({ invoice })` and returns `{ preimage, fees_paid? }`. Used to pay L402, X402 and MPP invoices.
- credentials (optional): a credential from a previous paid request (`response.payment.credentials`). When provided it is applied to the request before it is sent so the server can authorize it without a new payment β€” see [Payment info & polling](#payment-info--polling).

@@ -201,7 +202,3 @@ ##### Examples

await fetch402(
"https://example.com/protected-resource",
{},
{ wallet: nwc },
)
await fetch402("https://example.com/protected-resource", {}, { wallet: nwc })
.then((res) => res.json())

@@ -212,2 +209,43 @@ .then(console.log)

#### Payment info & polling
All of the 402 fetch helpers (`fetch402`, `fetchWithL402`, `fetchWithX402`, `fetchWithMpp`) return a standard `fetch` `Response`. When a payment was made (or a supplied credential was reused) the response also carries a `payment` property:
```ts
interface PaymentInfo {
paid: boolean; // whether a lightning payment was made for this request
amount: number; // amount of the paid invoice, in satoshis (0 when paid is false)
feesPaid?: number; // routing fees in millisatoshis, when reported by the wallet
preimage?: string; // payment preimage, when a payment was made
credentials: {
// reusable credential β€” pass back via options.credentials
header: string; // e.g. "Authorization" (L402/MPP) or "payment-signature" (x402)
value: string;
};
}
```
This lets you inspect what a request cost, and β€” by passing `credentials` back on a follow-up request β€” authorize subsequent calls without paying again (e.g. polling a long-running video/song generation job).
> **Important:** when you pass `credentials`, the helper reuses them and **never pays a second time**. If the server still responds with a `402` (e.g. the credential expired or the balance is depleted), that `402` response is returned to you as-is β€” the library will not silently pay another invoice. You decide what to do next: retry the same credential, or make a fresh unauthenticated request to pay again.
```js
// First request (no credentials): pays once and returns the content plus a reusable credential
const res = await fetch402(url, { method: "POST", body }, { wallet: nwc });
const job = await res.json();
console.info(`Paid ${res.payment.amount} sats`);
// Follow-up requests reuse the credential β€” these NEVER pay again
const pollRes = await fetch402(
`${url}/status/${job.id}`,
{},
{ wallet: nwc, credentials: res.payment.credentials },
);
if (pollRes.status === 402) {
// credential not (yet) accepted β€” retry the same credential later, do not re-pay
} else {
console.info(await pollRes.json());
}
```
#### L402

@@ -226,3 +264,4 @@

- options:
- wallet: any object (e.g. a NWC client) that implements `payInvoice({ invoice })` and returns `{ preimage }`. Used to pay the L402 invoice.
- wallet: any object (e.g. a NWC client) that implements `payInvoice({ invoice })` and returns `{ preimage, fees_paid? }`. Used to pay the L402 invoice.
- credentials (optional): a credential from a previous paid request β€” see [Payment info & polling](#payment-info--polling).

@@ -252,5 +291,5 @@ ##### Examples

Similar to L402 X402 is an open protocol for machine-to-machine payments built on the HTTP 402 Payment Required status code.
It enables APIs and resources to request payments inline, without prior registration or authentication.
It enables APIs and resources to request payments inline, without prior registration or authentication.
This library includes a `fetchWithX402` function to consume X402-protected resources that support the lightning network.
This library includes a `fetchWithX402` function to consume X402-protected resources that support the lightning network.
(Note: X402 works also with other coins and network. This library supports X402 resources that accept Bitcoin on the lightning network)

@@ -263,3 +302,4 @@

- options:
- wallet: any object (e.g. a NWC client) that implements `payInvoice({ invoice })` and returns `{ preimage }`. Used to pay the X402 invoice.
- wallet: any object (e.g. a NWC client) that implements `payInvoice({ invoice })` and returns `{ preimage, fees_paid? }`. Used to pay the X402 invoice.
- credentials (optional): a credential from a previous paid request β€” see [Payment info & polling](#payment-info--polling).

@@ -291,3 +331,3 @@ ##### Examples

This library includes a `fetchWithMpp` function to consume MPP-protected resources that support the lightning network.
This library includes a `fetchWithMpp` function to consume MPP-protected resources that support the lightning network.
(Note: MPP works also with other payment methods. This library supports resources that accept Bitcoin on the lightning network)

@@ -300,3 +340,4 @@

- options:
- wallet: any object (e.g. a NWC client) that implements `payInvoice({ invoice })` and returns `{ preimage }`. Used to pay the X402 invoice.
- wallet: any object (e.g. a NWC client) that implements `payInvoice({ invoice })` and returns `{ preimage, fees_paid? }`. Used to pay the MPP invoice.
- credentials (optional): a credential from a previous paid request β€” see [Payment info & polling](#payment-info--polling).

@@ -369,2 +410,26 @@ ##### Examples

### BIP21 (`bitcoin:` URIs)
Parse [BIP21](https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki) payment URIs, including the unified-QR `lightning=` fallback parameter.
```js
import { parseBip21 } from "@getalby/lightning-tools";
const result = parseBip21(
"bitcoin:bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4?amount=0.001&label=Donation&lightning=lnbc...",
);
if (result) {
result.address; // "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"
result.amount; // 0.001 (BTC)
result.amountSats; // 100000
result.label; // "Donation"
result.lightning; // BOLT11 fallback, if present
result.lno; // BOLT12 offer, if present
result.unknownRequiredParams; // reject the URI if non-empty (BIP21 `req-*` rule)
}
```
`parseBip21` returns `null` for inputs that don't start with the `bitcoin:` scheme. Address validation is intentionally out of scope β€” validate the returned `address` with your own check if needed.
### πŸ€– Lightning Address Proxy

@@ -371,0 +436,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display