// Vectors: the BOLT 11 spec examples (lightning/bolts, 11-payment-encoding.md § Examples), plus the
// regtest and signet invoices Zeus uses in its own utils/Bolt11Utils.test.ts. Every spec invoice below is
// signed with priv_key e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734.

import { describe, expect, it } from 'bun:test';
import { clearBolt11Cache, decodeBolt11, decodeBolt11Invoice } from './bolt11';
import { BackendError } from './types';

// ── spec vectors ─────────────────────────────────────────────────────────────────────────────────

/** no amount, description "Please consider supporting this project" */
const NO_AMOUNT =
  'lnbc1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq9qrsgq357wnc5r2ueh7ck6q93dj32dlqnls087fxdwk8qakdyafkq3yap9us6v52vjjsrvywa6rt52cm9r9zqt8r2t7mlcwspyetp5h2tztugp9lfyql';

/** 2500u, description "1 cup coffee", expiry 60 */
const COFFEE_250U =
  'lnbc2500u1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpu9qrsgquk0rl77nj30yxdy8j9vdx85fkpmdla2087ne0xh8nhedh8w27kyke0lp53ut353s06fv3qfegext0eh0ymjpf39tuven09sam30g4vgpfna3rh';

/** 2500u, UTF-8 description, expiry 60 */
const NONSENSE_250U =
  'lnbc2500u1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpu9qrsgqhtjpauu9ur7fw2thcl4y9vfvh4m9wlfyz2gem29g5ghe2aak2pm3ps8fdhtceqsaagty2vph7utlgj48u0ged6a337aewvraedendscp573dxr';

/** 20m, description_hash instead of a description */
const HASHED_20M =
  'lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qrsgq7ea976txfraylvgzuxs8kgcw23ezlrszfnh8r6qtfpr6cxga50aj6txm9rxrydzd06dfeawfk6swupvz4erwnyutnjq7x39ymw6j38gp7ynn44';

/** the same on testnet, with a P2PKH fallback address */
const TESTNET_20M_FALLBACK =
  'lntb20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygshp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfpp3x9et2e20v6pu37c5d9vax37wxq72un989qrsgqdj545axuxtnfemtpwkc45hx9d2ft7x04mt8q7y6t0k2dge9e7h8kpy9p34ytyslj3yu569aalz2xdk8xkd7ltxqld94u8h2esmsmacgpghe9k8';

/** 20m with a fallback address and a two-hop route hint */
const MAINNET_20M_ROUTES =
  'lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85fr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzq9qrsgqdfjcdk6w3ak5pca9hwfwfh63zrrz06wwfya0ydlzpgzxkn5xagsqz7x9j4jwe7yj7vaf2k9lqsdk45kts2fd0fkr28am0u4w95tt2nsq76cqw0';

/** 9678785340p — a msat amount that is not a whole number of sats */
const PICO_9678785340P =
  'lnbc9678785340p1pwmna7lpp5gc3xfm08u9qy06djf8dfflhugl6p7lgza6dsjxq454gxhj9t7a0sd8dgfkx7cmtwd68yetpd5s9xar0wfjn5gpc8qhrsdfq24f5ggrxdaezqsnvda3kkum5wfjkzmfqf3jkgem9wgsyuctwdus9xgrcyqcjcgpzgfskx6eqf9hzqnteypzxz7fzypfhg6trddjhygrcyqezcgpzfysywmm5ypxxjemgw3hxjmn8yptk7untd9hxwg3q2d6xjcmtv4ezq7pqxgsxzmnyyqcjqmt0wfjjq6t5v4khxsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsxqyjw5qcqp2rzjq0gxwkzc8w6323m55m4jyxcjwmy7stt9hwkwe2qxmy8zpsgg7jcuwz87fcqqeuqqqyqqqqlgqqqqn3qq9q9qrsgqrvgkpnmps664wgkp43l22qsgdw4ve24aca4nymnxddlnp8vh9v2sdxlu5ywdxefsfvm0fq3sesf08uf6q9a2ke0hc9j6z6wlxg5z5kqpu2v9wz';

/** 25m advertising features 8, 14 and 99 */
const FEATURES_25M =
  'lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqqsgq2a25dxl5hrntdtn6zvydt7d66hyzsyhqs4wdynavys42xgl6sgx9c4g7me86a27t07mdtfry458rtjr0v92cnmswpsjscgt2vcse3sgpz3uapa';

/** the same invoice in the all-uppercase QR form */
const FEATURES_25M_UPPER =
  'LNBC25M1PVJLUEZPP5QQQSYQCYQ5RQWZQFQQQSYQCYQ5RQWZQFQQQSYQCYQ5RQWZQFQYPQDQ5VDHKVEN9V5SXYETPDEESSP5ZYG3ZYG3ZYG3ZYG3ZYG3ZYG3ZYG3ZYG3ZYG3ZYG3ZYG3ZYG3ZYGS9Q5SQQQQQQQQQQQQQQQQSGQ2A25DXL5HRNTDTN6ZVYDT7D66HYZSYHQS4WDYNAVYS42XGL6SGX9C4G7ME86A27T07MDTFRY458RTJR0V92CNMSWPSJSCGT2VCSE3SGPZ3UAPA';

/** 10m carrying payment metadata 0x01fafaf0 */
const METADATA_10M =
  'lnbc10m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp9wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2mq8q8a04uqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q2gqqqqqqsgq7hf8he7ecf7n4ffphs6awl9t6676rrclv9ckg3d3ncn7fct63p6s365duk5wrk202cfy3aj5xnnp5gs3vrdvruverwwq7yzhkf5a3xqpd05wjc';

/** a valid invoice whose signature is high-S */
const HIGH_S_SIGNATURE =
  'lnbc1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq9qrsgq357wnc5r2ueh7ck6q93dj32dlqnls087fxdwk8qakdyafkq3yap2r09nt4ndd0unm3z9u5t48y6ucv4r5sg7lk98c77ctvjczkspk5qprc90gx';

/** a high-S signature that does not match the invoice's own `n` payee field */
const PAYEE_MISMATCH =
  'lnbc25m1p70xwfzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsp5cfzp9ugllvk03rltd6hvndxj26ux6gcxc5azyxk060rj9tzghct5zvjlps76gx8wpq5yuu79688k8gnm2c0al6v608s96l0xzrrlqqwnzxmu';

/** bech32 checksum is invalid */
const BAD_CHECKSUM =
  'lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrnt';

/** signature is not recoverable */
const UNRECOVERABLE_SIGNATURE =
  'lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqwgt7mcn5yqw3yx0w94pswkpq6j9uh6xfqqqtsk4tnarugeektd4hg5975x9am52rz4qskukxdmjemg92vvqz8nvmsye63r5ykel43pgz7zq0g2';

/** string is too short */
const TOO_SHORT =
  'lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6na6hlh';

/** invalid amount multiplier */
const BAD_MULTIPLIER =
  'lnbc2500x1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqrrzc4cvfue4zp3hggxp47ag7xnrlr8vgcmkjxk3j5jqethnumgkpqp23z9jclu3v0a7e0aruz366e9wqdykw6dxhdzcjjhldxq0w6wgqcnu43j';

/** invalid sub-millisatoshi precision */
const SUB_MSAT_PRECISION =
  'lnbc2500000001p1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgq0lzc236j96a95uv0m3umg28gclm5lqxtqqwk32uuk4k6673k6n5kfvx3d2h8s295fad45fdhmusm8sjudfhlf6dcsxmfvkeywmjdkxcp99202x';

// ── Zeus fixtures (utils/Bolt11Utils.test.ts) ────────────────────────────────────────────────────

/** regtest, 1230n = 123 sat, no expiry field */
const REGTEST_1230N =
  'lnbcrt1230n1pj429x7pp57t97q4awqj3f529snr0pa6senk83sq5pp760qf5a4jzvd7xgwcksdqqcqzzsxqrrsssp57eqtv7vxr46arupna3w4ct0lkf2mqmz9wt044cwkks0rwlnhfr5s9qyyssqragwpwav7nfwv2xyuuamxxj4pnnpzv2hlw7j473repd3sq7st698ta9kmzmygt0w7tmncl56a6mnma0w7e5dlpqd0wy6x3v35rssldspjhh8p0';

/** signet, 567780n = 56778 sat */
const SIGNET_567780N =
  'lntbs567780n1pnqr26ypp5c0wcrpzwxwqnwu2nld5q36dfc9yjrfdp87nn9d5y093jjncvqresdq0w3jhxar8v3n8xeccqzpuxqrrsssp5r94e3nwnw63gjaxc8wex38ufv2m6442vnrw49m7dad9jdum3tdsq9qyyssqk4dvvuk7zhhju8ztf7nfc2hzqq9gqtzuyc0ljz8nl93laxwv4869lt9fsxkxacje6eh4ur5ymg83hvakn4tfpzdu6fq49705sar7fxspga8qjp';

/** Every spec vector shares this payee, payment hash and timestamp. */
const SPEC_PAYEE = '03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad';
const SPEC_PAYMENT_HASH = '0001020304050607080900010203040506070809000102030405060708090102';
const SPEC_TIMESTAMP = 1496314658;
// ── amounts ──────────────────────────────────────────────────────────────────────────────────────

describe('decodeBolt11 amounts', () => {
  it('returns null for a zero-amount invoice', () => {
    expect(decodeBolt11(NO_AMOUNT).amountMsat).toBeNull();
  });

  it('parses every multiplier as an exact msat string', () => {
    // 20m = 0.02 BTC, 2500u = 0.0025 BTC, 1230n = 123 sat, 9678785340p = 967878534 msat.
    expect(decodeBolt11(HASHED_20M).amountMsat).toBe('2000000000');
    expect(decodeBolt11(COFFEE_250U).amountMsat).toBe('250000000');
    expect(decodeBolt11(REGTEST_1230N).amountMsat).toBe('123000');
    expect(decodeBolt11(PICO_9678785340P).amountMsat).toBe('967878534');
  });

  it('keeps amountMsat a string and never widens it to a number', () => {
    const amount = decodeBolt11(PICO_9678785340P).amountMsat;
    expect(typeof amount).toBe('string');
    // A pico amount need not be a whole sat: the msat string is exact where `satoshis` cannot be.
    expect(decodeBolt11Invoice(PICO_9678785340P).satoshis).toBeNull();
    expect(decodeBolt11Invoice(REGTEST_1230N).satoshis).toBe(123);
  });

  it('rejects an unknown multiplier', () => {
    expect(() => decodeBolt11(BAD_MULTIPLIER)).toThrow(BackendError);
  });

  it('rejects sub-millisatoshi precision instead of rounding it', () => {
    expect(() => decodeBolt11(SUB_MSAT_PRECISION)).toThrow(BackendError);
  });
});

// ── fields ───────────────────────────────────────────────────────────────────────────────────────

describe('decodeBolt11 fields', () => {
  it('extracts the payment hash, destination and timestamp', () => {
    const decoded = decodeBolt11(NO_AMOUNT);
    expect(decoded.paymentHash).toBe(SPEC_PAYMENT_HASH);
    expect(decoded.destination).toBe(SPEC_PAYEE);
    expect(decoded.timestamp).toBe(SPEC_TIMESTAMP);
  });

  it('recovers a key from a high-S signature instead of rejecting it', () => {
    // The spec's high-S vector is the donation invoice re-signed with s negated while keeping recovery
    // flag 1, so it recovers a *different* (valid) key. What matters is that recovery runs at all —
    // implementations that enforce low-S during recovery fail here.
    expect(decodeBolt11(HIGH_S_SIGNATURE).destination).toMatch(/^0[23][0-9a-f]{64}$/);
    expect(decodeBolt11(HIGH_S_SIGNATURE).destination).not.toBe(SPEC_PAYEE);
    expect(decodeBolt11(HIGH_S_SIGNATURE).paymentHash).toBe(SPEC_PAYMENT_HASH);
  });

  it('returns the description for a `d` invoice and null for an `h` one', () => {
    expect(decodeBolt11(NO_AMOUNT).description).toBe('Please consider supporting this project');
    expect(decodeBolt11(COFFEE_250U).description).toBe('1 cup coffee');
    expect(decodeBolt11(NONSENSE_250U).description).toBe('ナンセンス 1杯');

    const hashed = decodeBolt11Invoice(HASHED_20M);
    expect(decodeBolt11(HASHED_20M).description).toBeNull();
    expect(hashed.descriptionHash).toBe('3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1');
  });

  it('defaults expiry to 3600 when there is no `x` field', () => {
    expect(decodeBolt11(NO_AMOUNT).expiry).toBe(3600);
    expect(decodeBolt11Invoice(NO_AMOUNT).expiry).toBeNull();
    expect(decodeBolt11(REGTEST_1230N).expiry).toBe(3600);
  });

  it('reads an explicit expiry', () => {
    expect(decodeBolt11(COFFEE_250U).expiry).toBe(60);
    expect(decodeBolt11(PICO_9678785340P).expiry).toBe(604800);
    expect(decodeBolt11Invoice(COFFEE_250U).expiresAt).toBe(SPEC_TIMESTAMP + 60);
  });

  it('expands the feature bitmap to the indices of the set bits', () => {
    expect(decodeBolt11(NO_AMOUNT).features).toEqual(['8', '14']);
    expect(decodeBolt11(FEATURES_25M).features).toEqual(['8', '14', '99']);
  });

  it('reports route hints and parses their hops', () => {
    expect(decodeBolt11(NO_AMOUNT).routeHints).toBe(false);

    const decoded = decodeBolt11Invoice(MAINNET_20M_ROUTES);
    expect(decodeBolt11(MAINNET_20M_ROUTES).routeHints).toBe(true);
    expect(decoded.routes).toHaveLength(1);
    expect(decoded.routes[0]).toEqual([
      {
        pubkey: '029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255',
        shortChannelId: '66051x263430x1800',
        feeBaseMsat: 1,
        feeProportionalMillionths: 20,
        cltvExpiryDelta: 3,
      },
      {
        pubkey: '039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255',
        shortChannelId: '197637x395016x2314',
        feeBaseMsat: 2,
        feeProportionalMillionths: 30,
        cltvExpiryDelta: 4,
      },
    ]);
  });

  it('parses payment secret, metadata and fallback addresses', () => {
    expect(decodeBolt11Invoice(NO_AMOUNT).paymentSecret).toBe(
      '1111111111111111111111111111111111111111111111111111111111111111',
    );
    expect(decodeBolt11Invoice(METADATA_10M).metadata).toBe('01fafaf0');

    const fallback = decodeBolt11Invoice(TESTNET_20M_FALLBACK).fallbacks[0];
    // 17 is the P2PKH marker; the program is the 20-byte hash160 of mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP.
    expect(fallback?.version).toBe(17);
    expect(fallback?.programHex).toHaveLength(40);
  });

  it('leaves cltvExpiry null when the invoice omits `c`', () => {
    expect(decodeBolt11(NO_AMOUNT).cltvExpiry).toBeNull();
  });
});

// ── networks ─────────────────────────────────────────────────────────────────────────────────────

describe('decodeBolt11 networks', () => {
  it('resolves every bech32 network prefix', () => {
    expect(decodeBolt11Invoice(NO_AMOUNT).network).toBe('bitcoin');
    expect(decodeBolt11Invoice(TESTNET_20M_FALLBACK).network).toBe('testnet');
    expect(decodeBolt11Invoice(SIGNET_567780N).network).toBe('signet');
    expect(decodeBolt11Invoice(REGTEST_1230N).network).toBe('regtest');
  });

  it('parses the amount out of a multi-letter network prefix', () => {
    // `lnbcrt1230n` and `lntbs567780n` are the cases where a greedy hrp match eats the amount.
    expect(decodeBolt11(REGTEST_1230N).amountMsat).toBe('123000');
    expect(decodeBolt11(SIGNET_567780N).amountMsat).toBe('56778000');
  });

  it('rejects an unknown network prefix', () => {
    expect(() => decodeBolt11(NO_AMOUNT.replace(/^lnbc/, 'lnxyz'))).toThrow(BackendError);
  });
});

// ── malformed input ──────────────────────────────────────────────────────────────────────────────

describe('decodeBolt11 rejections', () => {
  const expectBadInvoice = (invoice: string) => {
    let caught: unknown;
    try {
      decodeBolt11(invoice);
    } catch (err) {
      caught = err;
    }
    expect(caught).toBeInstanceOf(BackendError);
    expect((caught as BackendError).status).toBe(400);
    expect((caught as BackendError).code).toBe('BAD_INVOICE');
  };

  it('throws BackendError(400, BAD_INVOICE) on a corrupted checksum', () => {
    expectBadInvoice(BAD_CHECKSUM);
    // The same invoice with one data character flipped.
    expectBadInvoice(`${COFFEE_250U.slice(0, -1)}${COFFEE_250U.endsWith('h') ? 'w' : 'h'}`);
  });

  it('throws on a signature that cannot be recovered', () => {
    expectBadInvoice(UNRECOVERABLE_SIGNATURE);
  });

  it('throws when the recovered key contradicts an explicit `n` payee field', () => {
    expectBadInvoice(PAYEE_MISMATCH);
  });

  it('throws on a truncated payment request', () => {
    expectBadInvoice(TOO_SHORT);
  });

  it('throws on input that is not a payment request at all', () => {
    expectBadInvoice('');
    expectBadInvoice('not an invoice');
    expectBadInvoice(NO_AMOUNT.replace(/^ln/, ''));
  });

  it('throws on a mixed-case payment request but accepts the uppercase QR form', () => {
    expectBadInvoice(`LNBC${NO_AMOUNT.slice(4)}`);
    expect(decodeBolt11(FEATURES_25M_UPPER)).toEqual(decodeBolt11(FEATURES_25M));
    // The contract's `bolt11` is always the canonical lowercase form.
    expect(decodeBolt11(FEATURES_25M_UPPER).bolt11).toBe(FEATURES_25M);
  });
});

// ── cache ────────────────────────────────────────────────────────────────────────────────────────

describe('the LRU cache', () => {
  it('returns the same object for a repeat decode and survives a clear', () => {
    clearBolt11Cache();
    const first = decodeBolt11Invoice(COFFEE_250U);
    expect(decodeBolt11Invoice(COFFEE_250U)).toBe(first);
    expect(decodeBolt11Invoice(COFFEE_250U.toUpperCase())).toBe(first);

    clearBolt11Cache();
    const afterClear = decodeBolt11Invoice(COFFEE_250U);
    expect(afterClear).not.toBe(first);
    expect(afterClear).toEqual(first);
  });

  it('hands every decodeBolt11 caller its own object', () => {
    const first = decodeBolt11(COFFEE_250U);
    const second = decodeBolt11(COFFEE_250U);
    expect(second).not.toBe(first);
    expect(second).toEqual(first);
  });
});
