@ghostlytools/cli 1.3.3 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +2 -1
  3. package/dist/assets/{api-Bg7bh_6E.js → api-BCwaCbKh.js} +2 -2
  4. package/dist/assets/{api-B1fADtqd.js → api-Bp_hWAiU.js} +44 -9
  5. package/dist/assets/api-Bp_hWAiU.js.map +1 -0
  6. package/dist/assets/{apiKit-BsUZPHLn.js → apiKit-3MLUyuhL.js} +2 -2
  7. package/dist/assets/{apiKit-BsUZPHLn.js.map → apiKit-3MLUyuhL.js.map} +1 -1
  8. package/dist/assets/{apps-DepomCiX.js → apps-B6gqk4GD.js} +1 -1
  9. package/dist/assets/{apps-BaUNt-5c.js → apps-BXw3Ui_3.js} +2 -2
  10. package/dist/assets/{apps-BaUNt-5c.js.map → apps-BXw3Ui_3.js.map} +1 -1
  11. package/dist/assets/{arkade-CNnMaoEQ.js → arkade-CyplNY_v.js} +2 -2
  12. package/dist/assets/{arkade-CNnMaoEQ.js.map → arkade-CyplNY_v.js.map} +1 -1
  13. package/dist/assets/{backup-Bbz3lyC5.js → backup-BGE4BDzf.js} +4 -4
  14. package/dist/assets/{backup-Bbz3lyC5.js.map → backup-BGE4BDzf.js.map} +1 -1
  15. package/dist/assets/{backup-B5Q1W-Ey.js → backup-Cm8ptQQA.js} +1 -1
  16. package/dist/assets/{buttons-DS1iURp8.js → buttons-52NT4sA_.js} +3 -3
  17. package/dist/assets/{buttons-DS1iURp8.js.map → buttons-52NT4sA_.js.map} +1 -1
  18. package/dist/assets/buttons-PCvHRlkS.js +2 -0
  19. package/dist/assets/{envelope-fNC941z4.js → envelope-CsRRCrL3.js} +2 -2
  20. package/dist/assets/{envelope-fNC941z4.js.map → envelope-CsRRCrL3.js.map} +1 -1
  21. package/dist/assets/{groupFiles-6qnLCnOm.js → groupFiles-BurezP4Z.js} +3 -3
  22. package/dist/assets/{groupFiles-6qnLCnOm.js.map → groupFiles-BurezP4Z.js.map} +1 -1
  23. package/dist/assets/{host-Dj3C1fn3.js → host-C74Sy5jT.js} +8 -8
  24. package/dist/assets/{host-Dj3C1fn3.js.map → host-C74Sy5jT.js.map} +1 -1
  25. package/dist/assets/{hyperdhtRelay-CrgYQN-g.js → hyperdhtRelay-D0MvfMzX.js} +2 -2
  26. package/dist/assets/{hyperdhtRelay-CrgYQN-g.js.map → hyperdhtRelay-D0MvfMzX.js.map} +1 -1
  27. package/dist/assets/{irohWeb-DSyLp5Re.js → irohWeb-DynqwRwl.js} +2 -2
  28. package/dist/assets/{irohWeb-DSyLp5Re.js.map → irohWeb-DynqwRwl.js.map} +1 -1
  29. package/dist/assets/{main-BDd11Yd1.js → main-DNzQOdNG.js} +23 -17
  30. package/dist/assets/{main-BDd11Yd1.js.map → main-DNzQOdNG.js.map} +1 -1
  31. package/dist/assets/{mainline-CLTo0nGa.js → mainline-COqCHn1R.js} +2 -2
  32. package/dist/assets/{mainline-CLTo0nGa.js.map → mainline-COqCHn1R.js.map} +1 -1
  33. package/dist/assets/{networkReason-DwcadZz5.js → networkReason-WikfjkIw.js} +2 -2
  34. package/dist/assets/{networkReason-DwcadZz5.js.map → networkReason-WikfjkIw.js.map} +1 -1
  35. package/dist/assets/{nwc-BNFyyECH.js → nwc-ClsjYuyP.js} +3 -3
  36. package/dist/assets/{nwc-BNFyyECH.js.map → nwc-ClsjYuyP.js.map} +1 -1
  37. package/dist/assets/{nwcProvider-BbjJOfLD.js → nwcProvider-ksMYZra9.js} +2 -2
  38. package/dist/assets/{nwcProvider-BbjJOfLD.js.map → nwcProvider-ksMYZra9.js.map} +1 -1
  39. package/dist/assets/{registry-CoLHpxkb.js → registry-CJh9L_kF.js} +3 -3
  40. package/dist/assets/{registry-CoLHpxkb.js.map → registry-CJh9L_kF.js.map} +1 -1
  41. package/dist/assets/{registry-DoTb6uEr.js → registry-S7SFF_JB.js} +1 -1
  42. package/dist/assets/{resolve-BIDJ-UUl.js → resolve-BJxBhU5j.js} +2 -2
  43. package/dist/assets/{resolve-BIDJ-UUl.js.map → resolve-BJxBhU5j.js.map} +1 -1
  44. package/dist/assets/{server-Bx3RGkBC.js → server-C05N9q4G.js} +28 -15
  45. package/dist/assets/server-C05N9q4G.js.map +1 -0
  46. package/dist/assets/{src-D7gcJElT.js → src-RQ6OJOUO.js} +7 -1
  47. package/dist/assets/src-RQ6OJOUO.js.map +1 -0
  48. package/dist/assets/{turnRefs-PN644M9o.js → turnRefs-u-U75DFx.js} +2 -2
  49. package/dist/assets/{turnRefs-PN644M9o.js.map → turnRefs-u-U75DFx.js.map} +1 -1
  50. package/dist/assets/{usdt-B2TLRlYa.js → usdt-A1B3r3Mq.js} +3 -3
  51. package/dist/assets/{usdt-B2TLRlYa.js.map → usdt-A1B3r3Mq.js.map} +1 -1
  52. package/dist/ghostly.mjs +1 -1
  53. package/package.json +1 -1
  54. package/dist/assets/api-B1fADtqd.js.map +0 -1
  55. package/dist/assets/buttons-CUd7j0ZA.js +0 -2
  56. package/dist/assets/server-Bx3RGkBC.js.map +0 -1
  57. package/dist/assets/src-D7gcJElT.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"groupFiles-6qnLCnOm.js","names":[],"sources":["../../../browser/src/engine/paymentAdapters/away.ts","../../../browser/src/engine/paymentAdapters/modeGate.ts","../../../browser/src/shared/ice.ts","../../../browser/src/engine/paymentAdapters/providers/breezDatabases.ts","../../../browser/src/engine/paymentAdapters/providers/cashuMint.ts","../../../browser/src/engine/paymentAdapters/providers/sources.ts","../../../browser/src/engine/paymentAdapters/providers/lightningService.ts","../../../browser/src/engine/paymentAdapters/providers/recoveryPhrase.ts","../../../browser/src/engine/paymentAdapters/providers/lightningCards.ts","../../../browser/src/shared/storedFiles.ts","../../../browser/src/shared/types.ts","../../../browser/src/engine/paymentAdapters/walletInstances.ts","../../../browser/src/shared/paymentIds.ts","../../../browser/src/shared/replies.ts","../../../browser/src/engine/groupFiles.ts"],"sourcesContent":["import { engineText } from \"@ghostly/core\";\n\n/*\n * Wallets at home on another device (WISP 06 § Wallets that stay home): their database lives there, and no other\n * device opens them. The engine says which, by wallet id (`<type>:<network>`, `lightning:<network>:<card>`), before\n * any wallet opens; each wallet asks here before it opens its SDK, so no path that opens one (a start, a retry, a\n * restore, an unlock) opens a wallet that is away.\n */\n\nlet away = new Map<string, string>();\n\n/** The wallets away from this device, by wallet id, with the name of their home device (\"\" when it has none here). */\nexport function setAwayWallets(next: ReadonlyMap<string, string>): void {\n away = new Map(next);\n}\n\n/** The home device's name of a wallet away from this device, or undefined when it is here. */\nexport function awayFrom(id: string): string | undefined {\n return away.get(id);\n}\n\n/** A wallet that cannot be used here: it is at home on another device. */\nexport class WalletAwayError extends Error {\n constructor(wallet: string, device: string) {\n super(device ? engineText(\"walletAway\", { wallet, device }) : engineText(\"walletAwayUnnamed\", { wallet }));\n this.name = \"WalletAwayError\";\n }\n}\n\n/**\n * Whether a single-writer wallet (Ark, Bark, Spark and the Breez source, Fedimint) may open its SDK now: the engine\n * says, from a good turn read under 60 seconds old (WISP 06 § Wallets). Every open asks, the retries of each wallet\n * included, so a device replaced while it was offline does not reopen one on a retry.\n */\nlet turnGate: () => Promise<boolean> = async () => true;\nexport function setSingleWriterGate(gate: (() => Promise<boolean>) | null): void {\n turnGate = gate ?? (async () => true);\n}\n\n/** Throws (the wallet tries again later, as it does when its server does not answer) without a fresh good turn read. */\nexport async function requireTurn(): Promise<void> {\n if (!(await turnGate().catch(() => false))) throw new Error(\"Checking which device is active\");\n}\n\n/** Throws when the wallet `id` is at home on another device. */\nexport function refuseAway(id: string, wallet: string): void {\n const device = awayFrom(id);\n if (device !== undefined) throw new WalletAwayError(wallet, device);\n}\n","import type { WalletNetwork } from \"@ghostly/core\";\nimport type { WalletMode } from \"../../shared/mints\";\n\n/** A wallet of the mode being left stopped opening: the switch goes ahead without waiting for its network. */\nexport class ModeChanged extends Error {\n constructor() { super(\"The wallets switched mode\"); this.name = \"ModeChanged\"; }\n}\n\nexport const networkLabel = (network: WalletNetwork) => network === \"mainnet\" ? \"Mainnet\" : \"Testnet\";\n\n/**\n * What was asked of one network's wallet belongs to the other network's (a backup, a chain): nothing was changed.\n * `network` is where it belongs, so the engine can hand it to that wallet instead.\n */\nexport class WrongNetworkError extends Error {\n constructor(readonly network: WalletNetwork, message: string) { super(message); this.name = \"WrongNetworkError\"; }\n}\n\n/**\n * Opening a wallet waits on its network (an Ark server, an RPC), which can take minutes or never answer.\n * A mode switch must not wait behind that: `within` gives up on the wait as soon as the mode changes, and\n * whatever still arrives afterwards is handed to `dispose`. Nothing is saved before that wait ends, so\n * giving up leaves the profile as it was. Every wait on the network inside a wallet's queue goes through it,\n * reading a balance too, or the switch, queued behind it, waits anyway.\n */\nexport class ModeGate {\n constructor(private target: WalletMode = \"mainnet\") {}\n private controller = new AbortController();\n /** The mode its owner works in, once it says so: a wait that starts while a switch away from it is queued ends at once. */\n private current?: WalletMode;\n\n /** A switch to `mode` was asked for: waits for a wallet of another mode end now. */\n switching(mode: WalletMode) {\n if (this.target === mode) return;\n this.target = mode;\n this.controller.abort();\n this.controller = new AbortController();\n }\n\n /** The owner's switch to `mode` is applied (its queue reached it): waits are for this mode again. */\n entered(mode: WalletMode) { this.current = mode; }\n\n /**\n * A creation took too long: the waits under way end now, and so does every wait that starts until `resume`\n * (a creation still sealing its seed has not reached its server yet, and must not wait there either).\n */\n interrupt() { this.cut = true; this.controller.abort(); this.controller = new AbortController(); }\n resume() { this.cut = false; }\n private cut = false;\n\n /** The wallet stopped: every wait ends now, and any later one at once. */\n close() { this.closed = true; this.controller.abort(); }\n private closed = false;\n\n within<T>(work: Promise<T>, dispose?: (value: T) => unknown): Promise<T> {\n const signal = this.controller.signal;\n return new Promise<T>((resolve, reject) => {\n const give = () => { reject(new ModeChanged()); void work.then((value) => dispose?.(value), () => {}); };\n if (this.closed || this.cut || (this.current && this.current !== this.target)) return give();\n signal.addEventListener(\"abort\", give, { once: true });\n work.then(\n (value) => { signal.removeEventListener(\"abort\", give); if (!signal.aborted) resolve(value); },\n (error) => { signal.removeEventListener(\"abort\", give); if (!signal.aborted) reject(error); },\n );\n });\n }\n}\n","import type { IceServerSetting } from \"./types\";\n\n/**\n * What is wrong with a TURN/STUN server, or null. Browsers refuse to create any WebRTC connection when\n * one server in the list is malformed, so a bad entry here would silently cut off calls and files.\n */\nexport function iceServerProblem(server: IceServerSetting): string | null {\n // A setting from outside the form (the CLI, `ghostly engine`, an import) may have no urls at all: before, a TypeError.\n if (typeof server?.urls !== \"string\") return \"Each ICE server needs urls: one address, or several comma-separated (turn:host:port)\";\n const urls = server.urls.split(/[\\s,]+/).filter(Boolean);\n if (!urls.length) return \"Enter the TURN server address\";\n for (const url of urls) {\n if (!/^(stun|turns?):[^\\s/?#]+(\\?transport=(udp|tcp))?$/i.test(url)) return `“${url}” is not a TURN or STUN address (turn:host:port)`;\n if (/^turns?:/i.test(url) && (!server.username?.trim() || !server.credential?.trim())) return \"A TURN server needs its username and credential\";\n }\n return null;\n}\n\n/**\n * A server as WebRTC writes it (`urls` a list, as every TURN guide does) in this setting's own form: the addresses\n * comma-separated. Anything else is left as it is, for `iceServerProblem` to say what is wrong.\n */\nexport function iceServerSetting(server: IceServerSetting): IceServerSetting {\n const urls: unknown = server?.urls;\n if (!Array.isArray(urls) || !urls.every((url) => typeof url === \"string\")) return server;\n return { ...server, urls: urls.join(\", \") };\n}\n","import { sha256 } from \"@noble/hashes/sha2.js\";\nimport { databaseName } from \"../../../shared/idb\";\n\n/**\n * Where the Breez SDK keeps a wallet on this device, and which profile it belongs to.\n *\n * The SDK keeps its IndexedDB databases under one storage name (`breezDatabaseFiles`). That name is one per **profile,\n * network and seed**: the Spark rail and a Breez Lightning card of the same phrase in one profile are one SDK instance\n * over one database (`openBreez`), two phrases are two wallets, and a profile restored as a copy on the same device\n * starts from the phrase with a database of its own. Two SDK instances over one database would each think the leaves\n * are theirs.\n *\n * Before this, the name came from the network and the seed only, so every profile of the origin holding that phrase\n * opened the same database. Such a database is kept, under its old name, for **one** profile: the first to open the\n * phrase on this device after the change claims it in a small database of the device (`ghostly-breez`), outside every\n * profile database (a backup copies a profile's database whole, so a mark in it would be the copy's too). Any other\n * profile gets a new name. The same register lists every name a profile uses, so deleting a profile deletes its Breez\n * databases without opening its sealed phrases.\n */\nexport type BreezDatabaseNetwork = \"mainnet\" | \"regtest\";\nconst netOf = (network: string): BreezDatabaseNetwork => (network === \"bitcoin\" || network === \"mainnet\" ? \"mainnet\" : \"regtest\");\nconst hex = (bytes: Uint8Array) => Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\nconst digest = (text: string) => hex(sha256(new TextEncoder().encode(text))).slice(0, 16);\n\n/** What a Breez database name looks like, old or new: nothing about the seed or the profile shows in it. */\nexport const BREEZ_DATABASE = /^ghostly-breez-(mainnet|regtest)-[0-9a-f]{16}$/;\n\n/** The name every profile of the origin used for this phrase before databases were per profile. */\nexport const breezLegacyName = (network: string, mnemonic: string) => `ghostly-breez-${netOf(network)}-${digest(`ghostly-breez:${netOf(network)}:${mnemonic}`)}`;\n\n/** The name of this phrase's Breez database in one profile (`space`: the profile's peer database, WISP 04). */\nexport const breezDatabaseName = (network: string, mnemonic: string, space: string = databaseName()) =>\n `ghostly-breez-${netOf(network)}-${digest(`ghostly-breez:${space}:${netOf(network)}:${mnemonic}`)}`;\n\n/**\n * The databases the SDK keeps under one storage name. SDK 0.26 roots them there: `<name>/<network>/<identity>` and\n * `<name>/<network>/<identity>-tree`, the identity read from the seed inside the SDK. Older builds kept `<name>`\n * itself. A browser without `indexedDB.databases()` cannot list them: only the bare names are known there.\n */\nexport async function breezDatabaseFiles(name: string): Promise<string[]> {\n const files = [name, `${name}-tree`];\n if (typeof indexedDB.databases !== \"function\") return files;\n const under = (await indexedDB.databases()).map((d) => d.name ?? \"\").filter((file) => file.startsWith(`${name}/`));\n return [...new Set([...files, ...under])];\n}\n\ninterface Entry { name: string; space: string; network: BreezDatabaseNetwork; legacy?: true }\nconst REGISTER = \"ghostly-breez\", STORE = \"databases\";\n\nconst request = <T>(r: IDBRequest<T>) => new Promise<T>((resolve, reject) => { r.onsuccess = () => resolve(r.result); r.onerror = () => reject(r.error); });\nfunction openRegister(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const open = indexedDB.open(REGISTER, 1);\n open.onupgradeneeded = () => { if (!open.result.objectStoreNames.contains(STORE)) open.result.createObjectStore(STORE); };\n open.onsuccess = () => resolve(open.result);\n open.onerror = () => reject(open.error);\n open.onblocked = () => reject(new Error(\"The Breez register is blocked\"));\n });\n}\nasync function withRegister<T>(mode: IDBTransactionMode, run: (store: IDBObjectStore) => Promise<T> | T): Promise<T> {\n const db = await openRegister();\n try {\n const tx = db.transaction(STORE, mode);\n const done = new Promise<void>((resolve, reject) => { tx.oncomplete = () => resolve(); tx.onerror = tx.onabort = () => reject(tx.error); });\n const result = await run(tx.objectStore(STORE));\n await done;\n return result;\n } finally { db.close(); }\n}\n\n/**\n * Whether the SDK keeps a wallet under this storage name on this device (`breezDatabaseFiles`). A browser that cannot\n * list databases is answered yes: the first profile then takes the old name whether or not it is there, which is\n * safe, since only one profile ever has it.\n */\nasync function exists(name: string): Promise<boolean> {\n if (typeof indexedDB.databases !== \"function\") return true;\n return (await indexedDB.databases()).some((d) => d.name === name || !!d.name?.startsWith(`${name}/`));\n}\n\n/**\n * The Breez database this profile opens for this phrase: the old shared one when this profile owns it (or is the\n * first to claim it), else the profile's own. Noted in the register either way. A device whose IndexedDB cannot be\n * read gets the profile's own name: never a database another profile may use.\n */\nexport async function breezDatabase(network: string, mnemonic: string, space: string = databaseName()): Promise<string> {\n const fresh = breezDatabaseName(network, mnemonic, space);\n if (typeof indexedDB === \"undefined\") return fresh;\n const legacy = breezLegacyName(network, mnemonic);\n try {\n const legacyHere = await exists(legacy);\n return await withRegister(\"readwrite\", async (store) => {\n // Both reads and the write in one transaction: two profiles opening at once cannot both claim the old one.\n const [owner, own] = await Promise.all([request(store.get(legacy)) as Promise<Entry | undefined>, request(store.get(fresh)) as Promise<Entry | undefined>]);\n if (owner?.space === space) return legacy;\n if (!own && !owner && legacyHere) { store.put({ name: legacy, space, network: netOf(network), legacy: true } satisfies Entry, legacy); return legacy; }\n if (!own) store.put({ name: fresh, space, network: netOf(network) } satisfies Entry, fresh);\n return fresh;\n });\n } catch { return fresh; }\n}\n\n/** The names this profile has noted for this phrase (nothing is claimed): what removing the wallet deletes. */\nexport async function breezDatabasesFor(network: string, mnemonic: string, space: string = databaseName()): Promise<string[]> {\n const names = [breezLegacyName(network, mnemonic), breezDatabaseName(network, mnemonic, space)];\n return (await breezDatabasesOf(space)).filter((name) => names.includes(name));\n}\n\n/**\n * The name this profile uses for this phrase, read without claiming anything: the one it noted (`breezDatabase`), else\n * the profile's own name. What a handoff deletes and compares (WISP 06 § Wallets): never the old shared database of\n * another profile that has not opened the phrase since the change.\n */\nexport async function breezDatabaseInUse(network: string, mnemonic: string, space: string = databaseName()): Promise<string> {\n return (await breezDatabasesFor(network, mnemonic, space))[0] ?? breezDatabaseName(network, mnemonic, space);\n}\n\n/** Every Breez database name noted for a profile of this device. */\nexport async function breezDatabasesOf(space: string): Promise<string[]> {\n if (typeof indexedDB === \"undefined\") return [];\n try { return ((await withRegister(\"readonly\", (store) => request(store.getAll()))) as Entry[]).filter((e) => e?.space === space && BREEZ_DATABASE.test(e.name)).map((e) => e.name); }\n catch { return []; }\n}\n\nconst deleteDatabase = (name: string) => new Promise<void>((resolve) => {\n try { const r = indexedDB.deleteDatabase(name); r.onsuccess = r.onerror = r.onblocked = () => resolve(); } catch { resolve(); }\n});\n\nconst deleteStrictly = (name: string) => new Promise<void>((resolve, reject) => {\n const r = indexedDB.deleteDatabase(name);\n r.onsuccess = () => resolve();\n r.onerror = () => reject(r.error ?? new Error(`${name} was not deleted`));\n r.onblocked = () => reject(new Error(`${name} is still open`));\n});\n\n/**\n * Deletes one Breez wallet's databases (all the SDK keeps under the name) and its note in the register. Anything else\n * is refused. `strict` (a handoff, WISP 06 § Wallets): a delete that is blocked or fails rejects and the note stays,\n * so the caller tries again.\n */\nexport async function dropBreezDatabase(name: string, options: { strict?: boolean } = {}): Promise<void> {\n if (!BREEZ_DATABASE.test(name) || typeof indexedDB === \"undefined\") return;\n for (const file of await breezDatabaseFiles(name).catch(() => [name, `${name}-tree`])) await (options.strict ? deleteStrictly(file) : deleteDatabase(file));\n await withRegister(\"readwrite\", (store) => { store.delete(name); }).catch(() => {});\n}\n\n/** Deletes every Breez database a profile used on this device (the profile is being deleted). */\nexport async function dropBreezDatabasesOf(space: string): Promise<void> {\n for (const name of await breezDatabasesOf(space)) await dropBreezDatabase(name);\n}\n","import { decodeBolt11, engineError } from \"@ghostly/core\";\nimport type { WalletMode } from \"../../../shared/mints\";\nimport type { CashuWallet } from \"../../wallet\";\nimport type { LightningInvoice, LightningPaymentRef, LightningProvider, LightningProviderDescriptor } from \"./lightning\";\nimport { NothingSpentError, PROVIDER_PLATFORMS } from \"./types\";\n\nexport const CASHU_MINT_SOURCE = \"cashu-mint\";\n\ninterface MintRef { mint: string; quote: string }\nconst parseRef = (ref: string | undefined): MintRef => {\n const parsed = JSON.parse(ref ?? \"null\") as MintRef | null;\n if (!parsed?.mint || !parsed.quote) throw new Error(\"Unknown Cashu quote\");\n return parsed;\n};\n\n/**\n * Lightning through the Cashu mints: invoices are mint quotes that turn into ecash once paid, and\n * invoices are paid by melting ecash. The default source, and what Lightning was before sources.\n *\n * The Cashu wallet already polls its quotes and pending melts, mints and books them and says so\n * (`onQuotePaid`, `onMeltResolved`): the engine forwards those, and never polls this source itself.\n */\nexport class CashuMintLightning implements LightningProvider {\n readonly capabilities = { receive: true, send: true, balance: true, lookup: true };\n readonly settlesItself = true;\n /** The melt quote shown to the person is the one paid: quoting again could change the fee. */\n private readonly quotes = new Map<string, { quote: string; mint: string; amount: number; feeReserve: number }>();\n\n constructor(private readonly wallet: CashuWallet, private readonly mode: WalletMode) {}\n\n async info() {\n // Test mints issue invoices for worthless sats (often with a mainnet prefix): the mode says which it is.\n return { network: this.mode === \"mainnet\" ? \"bitcoin\" as const : \"testnet\" as const, balance: (await this.wallet.view(this.mode)).balance };\n }\n\n async createInvoice(amount: number): Promise<LightningInvoice> {\n const quote = await this.wallet.receiveLightning(amount, undefined, this.mode);\n const decoded = decodeBolt11(quote.invoice);\n if (!decoded?.paymentHash) throw new Error(\"The mint returned an invoice that cannot be read\");\n return { invoice: quote.invoice, paymentHash: decoded.paymentHash, amount, expiresAt: quote.expiresAt ?? decoded.expiresAt * 1000, ref: JSON.stringify({ mint: quote.mint, quote: quote.quote }) };\n }\n\n async invoiceStatus(invoice: LightningInvoice) {\n const { mint, quote } = parseRef(invoice.ref);\n // A test mint's invoice reads paid by itself: until a payer says it paid, it is open (see CashuWallet.vouch).\n const state = await this.wallet.isHeld(quote) ? \"UNPAID\" : await this.wallet.mintQuoteState(mint, quote);\n if (state === \"PAID\" || state === \"ISSUED\") return { state: \"paid\" as const, amount: invoice.amount };\n return { state: invoice.expiresAt < Date.now() ? \"expired\" as const : \"open\" as const };\n }\n\n /** A real melt quote: its fee reserve is what paying may cost at most. It is kept for `payInvoice`. */\n async estimateFee(invoice: string) {\n const quote = await this.wallet.quoteInvoice(invoice, this.mode);\n this.quotes.set(invoice.trim().toLowerCase(), quote);\n return quote.feeReserve;\n }\n\n /** The quote behind a fee just shown, when there is one: `payQuote` pays exactly that. */\n quoteFor(invoice: string) {\n return this.quotes.get(invoice.trim().toLowerCase());\n }\n\n /** The melt quote `payInvoice` pays, when its fee was shown first: what the mint is asked about later. */\n paymentRef(invoice: string) {\n const quote = this.quotes.get(invoice.trim().toLowerCase());\n return quote ? JSON.stringify({ mint: quote.mint, quote: quote.quote } satisfies MintRef) : undefined;\n }\n\n /**\n * A payment the app was cut off from (the tab closed while it was being paid). A melt written down is the wallet's\n * to settle and report. With none, the mint's word on the quote decides: paid, or never melted (the melt is written\n * down before the mint is handed anything, so an unpaid quote with no melt spent nothing).\n */\n async interruptedPayment(payment: LightningPaymentRef) {\n if (await this.wallet.meltInFlight(payment.invoice)) return \"pending\" as const;\n if (!payment.ref) return \"unknown\" as const;\n const { mint, quote } = parseRef(payment.ref);\n const state = await this.wallet.meltQuoteState(mint, quote);\n // Asked again: a melt written while the mint was being asked is the wallet's to settle.\n if (await this.wallet.meltInFlight(payment.invoice)) return \"pending\" as const;\n return state === \"PAID\" ? \"paid\" as const : state === \"PENDING\" ? \"pending\" as const : \"failed\" as const;\n }\n\n async payInvoice(invoice: string, maxFee: number, note?: string) {\n const key = invoice.trim().toLowerCase();\n let quote = this.quotes.get(key);\n // Nothing reaches the mint's melt before `payQuote`, and it throws only when the sats did not leave.\n try {\n quote ??= await this.wallet.quoteInvoice(invoice, this.mode);\n if (quote.feeReserve > maxFee) throw engineError(\"lightningFeeTooHigh\", { fee: quote.feeReserve });\n } catch (error) {\n throw new NothingSpentError(error instanceof Error ? error.message : String(error));\n }\n this.quotes.delete(key);\n const ref = JSON.stringify({ mint: quote.mint, quote: quote.quote });\n let paid: boolean;\n try { paid = await this.wallet.payQuote(quote.quote, quote.mint, note); }\n catch (error) { throw new NothingSpentError(error instanceof Error ? error.message : String(error)); }\n return paid ? { state: \"paid\" as const, ref } : { state: \"pending\" as const, ref };\n }\n\n async paymentStatus(payment: LightningPaymentRef) {\n const { mint, quote } = parseRef(payment.ref);\n const state = await this.wallet.meltQuoteState(mint, quote);\n return { state: state === \"PAID\" ? \"paid\" as const : state === \"PENDING\" ? \"pending\" as const : \"failed\" as const };\n }\n\n async close() { this.quotes.clear(); }\n}\n\nexport const cashuMint: LightningProviderDescriptor = {\n id: CASHU_MINT_SOURCE,\n label: \"Cashu mints\",\n kind: \"lightning\",\n description: \"Invoices are paid into, and paid from, your Cashu balance. The mints hold the sats.\",\n networks: [\"bitcoin\", \"testnet\"],\n platforms: PROVIDER_PLATFORMS,\n fields: [],\n custodial: true,\n async create(_settings, host) { return new CashuMintLightning(host.cashu as CashuWallet, host.mode); },\n};\n","import { STORES, store, transact, wrap } from \"../../../shared/idb\";\nimport type { WalletMode } from \"../../../shared/mints\";\nimport { ModeChanged, ModeGate } from \"../modeGate\";\nimport { newDeviceKey, sealSeed, unsealSeed, type EncryptedSeed } from \"../persistence\";\nimport { requireTurn } from \"../away\";\nimport { describeProvider, networkMode, offeredIn, redact, sourceProblem, type ProviderDescriptor, type ProviderDescriptorView, type ProviderHost, type ProviderKind, type ProviderNetwork, type ProviderSettings, type SourceProblem } from \"./types\";\n\n/**\n * What is stored for a source, under `<kind>Source-<mode>` (or `<kind>Source-<mode>-<key>`, one Lightning card of\n * several) in the settings store: never in the Settings object (which the UI sees), and its secrets sealed with a\n * device key, like the Ark and USDT seeds.\n */\ninterface StoredSource {\n providerId: string;\n config: Record<string, string>;\n secrets?: EncryptedSeed;\n deviceKey?: string;\n savedAt: number;\n}\n\n/** The last balance a source read, under `<kind>SourceSeen-<mode>`: shown while it reconnects after a restart. */\ninterface SeenBalance { providerId: string; balance?: number; unconfirmed?: number; at: number }\n\nexport interface SourceView {\n mode: WalletMode;\n /** Absent: no source (on-chain has none until one is chosen). */\n providerId?: string;\n label?: string;\n /** The built-in default of this kind (the Cashu mints for Lightning). */\n isDefault?: boolean;\n custodial?: boolean;\n /** The non-secret settings, as saved. */\n config?: Record<string, string>;\n /** The names of the secret fields that are saved. Their values never leave the engine. */\n secrets?: string[];\n status: \"none\" | \"connecting\" | \"ready\" | \"error\";\n network?: ProviderNetwork;\n alias?: string;\n /** Spendable sats, when the source says. */\n balance?: number;\n /** What the source holds was read once since it connected (or it says it shows none): until then `balance` says nothing. */\n read?: true;\n /** On-chain: sats in the mempool. */\n unconfirmed?: number;\n /**\n * The balance shown is the last one read (at this time), not a fresh one: the source is not connected.\n * Absent once it reads again.\n */\n balanceAt?: number;\n /**\n * While `connecting` after a failed attempt, why it failed (it is being tried again by itself); while\n * `error`, why the source is unavailable.\n */\n error?: string;\n /** What kind of failure `error` is: no answer (tried again by itself), a setting to change, or other. */\n problem?: SourceProblem;\n /** Attempts that failed in a row, and when the next one starts (it also starts on Retry, or on waking). */\n failures?: number;\n retryAt?: number;\n /** The providers that can be picked here (this platform, this mode), in registry order. */\n offered: ProviderDescriptorView[];\n}\n\ninterface Connectable { info(): Promise<{ network: ProviderNetwork; alias?: string }>; close(): Promise<void> }\n\nexport interface SourcesOptions<P> {\n kind: ProviderKind;\n /** The network this source belongs to: Mainnet and Testnet each have their own, both connected at once. */\n network: WalletMode;\n descriptors: () => readonly ProviderDescriptor<P>[];\n /** Read each time a source connects. */\n host: () => Omit<ProviderHost, \"mode\" | \"signal\">;\n changed: () => void;\n /** Used while nothing is stored for the mode. */\n defaultId?: string;\n /** One of several sources of this kind and mode (a Lightning card): its own storage keys. Absent: the mode's one. */\n key?: string;\n /** Asked before the source of a mode changes: a refusal (money in flight through it) stops the change. */\n refuseReplacing?: (providerId: string, mode: WalletMode) => Promise<string | undefined>;\n /** Balance and the like, read after connecting and then every `refreshMs`. */\n refresh?: (provider: P) => Promise<Partial<Pick<SourceView, \"balance\" | \"unconfirmed\">>>;\n refreshMs?: number;\n /** How long to wait before the next attempt after `failures` failed ones (tests shorten it). */\n backoff?: (failures: number) => number;\n}\n\n/** Where a source is stored: the mode's one, or one of several (`key`). */\nexport const sourceKey = (kind: ProviderKind, mode: WalletMode, key?: string) => `${kind}Source-${mode}${key ? `-${key}` : \"\"}`;\nconst seenKey = (kind: ProviderKind, mode: WalletMode, key?: string) => `${kind}SourceSeen-${mode}${key ? `-${key}` : \"\"}`;\n\n/**\n * A source that does not answer is tried again by itself: 2 s, 4 s, 8 s… up to 5 minutes, ±20 % so\n * that many apps coming back online do not knock at once.\n */\nexport const RETRY_MAX_MS = 5 * 60_000;\nexport function retryDelay(failures: number, random = Math.random): number {\n const base = Math.min(RETRY_MAX_MS, 2_000 * 2 ** Math.max(0, failures - 1));\n return Math.round(base * (0.8 + 0.4 * random()));\n}\n/**\n * \"Unavailable\" only once it has failed this many times in a row for at least this long: a slow server at\n * start-up is \"Connecting…\". A wrong setting is unavailable at once (waiting does not fix it).\n */\nexport const SUSTAINED_FAILURES = 3;\nexport const SUSTAINED_MS = 30_000;\n\n/**\n * Gives up on a connection when its signal aborts (a change of settings interrupts an attempt), even if the\n * provider does not listen to it; what still arrives afterwards is closed.\n */\nfunction abortable<P extends Connectable>(work: Promise<P>, signal: AbortSignal): Promise<P> {\n return new Promise<P>((resolve, reject) => {\n const give = () => { reject(new Error(\"The connection attempt was interrupted\")); void work.then((p) => p.close().catch(() => {}), () => {}); };\n if (signal.aborted) return give();\n signal.addEventListener(\"abort\", give, { once: true });\n work.then(\n (value) => { signal.removeEventListener(\"abort\", give); if (!signal.aborted) resolve(value); },\n (error) => { signal.removeEventListener(\"abort\", give); if (!signal.aborted) reject(error); },\n );\n });\n}\n\n/**\n * The active source of one kind (Lightning, on-chain) of one network: Mainnet and Testnet each have their\n * own, both connected at once. Every change is serialized.\n */\nexport class ProviderSources<P extends Connectable> {\n private readonly mode: WalletMode;\n private stored?: StoredSource;\n private descriptor?: ProviderDescriptor<P>;\n private provider?: P;\n /** The connected source's secrets, only to blank them out of error messages. */\n private secrets: Record<string, string> = {};\n private controller = new AbortController();\n private readonly gate = new ModeGate();\n private queue: Promise<unknown> = Promise.resolve();\n private connecting?: Promise<void>;\n private timer?: ReturnType<typeof setTimeout>;\n private retry?: ReturnType<typeof setTimeout>;\n /** Failed attempts in a row, and since when: what makes a failure sustained. */\n private failures = 0;\n private failingSince?: number;\n private seen?: SeenBalance;\n private stopped = false;\n view: SourceView = { mode: \"mainnet\", status: \"none\", offered: [] };\n\n constructor(private readonly options: SourcesOptions<P>) { this.mode = options.network; this.view = { mode: options.network, status: \"none\", offered: [] }; }\n\n private serial<T>(run: () => Promise<T>): Promise<T> { const next = this.queue.then(run, run); this.queue = next.catch(() => {}); return next; }\n private find(id: string) { return this.options.descriptors().find((d) => d.id === id); }\n private offered() { return this.options.descriptors().filter((d) => offeredIn(d, this.options.host().platform, this.mode)).map((d) => describeProvider(d as ProviderDescriptor<unknown>)); }\n private async load(mode: WalletMode) {\n const settings = await store(STORES.settings, \"readonly\");\n const [stored, seen] = await Promise.all([wrap<StoredSource | undefined>(settings.get(sourceKey(this.options.kind, mode, this.options.key))), wrap<SeenBalance | undefined>(settings.get(seenKey(this.options.kind, mode, this.options.key)))]);\n this.stored = stored; this.seen = seen;\n this.failures = 0; this.failingSince = undefined;\n }\n\n /** The source of the mode (stored, else the default), not connected yet: with the last balance it read. */\n private idle(): SourceView {\n const id = this.stored?.providerId ?? this.options.defaultId;\n const descriptor = id ? this.find(id) : undefined;\n const seen = id && this.seen?.providerId === id ? this.seen : undefined;\n return {\n mode: this.mode, offered: this.offered(), providerId: id, label: descriptor?.label ?? id, custodial: descriptor?.custodial,\n isDefault: !!id && id === this.options.defaultId, config: this.stored?.config,\n secrets: descriptor?.fields.filter((f) => f.kind === \"secret\" && this.stored?.secrets).map((f) => f.name),\n status: !id ? \"none\" : descriptor ? \"connecting\" : \"error\",\n error: id && !descriptor ? `${id} is not available in this version of Ghostly` : undefined,\n ...(seen ? { balance: seen.balance, unconfirmed: seen.unconfirmed, balanceAt: seen.at } : {}),\n };\n }\n\n async start() { await this.load(this.mode); this.view = this.idle(); }\n\n /** One saved secret of the source (a handoff asks whether an LND card pins its certificate, and a Breez phrase), or undefined. */\n async secret(name: string): Promise<string | undefined> {\n const stored = this.stored;\n if (!stored?.secrets) return undefined;\n const secrets = JSON.parse(await unsealSeed(stored.secrets, stored.deviceKey ?? \"\")) as Record<string, unknown>;\n const value = secrets[name];\n return typeof value === \"string\" && value ? value : undefined;\n }\n\n /** The list of providers changed (a plugin registered): the picker is told, nothing reconnects. */\n refreshOffered() { this.view = { ...this.view, offered: this.offered() }; this.options.changed(); }\n\n /**\n * Connects the mode's source in the background, and keeps trying while it cannot be reached: \"Connecting…\"\n * (with the last balance it read) until the failure is sustained, then \"Unavailable\", still retrying with\n * backoff. `eager` (Retry, waking up): shows \"Connecting…\" again while it tries.\n */\n ensureReady(eager = false): Promise<void> {\n return this.connecting ??= this.serial(() => this.connect(eager)).finally(() => { this.connecting = undefined; });\n }\n\n /** Retry, now: the wait before the next attempt is skipped. Resolves when the attempt ends. */\n retryNow(): Promise<void> {\n if (this.provider || this.stopped) return Promise.resolve();\n clearTimeout(this.retry);\n return this.ensureReady(true);\n }\n\n /**\n * The app is back (in front, online, the computer woke): a source that is not connected is tried now, and\n * a connected one reads its balance now.\n */\n wake() {\n if (this.stopped) return;\n if (this.provider) { void this.refresh(); return; }\n if (this.failures && !this.connecting) void this.retryNow();\n }\n\n private async connect(eager = false) {\n clearTimeout(this.retry);\n if (this.stopped || this.provider) return;\n const id = this.stored?.providerId ?? this.options.defaultId;\n const descriptor = id ? this.find(id) : undefined;\n if (!id || !descriptor) { this.view = this.idle(); this.options.changed(); return; }\n if (eager && this.view.status === \"error\") { this.view = { ...this.view, status: \"connecting\", retryAt: undefined }; this.options.changed(); }\n const signal = this.controller.signal;\n let secrets: Record<string, string> = {};\n try {\n if (this.stored?.secrets) secrets = JSON.parse(await unsealSeed(this.stored.secrets, this.stored.deviceKey ?? \"\")) as Record<string, string>;\n const { provider, network, alias } = await this.open(descriptor, { config: this.stored?.config ?? {}, secrets });\n this.descriptor = descriptor; this.provider = provider; this.secrets = secrets;\n this.failures = 0; this.failingSince = undefined;\n this.view = { ...this.idle(), status: \"ready\", network, alias };\n this.options.changed();\n void this.refresh();\n } catch (error) {\n // Stopped, another mode, or interrupted by a change of settings: whoever did it connects next.\n if (this.stopped || error instanceof ModeChanged || signal.aborted) return;\n const now = Date.now();\n this.failures++;\n this.failingSince ??= now;\n const problem = sourceProblem(error);\n const sustained = problem === \"config\" || (this.failures >= SUSTAINED_FAILURES && now - this.failingSince >= SUSTAINED_MS);\n const delay = (this.options.backoff ?? retryDelay)(this.failures);\n this.view = { ...this.idle(), status: sustained ? \"error\" : \"connecting\", error: `Could not connect to ${descriptor.label}: ${redact(error, secrets)}`, problem, failures: this.failures, retryAt: now + delay };\n this.options.changed();\n this.retry = setTimeout(() => void this.ensureReady(), delay);\n }\n }\n\n /** Creates a provider and checks it is on a network of this mode before anything uses it. */\n private async open(descriptor: ProviderDescriptor<P>, settings: ProviderSettings, controller = this.controller) {\n // The Breez source shares its database with the Spark wallet of its phrase: one writer, after a fresh turn read.\n if (descriptor.id === \"breez\") await requireTurn();\n const signal = controller.signal;\n const provider = await this.gate.within(abortable(descriptor.create(settings, { ...this.options.host(), mode: this.mode, signal }), signal), (p) => p.close());\n try {\n const { network, alias } = await this.gate.within(provider.info());\n if (!descriptor.networks.includes(network)) throw new Error(`it runs on ${network}, which ${descriptor.label} does not support here`);\n if (networkMode(network) !== this.mode) throw new Error(this.mode === \"mainnet\" ? `it runs on ${network}, a test network: set it up as a Testnet wallet instead` : \"it runs on Bitcoin, real money: set it up as a Mainnet wallet instead\");\n return { provider, network, alias };\n } catch (error) { await provider.close().catch(() => {}); throw error; }\n }\n\n /**\n * Makes `providerId` this mode's source, with the values typed in its form. It is connected and its\n * network checked first; only then are its settings saved (secrets sealed) and the old source closed.\n */\n set(providerId: string, values: Record<string, string>): Promise<void> {\n const interrupted = this.interrupt();\n return this.afterChange(interrupted, this.serial(async () => {\n const { descriptor, settings } = this.settingsOf(providerId, values);\n await this.guard();\n await this.apply(descriptor, settings, false);\n }));\n }\n\n /** The form's values as a source's settings, checked: its secrets apart. Throws what to fix. */\n settingsOf(providerId: string, values: Record<string, string>): { descriptor: ProviderDescriptor<P>; settings: ProviderSettings } {\n const descriptor = this.find(providerId);\n if (!descriptor || !offeredIn(descriptor, this.options.host().platform, this.mode)) throw new Error(\"That source is not available here\");\n const settings: ProviderSettings = { config: {}, secrets: {} };\n for (const field of descriptor.fields) {\n const value = (values[field.name] ?? \"\").trim();\n if (!value && !field.optional) throw new Error(`Enter ${field.label.toLowerCase()}`);\n if (field.kind === \"select\" && value && !field.options?.some((o) => o.value === value)) throw new Error(`Choose ${field.label.toLowerCase()}`);\n if (value) (field.kind === \"secret\" ? settings.secrets : settings.config)[field.name] = value;\n }\n descriptor.validate?.(settings, this.mode);\n return { descriptor, settings };\n }\n\n /**\n * Whether the saved source is this provider with these very settings (the same wallet added twice). Its secrets are\n * unsealed here to compare, and never leave.\n */\n async holds(providerId: string, settings: ProviderSettings): Promise<boolean> {\n const stored = this.stored;\n if (!stored || stored.providerId !== providerId) return false;\n const same = (a: Record<string, string>, b: Record<string, string>) => {\n const keys = Object.keys(a).sort();\n return JSON.stringify(keys) === JSON.stringify(Object.keys(b).sort()) && keys.every((k) => a[k] === b[k]);\n };\n if (!same(stored.config, settings.config)) return false;\n const secrets = stored.secrets ? JSON.parse(await unsealSeed(stored.secrets, stored.deviceKey ?? \"\")) as Record<string, string> : {};\n return same(secrets, settings.secrets);\n }\n\n /**\n * Changes the `changeable` fields (a server address) of this mode's saved source, keeping its sealed\n * secrets and whose money it is: the wallet stays, only where it reads the chain changes. Connected and\n * checked first, like `set`; a refusal leaves the saved source as it was.\n */\n reconfigure(values: Record<string, string>): Promise<void> {\n const interrupted = this.interrupt();\n return this.afterChange(interrupted, this.serial(async () => {\n const stored = this.stored;\n const descriptor = stored ? this.find(stored.providerId) : undefined;\n if (!stored || !descriptor) throw new Error(\"There is no saved source to change\");\n const changeable = descriptor.fields.filter((f) => f.changeable);\n if (!changeable.length) throw new Error(`${descriptor.label} has no server to change`);\n const config = { ...stored.config };\n for (const field of changeable) {\n if (!(field.name in values)) continue;\n const value = (values[field.name] ?? \"\").trim();\n if (!value && !field.optional) throw new Error(`Enter ${field.label.toLowerCase()}`);\n if (value) config[field.name] = value; else delete config[field.name];\n }\n const secrets = stored.secrets ? JSON.parse(await unsealSeed(stored.secrets, stored.deviceKey ?? \"\")) as Record<string, string> : {};\n const settings: ProviderSettings = { config, secrets };\n descriptor.validate?.(settings, this.mode);\n await this.apply(descriptor, settings, true);\n }));\n }\n\n /** A change refused after interrupting an attempt: the saved source goes on connecting as before. */\n private afterChange(interrupted: boolean, change: Promise<void>): Promise<void> {\n return change.finally(() => { if (interrupted && !this.provider && !this.stopped) void this.ensureReady(); });\n }\n\n /**\n * A connection attempt under way (or waiting to try again) gives way to a change of settings, instead of\n * holding it for its time-out. Says whether there was one.\n */\n private interrupt(): boolean {\n if (this.provider || (!this.connecting && !this.failures)) return false;\n clearTimeout(this.retry);\n this.controller.abort(); this.controller = new AbortController();\n return true;\n }\n\n /** Connects `settings`, then saves them and replaces the current source. `same`: the same wallet, reconfigured. */\n private async apply(descriptor: ProviderDescriptor<P>, settings: ProviderSettings, same: boolean) {\n const providerId = descriptor.id;\n let opened: Awaited<ReturnType<typeof this.open>>;\n // Its own signal: closing the source it replaces aborts that one's, not this one's.\n const controller = new AbortController();\n try { opened = await this.open(descriptor, settings, controller); }\n catch (error) { throw error instanceof ModeChanged ? error : new Error(`Could not connect to ${descriptor.label}: ${redact(error, settings.secrets)}`); }\n const deviceKey = newDeviceKey();\n const stored: StoredSource = { providerId, config: settings.config, savedAt: Date.now(),\n ...(Object.keys(settings.secrets).length ? { secrets: await sealSeed(JSON.stringify(settings.secrets), deviceKey), deviceKey } : {}) };\n // The default with nothing to configure is what an empty mode already uses: nothing to store.\n const plain = providerId === this.options.defaultId && !Object.keys(settings.config).length && !stored.secrets;\n // Another wallet's last balance is not this one's.\n if (!same) this.seen = undefined;\n try {\n await transact([STORES.settings], (s) => {\n if (plain) s[STORES.settings].delete(sourceKey(this.options.kind, this.mode, this.options.key)); else s[STORES.settings].put(stored, sourceKey(this.options.kind, this.mode, this.options.key));\n if (!same) s[STORES.settings].delete(seenKey(this.options.kind, this.mode, this.options.key));\n });\n }\n catch (error) { await opened.provider.close().catch(() => {}); throw error; }\n await this.disconnect();\n this.controller = controller;\n this.failures = 0; this.failingSince = undefined;\n this.stored = plain ? undefined : stored; this.descriptor = descriptor; this.provider = opened.provider; this.secrets = settings.secrets;\n this.view = { ...this.idle(), status: \"ready\", network: opened.network, alias: opened.alias };\n this.options.changed();\n void this.refresh();\n }\n\n /** Forgets this mode's source (and its sealed secrets): the default, if there is one, takes over. */\n clear(): Promise<void> {\n this.interrupt();\n return this.serial(async () => {\n await this.guard();\n await transact([STORES.settings], (s) => { s[STORES.settings].delete(sourceKey(this.options.kind, this.mode, this.options.key)); s[STORES.settings].delete(seenKey(this.options.kind, this.mode, this.options.key)); });\n await this.disconnect(true);\n this.stored = undefined; this.seen = undefined;\n this.failures = 0; this.failingSince = undefined;\n this.view = this.idle(); this.options.changed();\n }).then(() => this.ensureReady());\n }\n\n /**\n * Forgets this source for good (its card is removed): its settings, sealed secrets and last balance go, it is\n * closed, and nothing takes over. Refused while a payment through it has not ended.\n */\n forget(): Promise<void> {\n this.interrupt();\n return this.serial(async () => {\n await this.guard();\n await transact([STORES.settings], (s) => { s[STORES.settings].delete(sourceKey(this.options.kind, this.mode, this.options.key)); s[STORES.settings].delete(seenKey(this.options.kind, this.mode, this.options.key)); });\n this.stopped = true;\n this.gate.close();\n await this.disconnect(true);\n this.stored = undefined; this.seen = undefined;\n this.view = { mode: this.mode, status: \"none\", offered: [] };\n });\n }\n\n private async guard() {\n const current = this.stored?.providerId ?? this.options.defaultId;\n const refusal = current ? await this.options.refuseReplacing?.(current, this.mode) : undefined;\n if (refusal) throw new Error(refusal);\n }\n\n /** The connected source, waiting for a connection under way. Throws when there is none. */\n async use(): Promise<{ provider: P; descriptor: ProviderDescriptor<P> }> {\n if (!this.provider) await this.ensureReady();\n if (!this.provider || !this.descriptor) throw new Error(this.view.status === \"none\" ? `No ${this.options.kind === \"onchain\" ? \"Bitcoin\" : \"Lightning\"} source is set up` : this.view.error ?? \"The source is still connecting\");\n return { provider: this.provider, descriptor: this.descriptor };\n }\n\n /** A change waiting on its connection gives up now (a creation that took too long); nothing is saved. */\n cutShort() { this.gate.interrupt(); }\n resume() { this.gate.resume(); }\n\n /** The connected source's id, when one is connected right now. */\n get activeId(): string | undefined { return this.provider ? this.descriptor?.id : undefined; }\n get active(): P | undefined { return this.provider; }\n\n async refresh() {\n clearTimeout(this.timer);\n const provider = this.provider;\n if (!provider) return;\n // A kind of source with nothing to read shows no balance: what it shows is read.\n if (!this.options.refresh) { if (!this.view.read) { this.view = { ...this.view, read: true }; this.options.changed(); } return; }\n const before = this.view;\n try {\n const details = await this.options.refresh(provider);\n if (provider !== this.provider) return;\n this.view = { ...this.view, ...details, read: true, balanceAt: undefined, error: undefined };\n this.remember(details);\n } catch (error) {\n if (provider !== this.provider) return;\n this.view = { ...this.view, error: `Could not read the balance: ${redact(error, this.secrets)}` };\n }\n // A read that says what the view already did (the same balance, or none) changes nothing: every 15 s it made a\n // whole wallet view and engine state, with no wallet at all.\n const after = this.view as unknown as Record<string, unknown>, was = before as unknown as Record<string, unknown>;\n if (Object.keys({ ...was, ...after }).some(key => was[key] !== after[key])) this.options.changed();\n if (this.provider) this.timer = setTimeout(() => void this.refresh(), this.options.refreshMs ?? 15_000);\n }\n\n /** Keeps the balance read, for the next start: written only when it changed. */\n private remember({ balance, unconfirmed }: Partial<Pick<SourceView, \"balance\" | \"unconfirmed\">>) {\n const providerId = this.descriptor?.id;\n // The default (the Cashu mints) connects without a network: nothing to show meanwhile.\n if (!providerId || providerId === this.options.defaultId || (balance === undefined && unconfirmed === undefined)) return;\n const was = this.seen;\n if (was?.providerId === providerId && was.balance === balance && was.unconfirmed === unconfirmed) return;\n const seen: SeenBalance = { providerId, balance, unconfirmed, at: Date.now() }, key = seenKey(this.options.kind, this.mode, this.options.key);\n this.seen = seen;\n void transact([STORES.settings], (s) => { s[STORES.settings].put(seen, key); }).catch(() => {});\n }\n\n /** `forget`: the source is removed, so what it keeps on this device goes too (a provider's own `forget`). */\n private async disconnect(forget = false) {\n clearTimeout(this.timer); clearTimeout(this.retry);\n this.controller.abort(); this.controller = new AbortController();\n const provider = this.provider as (P & { forget?: () => Promise<void> }) | undefined;\n this.provider = undefined; this.descriptor = undefined; this.secrets = {};\n await (forget && provider?.forget ? provider.forget() : provider?.close())?.catch(() => {});\n }\n\n /** Shutting down: nothing reconnects afterwards. */\n async stop() { this.stopped = true; this.gate.close(); await this.serial(() => this.disconnect()); }\n}\n","import { decodeBolt11, engineError, requestLnurlInvoice, resolveLightningDestination, type LnurlPayParams, type LnurlSuccessAction } from \"@ghostly/core\";\nimport { STORES, store, transact, wrap } from \"../../../shared/idb\";\nimport { BITCOIN_INVOICE_ON_TESTNET, type WalletMode } from \"../../../shared/mints\";\nimport { CASHU_MINT_SOURCE, CashuMintLightning } from \"./cashuMint\";\nimport type { LightningInvoice, LightningProvider, LightningProviderDescriptor } from \"./lightning\";\nimport { ProviderSources, type SourceView } from \"./sources\";\nimport { isNothingSpentError, redact, type ProviderHost } from \"./types\";\n\n/**\n * One Lightning operation, journaled under `lightningOp-<direction>-<payment hash>` in the settings store\n * BEFORE the provider is asked to spend: after a crash or a lost answer it is only ever reconciled (asked\n * about), never paid again. Incoming invoices are journaled too, so whoever paid a chat request is told\n * once the source sees it paid.\n */\nexport interface LightningOp {\n direction: \"in\" | \"out\";\n providerId: string;\n mode: WalletMode;\n paymentHash: string;\n invoice: string;\n amount: number;\n /** The chat payment request this pays, or is the invoice of. */\n paymentId?: string;\n note?: string;\n ref?: string;\n expiresAt: number;\n createdAt: number;\n /** The source books and reports this itself (the Cashu wallet): the engine does not poll it. */\n selfSettled?: boolean;\n /**\n * The Lightning card it went through (see `lightningCards.ts`). Absent on operations journaled before a network could\n * have several cards: those belong to the card that took over the network's one source (`legacyCard`).\n */\n card?: string;\n /** in: open → paid | expired. out: sending → paid | pending | failed | unknown (a lost answer). */\n state: \"open\" | \"paid\" | \"expired\" | \"sending\" | \"pending\" | \"failed\" | \"unknown\";\n maxFee?: number;\n fee?: number;\n error?: string;\n settledAt?: number;\n}\nexport type LightningOpView = Omit<LightningOp, \"ref\" | \"note\" | \"selfSettled\">;\n\n/** Options of one Lightning card's service. Without `card`, it is the network's only one (tests, older callers). */\nexport interface LightningServiceOptions {\n fetch?: typeof fetch;\n /** The card this service is: its operations carry the id, and it reads only its own. */\n card?: string;\n /** Where its source is stored: `lightningSource-<network>-<key>`; absent, the network's one key. */\n key?: string;\n /** Whether a journaled operation is this card's. Default: one carrying its id, or one carrying none. */\n owns?: (op: LightningOp) => boolean;\n}\nexport interface LightningView extends SourceView {\n capabilities?: LightningProvider[\"capabilities\"];\n /** This mode's latest operations, newest first. */\n recent: LightningOpView[];\n}\n/** `mint`: the Cashu mint that will pay, when the source is the mints; otherwise the source's id. */\nexport interface LightningQuote { quote: string; mint: string; amount: number; feeReserve: number; source: string }\n/** What a person sees of a resolved Lightning address or LNURL before choosing an amount. Amounts are whole sats. */\nexport interface LnurlView {\n id: string;\n kind: \"address\" | \"lnurl\";\n /** The address or LNURL, as normalised. */\n text: string;\n /** The host that learned of the request. */\n domain: string;\n /** The host the invoice is asked from, when it is another. */\n callbackDomain: string;\n minSat: number;\n maxSat: number;\n /** What the service says the payment is for. */\n description: string;\n /** How long a comment may be (LUD-12); 0 when none is taken. */\n commentAllowed: number;\n}\n/** Who an event is about. `mint`: the Cashu mint it went through, when it did. */\nexport interface LightningEventOp { paymentId?: string; providerId: string; mint?: string }\n\nexport interface LightningEvents {\n changed(): void;\n /** One of our invoices was paid. `paymentId`: the chat request it belongs to. */\n received(op: LightningEventOp): void;\n /** A payment left pending or unknown ended, one way or the other. */\n resolved(op: LightningEventOp, paid: boolean): void;\n}\n\nconst PREFIX = \"lightningOp-\";\nconst opKey = (direction: LightningOp[\"direction\"], hash: string) => `${PREFIX}${direction}-${hash}`;\nconst RANGE = () => IDBKeyRange.bound(PREFIX, `${PREFIX}\\uffff`);\n/** Every Lightning operation journaled, of every network and card. */\n/** Lightning payments whose call to the source has not returned, on any card of this page (WISP 06 § Payments in flight). */\nlet paying = 0;\nexport const lightningPaying = (): number => paying;\n\nexport async function readLightningJournal(): Promise<LightningOp[]> { return wrap<LightningOp[]>((await store(STORES.settings, \"readonly\")).getAll(RANGE())); }\nconst IN_POLL_MS = 4_000;\nconst OUT_POLL_MS = 15_000;\nconst RECENT = 20;\n/** Resolved Lightning addresses are kept this long for the amount to be chosen; this many at most. */\nconst LNURL_TTL_MS = 10 * 60_000;\nconst LNURL_KEPT = 20;\n/** A source that cannot tell its fee beforehand gets this ceiling, the same one chat requests are paid under. */\nexport const defaultFeeCap = (amount: number) => Math.max(10, Math.ceil(amount * 0.03));\n\n/**\n * Lightning of one network: its Lightning card, the invoice a chat request on that network carries and paying a\n * contact's invoice on it all come here, and go to this network's active source. The engine has one per network.\n */\nexport class LightningService {\n readonly sources: ProviderSources<LightningProvider>;\n private readonly quotes = new Map<string, { providerId: string; provider: LightningProvider; invoice: string; amount: number; maxFee: number; paymentHash: string }>();\n private readonly mode: WalletMode;\n private timer?: ReturnType<typeof setTimeout>;\n private passes: Promise<void> = Promise.resolve();\n private stopped = false;\n private recent: LightningOpView[] = [];\n /** Operations written in this run (`put`): a reconcile pass that wrote one is news, recent or not. */\n private writes = 0;\n /** Lightning addresses and LNURLs resolved, until an amount is chosen for them. */\n private readonly lnurls = new Map<string, { params: LnurlPayParams; at: number }>();\n\n constructor(readonly network: WalletMode, descriptors: () => readonly LightningProviderDescriptor[], host: () => Omit<ProviderHost, \"mode\" | \"signal\">, private readonly events: LightningEvents, defaultId?: string, private readonly options: LightningServiceOptions = {}) {\n this.mode = network;\n this.sources = new ProviderSources<LightningProvider>({\n kind: \"lightning\", network, descriptors, host, defaultId, key: options.key,\n changed: () => { this.schedule(0); events.changed(); },\n refuseReplacing: (providerId, mode) => this.refusal(providerId, mode),\n refresh: async (provider) => (provider.capabilities.balance ? { balance: (await provider.info()).balance } : {}),\n });\n }\n\n /** The card this service is, when the network has several. */\n get card(): string | undefined { return this.options.card; }\n /** Whether a journaled operation went through this card (and this network). */\n owns(op: LightningOp): boolean {\n if (op.mode !== this.mode) return false;\n return this.options.owns ? this.options.owns(op) : op.card === undefined || op.card === this.options.card;\n }\n\n get view(): LightningView {\n return { ...this.sources.view, capabilities: this.sources.active?.capabilities, recent: this.recent };\n }\n\n async start() { await this.sources.start(); await this.loadRecent(); }\n ensureReady() { return this.sources.ensureReady(); }\n async stop() { this.stopped = true; clearTimeout(this.timer); await this.sources.stop(); }\n refreshOffered() { this.sources.refreshOffered(); }\n\n // -- receiving -----------------------------------------------------------------\n\n async createInvoice(amount: number, context: { memo?: string; paymentId?: string } = {}): Promise<LightningInvoice & { source: string }> {\n const { provider, descriptor } = await this.sources.use();\n if (!provider.capabilities.receive) throw new Error(`${descriptor.label} cannot create invoices`);\n const created = await provider.createInvoice(amount, context.memo ?? \"Ghostly\");\n const decoded = decodeBolt11(created.invoice);\n // What goes to a contact must be the invoice we asked for: this amount, this hash, the right chain.\n if (!decoded || decoded.amountSat !== amount || (decoded.paymentHash && decoded.paymentHash !== created.paymentHash) || created.amount !== amount) throw new Error(`${descriptor.label} returned an invoice that does not match the request`);\n this.checkNetwork(decoded.network);\n await this.put({ direction: \"in\", providerId: descriptor.id, mode: this.mode, ...this.cardField(), paymentHash: created.paymentHash, invoice: created.invoice, amount, paymentId: context.paymentId, ref: created.ref, expiresAt: created.expiresAt, createdAt: Date.now(), selfSettled: !!provider.settlesItself, state: \"open\" });\n this.schedule(IN_POLL_MS);\n return { ...created, source: descriptor.id };\n }\n\n /** The Cashu wallet minted the ecash of one of its invoices (it polls its own quotes). */\n async reportInvoicePaid(invoice: string, context: { paymentId?: string; mint?: string } = {}) {\n const hash = decodeBolt11(invoice)?.paymentHash;\n const op = hash ? await this.get(\"in\", hash) : undefined;\n if (op && op.state !== \"paid\") await this.put({ ...op, state: \"paid\", settledAt: Date.now() });\n // Quotes made before sources carry their chat request themselves.\n this.events.received({ paymentId: op?.paymentId ?? context.paymentId, providerId: op?.providerId ?? CASHU_MINT_SOURCE, mint: context.mint });\n await this.loadRecent(); this.events.changed();\n }\n\n // -- Lightning addresses and LNURLs (LUD-16, LUD-06) -----------------------------\n\n /**\n * Fetches what a Lightning address or LNURL asks for, and keeps it until an amount is chosen. The\n * service's domain learns of the request: the caller says so before asking.\n */\n async resolveDestination(text: string): Promise<LnurlView> {\n const params = await resolveLightningDestination(text, { fetch: this.options.fetch });\n const now = Date.now();\n for (const [id, entry] of this.lnurls) if (entry.at + LNURL_TTL_MS < now) this.lnurls.delete(id);\n while (this.lnurls.size >= LNURL_KEPT) this.lnurls.delete(this.lnurls.keys().next().value!);\n const id = crypto.randomUUID();\n this.lnurls.set(id, { params, at: now });\n const { destination, callbackDomain, minSat, maxSat, description, commentAllowed } = params;\n return { id, kind: destination.kind, text: destination.text, domain: destination.domain, callbackDomain, minSat, maxSat, description, commentAllowed };\n }\n\n /**\n * The invoice for `amountSat` from a resolved destination: checked (the amount, a commitment to what\n * was shown, not expired, a network of this mode) before it is quoted and paid like any other invoice.\n */\n async destinationInvoice(id: string, amountSat: number, comment?: string): Promise<{ invoice: string; successAction?: LnurlSuccessAction; note: string }> {\n const entry = this.lnurls.get(id);\n if (!entry || entry.at + LNURL_TTL_MS < Date.now()) { this.lnurls.delete(id); throw new Error(\"That address was resolved too long ago: check it again\"); }\n const { invoice, successAction } = await requestLnurlInvoice(entry.params, amountSat, comment, { fetch: this.options.fetch });\n this.checkNetwork(invoice.network);\n return { invoice: invoice.invoice, successAction, note: entry.params.destination.text };\n }\n\n /** Whether this card resolved that address (the invoice is asked where it was resolved). */\n hasDestination(id: string) { return this.lnurls.has(id); }\n\n // -- paying --------------------------------------------------------------------\n\n /** What paying this invoice costs at most, from the active source. Nothing is spent. */\n async quote(text: string): Promise<LightningQuote> {\n const decoded = decodeBolt11(text);\n if (!decoded) throw new Error(\"That is not a Lightning invoice\");\n if (decoded.amountSat === null) throw new Error(\"Invoices without an amount are not supported\");\n if (!decoded.paymentHash) throw new Error(\"That invoice has no payment hash\");\n if (decoded.expiresAt * 1000 < Date.now()) throw engineError(\"invoiceExpired\");\n this.checkNetwork(decoded.network);\n const { provider, descriptor } = await this.sources.use();\n // A Bitcoin invoice on Testnet: only the mints can tell test sats from real ones (`fakesLightning`, checked when\n // they quote it). Any other Testnet source that pays one is a real node, and would pay it with real money.\n if (this.mode === \"testnet\" && decoded.network === \"bitcoin\" && !(provider instanceof CashuMintLightning)) throw new Error(BITCOIN_INVOICE_ON_TESTNET);\n if (!provider.capabilities.send) throw new Error(`${descriptor.label} cannot pay invoices`);\n const maxFee = provider.estimateFee ? await provider.estimateFee(decoded.invoice, decoded.amountSat) : defaultFeeCap(decoded.amountSat);\n if (!Number.isSafeInteger(maxFee) || maxFee < 0) throw new Error(`${descriptor.label} returned an invalid fee`);\n const id = crypto.randomUUID();\n this.quotes.set(id, { providerId: descriptor.id, provider, invoice: decoded.invoice, amount: decoded.amountSat, maxFee, paymentHash: decoded.paymentHash });\n const mint = provider instanceof CashuMintLightning ? provider.quoteFor(decoded.invoice)?.mint : undefined;\n return { quote: id, mint: mint ?? descriptor.id, amount: decoded.amountSat, feeReserve: maxFee, source: descriptor.id };\n }\n\n hasQuote(id: string) { return this.quotes.has(id); }\n\n /**\n * Pays a quote. True once paid; false while in flight or unknown (reconciled in the background, never\n * paid again). Throws only when nothing left the wallet.\n */\n async pay(quoteId: string, context: { note?: string; paymentId?: string } = {}): Promise<boolean> {\n const quote = this.quotes.get(quoteId);\n if (!quote) throw new Error(\"This quote is no longer valid: check the invoice again\");\n if (this.sources.active !== quote.provider) { this.quotes.delete(quoteId); throw new Error(\"The Lightning source changed: check the invoice again\"); }\n this.quotes.delete(quoteId);\n const existing = await this.get(\"out\", quote.paymentHash);\n if (existing && existing.state !== \"failed\") throw new Error(existing.state === \"paid\" ? \"This invoice is already paid\" : \"This invoice is already being paid\");\n let op: LightningOp = { direction: \"out\", providerId: quote.providerId, mode: this.mode, ...this.cardField(), paymentHash: quote.paymentHash, invoice: quote.invoice, amount: quote.amount, paymentId: context.paymentId, note: context.note?.slice(0, 140), expiresAt: 0, createdAt: Date.now(), selfSettled: !!quote.provider.settlesItself, state: \"sending\", maxFee: quote.maxFee, ref: quote.provider.paymentRef?.(quote.invoice) };\n // Written down before the provider sees it: from here on the sats may be gone.\n await this.put(op);\n paying += 1;\n try {\n const result = await quote.provider.payInvoice(quote.invoice, quote.maxFee, context.note);\n op = result.state === \"paid\" ? { ...op, state: \"paid\", fee: result.fee, ref: result.ref ?? op.ref, settledAt: Date.now() } : { ...op, state: \"pending\", ref: result.ref ?? op.ref };\n } catch (error) {\n if (isNothingSpentError(error)) { await this.put({ ...op, state: \"failed\", error: redact(error) }); throw error; }\n // A source that cannot look payments up never settles this by itself: say so rather than \"being checked\".\n op = { ...op, state: \"unknown\", error: quote.provider.capabilities.lookup ? \"No answer from the source. It is being checked; nothing is paid again.\" : \"No answer from the source, and it cannot be asked: check this payment in the wallet itself. It is never paid again.\" };\n } finally { paying -= 1; }\n await this.put(op);\n if (op.state !== \"paid\") this.schedule(OUT_POLL_MS);\n return op.state === \"paid\";\n }\n\n /** The Cashu wallet settled a melt it had left pending. */\n async reportPaymentResolved(invoice: string, paid: boolean, context: { paymentId?: string; mint?: string } = {}) {\n const hash = decodeBolt11(invoice)?.paymentHash;\n const op = hash ? await this.get(\"out\", hash) : undefined;\n if (op) await this.put({ ...op, state: paid ? \"paid\" : \"failed\", settledAt: Date.now() });\n this.events.resolved({ paymentId: op?.paymentId ?? context.paymentId, providerId: op?.providerId ?? CASHU_MINT_SOURCE, mint: context.mint }, paid);\n await this.loadRecent(); this.events.changed();\n }\n\n // -- reconciling ---------------------------------------------------------------\n\n /** Asks the active source about everything of its that is open, pending or unknown. One pass at a time. */\n reconcile(): Promise<void> {\n const pass = this.passes.then(() => this.reconcileOnce());\n this.passes = pass.catch(() => {});\n return pass;\n }\n\n private async reconcileOnce(): Promise<void> {\n if (this.stopped) return;\n let again = 0;\n const writes = this.writes;\n try {\n const provider = this.sources.active, providerId = this.sources.activeId;\n for (const op of await this.list()) {\n if (!this.owns(op)) continue;\n if (op.selfSettled) {\n // The source settles and reports its own. One cut off before its answer was written (`recover`) it may\n // never have heard of, and would never report: the source is asked what became of it.\n if (op.direction !== \"out\" || op.state !== \"unknown\") continue;\n // No source connected yet: asked again. Another source's waits until its own is active again.\n if (!provider) { again = again || OUT_POLL_MS; continue; }\n if (op.providerId !== providerId || !provider.interruptedPayment) continue;\n try {\n const outcome = await provider.interruptedPayment({ invoice: op.invoice, paymentHash: op.paymentHash, ref: op.ref });\n if (outcome === \"pending\") again = again || OUT_POLL_MS;\n if (outcome !== \"paid\" && outcome !== \"failed\") continue;\n // Written only over the state that was asked about: the source's own report wins.\n if ((await this.get(\"out\", op.paymentHash))?.state !== \"unknown\") continue;\n await this.put({ ...op, state: outcome, error: outcome === \"failed\" ? \"The payment did not go through\" : undefined, settledAt: Date.now() });\n this.events.resolved(op, outcome === \"paid\");\n } catch { again = again || OUT_POLL_MS; }\n continue;\n }\n // `sending` is a spend under way in this process: its own answer decides. One left by a crash is\n // made `unknown` by `recover` at the next start, and only then asked about.\n const waiting = op.direction === \"in\" ? op.state === \"open\" : [\"pending\", \"unknown\"].includes(op.state);\n if (!waiting) continue;\n // Another source's operation waits until that source is active again: only it can answer.\n if (!provider || op.providerId !== providerId || !provider.capabilities.lookup) continue;\n try {\n const unchanged = async () => (await this.get(op.direction, op.paymentHash))?.state === op.state;\n if (op.direction === \"in\") {\n const status = await provider.invoiceStatus({ invoice: op.invoice, paymentHash: op.paymentHash, amount: op.amount, expiresAt: op.expiresAt, ref: op.ref });\n if (!(await unchanged())) continue;\n if (status.state === \"paid\") { await this.put({ ...op, state: \"paid\", settledAt: Date.now() }); this.events.received(op); }\n else if (status.state === \"expired\" || op.expiresAt + 60_000 < Date.now()) await this.put({ ...op, state: \"expired\" });\n else again = again ? Math.min(again, IN_POLL_MS) : IN_POLL_MS;\n } else {\n const status = await provider.paymentStatus({ invoice: op.invoice, paymentHash: op.paymentHash, ref: op.ref });\n if (status.state === \"pending\") { again = again || OUT_POLL_MS; continue; }\n // Written only over the state that was asked about: anything newer wins.\n if (!(await unchanged())) continue;\n await this.put({ ...op, state: status.state, fee: status.fee ?? op.fee, error: status.state === \"failed\" ? \"The payment did not go through\" : undefined, settledAt: Date.now() });\n this.events.resolved(op, status.state === \"paid\");\n }\n } catch {\n // The source cannot be asked right now: ask again later, nothing changes.\n again = again ? Math.min(again, op.direction === \"in\" ? IN_POLL_MS : OUT_POLL_MS) : op.direction === \"in\" ? IN_POLL_MS : OUT_POLL_MS;\n }\n }\n } finally {\n // Only a pass that settled something, or whose recent list reads otherwise, is news to the wallet view.\n if (await this.loadRecent() || this.writes !== writes) this.events.changed();\n if (again) this.schedule(again);\n }\n }\n\n private schedule(ms: number) {\n if (this.stopped) return;\n clearTimeout(this.timer);\n this.timer = setTimeout(() => void this.reconcile(), ms);\n }\n\n /** Changing the source is refused while a payment through it has not ended: only it can say how. */\n private async refusal(providerId: string, mode: WalletMode) {\n const open = (await this.list()).filter((op) => op.direction === \"out\" && !op.selfSettled && op.providerId === providerId && op.mode === mode && this.owns(op) && [\"sending\", \"pending\", \"unknown\"].includes(op.state));\n return open.length ? `A Lightning payment through this source has not ended yet (${open.length}). Wait for it before changing the source.` : undefined;\n }\n\n private cardField(): Pick<LightningOp, \"card\"> { return this.options.card === undefined ? {} : { card: this.options.card }; }\n\n /** A Mainnet source refuses an invoice on a test chain. The reverse (test mints use lnbc) is `quote`'s to refuse. */\n private checkNetwork(network: string) {\n if (this.mode === \"mainnet\" && network !== \"bitcoin\") throw new Error(`That invoice is for ${network}, a test network: pay it from a Testnet wallet`);\n }\n\n private async get(direction: LightningOp[\"direction\"], hash: string) { return wrap<LightningOp | undefined>((await store(STORES.settings, \"readonly\")).get(opKey(direction, hash))); }\n private async put(op: LightningOp) { this.writes++; await transact([STORES.settings], (s) => { s[STORES.settings].put(op, opKey(op.direction, op.paymentHash)); }); }\n list(): Promise<LightningOp[]> { return readLightningJournal(); }\n\n /** Crash recovery: a spend journaled as `sending` never got its answer written, so its outcome is unknown. */\n async recover() {\n for (const op of await this.list()) if (op.direction === \"out\" && op.state === \"sending\" && this.owns(op)) await this.put({ ...op, state: \"unknown\", error: \"Interrupted. It is being checked; nothing is paid again.\" });\n this.schedule(0);\n }\n\n /** True when the recent list reads otherwise than before. */\n private async loadRecent(): Promise<boolean> {\n const was = this.recent;\n this.recent = (await this.list()).filter((op) => this.owns(op)).sort((a, b) => b.createdAt - a.createdAt).slice(0, RECENT)\n .map(({ ref: _ref, note: _note, selfSettled: _self, ...view }) => view);\n return JSON.stringify(was) !== JSON.stringify(this.recent);\n }\n}\n","import { generateMnemonic, validateMnemonic } from \"@scure/bip39\";\nimport { wordlist } from \"@scure/bip39/wordlists/english.js\";\n\n/**\n * Recovery phrases (BIP 39, English) for providers whose wallet Ghostly makes: the form generates one for\n * a new wallet and checks one typed to restore. Small on purpose, so a form can use it without loading\n * the provider.\n */\nexport const newRecoveryPhrase = () => generateMnemonic(wordlist);\n/** A phrase as BIP 39 wants it, however it was typed or pasted. */\nexport const normalizePhrase = (text = \"\") => text.trim().toLowerCase().split(/\\s+/).join(\" \");\nexport const isRecoveryPhrase = (text: string) => validateMnemonic(normalizePhrase(text), wordlist);\n","import { STORES, transact } from \"../../../shared/idb\";\nimport type { WalletMode } from \"../../../shared/mints\";\nimport { CASHU_MINT_SOURCE } from \"./cashuMint\";\nimport type { LightningProviderDescriptor } from \"./lightning\";\nimport { LightningService, readLightningJournal, type LightningEvents, type LightningOp, type LightningView } from \"./lightningService\";\nimport { sourceKey } from \"./sources\";\nimport { awayFrom } from \"../away\";\nimport { breezDatabaseInUse } from \"./breezDatabases\";\nimport { normalizePhrase } from \"./recoveryPhrase\";\nimport type { ProviderHost } from \"./types\";\n\n/**\n * One Lightning card of a network, as stored under `lightningCards-<network>`. Its source (and the source's sealed\n * secrets) is stored apart, under `lightningSource-<network>-<key>`: the card that took over a network's one source\n * (`LEGACY_CARD`) has no key and keeps reading `lightningSource-<network>`, where that source always was.\n */\nexport interface StoredLightningCard {\n id: string;\n name: string;\n providerId: string;\n /** Where its source is stored. Absent: the network's one key (the card made from it). */\n key?: string;\n createdAt: number;\n}\ninterface StoredLightningCards {\n cards: StoredLightningCard[];\n /** The card a chat request's invoice and Receive come from, unless another is picked. */\n receive: string;\n}\n\n/** A Lightning card as the pages see it: its source's view, its name, and whether it is the one receiving. */\nexport interface LightningCardView extends LightningView {\n card: string;\n name: string;\n /** The network's default for receiving: chat requests and Receive use it unless another card is picked. */\n receive: boolean;\n}\n\n/** The card of Lightning through the Cashu mints: at most one per network, shown while the network has mints. */\nexport const CASHU_CARD = \"cashu\";\n/** The card made from the one source a network had before it could have several. */\nexport const LEGACY_CARD = \"main\";\nexport const cardsKey = (network: WalletMode) => `lightningCards-${network}`;\nconst NAME_MAX = 32;\n\n/** Short source names for a card's default name (\"Alby Hub (NWC)\"): the form's label otherwise. */\nconst SHORT: Record<string, string> = { nwc: \"NWC\", lnd: \"LND\", \"core-lightning\": \"Core Lightning\", breez: \"Breez\", [CASHU_MINT_SOURCE]: \"Cashu\", fedimint: \"Fedimint\", webln: \"WebLN\" };\n\n/**\n * The cards of a network, made the first time this version opens it from what an older one kept: the network's one\n * source (`lightningSource-<network>`) becomes a card reading that same key, so no secret moves and nothing asks\n * again; with no source saved, the network used the Cashu mints, and that is its card. Either becomes the default for\n * receiving. Read and written in one transaction; a second run finds the cards and changes nothing. Never reads a\n * secret: the source's record names its provider next to its sealed secrets.\n */\nexport async function loadLightningCards(network: WalletMode, label: (providerId: string) => string, now = Date.now()): Promise<StoredLightningCards> {\n let result: StoredLightningCards | undefined;\n await transact([STORES.settings], (stores) => {\n const settings = stores[STORES.settings];\n const saved = settings.get(cardsKey(network));\n saved.onsuccess = () => {\n const found = saved.result as StoredLightningCards | undefined;\n if (found?.cards?.length) { result = found; return; }\n const legacy = settings.get(sourceKey(\"lightning\", network));\n legacy.onsuccess = () => {\n const providerId = (legacy.result as { providerId?: unknown } | undefined)?.providerId;\n const card: StoredLightningCard = typeof providerId === \"string\" && providerId && providerId !== CASHU_MINT_SOURCE\n ? { id: LEGACY_CARD, name: shortName(providerId, label), providerId, createdAt: now }\n : cashuCard(now);\n result = { cards: [card], receive: card.id };\n settings.put(result, cardsKey(network));\n };\n };\n });\n return result!;\n}\n\nconst cashuCard = (now: number): StoredLightningCard => ({ id: CASHU_CARD, name: SHORT[CASHU_MINT_SOURCE], providerId: CASHU_MINT_SOURCE, key: CASHU_CARD, createdAt: now });\nconst shortName = (providerId: string, label: (providerId: string) => string) => SHORT[providerId] ?? label(providerId);\n\nexport interface LightningCardsHost {\n descriptors: () => readonly LightningProviderDescriptor[];\n host: () => Omit<ProviderHost, \"mode\" | \"signal\">;\n events: LightningEvents;\n /** Whether the network has Cashu mints: the Cashu card shows only then. */\n hasMints: () => boolean;\n fetch?: typeof fetch;\n}\n\n/**\n * The Lightning cards of one network, each with its own source, balance and journal: \"Alby Hub (NWC)\", \"Home LND\",\n * the Cashu mints. A payment goes through the card picked for it; a chat request's invoice and Receive come from the\n * default for receiving unless another is picked. Adding, removing and renaming are serialized.\n */\nexport class LightningCards {\n private stored: StoredLightningCards = { cards: [], receive: CASHU_CARD };\n private readonly services = new Map<string, LightningService>();\n private queue: Promise<unknown> = Promise.resolve();\n /** A card being added: a creation that takes too long cuts its connection short. */\n private adding?: LightningService;\n private stopped = false;\n\n constructor(readonly network: WalletMode, private readonly options: LightningCardsHost) {}\n\n private serial<T>(run: () => Promise<T>): Promise<T> { const next = this.queue.then(run, run); this.queue = next.catch(() => {}); return next; }\n private label = (providerId: string) => this.options.descriptors().find((d) => d.id === providerId)?.label ?? providerId;\n\n private make(card: StoredLightningCard): LightningService {\n return new LightningService(this.network, this.options.descriptors, this.options.host, this.options.events,\n card.providerId === CASHU_MINT_SOURCE ? CASHU_MINT_SOURCE : undefined,\n { fetch: this.options.fetch, card: card.id, key: card.key, owns: (op) => this.owner(op) === card.id });\n }\n\n private starting?: Promise<void>;\n /** Opens the cards (once: a use before the engine started opens them too). */\n start(): Promise<void> { return this.starting ??= this.open(); }\n private async open() {\n this.stored = await loadLightningCards(this.network, this.label);\n for (const card of this.stored.cards) {\n const service = this.make(card);\n this.services.set(card.id, service);\n await service.start();\n }\n }\n\n /**\n * The card an operation went through. One journaled before cards: the Cashu card's when the mints made it, else the\n * card made from the network's one source (none, when that source was replaced before cards: nobody shows it).\n */\n owner(op: LightningOp): string {\n if (op.card !== undefined) return op.card;\n return op.providerId === CASHU_MINT_SOURCE ? CASHU_CARD : LEGACY_CARD;\n }\n\n /** The cards a person sees: the Cashu card only while the network has mints. In the order they were added. */\n private shown(): StoredLightningCard[] { return this.stored.cards.filter((c) => c.id !== CASHU_CARD || this.options.hasMints()); }\n\n /** The default for receiving, or the first card shown when that one is not (the Cashu card of a network with no mints). */\n get receivingId(): string {\n const shown = this.shown();\n return shown.find((c) => c.id === this.stored.receive)?.id ?? shown[0]?.id ?? this.stored.receive;\n }\n get receiving(): LightningService { return this.services.get(this.receivingId) ?? [...this.services.values()][0]; }\n\n /** A card by its id; without one, the default for receiving. Throws when it is gone. */\n card(id?: string): LightningService {\n if (id === undefined) { if (!this.services.size) throw new Error(\"Lightning is not open yet\"); return this.receiving; }\n const service = this.services.get(id);\n if (!service) throw new Error(\"That Lightning card is gone: pick another\");\n return service;\n }\n has(id: string) { return this.services.has(id); }\n all(): LightningService[] { return [...this.services.values()]; }\n\n views(): LightningCardView[] {\n const receive = this.receivingId;\n return this.shown().filter((card) => this.services.has(card.id)).map((card) => ({ ...this.services.get(card.id)!.view, card: card.id, name: card.name, receive: card.id === receive }));\n }\n /** The default for receiving's view: what a caller from before cards reads as the network's Lightning. None before start. */\n get view(): LightningCardView | undefined {\n const card = this.stored.cards.find((c) => c.id === this.receivingId) ?? this.stored.cards[0];\n return card && this.services.has(card.id) ? { ...this.services.get(card.id)!.view, card: card.id, name: card.name, receive: true } : undefined;\n }\n\n /** The card holding this quote (each card quotes through its own source). */\n withQuote(quote: string): LightningService | undefined { return this.all().find((s) => s.hasQuote(quote)); }\n\n /**\n * Adds a card with `providerId` and the values of its form: connected and checked first, like any source; only then\n * saved. Nothing is saved when it fails. The same wallet twice (same source, same settings) is refused. A card of the\n * person's own takes receiving over from the mints' card (what a source chosen before cards did); next to another\n * card of their own, the default stays.\n */\n add(providerId: string, values: Record<string, string>): Promise<string> {\n return this.serial(async () => {\n if (providerId === CASHU_MINT_SOURCE) {\n if (this.services.has(CASHU_CARD)) throw new Error(\"Lightning through your Cashu mints is already a card\");\n if (!this.options.hasMints()) throw new Error(\"Create a Cashu wallet first: this card pays and receives through its mints\");\n const card = cashuCard(Date.now());\n const service = this.make(card);\n await service.start();\n await this.save({ ...this.stored, cards: [...this.stored.cards, card] }, card.id);\n this.services.set(card.id, service);\n void service.ensureReady();\n return card.id;\n }\n const id = `ln-${[...crypto.getRandomValues(new Uint8Array(4))].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n const card: StoredLightningCard = { id, name: \"\", providerId, key: id, createdAt: Date.now() };\n const service = this.make(card);\n const same = await this.findSame(providerId, values);\n if (same) throw new Error(`That wallet is already your card “${this.stored.cards.find((c) => c.id === same)?.name}”`);\n await service.start();\n this.adding = service;\n try { await service.sources.set(providerId, values); }\n catch (error) { await service.stop(); throw error; }\n finally { this.adding = undefined; }\n card.name = this.freeName(service.view.alias ? `${service.view.alias.slice(0, 20)} (${shortName(providerId, this.label)})` : shortName(providerId, this.label));\n const receive = this.receivingId === CASHU_CARD ? id : this.stored.receive;\n // The mints' card of a network with no mints is not shown and holds nothing: a card of the person's own takes its\n // place, as the one source did before cards (a Cashu wallet made later does not bring it back).\n const hidden = this.services.has(CASHU_CARD) && !this.options.hasMints();\n const cards = [...this.stored.cards.filter((c) => !hidden || c.id !== CASHU_CARD), card];\n try { await this.save({ cards, receive }, card.id); }\n catch (error) { await service.sources.forget().catch(() => {}); await service.stop(); throw error; }\n this.services.set(id, service);\n if (hidden) { const gone = this.services.get(CASHU_CARD)!; this.services.delete(CASHU_CARD); await gone.stop(); }\n return id;\n });\n }\n\n /** The card that is already this wallet (the same source with the same settings), if one is. Throws what to fix in the form. */\n async findSame(providerId: string, values: Record<string, string>): Promise<string | undefined> {\n const { settings } = this.receiving.sources.settingsOf(providerId, values);\n for (const [id, service] of this.services) if (await service.sources.holds(providerId, settings)) return id;\n return undefined;\n }\n\n /** A name no other card of the network has: \"LND\", then \"LND 2\". */\n private freeName(name: string): string {\n const taken = new Set(this.stored.cards.map((c) => c.name.toLowerCase()));\n if (!taken.has(name.toLowerCase())) return name;\n for (let n = 2; ; n++) if (!taken.has(`${name} ${n}`.toLowerCase())) return `${name} ${n}`;\n }\n\n /**\n * Removes a card: its source and sealed secrets go (refused while a payment through it has not ended). The default\n * for receiving moves to the first card of the person's own left, else the mints' card. A network left with no card gets the Cashu card back, as before cards.\n */\n remove(id: string): Promise<void> {\n return this.serial(async () => {\n const service = this.services.get(id);\n if (!service) throw new Error(\"That Lightning card is gone\");\n await service.sources.forget();\n await service.stop();\n this.services.delete(id);\n let cards = this.stored.cards.filter((c) => c.id !== id);\n let added: LightningService | undefined;\n if (!cards.length) {\n const card = cashuCard(Date.now());\n cards = [card];\n added = this.make(card);\n this.services.set(card.id, added);\n await added.start();\n }\n const shown = cards.filter((c) => c.id !== CASHU_CARD || this.options.hasMints());\n const next = shown.find((c) => c.id !== CASHU_CARD) ?? shown[0] ?? cards[0];\n const receive = this.stored.receive === id || !cards.some((c) => c.id === this.stored.receive) ? next.id : this.stored.receive;\n await this.save({ cards, receive });\n if (added) void added.ensureReady();\n });\n }\n\n /** Makes a card the network's default for receiving. */\n setReceive(id: string): Promise<void> {\n return this.serial(async () => {\n if (!this.shown().some((c) => c.id === id)) throw new Error(\"That Lightning card is gone\");\n await this.save({ ...this.stored, receive: id });\n });\n }\n\n /** Renames a card: 1 to 32 characters, unique on its network. */\n rename(id: string, name: string): Promise<void> {\n return this.serial(async () => {\n const clean = name.replace(/\\s+/g, \" \").trim();\n if (!clean) throw new Error(\"Enter a name\");\n if (clean.length > NAME_MAX) throw new Error(`At most ${NAME_MAX} characters`);\n if (!this.services.has(id)) throw new Error(\"That Lightning card is gone\");\n if (this.stored.cards.some((c) => c.id !== id && c.name.toLowerCase() === clean.toLowerCase())) throw new Error(\"Another card has that name\");\n await this.save({ ...this.stored, cards: this.stored.cards.map((c) => (c.id === id ? { ...c, name: clean } : c)) });\n });\n }\n\n private async save(next: StoredLightningCards, receiveIfNone?: string) {\n const shown = next.cards.filter((c) => c.id !== CASHU_CARD || this.options.hasMints());\n // A first card shown takes the receiving over from one that is not (the Cashu card of a network with no mints).\n const receive = receiveIfNone && !shown.some((c) => c.id === next.receive) ? receiveIfNone : next.receive;\n const stored = { cards: next.cards, receive };\n await transact([STORES.settings], (s) => { s[STORES.settings].put(stored, cardsKey(this.network)); });\n this.stored = stored;\n this.options.events.changed();\n }\n\n // -- the Cashu wallet reports what its mints settled -------------------------------\n\n private async ownerOf(direction: LightningOp[\"direction\"], invoice: string): Promise<LightningService> {\n const op = (await readLightningJournal()).find((o) => o.direction === direction && o.invoice === invoice && o.mode === this.network);\n const owner = op ? this.owner(op) : CASHU_CARD;\n return (owner && this.services.get(owner)) || this.services.get(CASHU_CARD) || this.receiving;\n }\n async reportInvoicePaid(invoice: string, context: { paymentId?: string; mint?: string } = {}) { await (await this.ownerOf(\"in\", invoice)).reportInvoicePaid(invoice, context); }\n async reportPaymentResolved(invoice: string, paid: boolean, context: { paymentId?: string; mint?: string } = {}) { await (await this.ownerOf(\"out\", invoice)).reportPaymentResolved(invoice, paid, context); }\n\n // -- Lightning addresses: resolved by one card, and the invoice asked by that same one --------\n\n resolveDestination(text: string) { return this.receiving.resolveDestination(text); }\n destinationInvoice(id: string, amountSat: number, comment?: string) { return (this.all().find((s) => s.hasDestination(id)) ?? this.receiving).destinationInvoice(id, amountSat, comment); }\n\n // -- every card at once -----------------------------------------------------------\n\n list() { return readLightningJournal(); }\n /**\n * What a handoff needs to know of the cards (WISP 06 § Wallets), from their sealed settings: the LND cards that pin a\n * certificate, and each Breez card's database on this device, by wallet id.\n */\n async handoffFacts(): Promise<{ pinned: string[]; breez: Map<string, string> }> {\n const pinned: string[] = [], breez = new Map<string, string>();\n for (const [id, service] of this.services) {\n const wallet = `lightning:${this.network}:${id}`, providerId = service.view.providerId;\n if (providerId === \"lnd\" && await service.sources.secret(\"certificate\")) pinned.push(wallet);\n if (providerId === \"breez\") {\n const mnemonic = await service.sources.secret(\"mnemonic\");\n if (mnemonic) breez.set(wallet, await breezDatabaseInUse(this.network === \"mainnet\" ? \"bitcoin\" : \"regtest\", normalizePhrase(mnemonic)));\n }\n }\n return { pinned, breez };\n }\n\n /** The cards whose source is opened on this device: not one at home on another device (WISP 06 § Wallets that stay home). */\n private here(): LightningService[] { return [...this.services].filter(([id]) => awayFrom(`lightning:${this.network}:${id}`) === undefined).map(([, s]) => s); }\n async reconcile() { await Promise.all(this.here().map((s) => s.reconcile())); }\n async recover() { for (const s of this.here()) await s.recover(); }\n async ensureReady() { await Promise.all(this.here().map((s) => s.ensureReady())); }\n refreshOffered() { for (const s of this.all()) s.refreshOffered(); }\n wake() { for (const s of this.all()) s.sources.wake(); }\n cutShort() { this.adding?.sources.cutShort(); }\n resume() { this.adding?.sources.resume(); }\n async stop() { this.stopped = true; for (const s of this.all()) await s.stop(); }\n get isStopped() { return this.stopped; }\n}\n","import { FILE_BYTES_STEP, fileBytesOf, type FileBytes } from \"./fileBytes\";\nimport { fileStore, type StoredFile } from \"./idb\";\nimport type { FileTransferView } from \"./types\";\n\n/** The bytes a file holds: its metadata says, and a file stored before that says it with its Blob. */\nexport function storedSize(file: StoredFile): number {\n return file.metadata?.size ?? file.blob?.size ?? 0;\n}\n\n/**\n * A group's file (WISP 503) that is not here, read from its record: when the engine shows no transfer for it, it waits\n * for its person's Download. The engine keeps no view of such a file in the states it builds (one for every file a\n * group ever had would go into each of them), so the app's bubble and the CLI read it here. Null for any other file.\n */\nexport function waitingGroupFile(file: StoredFile | undefined): FileTransferView | null {\n if (!file?.group || file.direction !== \"in\" || file.transfer?.state !== \"transferring\") return null;\n return { state: \"transferring\", stage: \"asking\", transferred: 0, size: file.metadata?.size ?? file.transfer.size, direction: \"in\" };\n}\n\nasync function backend(file: StoredFile): Promise<FileBytes | null> {\n return file.bytes ? fileBytesOf(file.bytes) : null;\n}\n\n/** The file as a Blob backed by storage, or null where it cannot be had as one (see `FileBytes.blob`). */\nexport async function storedBlob(file: StoredFile, type: string): Promise<Blob | null> {\n if (file.blob) return file.blob.slice(0, file.blob.size, type);\n return (await backend(file))?.blob(file.id, type) ?? null;\n}\n\n/** At most `length` bytes from `offset`. */\nexport async function readStored(file: StoredFile, offset: number, length: number): Promise<Uint8Array> {\n if (file.blob) return new Uint8Array(await file.blob.slice(offset, offset + length).arrayBuffer());\n const bytes = await backend(file);\n if (!bytes) throw new Error(\"The file is gone\");\n return bytes.read(file.id, offset, length);\n}\n\n/** The file, a step at a time, from `offset`. */\nexport async function* streamStored(file: StoredFile, offset = 0): AsyncGenerator<Uint8Array> {\n const size = storedSize(file);\n while (offset < size) {\n const part = await readStored(file, offset, Math.min(FILE_BYTES_STEP, size - offset));\n if (!part.length) throw new Error(\"The file is shorter than it says\");\n offset += part.length;\n yield part;\n }\n}\n\n/** A file's record and its bytes, wherever they are. */\nexport async function removeStored(id: string): Promise<void> {\n const file = await fileStore.get(id);\n await fileStore.delete(id);\n if (file?.bytes) await (await backend(file))?.remove(id);\n}\n\n/** Writes are gathered to this size before they go to storage: a chunk from the wire is 16 KiB. */\nconst GATHER = 256 * 1024;\n\n/**\n * Writes one file in order, gathering small writes into larger ones. `written` counts what was handed over,\n * gathered or not; `flush` makes all of it durable.\n */\nexport class FileAppender {\n private buffer = new Uint8Array(GATHER);\n private buffered = 0;\n private stored: number;\n\n constructor(readonly bytes: FileBytes, readonly id: string, offset = 0) { this.stored = offset; }\n\n get written(): number { return this.stored + this.buffered; }\n\n async append(chunk: Uint8Array): Promise<void> {\n if (this.buffered + chunk.length > GATHER) await this.drain();\n if (chunk.length >= GATHER) {\n await this.bytes.append(this.id, this.stored, chunk);\n this.stored += chunk.length;\n return;\n }\n this.buffer.set(chunk, this.buffered);\n this.buffered += chunk.length;\n }\n\n private async drain(): Promise<void> {\n if (!this.buffered) return;\n const part = this.buffer.slice(0, this.buffered);\n await this.bytes.append(this.id, this.stored, part);\n this.stored += part.length;\n this.buffered = 0;\n }\n\n async flush(): Promise<void> {\n await this.drain();\n await this.bytes.flush(this.id);\n }\n\n async close(): Promise<void> {\n await this.drain();\n await this.bytes.close(this.id);\n }\n}\n","import type { DiscoveryStatus, GroupMention, GroupMessageFrame, ImageMeta, LinkPreview, PairingProgress, PaymentMethodName, RoutineCard, StatusCard, TaskCard, TypingKind, UsageCard, VideoMeta, VoiceMeta, WirePin, WireReaction } from \"@ghostly/core\";\nimport type { UsdtWalletView } from \"../engine/paymentAdapters/usdtWallet\";\nimport type { ArkWalletView } from \"../engine/paymentAdapters/arkWallet\";\nimport type { BarkWalletView } from \"../engine/paymentAdapters/barkWallet\";\nimport type { FedimintWalletView } from \"../engine/paymentAdapters/fedimintWallet\";\nimport type { SparkWalletView } from \"../engine/paymentAdapters/sparkWallet\";\nimport type { LightningView } from \"../engine/paymentAdapters/providers/lightningService\";\nimport type { LightningCardView } from \"../engine/paymentAdapters/providers/lightningCards\";\nimport type { BitcoinView } from \"../engine/paymentAdapters/providers/bitcoinService\";\nimport type { PaymentReview, PaymentTarget, VapidKeys, WakeTarget, WalletNetwork } from \"@ghostly/core\";\nimport type { ProviderDescriptorView } from \"../engine/paymentAdapters/providers/types\";\nimport type { CapsState, DeliveryMode, DhtDeliveryState, DhtDeliveryView, HoldKind } from \"@ghostly/core\";\nimport type { S3Config } from \"../backup/s3\";\nimport type { BackupReminders } from \"./backupReminder\";\nimport type { TransportCause, TransportEntry, TransportEvent } from \"../engine/transportLog\";\nimport type { PublicProfile, ProfileChoice } from '../profiles/public';\nimport type { NostrContactCache, NostrContactView, NostrSocialSettings, NostrSocialState } from \"../nostr/types\";\nimport type { ProofLedger, ProofAdapter } from \"@ghostly/core\";\nimport type { IdentityDisplay, IdentityLedger, IdentityStatus, IdentityTimelineEntry, SharedIdentity, VerifiedIdentity } from \"@ghostly/core\";\nimport type { DataLinkState, LinkStatus, LiveAttempt, ServiceAd, PairingState, NativeTransport, PairedTransport, TransportDescriptors, TransportWait } from \"@ghostly/core\";\nimport type { CommunityState, GroupByeFrame, GroupCommit, GroupRole, GroupState, GroupStatus } from \"@ghostly/core\";\n\n/** A link as stored in IndexedDB. Same fields Desktop keeps in its ChatSession. */\nexport interface StoredLink {\n /**\n * Paired 1:1 chats: how to wake the contact's closed web app, as it shared it (`wake/1`, WISP 401 § Wake-up push).\n * Absent until it does, and again once it stops sharing or its push service says the subscription is gone.\n */\n peerWake?: WakeTarget;\n /** Paired 1:1 chats: this side's token for this chat in its own wake-ups; a new one whenever the subscription changes. */\n wakeToken?: string;\n /** Paired 1:1 chats: muted here, so the contact is told to forget this side's subscription until it is unmuted. */\n wakeMuted?: boolean;\n deliveryMode?: DeliveryMode;\n dhtDeliveryState?: DhtDeliveryState;\n /** This side's layer-0 capability record and the contact's last good one (WISP 03). */\n capsState?: CapsState;\n publicProfiles?: PublicProfile[];\n profileChoice?: ProfileChoice;\n peerProofs?: ProofLedger;\n /** Identity proofs shared in this chat, both ways (WISP 300). */\n identities?: IdentityLedger;\n /** The contact's Nostr profile, follows and notes, cached under their proven key, loaded only on request. */\n nostrSocial?: Record<string, NostrContactCache>;\n profile?: \"paired-chat/1\";\n participationSeed?: string;\n transportSeeds?: Partial<Record<NativeTransport, string>>;\n peerDescriptors?: TransportDescriptors;\n peerTransports?: PairedTransport[];\n peerFallback?: boolean;\n /** Absent: no choice for this chat, the app's rule applies (\"Automatic\"). */\n preferredTransport?: PairedTransport;\n transportFallback?: boolean;\n /** The chat's connection story, newest last (see engine/transportLog.ts). Local only. */\n transportLog?: TransportEntry[];\n /** Every connection event, for the connection panel, newest last (see engine/transportLog.ts). Local only. */\n transportHistory?: TransportEvent[];\n pairedPeerKey?: string;\n /** The inviter's participation key, from a `ghostly1` code (the joiner's side): the only key that may be pinned. */\n peerParticipationKeyZ32?: string;\n /** Absent only in old releases, where first pin required an explicit code comparison. */\n peerTrust?: { version: 1; verifiedKey?: string; verifiedAt?: number };\n requireSignedSignals?: boolean;\n id: string;\n seedB64: string;\n peerPubKeyZ32: string;\n encKeyB64: string;\n createdAt: number;\n label?: string;\n /**\n * The name the contact asked to be shown by, as they last said it. `\"\"` means a paired contact said they\n * have none (removed, or not shared); absent means nothing was heard yet.\n */\n peerNick?: string;\n /** The contact's profile picture, as they last sent it (checked, small JPEG data URL). */\n peerAvatar?: string;\n /** Present on the side that created the link, until the peer shows up. */\n inviteCode?: string;\n /** Messages deleted on this device, by id, so a republished one is not stored again. */\n deletedIds?: string[];\n /**\n * Files of mine whose offer went on the DHT floor (WISP 403 § Files) and that were deleted here before they went live,\n * by wire id: the next session that carries files/3 says each is cancelled (`pf-abort`), so the contact's bubble does too.\n */\n withdrawnFiles?: string[];\n /** This side's reactions the contact has not confirmed yet (WISP 401 § Reactions), in the order of their numbers. */\n reactionsOut?: WireReaction[];\n /** The chat's pinned message (WISP 400 § Pinned message): the latest pin, either side's; `id` \"\" once unpinned. */\n pin?: StoredPin;\n /** This side's pin the contact has not confirmed yet (WISP 401 § Pinned message): said on each live session until it does. */\n pinOut?: WirePin;\n /** Ways of paying this device allows in this chat. Absent or true: allowed. */\n paymentMethods?: Partial<Record<PaymentMethodName, boolean>>;\n /**\n * For a way of paying, the networks this chat accepts it on (its cards on the Accept side). Absent for a method:\n * every network. A request, a payment or an ask of a network off here is refused, and the contact is told.\n */\n paymentNetworks?: Partial<Record<PaymentMethodName, WalletNetwork[]>>;\n /** Store-and-forward for this contact (WISP 404, `hold/1`). Absent: off, as for every chat from before it. */\n hold?: HoldState;\n /** An edge of a private group (WISP 900): the group, and the member at the other end. Not a chat. */\n group?: string;\n groupPeer?: string;\n /**\n * Not an edge but an entry session of the group's link (`group-entry/1`): `host` on the admin's\n * side, toward a joiner's member key; `guest` on the joiner's, toward the link's entry key.\n */\n groupEntry?: \"host\" | \"guest\";\n /**\n * An edge whose session is up: set when it opens, cleared when it drops while this app runs (not when the app quits).\n * Still set when the app starts, it was live when the app last ran, and is dialled at once (`resume`). Local only.\n */\n edgeLive?: boolean;\n /** When that session opened: a member's offer from before it is not answered after a restart (`resumeFloor`). Local only. */\n edgeLiveSince?: number;\n}\n\n/** What happened to a group's membership, as a line in its history. */\nexport type GroupEvent = \"created\" | \"joined\" | \"gone\" | \"admin\" | \"rotated\" | \"removed\" | \"left\" | \"forked\" | \"picture\" | \"renamed\";\n\n/** A private group as stored: an invitation not yet answered, or a group I am (or was) in. */\nexport interface StoredGroup {\n id: string;\n createdAt: number;\n /** Pending (or accepted, awaiting the welcome) invitation, on the invitee's side. */\n invitation?: {\n name: string;\n /** The inviter's member key, learned over the contact chat: the admin who must have admitted me. */\n admin: string;\n /** The contact chat it came over. */\n linkId: string;\n e: number;\n n: number;\n /** My member seed, once accepted. */\n seedB64?: string;\n /** Chain pieces that precede the welcome of a long chain. */\n pieces: { t: \"group-chain\"; g: string; commits: GroupCommit[] }[];\n /** Joining through the group's link: its entry key, the admin's side of `linkId` (an entry session, not a contact chat). */\n entry?: string;\n /**\n * Removed and invited again over a contact chat: the hash of the genesis of the chain I held. The id alone names no\n * group (anyone may sign a genesis under it): only a welcome whose chain starts there joins the history I kept.\n */\n genesis?: string;\n /**\n * Taken back by who sent it (`group-decline` over its chat), and when: kept a week so the list says so where the\n * invitation was, instead of it going with no word. Nothing is accepted or owed for it; another invitation replaces it.\n */\n takenBack?: number;\n };\n /**\n * On the admin's side: contact chats invited and not answered yet, and when. Kept so that an accept after my app\n * started again still lets them in.\n */\n invited?: Record<string, number>;\n /** On the admin's side: the group's link is on, with this entry key seed (`group-entry/1`). */\n entry?: { seedB64: string; createdAt: number };\n state?: GroupState;\n /** Member key → the contact chat that invited them (or me): the path for courtesy notices. */\n contacts?: Record<string, string>;\n /**\n * Members whose apps said (`paired-groups` version 3, on their edge or the link that admitted them) that they take\n * rosters past eight: an admin grows the group past eight only when every other member is here (WISP 902 § Bounds).\n */\n large?: string[];\n /**\n * On the admin's side: members I let in over a contact chat or the group's link whose edge has not come up with me\n * yet, so whose app may never have had the group's keys (its welcome lost on the way). A `group-decline` from that\n * chat takes such a member out again, and one that knocks on the link again gets its welcome again; one seen on an\n * edge once is a member, whatever its chat says after.\n */\n unseen?: string[];\n /**\n * Removed, then invited again over a contact chat: the names my app knew members by (member key → name). The history\n * stays while the invitation waits, and still names who wrote it; once in again, the names of members who are gone\n * since go on naming what they wrote.\n */\n formerNames?: Record<string, string>;\n /** Removed, then invited again: the removed group as it was, which a Decline puts back (with the history it kept). */\n previous?: StoredGroup;\n /**\n * Past 16 members, with hubs (WISP 902 · Group Mesh § Hubs): the hubs I kept edges with (as a hub, the other hubs),\n * where my edges go when the app starts again, before the beacon is read.\n */\n hubs?: string[];\n /** Members whose edge came up without `paired-groups` version 4: their apps take no hubs, so hubs keep edges with them. */\n legacy?: string[];\n /**\n * I left: the group is gone from the list and its history from the device. What is kept is the\n * edge to the admin, until the admin's commit removing me arrives or `at` is a week old, so\n * a leave said while the admin was away still reaches it. With hubs, the edges to my hubs too, and my\n * signed leave (`bye`) that they carry to the admin.\n */\n left?: { at: number; admin: string; hubs?: string[]; bye?: GroupByeFrame };\n /**\n * I declined an invitation while its inviter's chat (`linkId`) was down: the answer is said when that chat is up, or\n * dropped once `at` is a week old. Alone when nothing else of the group is kept; beside `state` when the invitation\n * was to a group I was removed from, which stays.\n */\n declined?: { at: number; linkId: string };\n /**\n * On the admin's side: members I removed while they could not be told (their edge was down, and no contact chat\n * carried the notice), by member key. `e`: the epoch of the commit that removed them; `at`: when. The edge to each\n * stays until it opens and that commit goes over it (`told`, the first time), or `at` is a week old: a member removed\n * while its app was closed otherwise never learns it, since every member closes the edge to someone out of the\n * roster. `opened`: when the edge's connection first opened; it closes a while after, told or not.\n */\n farewells?: Record<string, { at: number; e: number; told?: number; answered?: number; opened?: number }>;\n /** The group's pinned message (WISP 400 § Pinned message): the latest pin; `id` \"\" once unpinned. */\n pin?: StoredPin;\n /**\n * When the latest message of another member (`peer`) and the latest one that names me (`mention`) reached this\n * device, where that is later than the message's own time (`cameAt`): what makes the group unread survives a restart.\n */\n came?: { peer?: number; mention?: number };\n /** A community group (`group-community/1`) I am in: its session state. Mesh groups use `state`. */\n community?: CommunityState;\n /**\n * Joining a community group through its link (`group2/…`), or again after my admission lost a\n * race: my member seed, the entry session, and what arrived of the welcome.\n */\n joining?: { g: string; host: string; seedB64: string; linkId: string; name: string; inviter: string; invitedAt?: number; pieces: unknown[]; since: number; check?: boolean };\n}\n\nexport interface GroupMemberView {\n key: string;\n role: GroupRole;\n me: boolean;\n nick?: string;\n /** The pairwise edge to this member is open, or a hub I am connected to reaches them (always true for me). */\n online: boolean;\n /** Reached through a hub, not an edge of mine (WISP 902 · Group Mesh § Hubs). */\n viaHub?: boolean;\n /** A hub of the group now (listed in its beacon), and what the admin said: pinned as a hub, or never one. */\n hub?: boolean;\n hubRole?: \"pin\" | \"exclude\";\n /** Messages of the current epoch known to be missing from this member. */\n missing: number;\n /** The pairwise edge to this member as the engine sees it: absent for me, and until the edge exists. */\n edge?: GroupEdgeView;\n}\n\n/** One edge of a group's mesh (WISP 902): a paired link toward one member, which carries the group to and from them. */\nexport interface GroupEdgeView {\n linkId: string;\n /**\n * `open`: frames flow (the member is reachable). `connecting`: the member answered and the channel is being set up.\n * `waiting`: nothing heard from the member's app yet. `error`: the last attempt failed (`error` says why).\n */\n state: \"open\" | \"connecting\" | \"waiting\" | \"error\";\n /** The transport carrying the edge while it is open: WebRTC, or Iroh or HyperDHT where one side has none. */\n transport?: PairedTransport;\n /**\n * Waiting because this device has no free native slot for it (a member reached over Iroh or HyperDHT, and the\n * device's native listeners held by chats and other group links): it is tried again as one frees up.\n */\n noSlot?: true;\n /** When this device last heard from the member on this edge, in ms (0: never). */\n lastSeenAt: number;\n /** The engine's own words for why the last attempt failed (English, for logs and details); `cause` sums it up. */\n error?: string;\n /**\n * What `error` comes down to, for a short status: `relays`, its packets could not go out or be read (the Pkarr relays\n * failed); `session`, a connection opened and dropped in a way that says nothing about the member (it is dialled\n * again); `other`, anything else (a refused key, no common transport...).\n */\n cause?: GroupEdgeCause;\n}\n\nexport type GroupEdgeCause = \"relays\" | \"session\" | \"other\";\n\n/**\n * How far a join through a group's link got, as the joiner can know it: the knock is being left,\n * it is there for the admin's app to read, that app opened the entry session, it let me in. Or a member's app\n * answered that there is no room yet (`group-full`, WISP 902 § A full group): `full`, or `blocked` while the group\n * waits for every member's app to take more than eight; the joiner goes on knocking.\n */\nexport type GroupJoinStage = \"knocking\" | \"knocked\" | \"answered\" | \"admitted\" | \"full\" | \"blocked\";\n\nexport interface GroupView {\n id: string;\n name: string;\n createdAt: number;\n /** Which kind of group: a private mesh of up to 32 (`group-mesh/1`) or a community (`group-community/1`). */\n profile: \"mesh\" | \"community\";\n /** Absent while it is only an invitation. */\n status?: GroupStatus | \"lost\";\n statusReason?: string;\n epoch?: number;\n myKey?: string;\n isAdmin: boolean;\n /**\n * Admin work is off for this group on this device (WISP 06 § Forced takeover): no commit is signed and no door duty\n * taken until the person turns on \"Manage groups from this device\" (`setGroupManage`).\n */\n adminOff?: true;\n members: GroupMemberView[];\n /** On the invitee's side, until the welcome arrives. */\n /**\n * `waiting`: joining a community through its link, how many others were knocking with me when I last knocked.\n * `since`: joining a community through its link, when the knock started (a group kept here, knocking to get back in,\n * was created long before).\n * `full`: with stage `full` or `blocked`, the members the group has and the most it holds, as the answer said.\n * `unanswered`: accepted over a contact chat, said again as often as it is, and the inviter's app answered nothing\n * while that chat was up: the answer is offered again.\n * `takenBack`: who sent it took it back (the group is gone from its app, or the admin role from it): nothing to answer.\n */\n invitation?: { linkId: string; contact: string; admin: string; members: number; accepted: boolean; unanswered?: boolean; takenBack?: boolean; viaLink?: boolean; stage?: GroupJoinStage; waiting?: number; since?: number; full?: { n: number; max: number } };\n /** The group's link while it is on (`group1/<id>/<entry key>`); only the admin who made it sees it. */\n entryLink?: string;\n /** Contacts (by chat id) invited by me and not yet in. */\n invited: string[];\n /** Contact chats that are members, by chat id → member key. */\n memberLinks: Record<string, string>;\n lastMessageAt: number;\n /** When the latest message from another member arrived (what makes the group unread; mine and membership lines do not); absent for none. */\n lastPeerMessageAt?: number;\n /** When the latest message that names me arrived; absent for none. */\n lastMentionAt?: number;\n /** The latest reaction in the group, for the chat list. */\n lastReaction?: ReactionNote;\n /** The group's pin (WISP 400 § Pinned message): `id` \"\" once unpinned; absent when nothing was ever pinned. */\n pin?: PinView;\n canSend: boolean;\n /** The group's picture (a JPEG data URL the engine checked), set by its admin; absent for none. */\n picture?: string;\n /**\n * A private group past 16 members that runs on hubs (WISP 902 · Group Mesh § Hubs): `hub` when I am one. Absent for\n * the full mesh.\n */\n hubs?: { hub: boolean };\n /** Community groups: how this device is connected (a hub for others, or through hubs). */\n community?: { hub: boolean; hubs: number; connected: number };\n /** Private groups: the members typing now, in the order they started (WISP 902 · Group Mesh § Typing). Never stored. */\n typing?: GroupTypingView[];\n /** Private groups: this side's wake-up tokens, one per member it shared its subscription with (the push worker's table). */\n wakeTokens?: string[];\n /** Private groups: its members were told not to wake this side while the group is muted (`setWakeMuted`). */\n wakeMuted?: boolean;\n /**\n * The names of authors no longer in the roster (removed, left, or back with a new member key), by\n * member key, so their earlier messages still say who wrote them. Absent when there are none.\n */\n formerNames?: Record<string, string>;\n}\n\n/** A member typing in a group: `kind` when it is not plain typing, and a bot's status line, sanitized as in a 1:1 chat. */\nexport interface GroupTypingView { key: string; kind?: Exclude<TypingKind, \"typing\">; status?: string }\n\n/** One item held in this device's storage for the contact, or on its way there. */\nexport interface HeldEntry {\n seq: number;\n /** The id on the wire: the text's wire id, the file's wire id or the payment id. */\n id: string;\n /** The chat message this item is, for its delivery state. */\n messageId: string;\n kind: Exclude<HoldKind, \"manifest\">;\n /** What to rebuild the bundle from: the file's local id or the payment id; text is on the message. */\n ref?: string;\n /** The object's name in storage, chosen before the first upload and kept across retries. */\n name: string;\n bytes: number;\n ts: number;\n expires: number;\n state: \"queued\" | \"held\" | \"failed\";\n error?: string;\n}\n\n/** Everything store-and-forward keeps per chat: the switch, what the contact said, sequences and the outbox. */\nexport interface HoldState {\n /** This device offers `hold/1` in this chat: it accepts held items, and holds items for the contact when it can. */\n enabled: boolean;\n /** The contact's app accepts held items, as it last said (handshake or session). */\n peerAllows?: boolean;\n /** This device's mailbox folder for the contact in its storage, chosen once. */\n mailbox?: string;\n /** Sequence of the last item held for the contact. */\n outSeq: number;\n /** Highest sequence received from the contact and stored. */\n inSeq: number;\n /** Highest sequence the contact said it received of what was held for it. */\n peerAck: number;\n /** This device's pointer revision, and the contact's last seen one. */\n pointerRev: number;\n peerPointerRev: number;\n /** When the manifest and its addresses were last signed (they live seven days). */\n manifestSignedAt?: number;\n outbox: HeldEntry[];\n /** Items from the contact refused on the way in (changed, oversized, not for this chat). */\n refused: number;\n /** Their sequences, the last 32, told to the contact on this device's pointer. */\n refusedSeqs?: number[];\n /** Ways of paying the contact allowed at the last session, for requests held while it is away. */\n peerPaymentMethods?: PaymentMethodName[];\n}\n\n/** A file attached to a message. The bytes live in the `files` store under `id`. */\nexport interface MessageFile {\n id: string;\n name: string;\n size: number;\n mime: string;\n /** A voice message: its length and the shape of its sound. */\n voice?: VoiceMeta;\n /** A video: its length, size and a small first frame, when the sender sent them. */\n video?: VideoMeta;\n /** A picture: its size as it is shown, when the sender sent it. */\n image?: ImageMeta;\n}\n\nexport interface FileTransferView {\n state: \"transferring\" | \"done\" | \"failed\";\n transferred: number;\n size: number;\n error?: string;\n /**\n * files/3 (WISP 501 rev 0.3), while `transferring`: where it stands when it is not moving. `waiting`: no live\n * connection (it goes on by itself); `asking`: the receiver's person has not decided (on the receiving side,\n * this person); `queued`: accepted, waiting its turn; `paused`; `verifying`: every byte there, checking the digest.\n */\n stage?: \"preparing\" | \"waiting\" | \"asking\" | \"queued\" | \"paused\" | \"verifying\";\n /** files/3: the transfer can be paused, resumed and cancelled from here. */\n direction?: \"in\" | \"out\";\n pausedBy?: \"me\" | \"peer\";\n /** Bytes per second lately, while moving. */\n rate?: number;\n /** Receiving, `asking`: bytes this device can still take for files, when it says. */\n room?: number | null;\n /** Failed but the sender can offer it again (files/3). */\n retry?: boolean; /**\n * files/3, unfinished and not moving for a while (or not since the app started): the sender can send it again\n * (`resend`), the receiver ask for it again (`request`). Either goes on from what the receiver holds.\n */\n stalled?: boolean;\n /**\n * A group file (WISP 503) waiting, `waiting`: why. `nobody`: nobody this device is connected to has it; `busy`: its\n * holders answered busy; `damaged`: what came failed the author's digest (`stalled`: its person can ask again).\n */\n wait?: \"nobody\" | \"busy\" | \"damaged\";\n /** The same, in a few words (\"Nobody you are connected to has this file yet\"). */\n note?: string;\n}\n\n/** Ecash held by this peer. One row per proof; `reserved` while an operation is using it. */\nexport interface StoredProof {\n mint: string;\n id: string;\n amount: number;\n secret: string;\n C: string;\n dleq?: unknown;\n /** Handed to the mint for a Lightning payment that has not settled: not spendable, not yet gone. */\n reserved?: boolean;\n /**\n * Brought back by a profile restore: a copy of ecash as it was when the backup was made, which the profile it was\n * copied from (or this one, before the backup was restored) may have spent since. The mint is asked once, and a\n * proof it reads spent is dropped (see `CashuWallet.checkRestored`).\n */\n unchecked?: boolean;\n}\n\n/** A Lightning payment the mint has not settled yet. Its proofs stay in the wallet, reserved, until it does. */\nexport interface PendingMelt {\n /** The melt quote id. */\n quote: string;\n mint: string;\n /** The invoice, so it is not paid a second time while this one is in flight. */\n request: string;\n /** What the invoice pays. */\n amount: number;\n /** The reserved proofs handed to the mint. */\n secrets: string[];\n /** Everything that left the balance for this payment: the proofs above plus any swap fee. */\n outlay: number;\n /** NUT-08 blank outputs (`OutputData.serialize`), to unblind the fee change once the mint returns it. */\n outputs: unknown[];\n note?: string;\n /** Set when the invoice pays a contact's payment request. */\n paymentId?: string;\n createdAt: number;\n}\n\n/**\n * A swap at a mint, written down before the mint is asked, with everything that finishes it: the mint signs the\n * outputs of `swap` and nothing else, so its signatures can be asked for again (NUT-09) for as long as this is kept.\n * Deleted in the transaction that stores what the swap brought, or once the mint says it never happened.\n */\nexport interface PendingSwap {\n id: string;\n mint: string;\n /** `receive`: a token redeemed into this wallet. `send`: this wallet's ecash split, for a token or a Lightning payment. */\n kind: \"receive\" | \"send\";\n /** cashu-ts' `SerializedSwapPreview`: the inputs and the blinded outputs with their secrets. As secret as the ecash itself. */\n swap: unknown;\n createdAt: number;\n /** When the request to the mint ended with no answer. The mint may still act on it for a moment after that. */\n attemptEndedAt?: number;\n /**\n * The inputs were given back (the swap was overdue with nothing signed), and it holds nothing any more. Still asked\n * about for a while: a request that reached the mint late is recovered from what is written here.\n */\n released?: boolean;\n /** What it brought is stored; only the chat of its payment is still to be told (the app closed in between). */\n finished?: boolean;\n /**\n * Its inputs read spent at a mint that cannot say what it signed (no NUT-09): how many times that was the answer.\n * Kept, since the outputs are the only way back to that ecash, and asked about less and less often (`askAt`).\n */\n stuck?: number;\n /** Not asked about before this time. */\n askAt?: number;\n /** Came with a restored copy of the profile: what it brings is as old as the copy, and is checked like the copy's ecash. */\n restored?: boolean;\n /** `receive`: the token being redeemed. Redeeming it again while this is kept finishes this swap, and makes no other. */\n token?: string;\n /** `receive`: the history line of the ecash, written with it. */\n tx?: WalletTx;\n /** `receive`: the payment record written with the ecash (a contact's payment received, or one of ours taken back). */\n payment?: StoredPayment;\n}\n\n/** A Lightning invoice the mint issued for us; paid invoices turn into ecash. */\nexport interface StoredQuote {\n quote: string;\n mint: string;\n amount: number;\n invoice: string;\n createdAt: number;\n expiresAt: number | null;\n /** Set when the invoice belongs to a payment request sent to a peer. */\n paymentId?: string;\n /**\n * The mint says it issued this quote's ecash, but the wallet never stored it. Kept, not polled: it is the\n * only record of sats the user paid for.\n */\n issuedUnclaimed?: boolean;\n /** The mint said this invoice is paid, and its ecash is not claimed yet (the claim is tried again every round). */\n paid?: boolean;\n /**\n * The mint marks its invoices paid by itself (a test mint, `paysItsOwnInvoices`): its \"paid\" says nothing about a\n * payer. Not polled, never minted by itself: a payer saying it paid (`CashuWallet.vouch`) lets it be minted.\n */\n held?: boolean;\n /** Asked for on purpose, as test coins from a test mint (\"Get test coins\"): minted as soon as the mint says paid. */\n testCoins?: boolean;\n /**\n * The blinded outputs the mint is asked to sign for this invoice (`OutputData.serialize`), written down before it\n * is asked: the ecash it issues is for these, and its signatures for them can be asked for again (NUT-09).\n */\n outputs?: unknown[];\n}\n\nexport type PaymentState =\n | \"pending\" // sent, no word from the peer yet / request waiting to be paid\n | \"settled\"\n | \"failed\"\n | \"reclaimed\"; // the peer never took the ecash and it is back in the wallet\n\n/** A payment or payment request in a chat, by id. */\nexport interface StoredPayment {\n /** A request made because the contact asked to pay (its `pay-ask` id), or, on the payer's side, the ask it answers. */\n ask?: string;\n target?: PaymentTarget;\n txid?: string;\n id: string;\n linkId: string;\n kind: \"payment\" | \"request\";\n direction: \"in\" | \"out\";\n amount: number;\n unit: string;\n memo?: string;\n state: PaymentState;\n error?: string;\n createdAt: number;\n mint?: string;\n /** Outgoing ecash, kept until the peer confirms so it can be reclaimed. */\n token?: string;\n /** Requests: how the payer can pay. */\n invoice?: string;\n mints?: string[];\n /**\n * Real money or test coins: only a wallet of this network pays a request, or made a payment. Absent on records\n * from before wallets had their own network: its target, mints or invoice say (see `paymentNetwork`).\n */\n network?: WalletNetwork;\n /** Fedimint requests: the federations the payee takes ecash of (their ids). */\n federations?: string[];\n /** Fedimint payments: the federation of the notes, and the client operation that spent (or redeems) them. */\n federation?: string;\n fedimintOp?: string;\n /** Incoming Fedimint payments: a digest of the notes, kept after they are redeemed or refused, so the same notes sent again under another id are answered from it. */\n notesDigest?: string;\n requestId?: string;\n /** Requests we pay: a Lightning payment is in flight at the mint, so paying again would pay twice. */\n lightningPending?: boolean;\n /** Requests we pay: this device paid it over Lightning (the payee's wallet cannot say who paid an invoice). */\n paidHere?: boolean;\n /**\n * A request to a whole group (WISP 902 § Payments): the group's id. Its `linkId` is `group:<id>`, it went to\n * every member over their edges, and the first member whose payment settles it pays it; later ones are refused.\n */\n group?: string;\n /**\n * A request closed by the one who made it (their wallet for it was removed): it can no longer be paid, and a\n * payment still made to what it named is lost. Set on both sides, with `state` \"failed\".\n */\n closed?: boolean;\n}\n\n/** What pages see of a payment: everything but the token. */\nexport type PaymentView = Omit<StoredPayment, \"token\">;\n\nexport interface MintView {\n url: string;\n name: string;\n balance: number;\n /** Null until the mint answered once. */\n info: MintInfoView | null;\n /** What it still waits for (see `mintAwaiting`), when it does: removing it asks about these first. */\n awaiting?: WalletAwaitingView[];\n}\n\n/** What a mint says about itself and what it charges. */\n/** What a pasted piece of ecash says about itself. Mirrors `CashuInspection` in the UI's platform contract. */\nexport type CashuInspection =\n | { kind: \"token\"; amount: number; unit: string; mint: string; memo?: string; accepted: boolean }\n | { kind: \"request\"; amount: number | null; unit: string; mints: string[]; description?: string };\n\nexport interface MintInfoView {\n version?: string;\n motd?: string;\n /**\n * Fee for spending ecash, in sats per thousand proofs (NUT-02). A swap,\n * a redeem or a Lightning payment spends a handful of proofs and is charged\n * the sum, rounded up. 0 means spending ecash is free.\n */\n inputFeePpk: number;\n /** Lightning in and out: allowed amounts in sats, null when the mint sets no bound. */\n receive: { min: number | null; max: number | null } | null;\n send: { min: number | null; max: number | null } | null;\n}\n\nexport type WalletTxKind =\n | \"lightning-in\"\n | \"lightning-out\"\n | \"ecash-in\" // from a contact, or a pasted token\n | \"ecash-out\"\n | \"reclaimed\"\n | \"fee\"; // a Lightning payment failed, and the mint kept its fee for preparing it (`amount` 0)\n\n/** One movement of the wallet. `fee` is exact: what left the balance beyond `amount`, or what a redeem cost. */\nexport interface WalletTx {\n id: string;\n timestamp: number;\n mint: string;\n kind: WalletTxKind;\n /** Sats that moved, always positive; the kind gives the direction. */\n amount: number;\n fee: number;\n note?: string;\n}\n\n/** The kinds of wallet a profile can have: each is one card of the deck, on one network. */\nexport type WalletType = \"cashu\" | \"lightning\" | \"arkade\" | \"bark\" | \"spark\" | \"bitcoin\" | \"fedimint\" | \"usdt\";\nexport const WALLET_TYPES: readonly WalletType[] = [\"cashu\", \"lightning\", \"arkade\", \"bark\", \"spark\", \"bitcoin\", \"fedimint\", \"usdt\"];\n\n/** One network's wallets, whole: what `WalletView` shows of the network in use, for either network. */\nexport interface NetworkWalletsView {\n ark?: ArkWalletView;\n bark?: BarkWalletView;\n fedimint?: FedimintWalletView;\n spark?: SparkWalletView;\n usdt?: UsdtWalletView;\n /** The network's default Lightning card for receiving (what a caller from before cards reads as its Lightning). */\n lightning?: LightningView;\n /** Every Lightning card of the network, in the order they were added: each its own source, balance and history. */\n lightnings?: LightningCardView[];\n bitcoin?: BitcoinView;\n /** This network's Cashu mints (test mints and mints on this machine are Testnet's). */\n mints: MintView[];\n balance: number;\n /** Cashu: sats held for a payment or a swap the mints have not settled yet. Not in `balance`, and not gone. */\n setAside?: number;\n /** Cashu: swaps at these mints that are not finished yet, and the sats in them. Removing the wallet names them. */\n openSwaps?: number;\n swapsAmount?: number;\n /** Cashu: sats in swaps whose inputs read spent at a mint that cannot say what it gave for them. Not in `setAside`. */\n unconfirmed?: number;\n /** This network's history, newest first. */\n history: WalletTx[];\n feesPaid: number;\n /** What this network's wallets still wait for: removing one reads it (see `walletRemoval`). Always set by the engine. */\n awaiting?: WalletAwaitingView[];\n}\n\n/**\n * Something one wallet still waits for. `request`: a request of ours still open in a chat that only this wallet can\n * be paid through; `invoice`: an invoice of its own, not paid and not expired; `paid`: an invoice the mint says is\n * paid whose ecash is not claimed yet; `unclaimed`: ecash the mint says it issued for an invoice of ours that this\n * wallet never received; `sent`: ecash sent from it that the contact has not taken yet. Removing the wallet loses\n * what is paid to the first four; `sent` Cashu comes back once its mint is added again.\n */\nexport interface WalletAwaitingView {\n /** The wallet it goes through (Lightning through the Cashu mints is the Cashu wallet's). */\n type: WalletType;\n /** `swap`: sats in an exchange with a Cashu mint that the mint has not settled yet (`PendingSwap`). */\n kind: \"request\" | \"invoice\" | \"paid\" | \"unclaimed\" | \"sent\" | \"swap\";\n /** In the wallet's base unit: sats, or the token's smallest unit for USDT. */\n amount: number;\n /** The chat payment it belongs to, when it does. */\n paymentId?: string;\n /** Lightning: the card it goes through. */\n card?: string;\n}\n\n/**\n * A wallet the profile has: one type on one network, one of each but Lightning, which a network can have several\n * cards of. `id` is `<type>:<network>`, and `lightning:<network>:<card>` for a Lightning card. Its seed and secrets\n * stay in the engine; `config` holds only what can be shown (a server, a source).\n */\nexport interface WalletInstanceView {\n id: string;\n type: WalletType;\n network: WalletNetwork;\n config: Record<string, string>;\n /** Lightning: the card's id on its network, its name, and whether it is the network's default for receiving. */\n card?: string;\n name?: string;\n receive?: boolean;\n /**\n * At home on another device (WISP 06 § Wallets that stay home): that device's name (\"\" when this device does not know\n * it), and when its coins expire (Ark, Bark). It is never opened here, and takes no payment here.\n */\n home?: { device: string; expiresAt?: number };\n}\n\n/**\n * What New can make: one type on one network. `available` false: `reason` says why (Mainnet not validated yet,\n * one already there). `needs`: the one thing it asks for, when it cannot be made in one click.\n */\nexport interface WalletOffer {\n type: WalletType;\n network: WalletNetwork;\n available: boolean;\n reason?: string;\n /** Already made: its card is in the deck. */\n exists?: boolean;\n /** A network can have several of it (Lightning): New adds one more, whatever `exists` says. */\n several?: boolean;\n needs?: \"invite\" | \"provider\" | \"apiKey\";\n /** Lightning and on-chain: the sources that can be picked on this network. */\n providers?: ProviderDescriptorView[];\n}\n\n/** What New asks the engine to make (see `walletCreate`). */\nexport interface WalletCreate {\n type: WalletType;\n network: WalletNetwork;\n /** Lightning and on-chain: the source, and the values of its form (blank ones take the network's default). */\n providerId?: string;\n values?: Record<string, string>;\n /** Fedimint: the federation's invite code. */\n invite?: string;\n /** Spark on Mainnet: the person's Breez API key. */\n apiKey?: string;\n}\n\n/** What Remove asks the engine to take away (see `walletRemove`). */\nexport interface WalletRemove {\n type: WalletType;\n network: WalletNetwork;\n /** Lightning: the card to remove; absent, the network's default for receiving. */\n card?: string;\n /** The person confirmed that what it holds on this device becomes unreachable without its backup. */\n acceptLoss?: boolean;\n}\n\n/** \"Get test coins\" on a Testnet wallet: which one asks its faucet. */\nexport interface WalletTestCoins {\n type: WalletType;\n network: WalletNetwork;\n /** Lightning: the card asking (only the Cashu mints' card has a faucet); absent, the default one. */\n card?: string;\n}\n\n/** What the faucet gave: `amount` in the wallet's own unit (\"test sats\", \"TEST-USDT\"). */\nexport interface TestCoinsResult {\n amount: number;\n unit: string;\n /** Asked, and on its way (a transaction the chain has not confirmed): it shows up in a few seconds. */\n pending?: boolean;\n}\n\n/**\n * The first-run wallet setup of a new profile: the Mainnet kinds still to make, and why the last try at each failed.\n * Kept in the settings. Absent on a profile from before it, which is never set up by itself.\n */\nexport interface WalletSetupRecord {\n /** Kinds not made yet, tried again at each start. A kind with no Mainnet wallet here yet (on-chain) waits quietly. */\n left: WalletType[];\n /** Why the last try failed, per kind still left. */\n failed?: Partial<Record<WalletType, string>>;\n}\n\n/** What the Wallet page shows of the first-run setup: whether it runs now, and each kind it could not make. */\nexport interface WalletSetupView {\n running: boolean;\n failed: { type: WalletType; network: WalletNetwork; reason: string }[];\n}\n\nexport interface WalletView {\n /** The first-run setup, while it runs or has a kind it could not make; absent otherwise. */\n setup?: WalletSetupView;\n /** Both networks' wallets, open side by side. The flat fields below are Mainnet's, for a caller naming no network. */\n networks?: Record<WalletNetwork, NetworkWalletsView>;\n /** The wallets this profile has, in the deck's order. */\n wallets?: WalletInstanceView[];\n /** What New can make on each network. */\n offers?: WalletOffer[];\n ark?: ArkWalletView;\n /** Second's Ark (Bark): a different Ark server from `ark`, not interchangeable with it. */\n bark?: BarkWalletView;\n /** Federations joined with an invite code, and their ecash. */\n fedimint?: FedimintWalletView;\n /** Spark, wallet to wallet (the Breez SDK's Spark wallet; it can also be the Breez Lightning source). */\n spark?: SparkWalletView;\n usdt?: UsdtWalletView;\n /** The Lightning source of this mode (the Cashu mints by default) and its latest operations. */\n lightning?: LightningView;\n lightnings?: LightningCardView[];\n /** The on-chain Bitcoin source of this mode, if one is set up. */\n bitcoin?: BitcoinView;\n intents?: PaymentReview[];\n mints: MintView[];\n balance: number;\n /** Cashu: see `NetworkWalletsView`. */\n setAside?: number;\n openSwaps?: number;\n swapsAmount?: number;\n unconfirmed?: number;\n /** Newest first. */\n history: WalletTx[];\n feesPaid: number;\n /** Each Mainnet wallet's backup reminder, by wallet id: the page works out which asks now (`backupDue`). */\n backupReminders?: BackupReminders;\n}\n\nexport interface StoredMessage {\n wireId?: string;\n /**\n * `held`: in this device's storage, waiting for the contact to come back (WISP 404).\n * `queued`: unconfirmed, and sent again by itself under the same id once the chat can carry it.\n * `waiting`: not sent yet; it goes by itself when the chat can carry it (live, or the DHT text before it\n * confirmed), and can be cancelled meanwhile (\"Sends when live\", WISP 400).\n */\n delivery?: \"sending\" | \"sent\" | \"queued\" | \"waiting\" | \"held\" | \"delivered\" | \"failed\";\n deliveryError?: string;\n /** Until when a `queued` message is sent again by itself; after that it waits for Retry. */\n resendUntil?: number;\n /**\n * A file of mine that waits for the live link, said on the DHT floor meanwhile (WISP 403 § Files): `sent` its offer\n * went, `seen` the contact's app took it (its bubble is in place there), `unfit` it cannot go there (it waits as before).\n */\n fileOnFloor?: \"sent\" | \"seen\" | \"unfit\";\n linkId: string;\n id: string;\n text: string;\n sender: \"me\" | \"peer\";\n /**\n * The row's place in its history, which is sorted by it (WISP 400, requirement 10). Mine: when I sent it. Received:\n * when it was first stored on this device (`arrivalKey`), never the sender's clock, so a conversation reads in the\n * order things happened here. A row received before `sentAt` existed keeps the sender's time it was stored under.\n */\n timestamp: number;\n /** Received: when the sender says it sent it, by its own clock. Shown (`shownTime`), never sorted by. */\n sentAt?: number;\n /** `hold`: through the sender's storage while the other side was away (WISP 404). */\n via: \"pkarr\" | \"datalink\" | \"hold\";\n nick?: string;\n file?: MessageFile;\n paymentId?: string;\n /** Group messages: the member key of the sender. */\n member?: string;\n /** Group history lines that are not messages. */\n event?: GroupEvent;\n /** Group history: a payment between members, as the group knows it (WISP 902 § Payments). */\n groupPay?: GroupPayNote;\n /** Group messages: the places of the text that name members, by member key (WISP 902 § Mentions). */\n mentions?: GroupMention[];\n /** A group message someone else sent that names me (or everyone). */\n mentioned?: true;\n /**\n * A private group's message from another member: the frame it came in (sealed, signed by its author), kept on the\n * group's newest rows to hand on to members who were away (`GROUP_LIMITS.relay`, WISP 902 § Catch-up). The engine's\n * own: a row goes to a page without it (`withoutFrame`).\n */\n frame?: GroupMessageFrame;\n /** How this message travelled, as the engine saw it go or come (the message's details view). */\n details?: MessageDetails;\n /** A link preview that came with the text (WISP 401 § Link previews): made by the sender's app, never fetched here. */\n preview?: LinkPreview;\n /** The message this one answers (WISP 400 § Replies). */\n replyTo?: MessageReply;\n /**\n * Reactions to it (WISP 400 § Reactions), one per person: `me`, `peer` (a 1:1 chat's contact) or a group member's\n * key. One taken back stays with an empty emoji, so an older reaction arriving late does not bring it back.\n */\n reactions?: Record<string, MessageReaction>;\n /** A text edited after it was sent (WISP 400 § Edits): `text` is the latest version. */\n edit?: MessageEdit;\n /**\n * A forwarded text or file (WISP 400 § Forwards): how many times it has been forwarded, this hop included. Only the\n * count: never who wrote it first, nor where it came from.\n */\n forwarded?: number;\n /**\n * A bot's status card (WISP 405 · Status Cards): a task or a routine shown instead of `text`, which is its fallback.\n * It belongs to the version it came with: an edit brings its own, or leaves the message a text.\n */\n card?: StatusCard;\n /**\n * A reply that presses a button of this side's own message (WISP 406 · Message Buttons), as the author's engine took\n * it: set only when the button was open for this person. What `button.pressed` reports.\n */\n press?: MessagePress;\n /**\n * A card of mine (WISP 405 · Status Cards, WISP 406 · Message Buttons) whose text went on the DHT floor or into a hold,\n * which carry text alone: `due` until the card goes again live, as an edit of the card alone; `sent` once it did, or\n * once it was given up (too old).\n */\n cardRestore?: \"due\" | \"sent\";\n /** What `cardRestore` was named before it covered every kind of card (buttons only): read for rows kept since. */\n buttonsRestore?: \"due\" | \"sent\";\n}\n\n/**\n * A message that carries a task, a routine or a usage card, as the Tasks board and the chat list read it across every\n * chat and group (`statusCardIndex`): where it is (`linkId`: a chat's id, or `group:<id>`), its message, who sent it (`member`: a group\n * member's key, absent for my own), when it was sent and when its last edit was made. Nothing else of the message.\n */\nexport interface CardIndexRow {\n linkId: string;\n id: string;\n card: TaskCard | RoutineCard | UsageCard;\n sender: \"me\" | \"peer\";\n member?: string;\n timestamp: number;\n editedAt?: number;\n}\n\n/** A page of a chat's history, oldest first, and whether older messages remain (`messagePage`). */\nexport interface MessagePage { messages: StoredMessage[]; more: boolean }\n\n/**\n * A chat's pin as kept (WISP 400 § Pinned message): the message's id both sides know (`id`, \"\" unpinned), the pinner's\n * number (the highest wins), who pinned (`me`, `peer`, or a member's key) and when it came here. `messageId`: the row\n * here, when it was here then. A private group's keeps the pinner's signature (`k`, `sig`), to pass it on.\n */\nexport interface StoredPin extends WirePin { by: string; at: number; messageId?: string; k?: string; sig?: string }\n\n/** A chat's pin, as the pages and the CLI see it. `id`: the id both sides know the message by (\"\" once unpinned); `messageId`: the row here. */\nexport interface PinView { id: string; by: string; at: number; messageId?: string }\n\n/** One person's reaction to a message: the emoji (\"\" once taken back), their number (the highest wins), when it came. */\nexport interface MessageReaction { e: string; n: number; at: number }\n\n/** The latest reaction in a chat, as the chat list says it: \"Ana reacted ❤️ to \"…\"\". */\nexport interface ReactionNote {\n at: number;\n /** Who reacted: `me`, `peer`, or a group member's key. */\n by: string;\n emoji: string;\n /** A line of the message it went to. */\n snippet: string;\n /** The message was mine. */\n mine: boolean;\n /** The message's id here: an edit of it changes `snippet`. */\n message?: string;\n}\n\n/** How a message was edited (WISP 400 § Edits). */\nexport interface MessageEdit {\n /** The edit shown: its number (1 for the first; the highest one wins) and when its author made it. */\n seq: number;\n at: number;\n /** Earlier versions, oldest first (the newest `EDIT_HISTORY_KEEP`): when each was written, and its text. */\n history: MessageVersion[];\n /** Mine: the contact has not confirmed this edit yet. It goes by itself once the chat is live and both sides offer edit/1. */\n pending?: true;\n /**\n * Mine: the engine's own edit that sends a question's buttons again, the text unchanged (WISP 406 · Message Buttons),\n * never one a person or a bot made. The bot's event stream reports none for it; a later edit makes a new one without it.\n */\n restore?: true;\n}\n\nexport interface MessageVersion { at: number; text: string }\n\n/**\n * The message a reply answers, as kept with the reply (WISP 400 § Replies). What the replier's app saw of it goes\n * with the reply; the receiver keeps its own view of the original when it has it (`messageId`), and the line and the\n * author then come from there, not from the wire.\n */\n/** A button press on this side's message (WISP 406 · Message Buttons). */\nexport interface MessagePress {\n /** The message with the buttons, its id here. */\n messageId: string;\n button: string;\n label: string;\n /** Named by its label or id in the text, not by the reply (the DHT floor, or an app without buttons). */\n inferred?: true;\n}\n\nexport interface MessageReply {\n /** The original's id in this chat as both sides know it: a paired chat's wire, file or payment id; a group message id. */\n id: string;\n /** A line of the original, plain and short (`replySnippet`); empty when only its id came (over the DHT). */\n snippet: string;\n /** Who wrote the original; absent when nothing says (only its id came, and it is not here). */\n from?: \"me\" | \"peer\";\n /** A group's original: its author's member key. */\n member?: string;\n /** A button press (WISP 406 · Message Buttons): the original's button this reply presses, its text the label. */\n button?: string;\n /**\n * The original here, when it was found in this chat as the reply was kept (always, for a reply sent here). Without\n * it the line is only what the replier's app said: shown, but marked as not checked.\n */\n messageId?: string;\n}\n\n/**\n * The way a message went, as the engine saw it at that moment: a live transport, the DHT floor (a paired chat's\n * `dht-text/1` envelope), the sender's storage (`hold/1`), or a chat from before pairing (`legacy-*`).\n */\nexport type MessagePath = PairedTransport | \"dht\" | \"hold\" | \"legacy-datalink\" | \"legacy-dht\";\n\n/** One send of a message: when, over what, and how it ended. */\nexport interface MessageSend {\n at: number;\n path: MessagePath;\n /** The live session crossed a relay (WISP 100, \"Relayed\"); the relays' hosts. */\n relayed?: boolean;\n relays?: string[];\n /** The round trip measured on the session at the time. */\n rttMs?: number;\n /** Absent while the send is under way. */\n result?: \"sent\" | \"failed\";\n error?: string;\n}\n\n/**\n * What the engine remembers about one message's travels, for its details view (WISP 400 § Message details): facts\n * as they were when the message went or came, that nothing else keeps. Kept on the message row, bounded by\n * `MESSAGE_DETAILS_MAX_BYTES`. Never a key, a seed or anything else that opens the chat.\n */\nexport interface MessageDetails {\n /** Sends of this message, oldest first: the first and the last ones (`MESSAGE_DETAILS_MAX_SENDS`). */\n sends?: MessageSend[];\n /** Sends there were in all, when more than `sends` keeps. */\n attempts?: number;\n /** A received message: when it came and over what. */\n received?: Omit<MessageSend, \"result\" | \"error\">;\n /** The last successful send was handed to the transport. */\n sentAt?: number;\n /** The contact's receipt came (the message is `delivered`). */\n receiptAt?: number;\n /** Stored for an away contact, sealed for them. */\n heldAt?: number;\n /** A file: its last byte arrived here, or the contact confirmed it. */\n completedAt?: number;\n /** The frame the message travelled in. `wireBytes`: as sent on the channel (a JSON frame, a sealed record, a bundle). */\n wire?: { frame: string; protocol: string; plaintextBytes?: number; wireBytes?: number; chunks?: number; chunkBytes?: number };\n /** The DHT floor: the envelope's sequence and times, the sealed packet's size and nonce, the record read. */\n dht?: { seq?: number; issued?: number; expires?: number; packetBytes?: number; nonce?: string; recordKey?: string; records?: string[] };\n /** Store-and-forward: the item's sequence in the mailbox, its sealed size and expiry. */\n hold?: { seq?: number; bytes?: number; expires?: number; mailbox?: string };\n}\n\n/** A details record larger than this (as JSON) is trimmed: its oldest sends go first. */\nexport const MESSAGE_DETAILS_MAX_BYTES = 2048;\n/** Sends kept on a record: the first, and the last ones. */\nexport const MESSAGE_DETAILS_MAX_SENDS = 6;\n\n/** What the details view is made of: the row, its record, and what the engine knows around it right now. */\nexport interface MessageDetailsView {\n message: Pick<StoredMessage, \"id\" | \"wireId\" | \"linkId\" | \"sender\" | \"timestamp\" | \"sentAt\" | \"via\" | \"delivery\" | \"deliveryError\" | \"resendUntil\" | \"member\" | \"nick\"> & {\n kind: \"text\" | \"file\" | \"voice\" | \"payment\" | \"event\" | \"note\";\n /** UTF-8 bytes of the text. */\n textBytes: number;\n };\n details?: MessageDetails;\n /** The chat the message is in. Keys are the public participation keys (z-base-32), never a seed. */\n link?: {\n id: string;\n profile?: \"paired-chat/1\";\n deliveryMode?: DeliveryMode;\n /** Our own participation key on this link. */\n myKey: string;\n /** The contact's, as pinned; absent before the first contact. */\n peerKey?: string;\n /** The pinned key was compared by the two people (WISP 401 § verification). */\n verified: boolean;\n /** The session carrying the chat right now, if any. */\n transportNow?: PairedTransport;\n relayedNow?: boolean;\n rttNowMs?: number;\n };\n /** The file the message carries (files/2, files/3 or a held item), as stored. */\n file?: {\n id: string; name: string; size: number; mime: string;\n /** SHA-256 of the bytes, hex, once known (the sender's own, or checked on arrival). */\n digest?: string;\n protocol?: \"files/2\" | \"files/3\" | \"hold/1\";\n state?: string;\n stage?: string;\n transferred?: number;\n /** Why a transfer that ended without the file did. */\n error?: string;\n /** files/3: bytes confirmed durably, where a restart resumes. */\n confirmed?: number;\n /** files/3: when the offer was made. */\n since?: number;\n consented?: boolean;\n /** Where the bytes are kept on this device. */\n storage?: string;\n voice?: { duration: number; peaks: number };\n };\n /** The payment or request the message stands for: everything but the money itself. */\n payment?: {\n id: string; kind: \"payment\" | \"request\"; direction: \"in\" | \"out\"; amount: number; unit: string; state: PaymentState;\n method?: string; network?: string; provider?: string; asset?: string; mint?: string; txid?: string; ask?: string;\n createdAt: number; error?: string; memo?: string; group?: string;\n /** SHA-256 of the invoice, hex, when there is one (the invoice itself carries the amount and a payment hash). */\n invoiceDigest?: string;\n lightningPending?: boolean; paidHere?: boolean;\n };\n /** A group message: which group, and the member key of the sender when it is not this device. */\n group?: { id: string; member?: string; profile?: string };\n}\n\n/** The ways of paying a group note can name. */\nexport type GroupPayRail = \"cashu\" | \"lightning\" | \"arkade\" | \"bark\" | \"bitcoin\" | \"spark\" | \"usdt\" | \"fedimint\";\n\n/**\n * A payment between two members as the whole group sees it (WISP 902 § Payments): who pays whom, how much, over\n * what, and how it stands. No money and nothing to pay with: that travels only on the edge between the two.\n */\nexport interface GroupPayNote {\n id: string;\n /** `request`: a member asks to be paid; `payment`: a member sends without a request. */\n kind: \"request\" | \"payment\";\n /** Who pays: a member key, or `*` for a request any member may pay once. */\n from: string;\n /** Who is paid. */\n to: string;\n /** In the unit's smallest denomination (sats, token base units). */\n amount: string;\n unit: string;\n decimals: number;\n rail: GroupPayRail;\n /** Test money (test mints, test networks): worth nothing. */\n test?: boolean;\n memo?: string;\n /** The request answers the payer's own ask (Ark, Bark, on-chain, USDT): the payer started it. */\n ask?: boolean;\n createdAt: number;\n /** `sent`: the payer says it paid, the payee has not confirmed. `closed`: failed, refused or taken back. */\n state: \"open\" | \"sent\" | \"paid\" | \"closed\";\n /** Who paid it: the payee's word, or, when its wallet cannot tell (an invoice), the only member who said so. */\n by?: string;\n /** Members who said they paid it. */\n claims?: string[];\n /** The last note this device said about it, sent again when an edge opens. */\n mine?: GroupPayFrame;\n}\n\n/** `group-pay` on an edge (WISP 902 § Payments). Old apps drop it. */\nexport interface GroupPayFrame {\n t: \"group-pay\";\n g: string;\n id: string;\n k: \"req\" | \"pay\";\n f: string;\n to: string;\n v: string;\n u: string;\n d: number;\n r: GroupPayRail;\n x?: 1;\n m?: string;\n a?: 1;\n ts: number;\n st: GroupPayNote[\"state\"];\n by?: string;\n}\n\n/** A local web application the user chose to share. */\nexport interface StoredService {\n id: string;\n name: string;\n /** Normalized loopback URL, e.g. `http://localhost:3400` */\n target: string;\n enabled: boolean;\n createdAt: number;\n /**\n * The contacts allowed to reach this service, by participation key. Absent or\n * empty means nobody: a service is granted per contact, never to everyone at\n * once, and a service from before this existed starts closed rather than\n * keeping an exposure its owner never chose.\n */\n sharedWith?: string[];\n}\n\nexport interface IceServerSetting {\n urls: string;\n username?: string;\n credential?: string;\n}\n\nexport interface Settings {\n online: boolean;\n nick: string;\n /** This profile's picture, shown to paired contacts: a small square JPEG data URL. */\n avatar?: string;\n /**\n * Whether contacts are told this profile's name and picture (WISP 401 § name and picture). Absent means\n * yes; off, every chat and group member is told there is none, and the join notice names nobody.\n */\n shareProfile?: boolean;\n /** The Pkarr relays. Browsers read and write through them; the Desktop writes to them (so browser contacts see its packets). */\n relays: string[];\n /**\n * Where the DHT is reached directly (the Desktop): whether reads may use the relays too, \"Also use Pkarr relays\"\n * in Settings, Network. Absent means no: reads go to the DHT alone.\n */\n readRelays?: boolean;\n /** Extra ICE servers (typically TURN) on top of the built-in STUN set. */\n iceServers: IceServerSetting[];\n /**\n * The Iroh relays a browser build homes on (WISP 102, relay only): a page cannot send UDP, so every Iroh\n * packet goes through one. Absent or empty means the defaults (n0's public relays, as the desktop app's).\n */\n irohRelays?: string[];\n /**\n * The HyperDHT relay (wss://) this browser reaches the HyperDHT through, so paired chats can use HyperDHT\n * (WISP 103) beside WebRTC. Empty: none. Absent means the default (shared/hyperdhtRelay.ts). The Desktop\n * runs HyperDHT itself and ignores it.\n */\n hyperdhtRelay?: string;\n /** Cashu mints this peer holds ecash at and accepts ecash from. The first is where invoices are created. */\n mints: string[];\n /** False until the default mints were put in place once; after that the list is the user's. */\n mintsInitialized: boolean;\n /** The Nostr social layer: relays, automatic profile loading, publication. Absent means the defaults, everything off. */\n nostr?: NostrSocialSettings;\n /**\n * Identity cards show the public profile (picture, name, bio, counts) of a verified identity, read from its\n * network's public host when the card is on screen (PUBLIC-PROFILES.md). Absent means on; off drops what was kept.\n */\n publicProfiles?: boolean;\n /**\n * Whether paired contacts are told when this profile is typing (WISP 401 § Typing). Absent means yes; off,\n * nothing is said, and a contact's typing is still shown.\n */\n sendTyping?: boolean;\n /**\n * Whether files are downloaded without asking where the rules allow it (WISP 503: a group's voice messages, and its\n * files up to 8 MiB within 256 MiB a group). Absent means on; off, each waits for a Download.\n */\n autoDownloads?: boolean;\n /**\n * Whether this device sends the group files it keeps to members who ask (WISP 503 § Serving limits). Absent means\n * on; off, it answers that it is busy and does not say it holds them.\n */\n serveFiles?: boolean;\n /**\n * Where items are held for away contacts (WISP 404): the profile's S3 storage and its random space\n * (WISP 1000/1002), as set up under Profile → Backups. Kept here for the peer, which may run outside the\n * page; never copied into a backup.\n */\n holdStorage?: { s3: S3Config; space: string } | null;\n /** The first-run wallet setup (see `WalletSetupRecord`). Only the engine writes it: a settings patch cannot. */\n walletSetup?: WalletSetupRecord;\n /**\n * This profile's push subscription (the installed web app, WISP 401 § Wake-up push): shared with each paired\n * contact whose app offers `wake/1`, so it can wake this app while it is closed. Absent: not woken.\n */\n wake?: WakeSubscription;\n /**\n * A contact who held `wake` can no longer be told to forget it (its chat was deleted, or muted: a contact may ignore\n * `w: null`). The app replaces the subscription (a new endpoint and VAPID key pair, new tokens for the others) and\n * `setWakeSubscription` clears this. Set by the engine only.\n */\n wakeRotate?: boolean;\n /**\n * Private groups muted here (WISP 902 · Group Mesh § Wake-up push): their members are told to forget this profile's\n * subscription, as a muted chat's contact is. Only through `setWakeMuted` with the group's `group:<id>`.\n */\n wakeMutedGroups?: string[];\n /**\n * Private groups → members that were given a token on an edge a group on hubs later dropped: they still hold the\n * subscription with no edge to close, so their leaving the group (or this profile leaving it, or muting it) replaces\n * it. Cleared with every new subscription. Set by the engine only.\n */\n wakeHeldBy?: Record<string, string[]>;\n /**\n * In a profile on several devices (WISP 06 § Push and the phone): devices asked for a new subscription over their\n * device link, by signing key, with the endpoint they are to replace. A device that shares another endpoint (or none)\n * has done it. Set by the engine only.\n */\n wakeRenew?: Record<string, string>;\n /**\n * A push relay (https) this app hands a finished wake-up to when it may not post to the contact's push service\n * itself (a browser page: the services answer without CORS). Empty or absent: none; nobody runs one by default.\n */\n pushRelay?: string;\n /** The backup reminder of each Mainnet wallet (see `shared/backupReminder`). Only the engine writes it: a settings patch cannot. */\n backupReminders?: BackupReminders;\n}\n\n/** A browser push subscription and the VAPID key pair it was made with. */\nexport interface WakeSubscription {\n endpoint: string;\n p256dh: string;\n auth: string;\n vapid: VapidKeys;\n /**\n * In a profile on several devices (WISP 06 § Push and the phone): the signing key of the device whose subscription\n * this is. It moves with the profile, so a device with none of its own (a desktop) goes on giving contacts the\n * phone's. Set by the engine only; absent in a profile on one device, and for a subscription made before.\n */\n device?: string;\n}\n\n/**\n * What `updateSettings` takes: any settings, and of the Nostr ones only those that change. The rest of the\n * Nostr settings stay as stored, so a switch flipped from a page that has not seen the last save yet does not\n * put back what that save replaced.\n */\nexport type SettingsPatch = Partial<Omit<Settings, \"nostr\">> & { nostr?: Partial<NostrSocialSettings> };\n\n/** What pages see of store-and-forward in one chat. */\nexport interface LinkHoldView {\n /** The switch of this chat on this device. */\n enabled: boolean;\n /** The contact's app accepts held items, as it last said; undefined until it said anything. */\n peerAllows?: boolean;\n /** This device has storage to hold items in (Profile → Backups → S3 storage). */\n storage: boolean;\n /** Items can be held for the contact right now: switch on, contact allows it, storage set up, contact pinned. */\n canHold: boolean;\n /** Items held for the contact and not yet picked up. */\n outstanding: number;\n bytes: number;\n maxBytes: number;\n maxItems: number;\n ttlMs: number;\n /** Items from the contact refused on the way in. */\n refused: number;\n /** The last problem holding or picking up, for people. */\n error?: string;\n}\n\n/** A proof of this profile (Identities). */\nexport interface IdentityProofView {\n id: string;\n provider: string;\n subject: string;\n /** The proof key the statement authorizes. */\n key: string;\n verified: VerifiedIdentity;\n issuedAt: number;\n /** Seconds: the earlier of the statement's and the evidence's expiry. */\n expiresAt: number;\n createdAt: number;\n /** Chats it is currently shared in (or queued for). */\n sharedWith: number;\n /** The identity as a URI the profile's public DID can list (`alsoKnownAs`); absent when it has none. */\n publicUri?: string;\n /** What the provider checked (public: contacts receive it too). Removal uses it to take down a published proof. */\n evidence?: unknown;\n /** The identity's public profile on its network, once asked for (PUBLIC-PROFILES.md); absent while not asked or turned off. */\n publicProfile?: PublicProfileView;\n}\n\n/**\n * An identity's public profile, as its network publishes it (docs/wisps/PUBLIC-PROFILES.md): self-described by the\n * account, never part of the proof. Only for an identity with a current verified proof, with Settings → Load public\n * profiles on; kept on this device and asked again after a day.\n */\nexport interface PublicProfileView {\n /** False when the network has no profile for this identity (or none was read yet). */\n found: boolean;\n name?: string;\n /** How the network writes the account: \"@alice.bsky.social\", a Nostr `name`. */\n handle?: string;\n /** A short bio, plain text. */\n about?: string;\n /** The website the account names: an https address, shown as a plain link. */\n website?: string;\n /** A sanitized `data:image/jpeg;base64,…` URL, never a remote one. */\n avatar?: string;\n /** The profile names a picture that is not shown: which rule refused it, in words (\"it is a GIF; …\"). */\n avatarMiss?: string;\n followers?: number;\n following?: number;\n /** The hosts it was read from, which saw this device's IP address: \"nexus.pubky.app\". */\n hosts: string[];\n /** Seconds; 0 when it was never read. */\n fetchedAt: number;\n loading?: boolean;\n /** The last attempt failed (a copy read before, if any, is still shown). */\n error?: string;\n}\n\n/**\n * A verified identity's recent posts (docs/wisps/PUBLIC-PROFILES.md, \"Posts and follows\"): `loadPublicPosts`, asked\n * when its card is chosen in a contact's identities panel. Plain text, bounded, never markup.\n */\nexport interface PublicPostView {\n id: string;\n /** Seconds. */\n createdAt: number;\n text: string;\n /** Opens the post in its network's app or site. */\n url?: string;\n reply?: boolean;\n /** Its pictures, loaded only on a tap (`loadPublicPostImage` with the index here). `host`: who is asked. */\n images: { host?: string; alt?: string }[];\n}\n\nexport interface PublicPostsView {\n posts: PublicPostView[];\n /** More can be asked for (`more: true`). */\n more: boolean;\n /** The hosts asked, which saw this device's IP address. */\n hosts: string[];\n /** Seconds. */\n fetchedAt: number;\n /** Notes the person's own Nostr mute list hides. */\n hidden?: number;\n /** The identity's profile in its network's app or site. */\n profileUrl?: string;\n}\n\n/**\n * Who a verified identity follows and who follows it, only as far as it touches people the reader knows: the reader's\n * own identities and the identities contacts shared. Computed on this device from lists read from the same hosts as\n * the profile; the lists themselves are never shown or stored.\n */\nexport interface PublicGraphView {\n /** False when there was nobody to compare with (no identity of the reader's, no contact, on this network): nothing was read. */\n compared: boolean;\n /** Its follow list names one of the reader's identities. */\n followsYou: boolean;\n /** One of the reader's identities follows it (their followers list, or the reader's own follow list). */\n youFollow: boolean;\n /** Contacts whose shared identity it follows (`follows`) or who follow it (`followedBy`). */\n contacts: { linkId: string; follows: boolean; followedBy: boolean }[];\n /** A list was longer than this app reads: someone may be missing, never added. */\n partial?: boolean;\n hosts: string[];\n /** Seconds; 0 when nothing was read. */\n fetchedAt: number;\n}\n\n/** A post's picture, on the reader's tap: a re-encoded `data:image/jpeg` URL, or why there is none. */\nexport interface PublicPostImageView { src?: string; miss?: string; hosts: string[]; width?: number; height?: number }\n\n/** The profile's did:dht (WISP 310-did-dht): its own key, public to everyone, never tied to a chat. */\nexport interface ProfileDidView {\n /** `did:dht:…` */\n id: string;\n /** Identity proof ids the person listed as public, in order (some may not be publishable right now). */\n listed: string[];\n /** What the document says the subject is also known as: the listed identities that are verified and current. */\n alsoKnownAs: string[];\n /** The last packet put on Pkarr: when, and its version (the BEP44 sequence number). */\n published?: { at: number; versionId: string };\n /** The document out there is the current one. */\n upToDate: boolean;\n /** Why the last publish failed. */\n error?: string;\n}\n\n/** A contact's proof, as this app checked it. */\nexport interface ReceivedIdentityView {\n id: string;\n provider: string;\n subject: string;\n verified: VerifiedIdentity;\n /** A public name/picture looked up on request. */\n display?: IdentityDisplay;\n status: IdentityStatus;\n verifiedAt: number;\n checkedAt: number;\n expiresAt: number;\n error?: string;\n /** The provider's checks can go stale and the last one is old enough to repeat. */\n recheckDue: boolean;\n /** Its public profile, while the proof is verified and current (PUBLIC-PROFILES.md). */\n publicProfile?: PublicProfileView;\n}\n\nexport interface LinkIdentitiesView {\n /** Both sides offer identity proofs and the channel is open now. */\n support: boolean;\n /** Providers the contact's app said it can verify. */\n contactProviders?: string[];\n shared: SharedIdentity[];\n received: ReceivedIdentityView[];\n error?: string;\n}\n\nexport interface LinkView {\n /** Identity proofs in this chat; absent for chats that are not paired. */\n identities?: LinkIdentitiesView;\n /** The contact's Nostr data, one entry per key they proved in this chat. */\n nostr?: NostrContactView[];\n discoveryError?: string;\n publicProfiles?: PublicProfile[];\n profileChoice?: ProfileChoice;\n peerProofs?: Pick<ProofLedger, \"local\" | \"remote\">;\n peerProofSupport?: boolean;\n peerProofAdapters?: ProofAdapter[];\n participationKey?: string;\n peerParticipationKey?: string;\n proofError?: string;\n profile?: \"paired-chat/1\";\n pairing?: PairingState;\n /**\n * How far this chat's first pairing got (`@ghostly/core` `PairingProgress`): from the invite being\n * published to `live`. Absent for chats paired before this app start, DHT-only invites and legacy chats.\n */\n pairingProgress?: PairingProgress;\n peerVerified?: boolean;\n /**\n * `methods`: ways of paying both sides allow in this chat right now. `calls` / `services`: both sides offer\n * `calls/1` / `services/1` on the open session (paired chats only; they need a live connection).\n */\n capabilities?: {\n files: boolean; payments: boolean; methods?: Record<PaymentMethodName, boolean>; calls?: boolean; services?: boolean; largeFiles?: boolean;\n /**\n * The networks the contact has a wallet on, per way of paying, as it said on the open session. Absent: it said\n * none (an older app): any network may meet. A way of paying it has no wallet of is not in the map (one it has\n * but turned off here lists no network). A card is offered only where its network is in its list.\n */\n networks?: Partial<Record<PaymentMethodName, readonly WalletNetwork[]>>;\n };\n /** files/3 live in this chat: bytes the contact's device said it can still take for files, when it said. */\n peerFileRoom?: number | null;\n /** Ways of paying this device allows in this chat. */\n paymentMethods?: Record<PaymentMethodName, boolean>;\n /** For each way of paying, the networks this chat accepts it on. */\n paymentNetworks?: Partial<Record<PaymentMethodName, readonly WalletNetwork[]>>;\n /** Both sides announced private groups on the open session: this contact can be invited. */\n groups?: boolean;\n /** Paired chats: what each side offers after the handshake (`paired-capabilities`); `peer` is null until it says. */\n sessionOffers?: { mine: string[]; peer: string[] | null };\n /** Paired chats: why a call cannot be placed right now, or null when it can. */\n callsUnavailable?: string | null;\n /** Paired 1:1 chats: the contact is typing now (`typing/1` on the live session). Absent otherwise. Never stored. */\n peerTyping?: boolean;\n /** Paired 1:1 chats: this side's wake-up token for this chat (the push worker's table), when it shared its subscription. */\n wakeToken?: string;\n /** Paired 1:1 chats: the contact was told not to wake this side while the chat is muted (`setWakeMuted`). */\n wakeMuted?: boolean;\n /** Paired 1:1 chats: the contact shared how to wake its closed app, so a message sent while it is away wakes it. */\n peerWakes?: boolean;\n /** While `peerTyping`: what the contact is doing when it is not plain typing (recording a voice note, thinking). */\n peerTypingKind?: Exclude<TypingKind, \"typing\">;\n /** While `peerTyping`: the contact's (a bot's) status line, sanitized: one line, at most 40 characters, no links. */\n peerTypingStatus?: string;\n availableTransports?: PairedTransport[];\n /**\n * Every transport this app runs for the chat, started or not: DHT only releases the native ones, and an app with no\n * WebRTC (the Linux Desktop) then has none started, yet can still leave DHT only for any of these.\n */\n runnableTransports?: PairedTransport[];\n /** Transports a session with this contact would cross a relay on (a browser's HyperDHT or Iroh): a fallback, and shown as relayed. */\n relayedTransports?: PairedTransport[];\n deliveryMode?: DeliveryMode;\n dhtDelivery?: DhtDeliveryView;\n canSendText?: boolean;\n /** `hold`: the contact is away and what is sent now waits in this device's storage for it. */\n textDelivery?: \"stream\" | \"dht\" | \"hold\" | \"unavailable\";\n hold?: LinkHoldView;\n transportErrors?: Partial<Record<PairedTransport, string>>;\n preferredTransport?: PairedTransport;\n transportFallback?: boolean;\n /** No transport chosen for this chat: the app's rule applies. */\n transportAutomatic?: boolean;\n /** What the contact's app can use on this link, as it last said (a session, or its capability record); unknown before either. */\n peerTransports?: PairedTransport[];\n /**\n * The transport this chat is set to reach and is not on yet, and why (WISP 100, \"A chosen transport not reached\n * yet\"): waited for, never a failure. Absent when the chat is on it, or nothing limits where it goes.\n */\n transportWait?: TransportWait;\n /**\n * The last attempt to go live that did not, as this side saw it (WISP 100, \"Why a chat is not live\"): what it tried\n * and why each did not connect, whether this side dialled or answered, and when it dials again. Absent while live.\n */\n liveAttempt?: LiveAttempt;\n /** Which side dials this chat to go live (the other answers): paired chats. */\n liveDialer?: \"you\" | \"contact\";\n /** Round trip on the live session, once measured. */\n transportRttMs?: number;\n /**\n * The live session goes through relays, never directly (WISP 100, \"Relayed\"): a browser's Iroh. The relays'\n * hosts, this app's first. They see who talks to whom and when, not what is said.\n */\n transportRelayed?: { relays: string[] };\n /** While live: since when the current transport carries the chat, and why it was chosen (see engine/transportLog.ts). */\n transportLive?: { since: number; cause?: TransportCause };\n /** The chat's connection story, newest last; only for the chat on screen (`setActiveLink`). */\n transportLog?: TransportEntry[];\n /** Every connection event of the chat on screen, for its connection panel, newest last. */\n transportHistory?: TransportEvent[];\n /** Identities shared in this chat, both ways, as its timeline shows them, newest last; only for the chat on screen. Local only. */\n identityTimeline?: IdentityTimelineEntry[];\n /** When the contact last shared an identity here (milliseconds): the chat list's preview and order. Paired chats. */\n identitySharedAt?: number;\n /** The latest reaction in this chat, for the chat list; paired chats. */\n lastReaction?: ReactionNote;\n /** The chat's pin (WISP 400 § Pinned message): `id` \"\" once unpinned; absent when nothing was ever pinned. */\n pin?: PinView;\n id: string;\n myPubKeyZ32: string;\n peerPubKeyZ32: string;\n label?: string;\n /**\n * The name the contact asked to be shown by, as they last said it. `\"\"` means a paired contact said they\n * have none (removed, or not shared); absent means nothing was heard yet.\n */\n peerNick?: string;\n /** The contact's profile picture, as they last sent it (checked, small JPEG data URL). */\n peerAvatar?: string;\n inviteCode?: string;\n createdAt: number;\n status: LinkStatus;\n dataLink: DataLinkState;\n peerOnline: boolean;\n peerLastSeenAt: number;\n /** `null` when the peer does not advertise (offline, or a legacy client). */\n peerServices: ServiceAd[] | null;\n lastMessageAt: number;\n /** Newest of my messages the peer acknowledged through Pkarr. */\n peerAck: number;\n lastSyncAt: number;\n poll: { polling: boolean; nextAt: number; interval: number };\n}\n\nexport interface ServiceView extends StoredService {\n /** Requests served since the engine started. */\n requests: number;\n}\n\nexport interface EngineState {\n settings: Settings;\n /**\n * Limited mode (WISP 06 § When a device checks): the device could not read which device is active and was started\n * anyway. The profile is offline whatever `settings.online` says, and no wallet is open. Absent otherwise.\n */\n limited?: true;\n /**\n * Why limited mode is on, when it is a restored copy (WISP 06 § A backup restored where a device set exists): `checking`,\n * the turn is read every 30 seconds to learn whether another device runs the profile; `removed`, the read found the\n * device set moved after a removal, and only the person can start the copy as a profile of its own.\n */\n restoreCheck?: \"checking\" | \"removed\";\n /**\n * Whose push subscription `settings.wake` is, in a profile on several devices (WISP 06 § Push and the phone): `here`,\n * this device's own; `away`, another device's, given to contacts because this device has none of its own (the page\n * neither replaces nor turns off what is not its own). Absent in a profile on one device, or when nobody said.\n */\n wakeOwner?: \"here\" | \"away\";\n transport: {\n protocol: string; relays: string[];\n /** Present where Iroh runs in the browser (web app, extension): the relays it uses and the defaults. */\n iroh?: { relays: string[]; defaults: string[] };\n /** This client reaches the Mainline DHT itself (the Desktop): relays are for writes, and reads only when chosen. */\n direct?: boolean;\n /** How reads go and how each relay is doing, for the connection panel's Details. */\n discovery?: DiscoveryStatus;\n /** False where this client has no WebRTC (Ghostly Desktop on Linux: WebKitGTK has none). Absent where it has. */\n webrtc?: false;\n /**\n * This device's own WebRTC attempts say direct connections do not get through its network (a VPN, a firewall, a\n * carrier's NAT: `DirectPathWatch` in packages/core). Chats still go live through relays. Absent otherwise.\n */\n directBlocked?: true;\n /**\n * This device's clock seems to be off: this clock minus what the relays and several contacts say, in ms (positive\n * when it is ahead). From several sources agreeing, never one contact (`ClockWatch` in packages/core). Absent otherwise.\n */\n clockOffMs?: number;\n /**\n * False where a group's links have no transport at all here: no WebRTC and no native transport (WISP 902 §\n * Transports), so no member of a group can be reached from it. Absent where they have one (the Linux Desktop runs\n * Iroh and HyperDHT).\n */\n groupLinks?: false;\n };\n links: LinkView[];\n services: ServiceView[];\n /** Transfers since the peer started, by file id. */\n transfers: Record<string, FileTransferView>;\n /**\n * The kept transfers are back in `transfers`. False for the first moments after a start: a file with no transfer\n * yet may still be moving, not finished.\n */\n transfersRestored?: boolean;\n /**\n * The kept groups are in `groups`. Unset while the peer starts: until then a group missing from the list may only not\n * be loaded yet, not gone (the app forgets a gone group's draft and read mark only between two states that have it).\n */\n groupsLoaded?: true;\n wallet: WalletView;\n payments: Record<string, PaymentView>;\n /** This profile's identity proofs. */\n identityProofs: IdentityProofView[];\n /** This profile's public DID. Absent until the engine has loaded it. */\n did?: ProfileDidView;\n /** The Nostr social layer: the person's own keys' data and the effective settings. */\n nostr: NostrSocialState;\n /** Private groups and pending invitations (WISP 900). */\n groups: GroupView[];\n /** The links of groups' edges: not chats, but payments with a member travel on them. */\n edges?: LinkView[];\n}\n","import { WALLET_NETWORKS, decodeBolt11, engineText, walletNetworkOf, type PaymentMethodName, type PaymentNetworks, type WalletNetwork } from \"@ghostly/core\";\nimport { mintNetwork } from \"../../shared/mints\";\nimport { WALLET_TYPES, type NetworkWalletsView, type StoredPayment, type WalletInstanceView, type WalletType } from \"../../shared/types\";\nimport { CASHU_MINT_SOURCE } from \"./providers/cashuMint\";\nimport { CASHU_CARD, LEGACY_CARD } from \"./providers/lightningCards\";\nimport { networkLabel } from \"./modeGate\";\nimport { networkReason } from \"./networkReason\";\n\nexport const WALLET_NAMES: Record<WalletType, string> = {\n cashu: \"Cashu\", lightning: \"Lightning\", arkade: \"Ark\", bark: \"Bark\", spark: \"Spark\", bitcoin: \"Bitcoin\", fedimint: \"Fedimint\", usdt: \"USDT\",\n};\n\n/** How long a one-click creation waits on its server before it gives up, saving nothing (tests shorten it). */\nexport const createTiming = { timeoutMs: 60_000 };\n\n/**\n * The wallets a profile has, read from both networks' views: one per type and network, in the deck's order, and one\n * per Lightning card. A wallet exists once it holds something to open (a seed, a source, a mint); Lightning through\n * the Cashu mints comes with that network's Cashu wallet.\n */\nexport function walletInstances(networks: Record<WalletNetwork, NetworkWalletsView>): WalletInstanceView[] {\n const list: WalletInstanceView[] = [];\n for (const type of WALLET_TYPES) for (const network of WALLET_NETWORKS) {\n if (type === \"lightning\") { list.push(...lightningInstances(network, networks[network])); continue; }\n const config = instanceConfig(type, networks[network]);\n if (config) list.push({ id: `${type}:${network}`, type, network, config });\n }\n return list;\n}\n\n/** A network's Lightning cards (a view from before cards: its one source, as one card). */\nfunction lightningInstances(network: WalletNetwork, view: NetworkWalletsView): WalletInstanceView[] {\n const cards = view.lightnings ?? (view.lightning ? [{ ...view.lightning, card: view.lightning.providerId === CASHU_MINT_SOURCE || !view.lightning.providerId ? CASHU_CARD : LEGACY_CARD, name: \"\", receive: true }] : []);\n return cards.flatMap((ln) => {\n if (!ln.providerId || (ln.providerId === CASHU_MINT_SOURCE && !view.mints.length)) return [];\n return [{ id: `lightning:${network}:${ln.card}`, type: \"lightning\" as const, network, config: { providerId: ln.providerId, label: ln.label ?? \"\" }, card: ln.card, name: ln.name || ln.label || \"Lightning\", receive: ln.receive }];\n });\n}\n\nfunction instanceConfig(type: WalletType, view: NetworkWalletsView): Record<string, string> | undefined {\n const text = (value: unknown) => typeof value === \"string\" ? value : \"\";\n switch (type) {\n case \"cashu\": return view.mints.length ? { mint: view.mints[0].url, mints: String(view.mints.length) } : undefined;\n case \"lightning\": return undefined;\n case \"arkade\": return view.ark?.configured ? { chain: text(view.ark.network), provider: text(view.ark.provider) } : undefined;\n case \"bark\": return view.bark?.configured ? { chain: text(view.bark.network), provider: text(view.bark.provider) } : undefined;\n case \"spark\": return view.spark?.configured ? { chain: text(view.spark.network) } : undefined;\n case \"bitcoin\": return view.bitcoin?.providerId ? { providerId: view.bitcoin.providerId, label: text(view.bitcoin.label) } : undefined;\n case \"fedimint\": return view.fedimint?.federations.length ? { federations: String(view.fedimint.federations.length) } : undefined;\n case \"usdt\": return view.usdt?.configured ? { chain: text(view.usdt.network), provider: text(view.usdt.provider) } : undefined;\n }\n}\n\n/** For each way of paying, the networks this profile has a wallet on: what every chat tells its contact. */\nexport function paymentNetworksOf(wallets: readonly WalletInstanceView[]): PaymentNetworks {\n const networks: PaymentNetworks = {};\n for (const wallet of wallets) {\n const method = wallet.type as PaymentMethodName;\n networks[method] = [...new Set([...(networks[method] ?? []), wallet.network])];\n }\n return networks;\n}\n\n/**\n * The network of a payment or a request: written on it since wallets have their own, else read from what it\n * carries (a target's chain, a mint, an invoice's chain). A test mint's invoice looks like a Bitcoin one; its\n * mints say Testnet.\n */\nexport function paymentNetwork(payment: Pick<StoredPayment, \"network\" | \"target\" | \"mints\" | \"mint\" | \"invoice\">): WalletNetwork {\n if (payment.network) return payment.network;\n if (payment.target) return payment.target.method === \"cashu\" ? mintNetwork(payment.target.provider) : walletNetworkOf(payment.target.network);\n const mint = payment.mints?.[0] ?? payment.mint;\n if (mint) return mintNetwork(mint);\n const decoded = payment.invoice ? decodeBolt11(payment.invoice) : null;\n return decoded && decoded.network !== \"bitcoin\" ? \"testnet\" : \"mainnet\";\n}\n\n/** The refusal when a card of one network would pay for the other: test coins and real money never meet. */\nexport const crossNetwork = (card: WalletNetwork, asked: WalletNetwork) =>\n card === asked ? `This is a ${networkLabel(asked)} payment (${asked === \"mainnet\" ? \"real money\" : \"test coins\"}): a ${networkLabel(card)} wallet never pays it. Use a ${networkLabel(asked)} wallet.`\n : engineText(asked === \"mainnet\" ? \"mainnetPaymentOnTestnet\" : \"testnetPaymentOnMainnet\");\n\n/**\n * Real money goes out only once the person confirmed it as real money (\"Send real money\"), whatever screen asked:\n * a spend RPC on Mainnet without `confirmedReal` is refused before anything is spent. Test coins need no second step.\n */\nexport function assertConfirmedReal(network: WalletNetwork, confirmedReal: unknown): void {\n if (network === \"mainnet\" && confirmedReal !== true) throw new Error(REAL_MONEY_UNCONFIRMED);\n}\nexport const REAL_MONEY_UNCONFIRMED = engineText(\"realMoneyUnconfirmed\");\n\n/** One clear message for a creation that failed: nothing was saved, and trying again is safe. */\nexport function createFailure(label: string, error: unknown): string {\n const reason = (networkReason(error) ?? (error instanceof Error ? error.message : String(error))).replace(/\\.$/, \"\");\n return `Could not create the ${label} wallet: ${reason}. Nothing was saved; try again.`;\n}\n","/**\n * Payment ids are chosen by whoever sends them, and every member of a group sees the requests the others make to the\n * group. So a contact's request is kept under the id it came with, unless a record of another chat already holds that\n * id: then under `<id>.<chat>`. A `.` is never in a payment id on the wire (8 to 64 of `A-Z a-z 0-9 _ -`), so no\n * contact can name another's record, and two contacts' requests never share one.\n */\nexport const paymentAlias = (wireId: string, linkId: string): string => `${wireId}.${linkId}`;\n\n/** The id a payment record goes by on the wire, which is also the one its chat and its group's notes know it by. */\nexport const paymentWireId = (key: string): string => key.split(\".\", 1)[0];\n","import { replySnippet, type WireReply } from \"@ghostly/core\";\nimport { paymentWireId } from \"./paymentIds\";\nimport type { MessageReply, StoredMessage } from \"./types\";\n\n/*\n * Replies (WISP 400 § Replies) as the engine and the UI both need them: which id a message goes by on both sides of\n * its chat, and a reply as it is kept and as it goes on the wire.\n */\n\n/** A paired chat's text (`me_`/`peer_` + its 22-character wire id), or a held file (`peer_` + its 16-character one). */\nconst PAIRED_ROW = /^(?:me|peer)_([A-Za-z0-9_-]{22}|[A-Za-z0-9_-]{16})$/;\nconst WIRE_ID = /^[A-Za-z0-9_-]{8,64}$/;\nconst GROUP_LINK = \"group:\";\n\n/** The parts of a message, as the engine or the UI keeps it, that say which id both sides know it by. */\nexport interface ReplyTarget {\n id: string;\n linkId?: string;\n wireId?: string;\n paymentId?: string;\n file?: { id: string };\n event?: unknown;\n groupPay?: unknown;\n /** Set by the UI from the engine's row (`replyRef`), for what the UI's own copy no longer says. */\n ref?: string;\n}\n\n/**\n * The id a message of a chat goes by on both sides, which a reply names; none for what only this side has (a notice,\n * a group payment line, a refused payment). A paired chat: the text's wire id, a payment's id, a file's wire id. A\n * group: the message id, which is the same for every member.\n */\nexport function replyRef(message: ReplyTarget, group = message.linkId?.startsWith(GROUP_LINK) ?? false): string | undefined {\n if (message.ref) return message.ref;\n if (group) return message.paymentId || message.event || message.groupPay || !message.id.includes(\":\") ? undefined : message.id;\n if (message.paymentId) return paymentWireId(message.paymentId);\n if (message.wireId && WIRE_ID.test(message.wireId)) return message.wireId;\n const out = message.file?.id.match(/-out-([A-Za-z0-9_-]{8,64})$/);\n if (out) return out[1];\n const row = message.id.match(PAIRED_ROW);\n return row && !/^\\d+$/.test(row[1]) ? row[1] : undefined;\n}\n\n/** A reply to `original` as this side keeps it: always found, since it is here. The engine's row, or the UI's copy. */\nexport function replyTo(original: Pick<StoredMessage, \"id\" | \"text\" | \"member\"> & { sender: string }, ref: string): MessageReply {\n return { id: ref, snippet: replySnippet(original.text), from: original.sender === \"me\" ? \"me\" : \"peer\",\n ...(original.member && { member: original.member }), messageId: original.id };\n}\n\n/**\n * A reply as it goes on a paired chat's wire, from the side that sends it (`f`: who wrote the original, from the\n * sender's side). For a group, `f` is the author's member key, which the reply keeps.\n */\nexport function pairedWireReply(reply: MessageReply, sender: \"me\" | \"peer\" = \"me\"): WireReply {\n return { i: reply.id, s: reply.snippet, f: reply.from === sender ? \"sender\" : \"recipient\", ...(reply.button && { b: reply.button }) };\n}\n\n/** A paired chat's reply as it arrived (`f` from the sender's side), as this side keeps it. */\nexport function receivedPairedReply(reply: WireReply | { i: string }): MessageReply {\n if (!(\"f\" in reply)) return { id: reply.i, snippet: \"\" };\n return { id: reply.i, snippet: reply.s, from: reply.f === \"sender\" ? \"peer\" : \"me\", ...(reply.b && { button: reply.b }) };\n}\n\n/** A group's reply as it arrived or was sent (`f` the author's member key), as this member keeps it. */\nexport function groupReply(reply: WireReply, myKey: string): MessageReply {\n return { id: reply.i, snippet: reply.s, from: reply.f === myKey ? \"me\" : \"peer\", member: reply.f, ...(reply.b && { button: reply.b }) };\n}\n","import {\n ChatFiles,\n GROUP_FILE_LIMITS,\n GROUP_HAVE_FRAME,\n GROUP_WANT_FRAME,\n GROUP_WANT_NO_FRAME,\n RateWindow,\n fetchesByItself,\n fileMessageText,\n groupFileFallback,\n isGroupMessageId,\n randomBytes,\n readGroupHave,\n readGroupWant,\n readGroupWantNo,\n toBase64Url,\n transferEnded,\n type FileInfo,\n type FileTransferRecord,\n type GroupFileMeta,\n type GroupWantRefusal,\n type IncomingTarget,\n type OfferDecision,\n type OutgoingSource,\n} from \"@ghostly/core\";\nimport { fileBytes, fileBytesOf } from \"../shared/fileBytes\";\nimport { fileStore, type StoredFile } from \"../shared/idb\";\nimport { FileAppender, readStored, removeStored } from \"../shared/storedFiles\";\nimport type { FileTransferView, MessageFile } from \"../shared/types\";\n\n/*\n * Files and voice messages in groups (WISP 503 · Group Files), the engine's side. A file is announced as a group message\n * carrying its description (`fl`); each member's app then fetches the bytes from a member that holds them, over\n * `files/3` on the edge between the two (`ChatFiles`, WISP 501): it sends `group-want`, the holder offers the file with\n * `gm` naming the message, and what arrives is checked against the author's digest. Whoever stored it holds it, and says\n * so (`group-have`), so a member away during the announcement gets it later from whoever is there.\n */\n\n/** A group file's local id: its group's and a random part, never anything a member sends. */\nexport const groupFileId = (groupId: string, direction: \"in\" | \"out\", random = toBase64Url(randomBytes(12))) => `group-${groupId}-${direction}-${random}`;\n/** The group a local file id names, among `groupIds`. */\nexport const groupOfFile = (fileId: string, groupIds: Iterable<string>): string | undefined =>\n [...groupIds].find(g => fileId.startsWith(`group-${g}-in-`) || fileId.startsWith(`group-${g}-out-`));\n\n/** What a file's message says: its caption, or (when the author wrote none) the file itself, not the line older apps show. */\nexport const groupFileText = (text: string, meta: GroupFileMeta): string => text === groupFileFallback(meta) ? fileMessageText(meta) : text;\n\n/** Where a group's files are kept: the profile's file store and file storage (`storedGroupFiles`), or a test's memory. */\nexport interface GroupFileStore {\n /** The records of a group's files (`linkId` `group:<id>`). */\n list(groupId: string): Promise<StoredFile[]>;\n /** Every group a stored file is of, whether or not this device still has the group. */\n groups(): Promise<string[]>;\n get(id: string): Promise<StoredFile | undefined>;\n put(file: StoredFile): Promise<void>;\n patch(id: string, fields: Pick<StoredFile, \"transfer\" | \"group\">): Promise<void>;\n /** The record and its bytes. */\n remove(id: string): Promise<void>;\n /**\n * Writes a file in order: from its first byte (whatever was there goes), or with `resume` after what an earlier\n * transfer stored (the writer's `offset`).\n */\n write(id: string, resume?: boolean): Promise<GroupFileWriter>;\n /** At most `length` bytes of a kept file from `offset`. */\n read(file: StoredFile, offset: number, length: number): Promise<Uint8Array>;\n /** Bytes this device can still take for files, or null when the platform does not say. */\n room(): Promise<number | null>;\n}\n\nexport interface GroupFileWriter {\n /** Where it writes from: the bytes already stored. */\n offset: number;\n append(bytes: Uint8Array): Promise<void>;\n flush(): Promise<void>;\n /** SHA-256 of what was stored, read back from storage, base64url. */\n digest(): Promise<string>;\n /** Done writing for now: what is stored stays. */\n close(): Promise<void>;\n discard(): Promise<void>;\n}\n\n/** The profile's files store and file storage, as a 1:1 chat's files use them. */\nexport const storedGroupFiles: GroupFileStore = {\n list: groupId => fileStore.listForLink(`group:${groupId}`),\n groups: () => fileStore.groupIds(),\n get: id => fileStore.get(id),\n put: file => fileStore.put(file),\n patch: (id, fields) => fileStore.patch(id, fields),\n remove: id => removeStored(id),\n async write(id, resume = false) {\n const stored = await fileStore.get(id);\n const bytes = (stored?.bytes && await fileBytesOf(stored.bytes)) || await fileBytes();\n const size = await bytes.size(id);\n const offset = resume && size !== null ? size : 0;\n if (size !== null && size !== offset) await bytes.truncate(id, offset);\n if (stored && stored.bytes !== bytes.kind) await fileStore.patch(id, { bytes: bytes.kind });\n const appender = new FileAppender(bytes, id, offset);\n return {\n offset,\n append: chunk => appender.append(chunk),\n flush: () => appender.flush(),\n digest: async () => { await appender.close(); return bytes.digest(id); },\n close: () => appender.close(),\n discard: async () => { await appender.close().catch(() => {}); await bytes.remove(id); },\n };\n },\n read: (file, offset, length) => readStored(file, offset, length),\n room: async () => (await fileBytes()).room(),\n};\n\n/** What the engine gives the group files: where they are kept, their views, the profile's settings, an edge's files/3. */\nexport interface GroupFilesHost {\n store: GroupFileStore;\n transfers: Map<string, FileTransferView>;\n /** The file's view goes in the next state the engine sends, and then away: the file's record says the rest. */\n brief(fileId: string): void;\n settings(): { autoDownloads: boolean; serveFiles: boolean };\n /** A files/3 frame on an edge's open session; false when it could not go. */\n sendFiles(linkId: string, frame: Record<string, unknown>): boolean;\n /** A file's bytes on an edge, in the form its session agreed (`GhostLink.sendFileData`, files/4). Absent: `pf-data` frames. */\n sendData?(linkId: string, id: string, offset: number, data: Uint8Array): boolean;\n /** Resolves once the edge's session can take another data frame. */\n writable?(linkId: string): Promise<void> | undefined;\n}\n\n/** Me in a group I am active in, and who could read one of its messages. */\nexport interface GroupFileMembership {\n me: string;\n community: boolean;\n inRoster(key: string): boolean;\n /** In the roster of the message's epoch and in the roster now (WISP 503 § Asking). */\n couldRead(key: string, messageId: string): boolean;\n}\n\nexport interface GroupFileDeps extends GroupFilesHost {\n /** A group frame on an edge; false when it could not go. */\n send(linkId: string, frame: Record<string, unknown>): boolean;\n /** Member key → edge link id, for the group's edges. */\n edges(groupId: string): Map<string, string>;\n /** The edge is up, and the app at its other end announced group files (`GROUP_VERSION_FILES`). */\n ready(linkId: string): boolean;\n membership(groupId: string): GroupFileMembership | undefined;\n /** An application frame to everyone in a community. */\n sendApp(groupId: string, frame: Record<string, unknown>): Promise<void>;\n changed(): void;\n now(): number;\n}\n\n/** A group file this device knows of: one it announced, or one a member announced. */\ninterface Kept {\n id: string;\n groupId: string;\n message: string;\n author: string;\n meta: GroupFileMeta;\n /** I announced it: my copy is its first. */\n mine: boolean;\n /** Fetched without asking: it counts toward the group's automatic limit. */\n auto: boolean;\n /** Its person asked for it (Download). */\n asked: boolean;\n /** Its person paused it: what came stays, and it reads paused after a restart too, until Resume. */\n paused?: boolean;\n /** Every byte is here, checked against the author's digest: this device holds it. */\n held: boolean;\n /** Members said to hold it (`group-have`), or that offered it, newest first. The author is asked whether or not it is here. */\n holders: string[];\n /** A discard of what an earlier transfer stored, which the next one waits for. */\n idle?: Promise<void>;\n /** Its transfer's write under way to storage, which a Leave waits for before it deletes the file (`drop`). */\n writing?: Promise<unknown>;\n /** Bytes a transfer stored here that the next one goes on from, shown while it waits for a holder. */\n stored?: number;\n /** The transfer (its id on the edge) its person cancelled: what it stored is deleted, not kept for the next one. */\n dropped?: string;\n /**\n * The bytes kept from an earlier holder did not check out with the next one's tail: who served the bad part is not\n * known, so the next transfer starts from 0 (`fresh`) and the holder whose tail failed is not counted as damaged\n * (`unclear`, read once by `transferChanged`).\n */\n fresh?: boolean;\n unclear?: boolean;\n /**\n * The holder every byte stored here came from, in this run (none once two holders' bytes are mixed): its tail failing\n * the check is its own bytes failing, so it counts as damaged, however many transfers it split them into.\n */\n storedBy?: string;\n /**\n * Holders whose bytes failed the author's digest: not asked again for it, whatever the round, until its person asks\n * for it again (Download). Once every holder known is here, it is fetched no more (`DAMAGED`).\n */\n damaged: Set<string>;\n /** The holder its last transfer was left with (it stalled, or its person paused it): asked last, not first, next time. */\n left?: string;\n}\n\n/** A file being asked for: of one holder at a time. */\ninterface Want {\n /** The holder asked now and the edge it was asked on; none while nobody reachable has it. */\n holder?: string;\n linkId?: string;\n at: number;\n /** Holders asked in this round that had nothing to give: the next ask goes to another. */\n tried: Set<string>;\n /**\n * The holder's transfer, once it offered: its id on the edge, and the bytes stored when it last moved by\n * `wantMoveBytes` and when (-1 until its first change: a resumed transfer starts at what was stored before).\n */\n wire?: string;\n moved: number;\n movedAt: number;\n /** What the last holder asked in this round answered, when it was not the file: shown while the want waits. */\n why?: \"busy\" | \"damaged\";\n /**\n * Holders that answered `busy` in this round: one of them in reach tells this device when its place frees (its\n * `group-have`), so members never heard of as holders are not asked meanwhile.\n */\n busy?: Set<string>;\n}\n\n/** One edge's files/3, both ways. */\ninterface Edge {\n linkId: string;\n groupId: string;\n peer: string;\n files: ChatFiles;\n /** Transfers this side serves on the edge (their wire id) → the local file. */\n serving: Map<string, string>;\n /** Of each transfer served: the bytes the asker confirmed, when that last moved, and when it was offered. */\n servedMoved: Map<string, { confirmed: number; at: number; from: number }>;\n /** Transfers this side takes on the edge (the holder's wire id) → the local file. */\n taking: Map<string, string>;\n /** Transfers that ended on the edge, oldest first: the last `ENDED_KEPT` keep their record, the rest are forgotten. */\n ended: [\"in\" | \"out\", string][];\n /** Message ids to say in the next `group-have`, and the frames of it that went lately (`haveFrames` in `haveWindowMs`). */\n have: Set<string>;\n haveSent: RateWindow;\n /** Its member's `group-have` frames taken lately: no more than it may send (`haveFrames` in `haveWindowMs`); past that, dropped. */\n haveTaken: RateWindow;\n /** Message ids its member asked for while this device served all it serves at once: said in a `group-have` once a slot frees. */\n busy: Set<string>;\n /** Message ids its member said it does not hold (`gone`): not asked of it again until it says it does, or the edge's session closes or opens. */\n gone: Set<string>;\n}\n\n/**\n * Ended transfers an edge keeps the record of. A late frame for one of them is still answered as files/3 says (an abort\n * to an asker going on with a transfer cancelled here); older ones are forgotten, so an edge's records stay few\n * however many transfers it has seen.\n */\nconst ENDED_KEPT = 8;\n\n/** Why a want waits, as the person reads it (the app shows its own words for each `wait`). */\nconst WAITING_NOTES = {\n nobody: \"Nobody you are connected to has this file yet\",\n busy: \"Its holders are busy; asking again soon\",\n damaged: \"It arrived damaged; ask again\",\n} as const;\n/** Why a want stopped: every holder served bytes that are not the author's file. */\nconst DAMAGED = \"Every copy of this file arrived damaged and was deleted. Ask for it again later.\";\n\nexport class GroupFileDesk {\n private readonly kept = new Map<string, Kept>();\n /** `<group>\\n<message id>` → local id. */\n private readonly byMessage = new Map<string, string>();\n private readonly wants = new Map<string, Want>();\n /**\n * The want last asked on each edge (by link id), and its file: whether a file is asked of an edge's member is read here,\n * not by going through every want each time (`askedOf`). An entry the want has moved on from is dropped when read.\n */\n private readonly askedOn = new Map<string, { id: string; want: Want }>();\n private readonly edgesById = new Map<string, Edge>();\n /** My file about to be announced, by `<group>\\n<digest>`: the message that announces it takes this local id. */\n private readonly own = new Map<string, string>();\n /** Announcements sent per group, and taken per member of a group (WISP 503 § Rules for both, Pace). */\n private readonly sentPace = new Map<string, RateWindow>();\n private readonly sentTaken = new Map<string, RateWindow>();\n private readonly takenPace = new Map<string, RateWindow>();\n /** `<group>\\n<message id>` a `group-have` named before its message came → its holders, newest first. */\n private readonly ahead = new Map<string, string[]>();\n /** A community's ids for its next `group-have`, and when one last went. */\n private readonly communityHave = new Map<string, { ids: Set<string>; at: number }>();\n /** Bytes served today, and the day (UTC days since the epoch). */\n private served = { day: 0, bytes: 0 };\n\n constructor(private readonly deps: GroupFileDeps) {}\n\n // -- what the groups hand over -------------------------------------------------------------------------------\n\n /**\n * At start: each group's files as they were kept. One fetched by itself, or asked for, and not here yet is asked for\n * again; with automatic downloads off, one chosen by itself before waits for a Download instead.\n */\n async load(groupIds: string[]): Promise<void> {\n for (const groupId of groupIds) {\n for (const file of await this.deps.store.list(groupId).catch(() => [])) {\n if (!file.group || !file.metadata || !file.digest || this.kept.has(file.id)) continue;\n const { name, size, mime, voice, video, image } = file.metadata;\n const kept: Kept = { id: file.id, groupId, message: file.group.message, author: file.group.author,\n meta: { name, size, mime, d: file.digest, ...(voice && { voice }), ...(video && { video }), ...(image && { image }) },\n mine: file.direction === \"out\", auto: !!file.group.auto, asked: !!file.group.asked, held: file.transfer?.state === \"done\", holders: [], damaged: new Set() };\n this.add(kept);\n // All here: it shows no transfer (a file with none reads as finished; its record says done). A view for each\n // would go into every state the engine builds, so each state would cost more with every file the groups ever had.\n if (kept.held) continue;\n // Every holder served it damaged: it waits for its person to ask again, not fetched again by itself.\n if (file.transfer?.state === \"failed\") this.show(kept, { state: \"failed\", transferred: 0, size, direction: \"in\", retry: true, error: file.transfer.error ?? DAMAGED });\n // Paused by its person: still paused, with the bytes that came (what the next transfer goes on from).\n else if (file.group.paused) {\n kept.paused = true;\n kept.stored = await this.keptBytes(kept.id);\n this.show(kept, { state: \"transferring\", stage: \"paused\", pausedBy: \"me\", transferred: kept.stored, size, direction: \"in\" });\n }\n else if (kept.asked || (kept.auto && this.deps.settings().autoDownloads)) this.wants.set(kept.id, this.newWant());\n else if (kept.auto) await this.stop(kept, false);\n // Waiting for a Download: no transfer either. Its record says it is not here (`waitingGroupFile`).\n }\n }\n for (const [id] of this.wants) this.ask(this.kept.get(id)!);\n this.deps.changed();\n }\n\n /**\n * At start, once every group was read: the files of a group this device no longer has (`groupIds` are the groups it\n * has, a tombstone or a join under way among them) go, with their bytes. A group left before Leave took its files\n * left them behind, where nothing lists them.\n */\n async sweep(groupIds: string[]): Promise<void> {\n const here = new Set(groupIds);\n for (const groupId of await this.deps.store.groups().catch(() => [])) {\n if (here.has(groupId)) continue;\n for (const file of await this.deps.store.list(groupId).catch(() => [])) await this.deps.store.remove(file.id).catch(() => {});\n }\n }\n\n /**\n * The person stops a file that is coming, or that this device would fetch (Cancel, Decline): nobody is asked for it\n * any more, the transfer under way ends, what came of it is deleted, and it waits for a Download. Paused (`keep`),\n * what came stays, and Resume (`download`) goes on from there.\n */\n async halt(fileId: string, keep = false): Promise<void> {\n const kept = this.kept.get(fileId);\n if (!kept) throw new Error(\"No such file\");\n // Only a file that is coming pauses: one waiting for a Download has nothing to go on with.\n if (keep && !this.wants.has(fileId)) return;\n await this.stop(kept, keep);\n }\n\n /** Automatic downloads were turned off: files chosen by themselves and not here yet stop coming, and wait for a Download. */\n async autoDownloadsOff(): Promise<void> {\n for (const kept of [...this.kept.values()]) if (kept.auto && !kept.asked && this.wants.has(kept.id)) await this.stop(kept, false);\n }\n\n private async stop(kept: Kept, keep: boolean): Promise<void> {\n if (kept.held || kept.mine) return;\n const want = this.wants.get(kept.id);\n // Gone first: the transfer's end below asks nobody else (`transferChanged`), and a late offer is refused.\n this.wants.delete(kept.id);\n // Paused on a holder's transfer: Resume asks the others first.\n if (want?.wire && want.holder) kept.left = want.holder;\n const edge = want?.wire && want.linkId ? this.edgesById.get(want.linkId) : undefined;\n const record = edge?.files.get(\"in\", want!.wire!);\n const { size } = kept.meta;\n // One every holder served damaged waits for a Download as any other: its record no longer says it failed.\n const failed = !keep && this.deps.transfers.get(kept.id)?.state === \"failed\";\n if (!keep) { kept.stored = 0; kept.fresh = true; }\n if (edge && record && !transferEnded(record)) {\n // The transfer's own writer deletes what it stored (`openTarget`), or keeps it when paused.\n if (!keep) kept.dropped = record.id;\n edge.files.cancel(\"in\", record.id);\n edge.taking.delete(record.id);\n } else if (!keep) {\n // No transfer under way: what an earlier one stored goes, before the next one writes.\n kept.idle = (kept.idle ?? Promise.resolve()).then(() => this.deps.store.write(kept.id)).then(writer => writer.discard()).catch(() => {});\n }\n if (keep) this.show(kept, { state: \"transferring\", stage: \"paused\", pausedBy: \"me\", transferred: kept.stored ?? 0, size, direction: \"in\" });\n else this.waiting(kept);\n // Not fetched by itself any more, nor asked for: it stays stopped (or paused) after a restart, and counts toward no limit.\n const wasPaused = !!kept.paused;\n kept.paused = keep;\n if (kept.auto || kept.asked || wasPaused !== keep || failed) {\n kept.auto = kept.asked = false;\n await this.deps.store.patch(kept.id, { group: this.groupField(kept), ...(failed && { transfer: { state: \"transferring\", transferred: 0, size } }) }).catch(() => {});\n }\n }\n\n /**\n * An error when this group took as many announcements of mine as it takes in a minute, or as its members take from\n * one member in `announceWindowMs` (past that they would not fetch the file by themselves); null when one may go now.\n */\n mayAnnounce(groupId: string): string | null {\n const wait = Math.max(...this.sentPaces(groupId).map(pace => pace.wait()));\n if (!wait) return null;\n const minutes = Math.ceil(wait / 60_000);\n return `You sent many files to this group just now. Wait ${minutes > 1 ? `${minutes} minutes` : \"a minute\"}.`;\n }\n\n /** My file, stored under `fileId` with the author's digest, is about to be announced in the group: its message takes it. */\n outgoing(groupId: string, fileId: string, meta: GroupFileMeta): void {\n this.own.set(`${groupId}\\n${meta.d}`, fileId);\n }\n /** The announcement did not go: the file is not this group's after all. */\n notSent(groupId: string, meta: GroupFileMeta): void { this.own.delete(`${groupId}\\n${meta.d}`); }\n\n /**\n * A message of the group announces a file (mine, or a member's): the file its row shows. A member's is kept here\n * unfetched, and fetched now when it is one this device takes by itself and the member announced no more than the\n * group fetches that way. The same message again (a catch-up, a whole copy) is the same file.\n */\n async announced(groupId: string, m: { id: string; sender: string; file: GroupFileMeta }, me: string): Promise<MessageFile | undefined> {\n const known = this.byMessage.get(`${groupId}\\n${m.id}`);\n if (known) return messageFile(this.kept.get(known)!);\n const ownId = m.sender === me ? this.own.get(`${groupId}\\n${m.file.d}`) : undefined;\n if (ownId) {\n this.own.delete(`${groupId}\\n${m.file.d}`);\n for (const pace of this.sentPaces(groupId)) pace.take();\n const kept: Kept = { id: ownId, groupId, message: m.id, author: me, meta: m.file, mine: true, auto: false, asked: false, held: true, holders: [], damaged: new Set() };\n this.add(kept);\n const transfer = { state: \"done\" as const, transferred: m.file.size, size: m.file.size };\n await this.deps.store.patch(ownId, { group: { message: m.id, author: me }, transfer }).catch(() => {});\n this.finished(kept, \"out\");\n this.queueHave(kept);\n return messageFile(kept);\n }\n // More from one member than a group fetches by itself: past it, the file is kept and waits for a Download (WISP 503,\n // Pace). Counted as they come, so a catch-up of files sent over hours is one burst: none of them is lost to it.\n const paced = this.pace(this.takenPace, `${groupId}\\n${m.sender}`, GROUP_FILE_LIMITS.announceTaken, GROUP_FILE_LIMITS.announceWindowMs).take();\n const id = groupFileId(groupId, \"in\");\n const auto = paced && this.deps.settings().autoDownloads && fetchesByItself(m.file, this.keptAutomatically(groupId));\n const holders = this.ahead.get(`${groupId}\\n${m.id}`) ?? [];\n this.ahead.delete(`${groupId}\\n${m.id}`);\n const kept: Kept = { id, groupId, message: m.id, author: m.sender, meta: m.file, mine: false, auto, asked: false, held: false, holders, damaged: new Set() };\n const { name, size, mime, voice, video, image } = m.file;\n // Known at once: the same message coming again meanwhile (another copy of it) is this file.\n this.add(kept);\n // No bytes yet: where they go is decided when they come (`GroupFileStore.write`).\n await this.deps.store.put({ id, linkId: `group:${groupId}`, direction: \"in\", digest: m.file.d, createdAt: this.deps.now(),\n metadata: { name, size, mime, timestamp: this.deps.now(), voice, video, image },\n transfer: { state: \"transferring\", transferred: 0, size }, group: { message: m.id, author: m.sender, ...(auto && { auto: true }) } });\n if (auto) { this.wants.set(id, this.newWant()); this.ask(kept); }\n else this.waiting(kept);\n return messageFile(kept);\n }\n\n /** The person asks for a file this device did not fetch by itself (Download), or for one that stopped coming. */\n async download(fileId: string): Promise<void> {\n const kept = this.kept.get(fileId);\n if (!kept) throw new Error(\"No such file\");\n if (kept.held) return;\n const room = await this.deps.store.room().catch(() => null);\n if (room !== null && kept.meta.size > room) throw new Error(\"Not enough space on this device for this file\");\n if (!kept.asked || kept.paused) {\n kept.asked = true; kept.paused = false;\n await this.deps.store.patch(kept.id, { group: this.groupField(kept) }).catch(() => {});\n }\n // Asked again: every holder is worth asking again, one that served it damaged too.\n kept.damaged.clear();\n if (this.deps.transfers.get(kept.id)?.state === \"failed\") {\n await this.deps.store.patch(kept.id, { transfer: { state: \"transferring\", transferred: 0, size: kept.meta.size } }).catch(() => {});\n }\n const want = this.wants.get(kept.id);\n if (!want) this.wants.set(kept.id, this.newWant());\n else if (!want.wire) { want.tried.clear(); want.why = want.busy = undefined; }\n this.ask(kept);\n }\n\n /** The group is gone from this device: its files go too. */\n async drop(groupId: string): Promise<void> {\n for (const kept of [...this.kept.values()].filter(k => k.groupId === groupId)) {\n this.kept.delete(kept.id);\n this.byMessage.delete(`${groupId}\\n${kept.message}`);\n this.wants.delete(kept.id);\n this.deps.transfers.delete(kept.id);\n // A write under way goes first; after it, the file's transfer writes nothing more (`openTarget`).\n await kept.writing?.catch(() => {});\n await this.deps.store.remove(kept.id).catch(() => {});\n }\n for (const [linkId, edge] of this.edgesById) if (edge.groupId === groupId) { edge.files.detach(); this.edgesById.delete(linkId); }\n for (const key of [...this.ahead.keys()]) if (key.startsWith(`${groupId}\\n`)) this.ahead.delete(key);\n this.communityHave.delete(groupId);\n this.deps.changed();\n }\n\n // -- edges ---------------------------------------------------------------------------------------------------\n\n /** An edge's files/3 session opened (both sides agreed it) or closed. */\n session(groupId: string, peer: string, linkId: string, open: boolean): void {\n const edge = this.edge(groupId, peer, linkId);\n if (open) edge.files.attach(); else edge.files.detach();\n // Another session, or none: its member may have fetched since what it did not hold, and is asked again.\n edge.gone.clear();\n if (open) this.retry(groupId);\n }\n\n /** An edge came up and both sides said where they are: what this device holds goes to it, and wants waiting for a holder go. */\n edgeReady(groupId: string, peer: string, linkId: string): void {\n if (!this.deps.ready(linkId)) return;\n const edge = this.edge(groupId, peer, linkId);\n if (this.deps.settings().serveFiles) {\n const held = [...this.kept.values()].filter(k => k.groupId === groupId && k.held).map(k => k.message).slice(-GROUP_FILE_LIMITS.haveIds);\n if (held.length) this.sendHave(edge, held);\n }\n this.retry(groupId);\n }\n\n /** A `group-want`, `group-want-no` or `group-have` on an edge, from the member it is pinned to. */\n async frame(groupId: string, peer: string, linkId: string, frame: Record<string, unknown>): Promise<void> {\n if (frame.t === GROUP_WANT_FRAME) {\n const want = readGroupWant(frame, groupId);\n if (want) await this.serve(this.edge(groupId, peer, linkId), want.id);\n } else if (frame.t === GROUP_WANT_NO_FRAME) {\n const no = readGroupWantNo(frame, groupId);\n if (no) this.refused(groupId, peer, linkId, no.id, no.why);\n } else if (frame.t === GROUP_HAVE_FRAME) {\n const ids = readGroupHave(frame, groupId);\n // Each one makes its member worth asking again: taken at the pace it may send them, not faster.\n if (ids && this.edge(groupId, peer, linkId).haveTaken.take()) this.heard(groupId, peer, ids);\n }\n }\n\n /** A community's `group-have`, an application frame its signer sent the group. */\n communityApp(groupId: string, sender: string, frame: Record<string, unknown>): void {\n const ids = readGroupHave(frame);\n if (ids) this.heard(groupId, sender, ids);\n }\n\n /**\n * A files/3 frame on an edge. An offer is taken only for a file this device asked that member for (`gm` naming the\n * message): nobody can push a file into another's storage. What it brings belongs to the group's message, not to the\n * edge as a chat.\n */\n filesFrame(groupId: string, peer: string, linkId: string, frame: Record<string, unknown>): Promise<void> {\n const edge = this.edge(groupId, peer, linkId);\n if (frame.t === \"pf-offer\" && typeof frame.id === \"string\" && /^[A-Za-z0-9_-]{8,64}$/.test(frame.id) && !edge.taking.has(frame.id)) {\n const kept = isGroupMessageId(frame.gm) ? this.kept.get(this.byMessage.get(`${groupId}\\n${frame.gm}`) ?? \"\") : undefined;\n const want = kept && this.wants.get(kept.id);\n if (!kept || kept.held || !want || want.linkId !== linkId || want.wire) {\n this.deps.sendFiles(linkId, { t: \"pf-refuse\", id: frame.id, why: \"invalid\" });\n return Promise.resolve();\n }\n edge.taking.set(frame.id, kept.id);\n // It offers the file: it holds it, whether or not it ever said so.\n kept.holders = newestHolder(peer, kept.holders);\n want.wire = frame.id;\n want.why = undefined;\n want.moved = -1;\n want.movedAt = this.deps.now();\n }\n return edge.files.handle(frame);\n }\n\n /** Wants that waited too long for an offer, or for bytes, go to the next holder; `group-have`s gathered go. */\n tick(): void {\n const now = this.deps.now();\n for (const [id, want] of this.wants) {\n const kept = this.kept.get(id);\n if (!kept) { this.wants.delete(id); continue; }\n if (now - (want.wire ? want.movedAt : want.at) < GROUP_FILE_LIMITS.wantWaitMs) continue;\n if (want.wire) {\n // A holder that stalls, or sends too little to count (`wantMoveBytes`), is left: the transfer ends here, and the\n // next holder is asked (`transferChanged`).\n kept.left = want.holder;\n const edge = want.linkId ? this.edgesById.get(want.linkId) : undefined;\n if (edge?.files.get(\"in\", want.wire) && !transferEnded(edge.files.get(\"in\", want.wire)!)) { edge.files.cancel(\"in\", want.wire); continue; }\n want.wire = undefined;\n }\n if (want.holder) want.tried.add(want.holder);\n // Everyone asked in turn: a new round, as holders come and go.\n else { want.tried.clear(); want.why = want.busy = undefined; }\n this.ask(kept);\n }\n this.letGo();\n for (const edge of this.edgesById.values()) {\n if (edge.have.size && this.deps.ready(edge.linkId)) this.sendHave(edge, [...edge.have]);\n }\n for (const [groupId, have] of this.communityHave) {\n if (!have.ids.size || now - have.at < GROUP_FILE_LIMITS.communityHaveEveryMs) continue;\n const ids = [...have.ids].slice(-GROUP_FILE_LIMITS.haveIds);\n have.ids.clear();\n have.at = now;\n void this.deps.sendApp(groupId, { t: GROUP_HAVE_FRAME, ids }).catch(() => {});\n }\n }\n\n // -- asking --------------------------------------------------------------------------------------------------\n\n private newWant(): Want { return { at: this.deps.now(), tried: new Set(), moved: 0, movedAt: 0 }; }\n\n /**\n * Wants of this group that wait for a holder: asked now (an edge came up or freed, or a member said it holds\n * something). One queued behind an edge's file is asked only while an edge of the group is free to take it, and free\n * for it (`freeFor`): once every edge has a file asked of it, or the free ones are of members it already asked, it would\n * only queue again. So a file that ends asks the few wants it frees an edge for, not every want waiting (a member back\n * from away with hundreds of files to fetch asked each of them once per file, more so with a member in the group that\n * holds none of them, or serves none).\n */\n private retry(groupId: string): void {\n let free: [string, string][] | undefined;\n for (const [id, want] of this.wants) {\n const kept = this.kept.get(id);\n if (kept?.groupId !== groupId || want.holder) continue;\n if (this.deps.transfers.get(id)?.stage === \"queued\" && !this.freeFor(kept, want, free ??= this.freeEdges(groupId))) continue;\n this.ask(kept);\n // That ask may have taken a free edge.\n free = undefined;\n }\n }\n\n /** The group's edges that are up, with files/3 live and no file asked of their member: a want could be asked on them. */\n private freeEdges(groupId: string): [string, string][] {\n return [...this.deps.edges(groupId)].filter(([, linkId]) => this.deps.ready(linkId) && this.edgesById.get(linkId)?.files.live && !this.askedOf(linkId));\n }\n\n /** One of these free edges (member key, link id) is one `ask` would ask this want on: asked now, it would not only queue again. */\n private freeFor(kept: Kept, want: Want, free: [string, string][]): boolean {\n const membership = this.deps.membership(kept.groupId);\n if (!membership) return false;\n const known = this.knownHolders(kept, membership);\n // As in `ask`: the others only while no known holder is worth waiting for.\n const others = !this.waitForKnown(kept, want, known, this.deps.edges(kept.groupId));\n return free.some(([key, linkId]) => this.mayAsk(kept, want, key, linkId)\n && (known.includes(key) || (others && key !== membership.me && membership.couldRead(key, kept.message))));\n }\n\n /** The members known to hold the file (its author first), in the group's roster, but this device. */\n private knownHolders(kept: Kept, membership: GroupFileMembership | undefined): string[] {\n return [kept.author, ...kept.holders.filter(key => key !== kept.author)]\n .filter(key => membership && key !== membership.me && membership.inRoster(key));\n }\n\n /**\n * The author, its edge up, that answered busy or whose edge another file of this device's is asked of, serves when its\n * place frees: the others are asked only when it is not worth waiting for (out of reach), as after a restart here.\n * Only the author holds them back: any member can say it holds files, and one that answers busy, or serves the file\n * asked of it slowly, would keep every file it named here from coming while the author is away. Its busy only skips\n * it for the round.\n */\n private waitForKnown(kept: Kept, want: Want, known: string[], edges: Map<string, string>): boolean {\n if (!known.includes(kept.author)) return false;\n const linkId = edges.get(kept.author) ?? \"\";\n return this.deps.ready(linkId) && (!!want.busy?.has(kept.author) || this.askedOf(linkId, want));\n }\n\n /** The want may ask this member in this round: not asked yet, it did not serve it damaged, and did not say it is gone. */\n private mayAsk(kept: Kept, want: Want, key: string, linkId: string | undefined): boolean {\n return !want.tried.has(key) && !kept.damaged.has(key) && !this.edgesById.get(linkId ?? \"\")?.gone.has(kept.message);\n }\n\n /**\n * Asks the next holder for a file: the author first while its edge is up, then the holders whose edge is up, then every\n * other member whose edge is up and that could read its message. Who holds what is heard, not kept: a holder names few\n * of its files to an edge that opens, and none it was told of is known after a restart here, so a member in reach is\n * asked whether or not it said so (one that does not hold it answers `gone`, and is not asked again). Only over an\n * edge this device has. (A private group past 16 members, or a community, reaches a holder it has no edge with\n * over a file session of the two members, WISP 503 § The file session; that session is not built yet, so such a\n * holder is not asked, and the file waits for one that is reachable.)\n */\n private ask(kept: Kept): void {\n const want = this.wants.get(kept.id);\n if (!want || kept.held || want.wire) return;\n const membership = this.deps.membership(kept.groupId);\n const edges = this.deps.edges(kept.groupId);\n const known = this.knownHolders(kept, membership);\n // Every holder known served it damaged: asking again would fetch the same bytes, all of them, round after round.\n if (known.length && known.every(key => kept.damaged.has(key))) { this.stopDamaged(kept); return; }\n const others = this.waitForKnown(kept, want, known, edges) ? [] : [...edges.keys()].filter(key => !known.includes(key) && membership && key !== membership.me && membership.couldRead(key, kept.message));\n const order = [...known, ...others].filter(key => this.mayAsk(kept, want, key, edges.get(key)));\n // The holder last left for this file is asked once the others were.\n if (kept.left && order.includes(kept.left)) order.push(...order.splice(order.indexOf(kept.left), 1));\n let behind = false;\n for (const key of order) {\n const linkId = edges.get(key);\n if (!linkId || !this.deps.ready(linkId) || !this.edgesById.get(linkId)?.files.live) continue;\n // A holder serves this device one file at a time (WISP 503 § Serving limits): asked now, it would answer `busy`.\n // This one waits its turn, and is asked when the file before it ends (`transferChanged`).\n if (this.askedOf(linkId, want)) { behind = true; continue; }\n // Asked of it before the frame goes: its answer is this want's, however soon it comes.\n Object.assign(want, { holder: key, linkId, at: this.deps.now() });\n this.askedOn.set(linkId, { id: kept.id, want });\n if (!this.deps.send(linkId, { t: GROUP_WANT_FRAME, g: kept.groupId, id: kept.message })) { want.tried.add(key); continue; }\n if (want.holder === key) this.show(kept, { state: \"transferring\", stage: \"queued\", transferred: kept.stored ?? 0, size: kept.meta.size, direction: \"in\" });\n return;\n }\n Object.assign(want, { holder: undefined, linkId: undefined, at: this.deps.now() });\n if (behind) { this.show(kept, { state: \"transferring\", stage: \"queued\", transferred: kept.stored ?? 0, size: kept.meta.size, direction: \"in\" }); return; }\n // Why it waits: a holder busy, bytes that came damaged (its person can ask again), or nobody in reach has it.\n const wait = want.why ?? \"nobody\";\n this.show(kept, { state: \"transferring\", stage: \"waiting\", transferred: kept.stored ?? 0, size: kept.meta.size, direction: \"in\",\n wait, note: WAITING_NOTES[wait], ...(wait === \"damaged\" && { stalled: true }) });\n }\n\n /**\n * Another file is asked of, or coming from, the member at the other end of this edge. A want gets its edge only in\n * `ask`, which notes it in `askedOn`, and one edge has one want at a time: the want noted there is it, while it still\n * waits and is still on that edge.\n */\n private askedOf(linkId: string, except?: Want): boolean {\n const on = this.askedOn.get(linkId);\n if (!on || on.want === except) return false;\n if (on.want.linkId === linkId && this.wants.get(on.id) === on.want) return true;\n this.askedOn.delete(linkId);\n return false;\n }\n\n /** No holder serves the author's bytes: the want stops, and stays stopped after a restart, until Download. */\n private stopDamaged(kept: Kept): void {\n this.wants.delete(kept.id);\n const { size } = kept.meta;\n void this.deps.store.patch(kept.id, { transfer: { state: \"failed\", transferred: 0, size, error: DAMAGED } }).catch(() => {});\n this.show(kept, { state: \"failed\", transferred: 0, size, direction: \"in\", retry: true, error: DAMAGED });\n }\n\n /** A holder's no: the next one is asked. One that deleted the file is no holder any more. */\n private refused(groupId: string, peer: string, linkId: string, messageId: string, why: GroupWantRefusal): void {\n const kept = this.kept.get(this.byMessage.get(`${groupId}\\n${messageId}`) ?? \"\");\n const want = kept && this.wants.get(kept.id);\n if (!kept || !want || want.linkId !== linkId || want.wire) return;\n if (why === \"gone\") {\n kept.holders = kept.holders.filter(key => key !== peer);\n this.edgesById.get(linkId)?.gone.add(messageId);\n }\n if (why === \"busy\") { want.why = \"busy\"; (want.busy ??= new Set()).add(peer); }\n want.tried.add(peer);\n want.holder = want.linkId = undefined;\n this.ask(kept);\n // The edge is free for a file waiting its turn there.\n this.retry(groupId);\n }\n\n /** A member says it holds these files: remembered as a holder of each (newest first), and asked when a want waits. */\n private heard(groupId: string, from: string, ids: string[]): void {\n const membership = this.deps.membership(groupId);\n if (!membership || from === membership.me || !membership.inRoster(from)) return;\n const newest = (holders: string[]) => newestHolder(from, holders);\n const edge = this.edgesById.get(this.deps.edges(groupId).get(from) ?? \"\");\n // Its edge, while free: a want queued behind other edges' files is asked again only if it can be asked there.\n let free: [string, string][] | undefined;\n for (const message of ids) {\n const key = `${groupId}\\n${message}`, kept = this.kept.get(this.byMessage.get(key) ?? \"\");\n if (!kept) {\n // Its message has not come yet (it is on its way in a catch-up): who holds it is kept for when it does.\n this.ahead.set(key, newest(this.ahead.get(key) ?? []));\n if (this.ahead.size > GROUP_FILE_LIMITS.heardAhead) this.ahead.delete(this.ahead.keys().next().value!);\n continue;\n }\n if (kept.mine) continue;\n kept.holders = newest(kept.holders);\n edge?.gone.delete(message);\n const want = this.wants.get(kept.id);\n if (!want) continue;\n want.tried.delete(from);\n want.busy?.delete(from);\n if (want.holder) continue;\n if (this.deps.transfers.get(kept.id)?.stage === \"queued\" && !this.freeFor(kept, want, free ??= this.freeEdges(groupId).filter(([key]) => key === from))) continue;\n this.ask(kept);\n free = undefined;\n }\n }\n\n // -- serving -------------------------------------------------------------------------------------------------\n\n /** A member asks for a file: offered over files/3 on its edge, or a `group-want-no` saying why not. */\n private async serve(edge: Edge, messageId: string): Promise<void> {\n const kept = this.kept.get(this.byMessage.get(`${edge.groupId}\\n${messageId}`) ?? \"\");\n const here = !!kept?.held && !!(await this.deps.store.get(kept.id).catch(() => undefined));\n // Decided and offered in one step: two wants at once never both find the last free place.\n const why = this.mayServe(edge, messageId, here ? kept : undefined);\n if (why || !kept) { this.deps.send(edge.linkId, { t: GROUP_WANT_NO_FRAME, g: edge.groupId, id: messageId, why: why ?? \"gone\" }); return; }\n const wire = toBase64Url(randomBytes(12));\n edge.serving.set(wire, kept.id);\n edge.servedMoved.set(wire, { confirmed: 0, at: this.deps.now(), from: this.deps.now() });\n const { name, mime, size, voice, video, image } = kept.meta;\n const file: FileInfo = { id: wire, name, mime, size, timestamp: this.deps.now(), ...(voice && { voice }), ...(video && { video }), ...(image && { image }) };\n edge.files.offer(file, kept.meta.d);\n }\n\n /**\n * Why this device does not serve a file now, or null when it does (WISP 503 § Asking, Serving limits): `refused` to\n * someone who could not read the announcement, `gone` when it is not here (`kept` undefined), `busy` when serving is\n * off or full.\n */\n private mayServe(edge: Edge, messageId: string, kept: Kept | undefined): GroupWantRefusal | null {\n const membership = this.deps.membership(edge.groupId);\n if (!membership || !membership.couldRead(edge.peer, messageId)) return \"refused\";\n if (!kept) return \"gone\";\n if (!this.deps.settings().serveFiles || !edge.files.live) return \"busy\";\n this.letGo();\n const active = this.servingNow();\n // One place per member, whatever it confirms. One that has all its bytes, lately, waits only for the member's word\n // that its check passed, which may come after this want: that one alone is not its place.\n const theirs = active.filter(a => a.peer === edge.peer);\n const placed = theirs.length > 1 || theirs.some(a => a.sending || this.deps.now() - a.since >= GROUP_FILE_LIMITS.doneWaitMs);\n // All places taken: a member holding none gets the one held longest past `servePlaceMs`, if there is one.\n if (active.length >= GROUP_FILE_LIMITS.serveAtOnce && (placed || !this.makeRoom())) { edge.busy.add(messageId); return \"busy\"; }\n if (placed) return \"busy\";\n const today = Math.floor(this.deps.now() / 86_400_000);\n if (this.served.day !== today) this.served = { day: today, bytes: 0 };\n const reserved = this.servingNow().reduce((sum, a) => sum + a.size, 0);\n return this.served.bytes + reserved + kept.meta.size > GROUP_FILE_LIMITS.serveBytesPerDay ? \"busy\" : null;\n }\n\n /** Ends the served transfer that has held its place longest, once past `servePlaceMs`: false when none has. */\n private makeRoom(): boolean {\n const now = this.deps.now();\n let oldest: { edge: Edge; wire: string; from: number } | undefined;\n for (const edge of this.edgesById.values()) for (const wire of edge.serving.keys()) {\n const record = edge.files.get(\"out\", wire), from = edge.servedMoved.get(wire)?.from ?? now;\n if (record && !transferEnded(record) && now - from >= GROUP_FILE_LIMITS.servePlaceMs && (!oldest || from < oldest.from)) oldest = { edge, wire, from };\n }\n if (!oldest) return false;\n oldest.edge.files.cancel(\"out\", oldest.wire);\n return true;\n }\n\n /**\n * Transfers served now, all groups together: to whom, what each takes of the day (its size, or what it sent when that\n * is more), whether bytes are still to go, and since when it has not gone further.\n */\n private servingNow(): { peer: string; size: number; sending: boolean; since: number }[] {\n const active: { peer: string; size: number; sending: boolean; since: number }[] = [];\n for (const edge of this.edgesById.values()) for (const wire of edge.serving.keys()) {\n const record = edge.files.get(\"out\", wire);\n if (record && !transferEnded(record)) {\n active.push({ peer: edge.peer, size: Math.max(record.file.size, edge.files.sent(wire)), sending: record.confirmed < record.file.size,\n since: edge.servedMoved.get(wire)?.at ?? this.deps.now() });\n }\n }\n return active;\n }\n\n /**\n * An asker has a served transfer send parts again (WISP 503 § Serving limits): every byte sent counts toward the day,\n * so it goes on only while the day has room for it.\n */\n private mayResend(): boolean {\n const today = Math.floor(this.deps.now() / 86_400_000);\n if (this.served.day !== today) this.served = { day: today, bytes: 0 };\n return this.served.bytes + this.servingNow().reduce((sum, a) => sum + a.size, 0) < GROUP_FILE_LIMITS.serveBytesPerDay;\n }\n\n /**\n * Served transfers that no longer count end, their places free for others (WISP 503 § Serving limits): the asker's\n * session closed, it is not in the roster any more, it has not had `serveMoveBytes` more confirmed (or the rest of\n * its file) for `wantWaitMs` (the asker has moved on by then), or it has all its bytes and the member's next want has\n * overtaken its word for `doneWaitMs`.\n */\n private letGo(): void {\n const now = this.deps.now();\n const theirs = new Map<string, number>();\n for (const a of this.servingNow()) theirs.set(a.peer, (theirs.get(a.peer) ?? 0) + 1);\n for (const edge of this.edgesById.values()) for (const wire of [...edge.serving.keys()]) {\n const record = edge.files.get(\"out\", wire);\n if (!record || transferEnded(record)) continue;\n let moved = edge.servedMoved.get(wire);\n if (!moved) edge.servedMoved.set(wire, (moved = { confirmed: record.confirmed, at: now, from: now }));\n const gone = !edge.files.live || !this.deps.membership(edge.groupId)?.inRoster(edge.peer);\n const overtaken = record.confirmed === record.file.size && theirs.get(edge.peer)! > 1 && now - moved.at >= GROUP_FILE_LIMITS.doneWaitMs;\n if (gone || overtaken || now - moved.at >= GROUP_FILE_LIMITS.wantWaitMs) {\n edge.files.cancel(\"out\", wire);\n theirs.set(edge.peer, theirs.get(edge.peer)! - 1);\n }\n }\n }\n\n /**\n * Says on an edge which files this device holds, as many frames as the edge takes now (`haveFrames` in\n * `haveWindowMs`); the ids past that, and those of a frame that could not go, wait for the next tick with room.\n */\n private sendHave(edge: Edge, ids: string[]): void {\n for (const id of ids) edge.have.add(id);\n for (let i = 0; i < ids.length; i += GROUP_FILE_LIMITS.haveIds) {\n const frame = ids.slice(i, i + GROUP_FILE_LIMITS.haveIds);\n if (edge.haveSent.wait() || !this.deps.send(edge.linkId, { t: GROUP_HAVE_FRAME, g: edge.groupId, ids: frame })) return;\n edge.haveSent.take();\n for (const id of frame) edge.have.delete(id);\n }\n }\n\n /**\n * This device holds a new file: the group hears it with the next `group-have`, which gathers what there is to say\n * until the next tick.\n */\n private queueHave(kept: Kept): void {\n if (!this.deps.settings().serveFiles) return;\n if (this.deps.membership(kept.groupId)?.community) {\n let have = this.communityHave.get(kept.groupId);\n if (!have) this.communityHave.set(kept.groupId, (have = { ids: new Set(), at: 0 }));\n have.ids.add(kept.message);\n return;\n }\n for (const [key, linkId] of this.deps.edges(kept.groupId)) this.edge(kept.groupId, key, linkId).have.add(kept.message);\n }\n\n // -- files/3 on an edge --------------------------------------------------------------------------------------\n\n private edge(groupId: string, peer: string, linkId: string): Edge {\n const known = this.edgesById.get(linkId);\n if (known) return known;\n const edge: Edge = { linkId, groupId, peer, files: undefined as unknown as ChatFiles, serving: new Map(), servedMoved: new Map(), taking: new Map(), ended: [], have: new Set(),\n haveSent: new RateWindow(GROUP_FILE_LIMITS.haveFrames, GROUP_FILE_LIMITS.haveWindowMs, () => this.deps.now()),\n haveTaken: new RateWindow(GROUP_FILE_LIMITS.haveFrames, GROUP_FILE_LIMITS.haveWindowMs, () => this.deps.now()), busy: new Set(), gone: new Set() };\n edge.files = new ChatFiles({\n // An offer names the group message it serves (`gm`), which a 1:1 chat never carries.\n send: frame => {\n const kept = frame.t === \"pf-offer\" ? this.kept.get(edge.serving.get(frame.id as string) ?? \"\") : undefined;\n return this.deps.sendFiles(linkId, kept ? { ...frame, gm: kept.message } : frame);\n },\n decide: file => this.decide(edge, file),\n openTarget: record => this.openTarget(edge, record),\n openSource: record => this.openSource(edge, record),\n changed: (record, transferred) => this.transferChanged(edge, record, transferred),\n room: () => this.deps.store.room(),\n ...(this.deps.sendData && { sendData: (id: string, offset: number, data: Uint8Array) => this.deps.sendData!(linkId, id, offset, data) }),\n writable: () => this.deps.writable?.(linkId),\n resend: record => !edge.serving.has(record.id) || this.mayResend(),\n });\n this.edgesById.set(linkId, edge);\n return edge;\n }\n\n /** An offer that answers my want (`filesFrame` checked that): taken, unless it is not the file announced or there is no room. */\n private async decide(edge: Edge, file: FileInfo): Promise<OfferDecision> {\n const kept = this.kept.get(edge.taking.get(file.id) ?? \"\"), want = kept && this.wants.get(kept.id);\n if (!kept || kept.held || want?.linkId !== edge.linkId || want.wire !== file.id || file.size !== kept.meta.size) return { refuse: \"invalid\" };\n const room = await this.deps.store.room().catch(() => null);\n return room !== null && file.size > room ? { refuse: \"no-room\", room } : \"accept\";\n }\n\n private async openTarget(edge: Edge, record: FileTransferRecord): Promise<IncomingTarget> {\n const kept = this.kept.get(edge.taking.get(record.id) ?? \"\");\n if (!kept) throw new Error(\"No such file\");\n // What an earlier holder's transfer stored stays, and this one goes on from there (WISP 503: the asker accepts from\n // the offset it stored): every holder serves the author's bytes, and the whole file is checked at the end.\n await kept.idle;\n const writer = await this.deps.store.write(kept.id, !kept.fresh);\n kept.fresh = false;\n const resumed = writer.offset > 0;\n if (!resumed) kept.storedBy = edge.peer;\n else if (kept.storedBy !== edge.peer) kept.storedBy = undefined;\n let damaged = false;\n // Its group was left (`drop`): what the transfer still has to write (its checkpoint as the edge closes) is deleted,\n // not stored as a file nothing knows of.\n const gone = () => this.kept.get(kept.id) !== kept;\n const write = async <T>(op: () => Promise<T>): Promise<T> => {\n if (gone()) { await writer.discard(); throw new Error(\"No such file\"); }\n return await (kept.writing = op());\n };\n return {\n offset: writer.offset,\n append: bytes => write(() => writer.append(bytes)),\n flush: () => write(() => writer.flush()),\n // Against the author's digest, read back from storage, not the holder's word: a holder can withhold a file, never\n // serve another one.\n verify: async () => {\n damaged = (await write(() => writer.digest())) !== kept.meta.d;\n const want = this.wants.get(kept.id);\n if (damaged && want) want.why = \"damaged\";\n return !damaged;\n },\n // Only damaged bytes go, and those of a file its person cancelled. Left with a holder that stalled or stopped, what\n // came is kept for the next holder.\n discard: () => {\n // Whole from this holder and damaged: its bytes are not the author's, and it is not asked for them again.\n if (damaged) {\n kept.stored = 0;\n if (resumed && kept.storedBy !== edge.peer) kept.fresh = kept.unclear = true; else kept.damaged.add(edge.peer);\n kept.storedBy = undefined;\n }\n return (kept.idle = (damaged || kept.dropped === record.id || gone() ? writer.discard() : writer.close()).catch(() => {}));\n },\n };\n }\n\n private async openSource(edge: Edge, record: FileTransferRecord): Promise<OutgoingSource> {\n const kept = this.kept.get(edge.serving.get(record.id) ?? \"\");\n const stored = kept && await this.deps.store.get(kept.id);\n if (!kept || !stored) throw new Error(\"The file is gone\");\n return { read: (offset, length) => this.deps.store.read(stored, offset, length), digest: async () => kept.meta.d };\n }\n\n /** A transfer on the edge ended: its record stays among the last `ENDED_KEPT`, and the oldest past that is forgotten. */\n private transferOver(edge: Edge, record: FileTransferRecord): void {\n if (edge.ended.some(([direction, id]) => direction === record.direction && id === record.id)) return;\n edge.ended.push([record.direction, record.id]);\n while (edge.ended.length > ENDED_KEPT) edge.files.forget(...edge.ended.shift()!);\n }\n\n private transferChanged(edge: Edge, record: FileTransferRecord, transferred: number): void {\n if (transferEnded(record)) this.transferOver(edge, record);\n if (record.direction === \"out\") {\n // Only bytes confirmed for the first time are progress, and only `serveMoveBytes` of them (or the rest of the\n // file): one accepted back and forth stays where it was, and a byte now and then does not keep a place.\n const moved = edge.servedMoved.get(record.id);\n if (moved && record.confirmed > moved.confirmed && (record.confirmed - moved.confirmed >= GROUP_FILE_LIMITS.serveMoveBytes || record.confirmed === record.file.size)) {\n edge.servedMoved.set(record.id, { ...moved, confirmed: record.confirmed, at: this.deps.now() });\n }\n if (transferEnded(record) && edge.serving.has(record.id)) {\n // What was sent to a member counts toward the day's serving, done or not, and whatever it confirmed.\n const today = Math.floor(this.deps.now() / 86_400_000);\n if (this.served.day !== today) this.served = { day: today, bytes: 0 };\n this.served.bytes += edge.files.sent(record.id);\n edge.serving.delete(record.id);\n edge.servedMoved.delete(record.id);\n // A slot is free: those turned away as busy hear this device holds their file (with the next `group-have`), and\n // ask it again rather than wait out their round.\n const serving = this.deps.settings().serveFiles;\n for (const other of this.edgesById.values()) {\n if (serving) for (const id of other.busy) other.have.add(id);\n other.busy.clear();\n }\n }\n return;\n }\n const kept = this.kept.get(edge.taking.get(record.id) ?? \"\"), want = kept && this.wants.get(kept.id);\n // A transfer this device moved on from (the holder stalled, then went on) changes nothing.\n if (!kept || !want || want.wire !== record.id || want.linkId !== edge.linkId) return;\n // Moving: `wantMoveBytes` more since it last moved (or the rest of the file), not any byte.\n if (want.moved < 0) want.moved = transferred;\n else if (transferred - want.moved >= Math.min(GROUP_FILE_LIMITS.wantMoveBytes, kept.meta.size - want.moved)) { want.moved = transferred; want.movedAt = this.deps.now(); }\n if (record.state !== \"failed\" && record.state !== \"declined\" && record.state !== \"cancelled\") kept.stored = transferred;\n const { size } = kept.meta;\n switch (record.state) {\n case \"done\": {\n kept.held = true;\n this.wants.delete(kept.id);\n edge.taking.delete(record.id);\n void this.deps.store.patch(kept.id, { transfer: { state: \"done\", transferred: size, size } }).catch(() => {});\n this.finished(kept, \"in\");\n this.queueHave(kept);\n // The holder is free for the next file this device waits for.\n this.retry(kept.groupId);\n return;\n }\n case \"failed\": case \"declined\": case \"cancelled\":\n // Damaged (refused, its bytes deleted), stopped by the holder or left here: the next holder is asked.\n edge.taking.delete(record.id);\n // A resumed file that failed its check may owe its bad bytes to the earlier holder: this one is asked again, from 0.\n if (want.holder && !kept.unclear) want.tried.add(want.holder);\n kept.unclear = false;\n Object.assign(want, { wire: undefined, holder: undefined, linkId: undefined, moved: 0 });\n this.ask(kept);\n this.retry(kept.groupId);\n return;\n case \"verifying\":\n this.show(kept, { state: \"transferring\", stage: \"verifying\", transferred, size, direction: \"in\" });\n return;\n default:\n this.show(kept, { state: \"transferring\", transferred, size, direction: \"in\" });\n }\n }\n\n // -- bookkeeping ---------------------------------------------------------------------------------------------\n\n private add(kept: Kept): void {\n this.kept.set(kept.id, kept);\n this.byMessage.set(`${kept.groupId}\\n${kept.message}`, kept.id);\n }\n\n /** Bytes this device fetched by itself from the group and keeps (or is fetching): what `autoBytesPerGroup` bounds. */\n private keptAutomatically(groupId: string): number {\n let sum = 0;\n for (const kept of this.kept.values()) if (kept.groupId === groupId && kept.auto && !kept.mine) sum += kept.meta.size;\n return sum;\n }\n\n private groupField(kept: Kept): NonNullable<StoredFile[\"group\"]> {\n return { message: kept.message, author: kept.author, ...(kept.auto && { auto: true }), ...(kept.asked && { asked: true }), ...(kept.paused && { paused: true }) };\n }\n\n /** The bytes of a file kept here from transfers that did not finish. */\n private async keptBytes(id: string): Promise<number> {\n const writer = await this.deps.store.write(id, true).catch(() => undefined);\n if (!writer) return 0;\n await writer.close().catch(() => {});\n return writer.offset;\n }\n\n private pace(windows: Map<string, RateWindow>, key: string, limit: number, ms: number): RateWindow {\n let window = windows.get(key);\n if (!window) windows.set(key, (window = new RateWindow(limit, ms, () => this.deps.now())));\n return window;\n }\n\n /** My announcements in a group, in a minute and in the window the members count them over. */\n private sentPaces(groupId: string): RateWindow[] {\n return [this.pace(this.sentPace, groupId, GROUP_FILE_LIMITS.announcePerMinute, 60_000),\n this.pace(this.sentTaken, groupId, GROUP_FILE_LIMITS.announceTaken, GROUP_FILE_LIMITS.announceWindowMs)];\n }\n\n /**\n * The file waits for its person's Download: said in one state (the CLI's `file.offered`), then left to its record,\n * which says it is not here (`waitingGroupFile`). A view kept for each such file would go into every state the engine\n * builds, so each would cost more with every file the groups ever had.\n */\n private waiting(kept: Kept): void {\n this.show(kept, { state: \"transferring\", stage: \"asking\", transferred: 0, size: kept.meta.size, direction: \"in\" });\n this.deps.brief(kept.id);\n }\n\n /**\n * All here: said in one state (the CLI's `file.done`), then left to its record, which says it is done. A view kept for\n * each file fetched or sent would go into every state the engine builds until the next start.\n */\n private finished(kept: Kept, direction: \"in\" | \"out\"): void {\n this.show(kept, { state: \"done\", transferred: kept.meta.size, size: kept.meta.size, direction });\n this.deps.brief(kept.id);\n }\n\n /** A file's row shows `view`; the engine hears of it only when that is not what the row shows already. */\n private show(kept: Kept, view: FileTransferView): void {\n if (sameView(this.deps.transfers.get(kept.id), view)) return;\n this.deps.transfers.set(kept.id, view);\n this.deps.changed();\n }\n}\n\n/** Two views of a transfer that say the same: their fields are all plain values. */\nfunction sameView(a: FileTransferView | undefined, b: FileTransferView): boolean {\n if (!a) return false;\n const keys = new Set([...Object.keys(a), ...Object.keys(b)] as (keyof FileTransferView)[]);\n for (const key of keys) if (a[key] !== b[key]) return false;\n return true;\n}\n\n/** A file's holders with `key` the newest of them, the `holders` newest kept. */\nconst newestHolder = (key: string, holders: string[]): string[] => [key, ...holders.filter(other => other !== key)].slice(0, GROUP_FILE_LIMITS.holders);\n\nfunction messageFile(kept: Kept): MessageFile {\n const { name, size, mime, voice, video, image } = kept.meta;\n return { id: kept.id, name, size, mime, ...(voice && { voice }), ...(video && { video }), ...(image && { image }) };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"groupFiles-BurezP4Z.js","names":[],"sources":["../../../browser/src/engine/paymentAdapters/away.ts","../../../browser/src/engine/paymentAdapters/modeGate.ts","../../../browser/src/shared/ice.ts","../../../browser/src/engine/paymentAdapters/providers/breezDatabases.ts","../../../browser/src/engine/paymentAdapters/providers/cashuMint.ts","../../../browser/src/engine/paymentAdapters/providers/sources.ts","../../../browser/src/engine/paymentAdapters/providers/lightningService.ts","../../../browser/src/engine/paymentAdapters/providers/recoveryPhrase.ts","../../../browser/src/engine/paymentAdapters/providers/lightningCards.ts","../../../browser/src/shared/storedFiles.ts","../../../browser/src/shared/types.ts","../../../browser/src/engine/paymentAdapters/walletInstances.ts","../../../browser/src/shared/paymentIds.ts","../../../browser/src/shared/replies.ts","../../../browser/src/engine/groupFiles.ts"],"sourcesContent":["import { engineText } from \"@ghostly/core\";\n\n/*\n * Wallets at home on another device (WISP 06 § Wallets that stay home): their database lives there, and no other\n * device opens them. The engine says which, by wallet id (`<type>:<network>`, `lightning:<network>:<card>`), before\n * any wallet opens; each wallet asks here before it opens its SDK, so no path that opens one (a start, a retry, a\n * restore, an unlock) opens a wallet that is away.\n */\n\nlet away = new Map<string, string>();\n\n/** The wallets away from this device, by wallet id, with the name of their home device (\"\" when it has none here). */\nexport function setAwayWallets(next: ReadonlyMap<string, string>): void {\n away = new Map(next);\n}\n\n/** The home device's name of a wallet away from this device, or undefined when it is here. */\nexport function awayFrom(id: string): string | undefined {\n return away.get(id);\n}\n\n/** A wallet that cannot be used here: it is at home on another device. */\nexport class WalletAwayError extends Error {\n constructor(wallet: string, device: string) {\n super(device ? engineText(\"walletAway\", { wallet, device }) : engineText(\"walletAwayUnnamed\", { wallet }));\n this.name = \"WalletAwayError\";\n }\n}\n\n/**\n * Whether a single-writer wallet (Ark, Bark, Spark and the Breez source, Fedimint) may open its SDK now: the engine\n * says, from a good turn read under 60 seconds old (WISP 06 § Wallets). Every open asks, the retries of each wallet\n * included, so a device replaced while it was offline does not reopen one on a retry.\n */\nlet turnGate: () => Promise<boolean> = async () => true;\nexport function setSingleWriterGate(gate: (() => Promise<boolean>) | null): void {\n turnGate = gate ?? (async () => true);\n}\n\n/** Throws (the wallet tries again later, as it does when its server does not answer) without a fresh good turn read. */\nexport async function requireTurn(): Promise<void> {\n if (!(await turnGate().catch(() => false))) throw new Error(\"Checking which device is active\");\n}\n\n/** Throws when the wallet `id` is at home on another device. */\nexport function refuseAway(id: string, wallet: string): void {\n const device = awayFrom(id);\n if (device !== undefined) throw new WalletAwayError(wallet, device);\n}\n","import type { WalletNetwork } from \"@ghostly/core\";\nimport type { WalletMode } from \"../../shared/mints\";\n\n/** A wallet of the mode being left stopped opening: the switch goes ahead without waiting for its network. */\nexport class ModeChanged extends Error {\n constructor() { super(\"The wallets switched mode\"); this.name = \"ModeChanged\"; }\n}\n\nexport const networkLabel = (network: WalletNetwork) => network === \"mainnet\" ? \"Mainnet\" : \"Testnet\";\n\n/**\n * What was asked of one network's wallet belongs to the other network's (a backup, a chain): nothing was changed.\n * `network` is where it belongs, so the engine can hand it to that wallet instead.\n */\nexport class WrongNetworkError extends Error {\n constructor(readonly network: WalletNetwork, message: string) { super(message); this.name = \"WrongNetworkError\"; }\n}\n\n/**\n * Opening a wallet waits on its network (an Ark server, an RPC), which can take minutes or never answer.\n * A mode switch must not wait behind that: `within` gives up on the wait as soon as the mode changes, and\n * whatever still arrives afterwards is handed to `dispose`. Nothing is saved before that wait ends, so\n * giving up leaves the profile as it was. Every wait on the network inside a wallet's queue goes through it,\n * reading a balance too, or the switch, queued behind it, waits anyway.\n */\nexport class ModeGate {\n constructor(private target: WalletMode = \"mainnet\") {}\n private controller = new AbortController();\n /** The mode its owner works in, once it says so: a wait that starts while a switch away from it is queued ends at once. */\n private current?: WalletMode;\n\n /** A switch to `mode` was asked for: waits for a wallet of another mode end now. */\n switching(mode: WalletMode) {\n if (this.target === mode) return;\n this.target = mode;\n this.controller.abort();\n this.controller = new AbortController();\n }\n\n /** The owner's switch to `mode` is applied (its queue reached it): waits are for this mode again. */\n entered(mode: WalletMode) { this.current = mode; }\n\n /**\n * A creation took too long: the waits under way end now, and so does every wait that starts until `resume`\n * (a creation still sealing its seed has not reached its server yet, and must not wait there either).\n */\n interrupt() { this.cut = true; this.controller.abort(); this.controller = new AbortController(); }\n resume() { this.cut = false; }\n private cut = false;\n\n /** The wallet stopped: every wait ends now, and any later one at once. */\n close() { this.closed = true; this.controller.abort(); }\n private closed = false;\n\n within<T>(work: Promise<T>, dispose?: (value: T) => unknown): Promise<T> {\n const signal = this.controller.signal;\n return new Promise<T>((resolve, reject) => {\n const give = () => { reject(new ModeChanged()); void work.then((value) => dispose?.(value), () => {}); };\n if (this.closed || this.cut || (this.current && this.current !== this.target)) return give();\n signal.addEventListener(\"abort\", give, { once: true });\n work.then(\n (value) => { signal.removeEventListener(\"abort\", give); if (!signal.aborted) resolve(value); },\n (error) => { signal.removeEventListener(\"abort\", give); if (!signal.aborted) reject(error); },\n );\n });\n }\n}\n","import type { IceServerSetting } from \"./types\";\n\n/**\n * What is wrong with a TURN/STUN server, or null. Browsers refuse to create any WebRTC connection when\n * one server in the list is malformed, so a bad entry here would silently cut off calls and files.\n */\nexport function iceServerProblem(server: IceServerSetting): string | null {\n // A setting from outside the form (the CLI, `ghostly engine`, an import) may have no urls at all: before, a TypeError.\n if (typeof server?.urls !== \"string\") return \"Each ICE server needs urls: one address, or several comma-separated (turn:host:port)\";\n const urls = server.urls.split(/[\\s,]+/).filter(Boolean);\n if (!urls.length) return \"Enter the TURN server address\";\n for (const url of urls) {\n if (!/^(stun|turns?):[^\\s/?#]+(\\?transport=(udp|tcp))?$/i.test(url)) return `“${url}” is not a TURN or STUN address (turn:host:port)`;\n if (/^turns?:/i.test(url) && (!server.username?.trim() || !server.credential?.trim())) return \"A TURN server needs its username and credential\";\n }\n return null;\n}\n\n/**\n * A server as WebRTC writes it (`urls` a list, as every TURN guide does) in this setting's own form: the addresses\n * comma-separated. Anything else is left as it is, for `iceServerProblem` to say what is wrong.\n */\nexport function iceServerSetting(server: IceServerSetting): IceServerSetting {\n const urls: unknown = server?.urls;\n if (!Array.isArray(urls) || !urls.every((url) => typeof url === \"string\")) return server;\n return { ...server, urls: urls.join(\", \") };\n}\n","import { sha256 } from \"@noble/hashes/sha2.js\";\nimport { databaseName } from \"../../../shared/idb\";\n\n/**\n * Where the Breez SDK keeps a wallet on this device, and which profile it belongs to.\n *\n * The SDK keeps its IndexedDB databases under one storage name (`breezDatabaseFiles`). That name is one per **profile,\n * network and seed**: the Spark rail and a Breez Lightning card of the same phrase in one profile are one SDK instance\n * over one database (`openBreez`), two phrases are two wallets, and a profile restored as a copy on the same device\n * starts from the phrase with a database of its own. Two SDK instances over one database would each think the leaves\n * are theirs.\n *\n * Before this, the name came from the network and the seed only, so every profile of the origin holding that phrase\n * opened the same database. Such a database is kept, under its old name, for **one** profile: the first to open the\n * phrase on this device after the change claims it in a small database of the device (`ghostly-breez`), outside every\n * profile database (a backup copies a profile's database whole, so a mark in it would be the copy's too). Any other\n * profile gets a new name. The same register lists every name a profile uses, so deleting a profile deletes its Breez\n * databases without opening its sealed phrases.\n */\nexport type BreezDatabaseNetwork = \"mainnet\" | \"regtest\";\nconst netOf = (network: string): BreezDatabaseNetwork => (network === \"bitcoin\" || network === \"mainnet\" ? \"mainnet\" : \"regtest\");\nconst hex = (bytes: Uint8Array) => Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\nconst digest = (text: string) => hex(sha256(new TextEncoder().encode(text))).slice(0, 16);\n\n/** What a Breez database name looks like, old or new: nothing about the seed or the profile shows in it. */\nexport const BREEZ_DATABASE = /^ghostly-breez-(mainnet|regtest)-[0-9a-f]{16}$/;\n\n/** The name every profile of the origin used for this phrase before databases were per profile. */\nexport const breezLegacyName = (network: string, mnemonic: string) => `ghostly-breez-${netOf(network)}-${digest(`ghostly-breez:${netOf(network)}:${mnemonic}`)}`;\n\n/** The name of this phrase's Breez database in one profile (`space`: the profile's peer database, WISP 04). */\nexport const breezDatabaseName = (network: string, mnemonic: string, space: string = databaseName()) =>\n `ghostly-breez-${netOf(network)}-${digest(`ghostly-breez:${space}:${netOf(network)}:${mnemonic}`)}`;\n\n/**\n * The databases the SDK keeps under one storage name. SDK 0.26 roots them there: `<name>/<network>/<identity>` and\n * `<name>/<network>/<identity>-tree`, the identity read from the seed inside the SDK. Older builds kept `<name>`\n * itself. A browser without `indexedDB.databases()` cannot list them: only the bare names are known there.\n */\nexport async function breezDatabaseFiles(name: string): Promise<string[]> {\n const files = [name, `${name}-tree`];\n if (typeof indexedDB.databases !== \"function\") return files;\n const under = (await indexedDB.databases()).map((d) => d.name ?? \"\").filter((file) => file.startsWith(`${name}/`));\n return [...new Set([...files, ...under])];\n}\n\ninterface Entry { name: string; space: string; network: BreezDatabaseNetwork; legacy?: true }\nconst REGISTER = \"ghostly-breez\", STORE = \"databases\";\n\nconst request = <T>(r: IDBRequest<T>) => new Promise<T>((resolve, reject) => { r.onsuccess = () => resolve(r.result); r.onerror = () => reject(r.error); });\nfunction openRegister(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const open = indexedDB.open(REGISTER, 1);\n open.onupgradeneeded = () => { if (!open.result.objectStoreNames.contains(STORE)) open.result.createObjectStore(STORE); };\n open.onsuccess = () => resolve(open.result);\n open.onerror = () => reject(open.error);\n open.onblocked = () => reject(new Error(\"The Breez register is blocked\"));\n });\n}\nasync function withRegister<T>(mode: IDBTransactionMode, run: (store: IDBObjectStore) => Promise<T> | T): Promise<T> {\n const db = await openRegister();\n try {\n const tx = db.transaction(STORE, mode);\n const done = new Promise<void>((resolve, reject) => { tx.oncomplete = () => resolve(); tx.onerror = tx.onabort = () => reject(tx.error); });\n const result = await run(tx.objectStore(STORE));\n await done;\n return result;\n } finally { db.close(); }\n}\n\n/**\n * Whether the SDK keeps a wallet under this storage name on this device (`breezDatabaseFiles`). A browser that cannot\n * list databases is answered yes: the first profile then takes the old name whether or not it is there, which is\n * safe, since only one profile ever has it.\n */\nasync function exists(name: string): Promise<boolean> {\n if (typeof indexedDB.databases !== \"function\") return true;\n return (await indexedDB.databases()).some((d) => d.name === name || !!d.name?.startsWith(`${name}/`));\n}\n\n/**\n * The Breez database this profile opens for this phrase: the old shared one when this profile owns it (or is the\n * first to claim it), else the profile's own. Noted in the register either way. A device whose IndexedDB cannot be\n * read gets the profile's own name: never a database another profile may use.\n */\nexport async function breezDatabase(network: string, mnemonic: string, space: string = databaseName()): Promise<string> {\n const fresh = breezDatabaseName(network, mnemonic, space);\n if (typeof indexedDB === \"undefined\") return fresh;\n const legacy = breezLegacyName(network, mnemonic);\n try {\n const legacyHere = await exists(legacy);\n return await withRegister(\"readwrite\", async (store) => {\n // Both reads and the write in one transaction: two profiles opening at once cannot both claim the old one.\n const [owner, own] = await Promise.all([request(store.get(legacy)) as Promise<Entry | undefined>, request(store.get(fresh)) as Promise<Entry | undefined>]);\n if (owner?.space === space) return legacy;\n if (!own && !owner && legacyHere) { store.put({ name: legacy, space, network: netOf(network), legacy: true } satisfies Entry, legacy); return legacy; }\n if (!own) store.put({ name: fresh, space, network: netOf(network) } satisfies Entry, fresh);\n return fresh;\n });\n } catch { return fresh; }\n}\n\n/** The names this profile has noted for this phrase (nothing is claimed): what removing the wallet deletes. */\nexport async function breezDatabasesFor(network: string, mnemonic: string, space: string = databaseName()): Promise<string[]> {\n const names = [breezLegacyName(network, mnemonic), breezDatabaseName(network, mnemonic, space)];\n return (await breezDatabasesOf(space)).filter((name) => names.includes(name));\n}\n\n/**\n * The name this profile uses for this phrase, read without claiming anything: the one it noted (`breezDatabase`), else\n * the profile's own name. What a handoff deletes and compares (WISP 06 § Wallets): never the old shared database of\n * another profile that has not opened the phrase since the change.\n */\nexport async function breezDatabaseInUse(network: string, mnemonic: string, space: string = databaseName()): Promise<string> {\n return (await breezDatabasesFor(network, mnemonic, space))[0] ?? breezDatabaseName(network, mnemonic, space);\n}\n\n/** Every Breez database name noted for a profile of this device. */\nexport async function breezDatabasesOf(space: string): Promise<string[]> {\n if (typeof indexedDB === \"undefined\") return [];\n try { return ((await withRegister(\"readonly\", (store) => request(store.getAll()))) as Entry[]).filter((e) => e?.space === space && BREEZ_DATABASE.test(e.name)).map((e) => e.name); }\n catch { return []; }\n}\n\nconst deleteDatabase = (name: string) => new Promise<void>((resolve) => {\n try { const r = indexedDB.deleteDatabase(name); r.onsuccess = r.onerror = r.onblocked = () => resolve(); } catch { resolve(); }\n});\n\nconst deleteStrictly = (name: string) => new Promise<void>((resolve, reject) => {\n const r = indexedDB.deleteDatabase(name);\n r.onsuccess = () => resolve();\n r.onerror = () => reject(r.error ?? new Error(`${name} was not deleted`));\n r.onblocked = () => reject(new Error(`${name} is still open`));\n});\n\n/**\n * Deletes one Breez wallet's databases (all the SDK keeps under the name) and its note in the register. Anything else\n * is refused. `strict` (a handoff, WISP 06 § Wallets): a delete that is blocked or fails rejects and the note stays,\n * so the caller tries again.\n */\nexport async function dropBreezDatabase(name: string, options: { strict?: boolean } = {}): Promise<void> {\n if (!BREEZ_DATABASE.test(name) || typeof indexedDB === \"undefined\") return;\n for (const file of await breezDatabaseFiles(name).catch(() => [name, `${name}-tree`])) await (options.strict ? deleteStrictly(file) : deleteDatabase(file));\n await withRegister(\"readwrite\", (store) => { store.delete(name); }).catch(() => {});\n}\n\n/** Deletes every Breez database a profile used on this device (the profile is being deleted). */\nexport async function dropBreezDatabasesOf(space: string): Promise<void> {\n for (const name of await breezDatabasesOf(space)) await dropBreezDatabase(name);\n}\n","import { decodeBolt11, engineError } from \"@ghostly/core\";\nimport type { WalletMode } from \"../../../shared/mints\";\nimport type { CashuWallet } from \"../../wallet\";\nimport type { LightningInvoice, LightningPaymentRef, LightningProvider, LightningProviderDescriptor } from \"./lightning\";\nimport { NothingSpentError, PROVIDER_PLATFORMS } from \"./types\";\n\nexport const CASHU_MINT_SOURCE = \"cashu-mint\";\n\ninterface MintRef { mint: string; quote: string }\nconst parseRef = (ref: string | undefined): MintRef => {\n const parsed = JSON.parse(ref ?? \"null\") as MintRef | null;\n if (!parsed?.mint || !parsed.quote) throw new Error(\"Unknown Cashu quote\");\n return parsed;\n};\n\n/**\n * Lightning through the Cashu mints: invoices are mint quotes that turn into ecash once paid, and\n * invoices are paid by melting ecash. The default source, and what Lightning was before sources.\n *\n * The Cashu wallet already polls its quotes and pending melts, mints and books them and says so\n * (`onQuotePaid`, `onMeltResolved`): the engine forwards those, and never polls this source itself.\n */\nexport class CashuMintLightning implements LightningProvider {\n readonly capabilities = { receive: true, send: true, balance: true, lookup: true };\n readonly settlesItself = true;\n /** The melt quote shown to the person is the one paid: quoting again could change the fee. */\n private readonly quotes = new Map<string, { quote: string; mint: string; amount: number; feeReserve: number }>();\n\n constructor(private readonly wallet: CashuWallet, private readonly mode: WalletMode) {}\n\n async info() {\n // Test mints issue invoices for worthless sats (often with a mainnet prefix): the mode says which it is.\n return { network: this.mode === \"mainnet\" ? \"bitcoin\" as const : \"testnet\" as const, balance: (await this.wallet.view(this.mode)).balance };\n }\n\n async createInvoice(amount: number): Promise<LightningInvoice> {\n const quote = await this.wallet.receiveLightning(amount, undefined, this.mode);\n const decoded = decodeBolt11(quote.invoice);\n if (!decoded?.paymentHash) throw new Error(\"The mint returned an invoice that cannot be read\");\n return { invoice: quote.invoice, paymentHash: decoded.paymentHash, amount, expiresAt: quote.expiresAt ?? decoded.expiresAt * 1000, ref: JSON.stringify({ mint: quote.mint, quote: quote.quote }) };\n }\n\n async invoiceStatus(invoice: LightningInvoice) {\n const { mint, quote } = parseRef(invoice.ref);\n // A test mint's invoice reads paid by itself: until a payer says it paid, it is open (see CashuWallet.vouch).\n const state = await this.wallet.isHeld(quote) ? \"UNPAID\" : await this.wallet.mintQuoteState(mint, quote);\n if (state === \"PAID\" || state === \"ISSUED\") return { state: \"paid\" as const, amount: invoice.amount };\n return { state: invoice.expiresAt < Date.now() ? \"expired\" as const : \"open\" as const };\n }\n\n /** A real melt quote: its fee reserve is what paying may cost at most. It is kept for `payInvoice`. */\n async estimateFee(invoice: string) {\n const quote = await this.wallet.quoteInvoice(invoice, this.mode);\n this.quotes.set(invoice.trim().toLowerCase(), quote);\n return quote.feeReserve;\n }\n\n /** The quote behind a fee just shown, when there is one: `payQuote` pays exactly that. */\n quoteFor(invoice: string) {\n return this.quotes.get(invoice.trim().toLowerCase());\n }\n\n /** The melt quote `payInvoice` pays, when its fee was shown first: what the mint is asked about later. */\n paymentRef(invoice: string) {\n const quote = this.quotes.get(invoice.trim().toLowerCase());\n return quote ? JSON.stringify({ mint: quote.mint, quote: quote.quote } satisfies MintRef) : undefined;\n }\n\n /**\n * A payment the app was cut off from (the tab closed while it was being paid). A melt written down is the wallet's\n * to settle and report. With none, the mint's word on the quote decides: paid, or never melted (the melt is written\n * down before the mint is handed anything, so an unpaid quote with no melt spent nothing).\n */\n async interruptedPayment(payment: LightningPaymentRef) {\n if (await this.wallet.meltInFlight(payment.invoice)) return \"pending\" as const;\n if (!payment.ref) return \"unknown\" as const;\n const { mint, quote } = parseRef(payment.ref);\n const state = await this.wallet.meltQuoteState(mint, quote);\n // Asked again: a melt written while the mint was being asked is the wallet's to settle.\n if (await this.wallet.meltInFlight(payment.invoice)) return \"pending\" as const;\n return state === \"PAID\" ? \"paid\" as const : state === \"PENDING\" ? \"pending\" as const : \"failed\" as const;\n }\n\n async payInvoice(invoice: string, maxFee: number, note?: string) {\n const key = invoice.trim().toLowerCase();\n let quote = this.quotes.get(key);\n // Nothing reaches the mint's melt before `payQuote`, and it throws only when the sats did not leave.\n try {\n quote ??= await this.wallet.quoteInvoice(invoice, this.mode);\n if (quote.feeReserve > maxFee) throw engineError(\"lightningFeeTooHigh\", { fee: quote.feeReserve });\n } catch (error) {\n throw new NothingSpentError(error instanceof Error ? error.message : String(error));\n }\n this.quotes.delete(key);\n const ref = JSON.stringify({ mint: quote.mint, quote: quote.quote });\n let paid: boolean;\n try { paid = await this.wallet.payQuote(quote.quote, quote.mint, note); }\n catch (error) { throw new NothingSpentError(error instanceof Error ? error.message : String(error)); }\n return paid ? { state: \"paid\" as const, ref } : { state: \"pending\" as const, ref };\n }\n\n async paymentStatus(payment: LightningPaymentRef) {\n const { mint, quote } = parseRef(payment.ref);\n const state = await this.wallet.meltQuoteState(mint, quote);\n return { state: state === \"PAID\" ? \"paid\" as const : state === \"PENDING\" ? \"pending\" as const : \"failed\" as const };\n }\n\n async close() { this.quotes.clear(); }\n}\n\nexport const cashuMint: LightningProviderDescriptor = {\n id: CASHU_MINT_SOURCE,\n label: \"Cashu mints\",\n kind: \"lightning\",\n description: \"Invoices are paid into, and paid from, your Cashu balance. The mints hold the sats.\",\n networks: [\"bitcoin\", \"testnet\"],\n platforms: PROVIDER_PLATFORMS,\n fields: [],\n custodial: true,\n async create(_settings, host) { return new CashuMintLightning(host.cashu as CashuWallet, host.mode); },\n};\n","import { STORES, store, transact, wrap } from \"../../../shared/idb\";\nimport type { WalletMode } from \"../../../shared/mints\";\nimport { ModeChanged, ModeGate } from \"../modeGate\";\nimport { newDeviceKey, sealSeed, unsealSeed, type EncryptedSeed } from \"../persistence\";\nimport { requireTurn } from \"../away\";\nimport { describeProvider, networkMode, offeredIn, redact, sourceProblem, type ProviderDescriptor, type ProviderDescriptorView, type ProviderHost, type ProviderKind, type ProviderNetwork, type ProviderSettings, type SourceProblem } from \"./types\";\n\n/**\n * What is stored for a source, under `<kind>Source-<mode>` (or `<kind>Source-<mode>-<key>`, one Lightning card of\n * several) in the settings store: never in the Settings object (which the UI sees), and its secrets sealed with a\n * device key, like the Ark and USDT seeds.\n */\ninterface StoredSource {\n providerId: string;\n config: Record<string, string>;\n secrets?: EncryptedSeed;\n deviceKey?: string;\n savedAt: number;\n}\n\n/** The last balance a source read, under `<kind>SourceSeen-<mode>`: shown while it reconnects after a restart. */\ninterface SeenBalance { providerId: string; balance?: number; unconfirmed?: number; at: number }\n\nexport interface SourceView {\n mode: WalletMode;\n /** Absent: no source (on-chain has none until one is chosen). */\n providerId?: string;\n label?: string;\n /** The built-in default of this kind (the Cashu mints for Lightning). */\n isDefault?: boolean;\n custodial?: boolean;\n /** The non-secret settings, as saved. */\n config?: Record<string, string>;\n /** The names of the secret fields that are saved. Their values never leave the engine. */\n secrets?: string[];\n status: \"none\" | \"connecting\" | \"ready\" | \"error\";\n network?: ProviderNetwork;\n alias?: string;\n /** Spendable sats, when the source says. */\n balance?: number;\n /** What the source holds was read once since it connected (or it says it shows none): until then `balance` says nothing. */\n read?: true;\n /** On-chain: sats in the mempool. */\n unconfirmed?: number;\n /**\n * The balance shown is the last one read (at this time), not a fresh one: the source is not connected.\n * Absent once it reads again.\n */\n balanceAt?: number;\n /**\n * While `connecting` after a failed attempt, why it failed (it is being tried again by itself); while\n * `error`, why the source is unavailable.\n */\n error?: string;\n /** What kind of failure `error` is: no answer (tried again by itself), a setting to change, or other. */\n problem?: SourceProblem;\n /** Attempts that failed in a row, and when the next one starts (it also starts on Retry, or on waking). */\n failures?: number;\n retryAt?: number;\n /** The providers that can be picked here (this platform, this mode), in registry order. */\n offered: ProviderDescriptorView[];\n}\n\ninterface Connectable { info(): Promise<{ network: ProviderNetwork; alias?: string }>; close(): Promise<void> }\n\nexport interface SourcesOptions<P> {\n kind: ProviderKind;\n /** The network this source belongs to: Mainnet and Testnet each have their own, both connected at once. */\n network: WalletMode;\n descriptors: () => readonly ProviderDescriptor<P>[];\n /** Read each time a source connects. */\n host: () => Omit<ProviderHost, \"mode\" | \"signal\">;\n changed: () => void;\n /** Used while nothing is stored for the mode. */\n defaultId?: string;\n /** One of several sources of this kind and mode (a Lightning card): its own storage keys. Absent: the mode's one. */\n key?: string;\n /** Asked before the source of a mode changes: a refusal (money in flight through it) stops the change. */\n refuseReplacing?: (providerId: string, mode: WalletMode) => Promise<string | undefined>;\n /** Balance and the like, read after connecting and then every `refreshMs`. */\n refresh?: (provider: P) => Promise<Partial<Pick<SourceView, \"balance\" | \"unconfirmed\">>>;\n refreshMs?: number;\n /** How long to wait before the next attempt after `failures` failed ones (tests shorten it). */\n backoff?: (failures: number) => number;\n}\n\n/** Where a source is stored: the mode's one, or one of several (`key`). */\nexport const sourceKey = (kind: ProviderKind, mode: WalletMode, key?: string) => `${kind}Source-${mode}${key ? `-${key}` : \"\"}`;\nconst seenKey = (kind: ProviderKind, mode: WalletMode, key?: string) => `${kind}SourceSeen-${mode}${key ? `-${key}` : \"\"}`;\n\n/**\n * A source that does not answer is tried again by itself: 2 s, 4 s, 8 s… up to 5 minutes, ±20 % so\n * that many apps coming back online do not knock at once.\n */\nexport const RETRY_MAX_MS = 5 * 60_000;\nexport function retryDelay(failures: number, random = Math.random): number {\n const base = Math.min(RETRY_MAX_MS, 2_000 * 2 ** Math.max(0, failures - 1));\n return Math.round(base * (0.8 + 0.4 * random()));\n}\n/**\n * \"Unavailable\" only once it has failed this many times in a row for at least this long: a slow server at\n * start-up is \"Connecting…\". A wrong setting is unavailable at once (waiting does not fix it).\n */\nexport const SUSTAINED_FAILURES = 3;\nexport const SUSTAINED_MS = 30_000;\n\n/**\n * Gives up on a connection when its signal aborts (a change of settings interrupts an attempt), even if the\n * provider does not listen to it; what still arrives afterwards is closed.\n */\nfunction abortable<P extends Connectable>(work: Promise<P>, signal: AbortSignal): Promise<P> {\n return new Promise<P>((resolve, reject) => {\n const give = () => { reject(new Error(\"The connection attempt was interrupted\")); void work.then((p) => p.close().catch(() => {}), () => {}); };\n if (signal.aborted) return give();\n signal.addEventListener(\"abort\", give, { once: true });\n work.then(\n (value) => { signal.removeEventListener(\"abort\", give); if (!signal.aborted) resolve(value); },\n (error) => { signal.removeEventListener(\"abort\", give); if (!signal.aborted) reject(error); },\n );\n });\n}\n\n/**\n * The active source of one kind (Lightning, on-chain) of one network: Mainnet and Testnet each have their\n * own, both connected at once. Every change is serialized.\n */\nexport class ProviderSources<P extends Connectable> {\n private readonly mode: WalletMode;\n private stored?: StoredSource;\n private descriptor?: ProviderDescriptor<P>;\n private provider?: P;\n /** The connected source's secrets, only to blank them out of error messages. */\n private secrets: Record<string, string> = {};\n private controller = new AbortController();\n private readonly gate = new ModeGate();\n private queue: Promise<unknown> = Promise.resolve();\n private connecting?: Promise<void>;\n private timer?: ReturnType<typeof setTimeout>;\n private retry?: ReturnType<typeof setTimeout>;\n /** Failed attempts in a row, and since when: what makes a failure sustained. */\n private failures = 0;\n private failingSince?: number;\n private seen?: SeenBalance;\n private stopped = false;\n view: SourceView = { mode: \"mainnet\", status: \"none\", offered: [] };\n\n constructor(private readonly options: SourcesOptions<P>) { this.mode = options.network; this.view = { mode: options.network, status: \"none\", offered: [] }; }\n\n private serial<T>(run: () => Promise<T>): Promise<T> { const next = this.queue.then(run, run); this.queue = next.catch(() => {}); return next; }\n private find(id: string) { return this.options.descriptors().find((d) => d.id === id); }\n private offered() { return this.options.descriptors().filter((d) => offeredIn(d, this.options.host().platform, this.mode)).map((d) => describeProvider(d as ProviderDescriptor<unknown>)); }\n private async load(mode: WalletMode) {\n const settings = await store(STORES.settings, \"readonly\");\n const [stored, seen] = await Promise.all([wrap<StoredSource | undefined>(settings.get(sourceKey(this.options.kind, mode, this.options.key))), wrap<SeenBalance | undefined>(settings.get(seenKey(this.options.kind, mode, this.options.key)))]);\n this.stored = stored; this.seen = seen;\n this.failures = 0; this.failingSince = undefined;\n }\n\n /** The source of the mode (stored, else the default), not connected yet: with the last balance it read. */\n private idle(): SourceView {\n const id = this.stored?.providerId ?? this.options.defaultId;\n const descriptor = id ? this.find(id) : undefined;\n const seen = id && this.seen?.providerId === id ? this.seen : undefined;\n return {\n mode: this.mode, offered: this.offered(), providerId: id, label: descriptor?.label ?? id, custodial: descriptor?.custodial,\n isDefault: !!id && id === this.options.defaultId, config: this.stored?.config,\n secrets: descriptor?.fields.filter((f) => f.kind === \"secret\" && this.stored?.secrets).map((f) => f.name),\n status: !id ? \"none\" : descriptor ? \"connecting\" : \"error\",\n error: id && !descriptor ? `${id} is not available in this version of Ghostly` : undefined,\n ...(seen ? { balance: seen.balance, unconfirmed: seen.unconfirmed, balanceAt: seen.at } : {}),\n };\n }\n\n async start() { await this.load(this.mode); this.view = this.idle(); }\n\n /** One saved secret of the source (a handoff asks whether an LND card pins its certificate, and a Breez phrase), or undefined. */\n async secret(name: string): Promise<string | undefined> {\n const stored = this.stored;\n if (!stored?.secrets) return undefined;\n const secrets = JSON.parse(await unsealSeed(stored.secrets, stored.deviceKey ?? \"\")) as Record<string, unknown>;\n const value = secrets[name];\n return typeof value === \"string\" && value ? value : undefined;\n }\n\n /** The list of providers changed (a plugin registered): the picker is told, nothing reconnects. */\n refreshOffered() { this.view = { ...this.view, offered: this.offered() }; this.options.changed(); }\n\n /**\n * Connects the mode's source in the background, and keeps trying while it cannot be reached: \"Connecting…\"\n * (with the last balance it read) until the failure is sustained, then \"Unavailable\", still retrying with\n * backoff. `eager` (Retry, waking up): shows \"Connecting…\" again while it tries.\n */\n ensureReady(eager = false): Promise<void> {\n return this.connecting ??= this.serial(() => this.connect(eager)).finally(() => { this.connecting = undefined; });\n }\n\n /** Retry, now: the wait before the next attempt is skipped. Resolves when the attempt ends. */\n retryNow(): Promise<void> {\n if (this.provider || this.stopped) return Promise.resolve();\n clearTimeout(this.retry);\n return this.ensureReady(true);\n }\n\n /**\n * The app is back (in front, online, the computer woke): a source that is not connected is tried now, and\n * a connected one reads its balance now.\n */\n wake() {\n if (this.stopped) return;\n if (this.provider) { void this.refresh(); return; }\n if (this.failures && !this.connecting) void this.retryNow();\n }\n\n private async connect(eager = false) {\n clearTimeout(this.retry);\n if (this.stopped || this.provider) return;\n const id = this.stored?.providerId ?? this.options.defaultId;\n const descriptor = id ? this.find(id) : undefined;\n if (!id || !descriptor) { this.view = this.idle(); this.options.changed(); return; }\n if (eager && this.view.status === \"error\") { this.view = { ...this.view, status: \"connecting\", retryAt: undefined }; this.options.changed(); }\n const signal = this.controller.signal;\n let secrets: Record<string, string> = {};\n try {\n if (this.stored?.secrets) secrets = JSON.parse(await unsealSeed(this.stored.secrets, this.stored.deviceKey ?? \"\")) as Record<string, string>;\n const { provider, network, alias } = await this.open(descriptor, { config: this.stored?.config ?? {}, secrets });\n this.descriptor = descriptor; this.provider = provider; this.secrets = secrets;\n this.failures = 0; this.failingSince = undefined;\n this.view = { ...this.idle(), status: \"ready\", network, alias };\n this.options.changed();\n void this.refresh();\n } catch (error) {\n // Stopped, another mode, or interrupted by a change of settings: whoever did it connects next.\n if (this.stopped || error instanceof ModeChanged || signal.aborted) return;\n const now = Date.now();\n this.failures++;\n this.failingSince ??= now;\n const problem = sourceProblem(error);\n const sustained = problem === \"config\" || (this.failures >= SUSTAINED_FAILURES && now - this.failingSince >= SUSTAINED_MS);\n const delay = (this.options.backoff ?? retryDelay)(this.failures);\n this.view = { ...this.idle(), status: sustained ? \"error\" : \"connecting\", error: `Could not connect to ${descriptor.label}: ${redact(error, secrets)}`, problem, failures: this.failures, retryAt: now + delay };\n this.options.changed();\n this.retry = setTimeout(() => void this.ensureReady(), delay);\n }\n }\n\n /** Creates a provider and checks it is on a network of this mode before anything uses it. */\n private async open(descriptor: ProviderDescriptor<P>, settings: ProviderSettings, controller = this.controller) {\n // The Breez source shares its database with the Spark wallet of its phrase: one writer, after a fresh turn read.\n if (descriptor.id === \"breez\") await requireTurn();\n const signal = controller.signal;\n const provider = await this.gate.within(abortable(descriptor.create(settings, { ...this.options.host(), mode: this.mode, signal }), signal), (p) => p.close());\n try {\n const { network, alias } = await this.gate.within(provider.info());\n if (!descriptor.networks.includes(network)) throw new Error(`it runs on ${network}, which ${descriptor.label} does not support here`);\n if (networkMode(network) !== this.mode) throw new Error(this.mode === \"mainnet\" ? `it runs on ${network}, a test network: set it up as a Testnet wallet instead` : \"it runs on Bitcoin, real money: set it up as a Mainnet wallet instead\");\n return { provider, network, alias };\n } catch (error) { await provider.close().catch(() => {}); throw error; }\n }\n\n /**\n * Makes `providerId` this mode's source, with the values typed in its form. It is connected and its\n * network checked first; only then are its settings saved (secrets sealed) and the old source closed.\n */\n set(providerId: string, values: Record<string, string>): Promise<void> {\n const interrupted = this.interrupt();\n return this.afterChange(interrupted, this.serial(async () => {\n const { descriptor, settings } = this.settingsOf(providerId, values);\n await this.guard();\n await this.apply(descriptor, settings, false);\n }));\n }\n\n /** The form's values as a source's settings, checked: its secrets apart. Throws what to fix. */\n settingsOf(providerId: string, values: Record<string, string>): { descriptor: ProviderDescriptor<P>; settings: ProviderSettings } {\n const descriptor = this.find(providerId);\n if (!descriptor || !offeredIn(descriptor, this.options.host().platform, this.mode)) throw new Error(\"That source is not available here\");\n const settings: ProviderSettings = { config: {}, secrets: {} };\n for (const field of descriptor.fields) {\n const value = (values[field.name] ?? \"\").trim();\n if (!value && !field.optional) throw new Error(`Enter ${field.label.toLowerCase()}`);\n if (field.kind === \"select\" && value && !field.options?.some((o) => o.value === value)) throw new Error(`Choose ${field.label.toLowerCase()}`);\n if (value) (field.kind === \"secret\" ? settings.secrets : settings.config)[field.name] = value;\n }\n descriptor.validate?.(settings, this.mode);\n return { descriptor, settings };\n }\n\n /**\n * Whether the saved source is this provider with these very settings (the same wallet added twice). Its secrets are\n * unsealed here to compare, and never leave.\n */\n async holds(providerId: string, settings: ProviderSettings): Promise<boolean> {\n const stored = this.stored;\n if (!stored || stored.providerId !== providerId) return false;\n const same = (a: Record<string, string>, b: Record<string, string>) => {\n const keys = Object.keys(a).sort();\n return JSON.stringify(keys) === JSON.stringify(Object.keys(b).sort()) && keys.every((k) => a[k] === b[k]);\n };\n if (!same(stored.config, settings.config)) return false;\n const secrets = stored.secrets ? JSON.parse(await unsealSeed(stored.secrets, stored.deviceKey ?? \"\")) as Record<string, string> : {};\n return same(secrets, settings.secrets);\n }\n\n /**\n * Changes the `changeable` fields (a server address) of this mode's saved source, keeping its sealed\n * secrets and whose money it is: the wallet stays, only where it reads the chain changes. Connected and\n * checked first, like `set`; a refusal leaves the saved source as it was.\n */\n reconfigure(values: Record<string, string>): Promise<void> {\n const interrupted = this.interrupt();\n return this.afterChange(interrupted, this.serial(async () => {\n const stored = this.stored;\n const descriptor = stored ? this.find(stored.providerId) : undefined;\n if (!stored || !descriptor) throw new Error(\"There is no saved source to change\");\n const changeable = descriptor.fields.filter((f) => f.changeable);\n if (!changeable.length) throw new Error(`${descriptor.label} has no server to change`);\n const config = { ...stored.config };\n for (const field of changeable) {\n if (!(field.name in values)) continue;\n const value = (values[field.name] ?? \"\").trim();\n if (!value && !field.optional) throw new Error(`Enter ${field.label.toLowerCase()}`);\n if (value) config[field.name] = value; else delete config[field.name];\n }\n const secrets = stored.secrets ? JSON.parse(await unsealSeed(stored.secrets, stored.deviceKey ?? \"\")) as Record<string, string> : {};\n const settings: ProviderSettings = { config, secrets };\n descriptor.validate?.(settings, this.mode);\n await this.apply(descriptor, settings, true);\n }));\n }\n\n /** A change refused after interrupting an attempt: the saved source goes on connecting as before. */\n private afterChange(interrupted: boolean, change: Promise<void>): Promise<void> {\n return change.finally(() => { if (interrupted && !this.provider && !this.stopped) void this.ensureReady(); });\n }\n\n /**\n * A connection attempt under way (or waiting to try again) gives way to a change of settings, instead of\n * holding it for its time-out. Says whether there was one.\n */\n private interrupt(): boolean {\n if (this.provider || (!this.connecting && !this.failures)) return false;\n clearTimeout(this.retry);\n this.controller.abort(); this.controller = new AbortController();\n return true;\n }\n\n /** Connects `settings`, then saves them and replaces the current source. `same`: the same wallet, reconfigured. */\n private async apply(descriptor: ProviderDescriptor<P>, settings: ProviderSettings, same: boolean) {\n const providerId = descriptor.id;\n let opened: Awaited<ReturnType<typeof this.open>>;\n // Its own signal: closing the source it replaces aborts that one's, not this one's.\n const controller = new AbortController();\n try { opened = await this.open(descriptor, settings, controller); }\n catch (error) { throw error instanceof ModeChanged ? error : new Error(`Could not connect to ${descriptor.label}: ${redact(error, settings.secrets)}`); }\n const deviceKey = newDeviceKey();\n const stored: StoredSource = { providerId, config: settings.config, savedAt: Date.now(),\n ...(Object.keys(settings.secrets).length ? { secrets: await sealSeed(JSON.stringify(settings.secrets), deviceKey), deviceKey } : {}) };\n // The default with nothing to configure is what an empty mode already uses: nothing to store.\n const plain = providerId === this.options.defaultId && !Object.keys(settings.config).length && !stored.secrets;\n // Another wallet's last balance is not this one's.\n if (!same) this.seen = undefined;\n try {\n await transact([STORES.settings], (s) => {\n if (plain) s[STORES.settings].delete(sourceKey(this.options.kind, this.mode, this.options.key)); else s[STORES.settings].put(stored, sourceKey(this.options.kind, this.mode, this.options.key));\n if (!same) s[STORES.settings].delete(seenKey(this.options.kind, this.mode, this.options.key));\n });\n }\n catch (error) { await opened.provider.close().catch(() => {}); throw error; }\n await this.disconnect();\n this.controller = controller;\n this.failures = 0; this.failingSince = undefined;\n this.stored = plain ? undefined : stored; this.descriptor = descriptor; this.provider = opened.provider; this.secrets = settings.secrets;\n this.view = { ...this.idle(), status: \"ready\", network: opened.network, alias: opened.alias };\n this.options.changed();\n void this.refresh();\n }\n\n /** Forgets this mode's source (and its sealed secrets): the default, if there is one, takes over. */\n clear(): Promise<void> {\n this.interrupt();\n return this.serial(async () => {\n await this.guard();\n await transact([STORES.settings], (s) => { s[STORES.settings].delete(sourceKey(this.options.kind, this.mode, this.options.key)); s[STORES.settings].delete(seenKey(this.options.kind, this.mode, this.options.key)); });\n await this.disconnect(true);\n this.stored = undefined; this.seen = undefined;\n this.failures = 0; this.failingSince = undefined;\n this.view = this.idle(); this.options.changed();\n }).then(() => this.ensureReady());\n }\n\n /**\n * Forgets this source for good (its card is removed): its settings, sealed secrets and last balance go, it is\n * closed, and nothing takes over. Refused while a payment through it has not ended.\n */\n forget(): Promise<void> {\n this.interrupt();\n return this.serial(async () => {\n await this.guard();\n await transact([STORES.settings], (s) => { s[STORES.settings].delete(sourceKey(this.options.kind, this.mode, this.options.key)); s[STORES.settings].delete(seenKey(this.options.kind, this.mode, this.options.key)); });\n this.stopped = true;\n this.gate.close();\n await this.disconnect(true);\n this.stored = undefined; this.seen = undefined;\n this.view = { mode: this.mode, status: \"none\", offered: [] };\n });\n }\n\n private async guard() {\n const current = this.stored?.providerId ?? this.options.defaultId;\n const refusal = current ? await this.options.refuseReplacing?.(current, this.mode) : undefined;\n if (refusal) throw new Error(refusal);\n }\n\n /** The connected source, waiting for a connection under way. Throws when there is none. */\n async use(): Promise<{ provider: P; descriptor: ProviderDescriptor<P> }> {\n if (!this.provider) await this.ensureReady();\n if (!this.provider || !this.descriptor) throw new Error(this.view.status === \"none\" ? `No ${this.options.kind === \"onchain\" ? \"Bitcoin\" : \"Lightning\"} source is set up` : this.view.error ?? \"The source is still connecting\");\n return { provider: this.provider, descriptor: this.descriptor };\n }\n\n /** A change waiting on its connection gives up now (a creation that took too long); nothing is saved. */\n cutShort() { this.gate.interrupt(); }\n resume() { this.gate.resume(); }\n\n /** The connected source's id, when one is connected right now. */\n get activeId(): string | undefined { return this.provider ? this.descriptor?.id : undefined; }\n get active(): P | undefined { return this.provider; }\n\n async refresh() {\n clearTimeout(this.timer);\n const provider = this.provider;\n if (!provider) return;\n // A kind of source with nothing to read shows no balance: what it shows is read.\n if (!this.options.refresh) { if (!this.view.read) { this.view = { ...this.view, read: true }; this.options.changed(); } return; }\n const before = this.view;\n try {\n const details = await this.options.refresh(provider);\n if (provider !== this.provider) return;\n this.view = { ...this.view, ...details, read: true, balanceAt: undefined, error: undefined };\n this.remember(details);\n } catch (error) {\n if (provider !== this.provider) return;\n this.view = { ...this.view, error: `Could not read the balance: ${redact(error, this.secrets)}` };\n }\n // A read that says what the view already did (the same balance, or none) changes nothing: every 15 s it made a\n // whole wallet view and engine state, with no wallet at all.\n const after = this.view as unknown as Record<string, unknown>, was = before as unknown as Record<string, unknown>;\n if (Object.keys({ ...was, ...after }).some(key => was[key] !== after[key])) this.options.changed();\n if (this.provider) this.timer = setTimeout(() => void this.refresh(), this.options.refreshMs ?? 15_000);\n }\n\n /** Keeps the balance read, for the next start: written only when it changed. */\n private remember({ balance, unconfirmed }: Partial<Pick<SourceView, \"balance\" | \"unconfirmed\">>) {\n const providerId = this.descriptor?.id;\n // The default (the Cashu mints) connects without a network: nothing to show meanwhile.\n if (!providerId || providerId === this.options.defaultId || (balance === undefined && unconfirmed === undefined)) return;\n const was = this.seen;\n if (was?.providerId === providerId && was.balance === balance && was.unconfirmed === unconfirmed) return;\n const seen: SeenBalance = { providerId, balance, unconfirmed, at: Date.now() }, key = seenKey(this.options.kind, this.mode, this.options.key);\n this.seen = seen;\n void transact([STORES.settings], (s) => { s[STORES.settings].put(seen, key); }).catch(() => {});\n }\n\n /** `forget`: the source is removed, so what it keeps on this device goes too (a provider's own `forget`). */\n private async disconnect(forget = false) {\n clearTimeout(this.timer); clearTimeout(this.retry);\n this.controller.abort(); this.controller = new AbortController();\n const provider = this.provider as (P & { forget?: () => Promise<void> }) | undefined;\n this.provider = undefined; this.descriptor = undefined; this.secrets = {};\n await (forget && provider?.forget ? provider.forget() : provider?.close())?.catch(() => {});\n }\n\n /** Shutting down: nothing reconnects afterwards. */\n async stop() { this.stopped = true; this.gate.close(); await this.serial(() => this.disconnect()); }\n}\n","import { decodeBolt11, engineError, requestLnurlInvoice, resolveLightningDestination, type LnurlPayParams, type LnurlSuccessAction } from \"@ghostly/core\";\nimport { STORES, store, transact, wrap } from \"../../../shared/idb\";\nimport { BITCOIN_INVOICE_ON_TESTNET, type WalletMode } from \"../../../shared/mints\";\nimport { CASHU_MINT_SOURCE, CashuMintLightning } from \"./cashuMint\";\nimport type { LightningInvoice, LightningProvider, LightningProviderDescriptor } from \"./lightning\";\nimport { ProviderSources, type SourceView } from \"./sources\";\nimport { isNothingSpentError, redact, type ProviderHost } from \"./types\";\n\n/**\n * One Lightning operation, journaled under `lightningOp-<direction>-<payment hash>` in the settings store\n * BEFORE the provider is asked to spend: after a crash or a lost answer it is only ever reconciled (asked\n * about), never paid again. Incoming invoices are journaled too, so whoever paid a chat request is told\n * once the source sees it paid.\n */\nexport interface LightningOp {\n direction: \"in\" | \"out\";\n providerId: string;\n mode: WalletMode;\n paymentHash: string;\n invoice: string;\n amount: number;\n /** The chat payment request this pays, or is the invoice of. */\n paymentId?: string;\n note?: string;\n ref?: string;\n expiresAt: number;\n createdAt: number;\n /** The source books and reports this itself (the Cashu wallet): the engine does not poll it. */\n selfSettled?: boolean;\n /**\n * The Lightning card it went through (see `lightningCards.ts`). Absent on operations journaled before a network could\n * have several cards: those belong to the card that took over the network's one source (`legacyCard`).\n */\n card?: string;\n /** in: open → paid | expired. out: sending → paid | pending | failed | unknown (a lost answer). */\n state: \"open\" | \"paid\" | \"expired\" | \"sending\" | \"pending\" | \"failed\" | \"unknown\";\n maxFee?: number;\n fee?: number;\n error?: string;\n settledAt?: number;\n}\nexport type LightningOpView = Omit<LightningOp, \"ref\" | \"note\" | \"selfSettled\">;\n\n/** Options of one Lightning card's service. Without `card`, it is the network's only one (tests, older callers). */\nexport interface LightningServiceOptions {\n fetch?: typeof fetch;\n /** The card this service is: its operations carry the id, and it reads only its own. */\n card?: string;\n /** Where its source is stored: `lightningSource-<network>-<key>`; absent, the network's one key. */\n key?: string;\n /** Whether a journaled operation is this card's. Default: one carrying its id, or one carrying none. */\n owns?: (op: LightningOp) => boolean;\n}\nexport interface LightningView extends SourceView {\n capabilities?: LightningProvider[\"capabilities\"];\n /** This mode's latest operations, newest first. */\n recent: LightningOpView[];\n}\n/** `mint`: the Cashu mint that will pay, when the source is the mints; otherwise the source's id. */\nexport interface LightningQuote { quote: string; mint: string; amount: number; feeReserve: number; source: string }\n/** What a person sees of a resolved Lightning address or LNURL before choosing an amount. Amounts are whole sats. */\nexport interface LnurlView {\n id: string;\n kind: \"address\" | \"lnurl\";\n /** The address or LNURL, as normalised. */\n text: string;\n /** The host that learned of the request. */\n domain: string;\n /** The host the invoice is asked from, when it is another. */\n callbackDomain: string;\n minSat: number;\n maxSat: number;\n /** What the service says the payment is for. */\n description: string;\n /** How long a comment may be (LUD-12); 0 when none is taken. */\n commentAllowed: number;\n}\n/** Who an event is about. `mint`: the Cashu mint it went through, when it did. */\nexport interface LightningEventOp { paymentId?: string; providerId: string; mint?: string }\n\nexport interface LightningEvents {\n changed(): void;\n /** One of our invoices was paid. `paymentId`: the chat request it belongs to. */\n received(op: LightningEventOp): void;\n /** A payment left pending or unknown ended, one way or the other. */\n resolved(op: LightningEventOp, paid: boolean): void;\n}\n\nconst PREFIX = \"lightningOp-\";\nconst opKey = (direction: LightningOp[\"direction\"], hash: string) => `${PREFIX}${direction}-${hash}`;\nconst RANGE = () => IDBKeyRange.bound(PREFIX, `${PREFIX}\\uffff`);\n/** Every Lightning operation journaled, of every network and card. */\n/** Lightning payments whose call to the source has not returned, on any card of this page (WISP 06 § Payments in flight). */\nlet paying = 0;\nexport const lightningPaying = (): number => paying;\n\nexport async function readLightningJournal(): Promise<LightningOp[]> { return wrap<LightningOp[]>((await store(STORES.settings, \"readonly\")).getAll(RANGE())); }\nconst IN_POLL_MS = 4_000;\nconst OUT_POLL_MS = 15_000;\nconst RECENT = 20;\n/** Resolved Lightning addresses are kept this long for the amount to be chosen; this many at most. */\nconst LNURL_TTL_MS = 10 * 60_000;\nconst LNURL_KEPT = 20;\n/** A source that cannot tell its fee beforehand gets this ceiling, the same one chat requests are paid under. */\nexport const defaultFeeCap = (amount: number) => Math.max(10, Math.ceil(amount * 0.03));\n\n/**\n * Lightning of one network: its Lightning card, the invoice a chat request on that network carries and paying a\n * contact's invoice on it all come here, and go to this network's active source. The engine has one per network.\n */\nexport class LightningService {\n readonly sources: ProviderSources<LightningProvider>;\n private readonly quotes = new Map<string, { providerId: string; provider: LightningProvider; invoice: string; amount: number; maxFee: number; paymentHash: string }>();\n private readonly mode: WalletMode;\n private timer?: ReturnType<typeof setTimeout>;\n private passes: Promise<void> = Promise.resolve();\n private stopped = false;\n private recent: LightningOpView[] = [];\n /** Operations written in this run (`put`): a reconcile pass that wrote one is news, recent or not. */\n private writes = 0;\n /** Lightning addresses and LNURLs resolved, until an amount is chosen for them. */\n private readonly lnurls = new Map<string, { params: LnurlPayParams; at: number }>();\n\n constructor(readonly network: WalletMode, descriptors: () => readonly LightningProviderDescriptor[], host: () => Omit<ProviderHost, \"mode\" | \"signal\">, private readonly events: LightningEvents, defaultId?: string, private readonly options: LightningServiceOptions = {}) {\n this.mode = network;\n this.sources = new ProviderSources<LightningProvider>({\n kind: \"lightning\", network, descriptors, host, defaultId, key: options.key,\n changed: () => { this.schedule(0); events.changed(); },\n refuseReplacing: (providerId, mode) => this.refusal(providerId, mode),\n refresh: async (provider) => (provider.capabilities.balance ? { balance: (await provider.info()).balance } : {}),\n });\n }\n\n /** The card this service is, when the network has several. */\n get card(): string | undefined { return this.options.card; }\n /** Whether a journaled operation went through this card (and this network). */\n owns(op: LightningOp): boolean {\n if (op.mode !== this.mode) return false;\n return this.options.owns ? this.options.owns(op) : op.card === undefined || op.card === this.options.card;\n }\n\n get view(): LightningView {\n return { ...this.sources.view, capabilities: this.sources.active?.capabilities, recent: this.recent };\n }\n\n async start() { await this.sources.start(); await this.loadRecent(); }\n ensureReady() { return this.sources.ensureReady(); }\n async stop() { this.stopped = true; clearTimeout(this.timer); await this.sources.stop(); }\n refreshOffered() { this.sources.refreshOffered(); }\n\n // -- receiving -----------------------------------------------------------------\n\n async createInvoice(amount: number, context: { memo?: string; paymentId?: string } = {}): Promise<LightningInvoice & { source: string }> {\n const { provider, descriptor } = await this.sources.use();\n if (!provider.capabilities.receive) throw new Error(`${descriptor.label} cannot create invoices`);\n const created = await provider.createInvoice(amount, context.memo ?? \"Ghostly\");\n const decoded = decodeBolt11(created.invoice);\n // What goes to a contact must be the invoice we asked for: this amount, this hash, the right chain.\n if (!decoded || decoded.amountSat !== amount || (decoded.paymentHash && decoded.paymentHash !== created.paymentHash) || created.amount !== amount) throw new Error(`${descriptor.label} returned an invoice that does not match the request`);\n this.checkNetwork(decoded.network);\n await this.put({ direction: \"in\", providerId: descriptor.id, mode: this.mode, ...this.cardField(), paymentHash: created.paymentHash, invoice: created.invoice, amount, paymentId: context.paymentId, ref: created.ref, expiresAt: created.expiresAt, createdAt: Date.now(), selfSettled: !!provider.settlesItself, state: \"open\" });\n this.schedule(IN_POLL_MS);\n return { ...created, source: descriptor.id };\n }\n\n /** The Cashu wallet minted the ecash of one of its invoices (it polls its own quotes). */\n async reportInvoicePaid(invoice: string, context: { paymentId?: string; mint?: string } = {}) {\n const hash = decodeBolt11(invoice)?.paymentHash;\n const op = hash ? await this.get(\"in\", hash) : undefined;\n if (op && op.state !== \"paid\") await this.put({ ...op, state: \"paid\", settledAt: Date.now() });\n // Quotes made before sources carry their chat request themselves.\n this.events.received({ paymentId: op?.paymentId ?? context.paymentId, providerId: op?.providerId ?? CASHU_MINT_SOURCE, mint: context.mint });\n await this.loadRecent(); this.events.changed();\n }\n\n // -- Lightning addresses and LNURLs (LUD-16, LUD-06) -----------------------------\n\n /**\n * Fetches what a Lightning address or LNURL asks for, and keeps it until an amount is chosen. The\n * service's domain learns of the request: the caller says so before asking.\n */\n async resolveDestination(text: string): Promise<LnurlView> {\n const params = await resolveLightningDestination(text, { fetch: this.options.fetch });\n const now = Date.now();\n for (const [id, entry] of this.lnurls) if (entry.at + LNURL_TTL_MS < now) this.lnurls.delete(id);\n while (this.lnurls.size >= LNURL_KEPT) this.lnurls.delete(this.lnurls.keys().next().value!);\n const id = crypto.randomUUID();\n this.lnurls.set(id, { params, at: now });\n const { destination, callbackDomain, minSat, maxSat, description, commentAllowed } = params;\n return { id, kind: destination.kind, text: destination.text, domain: destination.domain, callbackDomain, minSat, maxSat, description, commentAllowed };\n }\n\n /**\n * The invoice for `amountSat` from a resolved destination: checked (the amount, a commitment to what\n * was shown, not expired, a network of this mode) before it is quoted and paid like any other invoice.\n */\n async destinationInvoice(id: string, amountSat: number, comment?: string): Promise<{ invoice: string; successAction?: LnurlSuccessAction; note: string }> {\n const entry = this.lnurls.get(id);\n if (!entry || entry.at + LNURL_TTL_MS < Date.now()) { this.lnurls.delete(id); throw new Error(\"That address was resolved too long ago: check it again\"); }\n const { invoice, successAction } = await requestLnurlInvoice(entry.params, amountSat, comment, { fetch: this.options.fetch });\n this.checkNetwork(invoice.network);\n return { invoice: invoice.invoice, successAction, note: entry.params.destination.text };\n }\n\n /** Whether this card resolved that address (the invoice is asked where it was resolved). */\n hasDestination(id: string) { return this.lnurls.has(id); }\n\n // -- paying --------------------------------------------------------------------\n\n /** What paying this invoice costs at most, from the active source. Nothing is spent. */\n async quote(text: string): Promise<LightningQuote> {\n const decoded = decodeBolt11(text);\n if (!decoded) throw new Error(\"That is not a Lightning invoice\");\n if (decoded.amountSat === null) throw new Error(\"Invoices without an amount are not supported\");\n if (!decoded.paymentHash) throw new Error(\"That invoice has no payment hash\");\n if (decoded.expiresAt * 1000 < Date.now()) throw engineError(\"invoiceExpired\");\n this.checkNetwork(decoded.network);\n const { provider, descriptor } = await this.sources.use();\n // A Bitcoin invoice on Testnet: only the mints can tell test sats from real ones (`fakesLightning`, checked when\n // they quote it). Any other Testnet source that pays one is a real node, and would pay it with real money.\n if (this.mode === \"testnet\" && decoded.network === \"bitcoin\" && !(provider instanceof CashuMintLightning)) throw new Error(BITCOIN_INVOICE_ON_TESTNET);\n if (!provider.capabilities.send) throw new Error(`${descriptor.label} cannot pay invoices`);\n const maxFee = provider.estimateFee ? await provider.estimateFee(decoded.invoice, decoded.amountSat) : defaultFeeCap(decoded.amountSat);\n if (!Number.isSafeInteger(maxFee) || maxFee < 0) throw new Error(`${descriptor.label} returned an invalid fee`);\n const id = crypto.randomUUID();\n this.quotes.set(id, { providerId: descriptor.id, provider, invoice: decoded.invoice, amount: decoded.amountSat, maxFee, paymentHash: decoded.paymentHash });\n const mint = provider instanceof CashuMintLightning ? provider.quoteFor(decoded.invoice)?.mint : undefined;\n return { quote: id, mint: mint ?? descriptor.id, amount: decoded.amountSat, feeReserve: maxFee, source: descriptor.id };\n }\n\n hasQuote(id: string) { return this.quotes.has(id); }\n\n /**\n * Pays a quote. True once paid; false while in flight or unknown (reconciled in the background, never\n * paid again). Throws only when nothing left the wallet.\n */\n async pay(quoteId: string, context: { note?: string; paymentId?: string } = {}): Promise<boolean> {\n const quote = this.quotes.get(quoteId);\n if (!quote) throw new Error(\"This quote is no longer valid: check the invoice again\");\n if (this.sources.active !== quote.provider) { this.quotes.delete(quoteId); throw new Error(\"The Lightning source changed: check the invoice again\"); }\n this.quotes.delete(quoteId);\n const existing = await this.get(\"out\", quote.paymentHash);\n if (existing && existing.state !== \"failed\") throw new Error(existing.state === \"paid\" ? \"This invoice is already paid\" : \"This invoice is already being paid\");\n let op: LightningOp = { direction: \"out\", providerId: quote.providerId, mode: this.mode, ...this.cardField(), paymentHash: quote.paymentHash, invoice: quote.invoice, amount: quote.amount, paymentId: context.paymentId, note: context.note?.slice(0, 140), expiresAt: 0, createdAt: Date.now(), selfSettled: !!quote.provider.settlesItself, state: \"sending\", maxFee: quote.maxFee, ref: quote.provider.paymentRef?.(quote.invoice) };\n // Written down before the provider sees it: from here on the sats may be gone.\n await this.put(op);\n paying += 1;\n try {\n const result = await quote.provider.payInvoice(quote.invoice, quote.maxFee, context.note);\n op = result.state === \"paid\" ? { ...op, state: \"paid\", fee: result.fee, ref: result.ref ?? op.ref, settledAt: Date.now() } : { ...op, state: \"pending\", ref: result.ref ?? op.ref };\n } catch (error) {\n if (isNothingSpentError(error)) { await this.put({ ...op, state: \"failed\", error: redact(error) }); throw error; }\n // A source that cannot look payments up never settles this by itself: say so rather than \"being checked\".\n op = { ...op, state: \"unknown\", error: quote.provider.capabilities.lookup ? \"No answer from the source. It is being checked; nothing is paid again.\" : \"No answer from the source, and it cannot be asked: check this payment in the wallet itself. It is never paid again.\" };\n } finally { paying -= 1; }\n await this.put(op);\n if (op.state !== \"paid\") this.schedule(OUT_POLL_MS);\n return op.state === \"paid\";\n }\n\n /** The Cashu wallet settled a melt it had left pending. */\n async reportPaymentResolved(invoice: string, paid: boolean, context: { paymentId?: string; mint?: string } = {}) {\n const hash = decodeBolt11(invoice)?.paymentHash;\n const op = hash ? await this.get(\"out\", hash) : undefined;\n if (op) await this.put({ ...op, state: paid ? \"paid\" : \"failed\", settledAt: Date.now() });\n this.events.resolved({ paymentId: op?.paymentId ?? context.paymentId, providerId: op?.providerId ?? CASHU_MINT_SOURCE, mint: context.mint }, paid);\n await this.loadRecent(); this.events.changed();\n }\n\n // -- reconciling ---------------------------------------------------------------\n\n /** Asks the active source about everything of its that is open, pending or unknown. One pass at a time. */\n reconcile(): Promise<void> {\n const pass = this.passes.then(() => this.reconcileOnce());\n this.passes = pass.catch(() => {});\n return pass;\n }\n\n private async reconcileOnce(): Promise<void> {\n if (this.stopped) return;\n let again = 0;\n const writes = this.writes;\n try {\n const provider = this.sources.active, providerId = this.sources.activeId;\n for (const op of await this.list()) {\n if (!this.owns(op)) continue;\n if (op.selfSettled) {\n // The source settles and reports its own. One cut off before its answer was written (`recover`) it may\n // never have heard of, and would never report: the source is asked what became of it.\n if (op.direction !== \"out\" || op.state !== \"unknown\") continue;\n // No source connected yet: asked again. Another source's waits until its own is active again.\n if (!provider) { again = again || OUT_POLL_MS; continue; }\n if (op.providerId !== providerId || !provider.interruptedPayment) continue;\n try {\n const outcome = await provider.interruptedPayment({ invoice: op.invoice, paymentHash: op.paymentHash, ref: op.ref });\n if (outcome === \"pending\") again = again || OUT_POLL_MS;\n if (outcome !== \"paid\" && outcome !== \"failed\") continue;\n // Written only over the state that was asked about: the source's own report wins.\n if ((await this.get(\"out\", op.paymentHash))?.state !== \"unknown\") continue;\n await this.put({ ...op, state: outcome, error: outcome === \"failed\" ? \"The payment did not go through\" : undefined, settledAt: Date.now() });\n this.events.resolved(op, outcome === \"paid\");\n } catch { again = again || OUT_POLL_MS; }\n continue;\n }\n // `sending` is a spend under way in this process: its own answer decides. One left by a crash is\n // made `unknown` by `recover` at the next start, and only then asked about.\n const waiting = op.direction === \"in\" ? op.state === \"open\" : [\"pending\", \"unknown\"].includes(op.state);\n if (!waiting) continue;\n // Another source's operation waits until that source is active again: only it can answer.\n if (!provider || op.providerId !== providerId || !provider.capabilities.lookup) continue;\n try {\n const unchanged = async () => (await this.get(op.direction, op.paymentHash))?.state === op.state;\n if (op.direction === \"in\") {\n const status = await provider.invoiceStatus({ invoice: op.invoice, paymentHash: op.paymentHash, amount: op.amount, expiresAt: op.expiresAt, ref: op.ref });\n if (!(await unchanged())) continue;\n if (status.state === \"paid\") { await this.put({ ...op, state: \"paid\", settledAt: Date.now() }); this.events.received(op); }\n else if (status.state === \"expired\" || op.expiresAt + 60_000 < Date.now()) await this.put({ ...op, state: \"expired\" });\n else again = again ? Math.min(again, IN_POLL_MS) : IN_POLL_MS;\n } else {\n const status = await provider.paymentStatus({ invoice: op.invoice, paymentHash: op.paymentHash, ref: op.ref });\n if (status.state === \"pending\") { again = again || OUT_POLL_MS; continue; }\n // Written only over the state that was asked about: anything newer wins.\n if (!(await unchanged())) continue;\n await this.put({ ...op, state: status.state, fee: status.fee ?? op.fee, error: status.state === \"failed\" ? \"The payment did not go through\" : undefined, settledAt: Date.now() });\n this.events.resolved(op, status.state === \"paid\");\n }\n } catch {\n // The source cannot be asked right now: ask again later, nothing changes.\n again = again ? Math.min(again, op.direction === \"in\" ? IN_POLL_MS : OUT_POLL_MS) : op.direction === \"in\" ? IN_POLL_MS : OUT_POLL_MS;\n }\n }\n } finally {\n // Only a pass that settled something, or whose recent list reads otherwise, is news to the wallet view.\n if (await this.loadRecent() || this.writes !== writes) this.events.changed();\n if (again) this.schedule(again);\n }\n }\n\n private schedule(ms: number) {\n if (this.stopped) return;\n clearTimeout(this.timer);\n this.timer = setTimeout(() => void this.reconcile(), ms);\n }\n\n /** Changing the source is refused while a payment through it has not ended: only it can say how. */\n private async refusal(providerId: string, mode: WalletMode) {\n const open = (await this.list()).filter((op) => op.direction === \"out\" && !op.selfSettled && op.providerId === providerId && op.mode === mode && this.owns(op) && [\"sending\", \"pending\", \"unknown\"].includes(op.state));\n return open.length ? `A Lightning payment through this source has not ended yet (${open.length}). Wait for it before changing the source.` : undefined;\n }\n\n private cardField(): Pick<LightningOp, \"card\"> { return this.options.card === undefined ? {} : { card: this.options.card }; }\n\n /** A Mainnet source refuses an invoice on a test chain. The reverse (test mints use lnbc) is `quote`'s to refuse. */\n private checkNetwork(network: string) {\n if (this.mode === \"mainnet\" && network !== \"bitcoin\") throw new Error(`That invoice is for ${network}, a test network: pay it from a Testnet wallet`);\n }\n\n private async get(direction: LightningOp[\"direction\"], hash: string) { return wrap<LightningOp | undefined>((await store(STORES.settings, \"readonly\")).get(opKey(direction, hash))); }\n private async put(op: LightningOp) { this.writes++; await transact([STORES.settings], (s) => { s[STORES.settings].put(op, opKey(op.direction, op.paymentHash)); }); }\n list(): Promise<LightningOp[]> { return readLightningJournal(); }\n\n /** Crash recovery: a spend journaled as `sending` never got its answer written, so its outcome is unknown. */\n async recover() {\n for (const op of await this.list()) if (op.direction === \"out\" && op.state === \"sending\" && this.owns(op)) await this.put({ ...op, state: \"unknown\", error: \"Interrupted. It is being checked; nothing is paid again.\" });\n this.schedule(0);\n }\n\n /** True when the recent list reads otherwise than before. */\n private async loadRecent(): Promise<boolean> {\n const was = this.recent;\n this.recent = (await this.list()).filter((op) => this.owns(op)).sort((a, b) => b.createdAt - a.createdAt).slice(0, RECENT)\n .map(({ ref: _ref, note: _note, selfSettled: _self, ...view }) => view);\n return JSON.stringify(was) !== JSON.stringify(this.recent);\n }\n}\n","import { generateMnemonic, validateMnemonic } from \"@scure/bip39\";\nimport { wordlist } from \"@scure/bip39/wordlists/english.js\";\n\n/**\n * Recovery phrases (BIP 39, English) for providers whose wallet Ghostly makes: the form generates one for\n * a new wallet and checks one typed to restore. Small on purpose, so a form can use it without loading\n * the provider.\n */\nexport const newRecoveryPhrase = () => generateMnemonic(wordlist);\n/** A phrase as BIP 39 wants it, however it was typed or pasted. */\nexport const normalizePhrase = (text = \"\") => text.trim().toLowerCase().split(/\\s+/).join(\" \");\nexport const isRecoveryPhrase = (text: string) => validateMnemonic(normalizePhrase(text), wordlist);\n","import { STORES, transact } from \"../../../shared/idb\";\nimport type { WalletMode } from \"../../../shared/mints\";\nimport { CASHU_MINT_SOURCE } from \"./cashuMint\";\nimport type { LightningProviderDescriptor } from \"./lightning\";\nimport { LightningService, readLightningJournal, type LightningEvents, type LightningOp, type LightningView } from \"./lightningService\";\nimport { sourceKey } from \"./sources\";\nimport { awayFrom } from \"../away\";\nimport { breezDatabaseInUse } from \"./breezDatabases\";\nimport { normalizePhrase } from \"./recoveryPhrase\";\nimport type { ProviderHost } from \"./types\";\n\n/**\n * One Lightning card of a network, as stored under `lightningCards-<network>`. Its source (and the source's sealed\n * secrets) is stored apart, under `lightningSource-<network>-<key>`: the card that took over a network's one source\n * (`LEGACY_CARD`) has no key and keeps reading `lightningSource-<network>`, where that source always was.\n */\nexport interface StoredLightningCard {\n id: string;\n name: string;\n providerId: string;\n /** Where its source is stored. Absent: the network's one key (the card made from it). */\n key?: string;\n createdAt: number;\n}\ninterface StoredLightningCards {\n cards: StoredLightningCard[];\n /** The card a chat request's invoice and Receive come from, unless another is picked. */\n receive: string;\n}\n\n/** A Lightning card as the pages see it: its source's view, its name, and whether it is the one receiving. */\nexport interface LightningCardView extends LightningView {\n card: string;\n name: string;\n /** The network's default for receiving: chat requests and Receive use it unless another card is picked. */\n receive: boolean;\n}\n\n/** The card of Lightning through the Cashu mints: at most one per network, shown while the network has mints. */\nexport const CASHU_CARD = \"cashu\";\n/** The card made from the one source a network had before it could have several. */\nexport const LEGACY_CARD = \"main\";\nexport const cardsKey = (network: WalletMode) => `lightningCards-${network}`;\nconst NAME_MAX = 32;\n\n/** Short source names for a card's default name (\"Alby Hub (NWC)\"): the form's label otherwise. */\nconst SHORT: Record<string, string> = { nwc: \"NWC\", lnd: \"LND\", \"core-lightning\": \"Core Lightning\", breez: \"Breez\", [CASHU_MINT_SOURCE]: \"Cashu\", fedimint: \"Fedimint\", webln: \"WebLN\" };\n\n/**\n * The cards of a network, made the first time this version opens it from what an older one kept: the network's one\n * source (`lightningSource-<network>`) becomes a card reading that same key, so no secret moves and nothing asks\n * again; with no source saved, the network used the Cashu mints, and that is its card. Either becomes the default for\n * receiving. Read and written in one transaction; a second run finds the cards and changes nothing. Never reads a\n * secret: the source's record names its provider next to its sealed secrets.\n */\nexport async function loadLightningCards(network: WalletMode, label: (providerId: string) => string, now = Date.now()): Promise<StoredLightningCards> {\n let result: StoredLightningCards | undefined;\n await transact([STORES.settings], (stores) => {\n const settings = stores[STORES.settings];\n const saved = settings.get(cardsKey(network));\n saved.onsuccess = () => {\n const found = saved.result as StoredLightningCards | undefined;\n if (found?.cards?.length) { result = found; return; }\n const legacy = settings.get(sourceKey(\"lightning\", network));\n legacy.onsuccess = () => {\n const providerId = (legacy.result as { providerId?: unknown } | undefined)?.providerId;\n const card: StoredLightningCard = typeof providerId === \"string\" && providerId && providerId !== CASHU_MINT_SOURCE\n ? { id: LEGACY_CARD, name: shortName(providerId, label), providerId, createdAt: now }\n : cashuCard(now);\n result = { cards: [card], receive: card.id };\n settings.put(result, cardsKey(network));\n };\n };\n });\n return result!;\n}\n\nconst cashuCard = (now: number): StoredLightningCard => ({ id: CASHU_CARD, name: SHORT[CASHU_MINT_SOURCE], providerId: CASHU_MINT_SOURCE, key: CASHU_CARD, createdAt: now });\nconst shortName = (providerId: string, label: (providerId: string) => string) => SHORT[providerId] ?? label(providerId);\n\nexport interface LightningCardsHost {\n descriptors: () => readonly LightningProviderDescriptor[];\n host: () => Omit<ProviderHost, \"mode\" | \"signal\">;\n events: LightningEvents;\n /** Whether the network has Cashu mints: the Cashu card shows only then. */\n hasMints: () => boolean;\n fetch?: typeof fetch;\n}\n\n/**\n * The Lightning cards of one network, each with its own source, balance and journal: \"Alby Hub (NWC)\", \"Home LND\",\n * the Cashu mints. A payment goes through the card picked for it; a chat request's invoice and Receive come from the\n * default for receiving unless another is picked. Adding, removing and renaming are serialized.\n */\nexport class LightningCards {\n private stored: StoredLightningCards = { cards: [], receive: CASHU_CARD };\n private readonly services = new Map<string, LightningService>();\n private queue: Promise<unknown> = Promise.resolve();\n /** A card being added: a creation that takes too long cuts its connection short. */\n private adding?: LightningService;\n private stopped = false;\n\n constructor(readonly network: WalletMode, private readonly options: LightningCardsHost) {}\n\n private serial<T>(run: () => Promise<T>): Promise<T> { const next = this.queue.then(run, run); this.queue = next.catch(() => {}); return next; }\n private label = (providerId: string) => this.options.descriptors().find((d) => d.id === providerId)?.label ?? providerId;\n\n private make(card: StoredLightningCard): LightningService {\n return new LightningService(this.network, this.options.descriptors, this.options.host, this.options.events,\n card.providerId === CASHU_MINT_SOURCE ? CASHU_MINT_SOURCE : undefined,\n { fetch: this.options.fetch, card: card.id, key: card.key, owns: (op) => this.owner(op) === card.id });\n }\n\n private starting?: Promise<void>;\n /** Opens the cards (once: a use before the engine started opens them too). */\n start(): Promise<void> { return this.starting ??= this.open(); }\n private async open() {\n this.stored = await loadLightningCards(this.network, this.label);\n for (const card of this.stored.cards) {\n const service = this.make(card);\n this.services.set(card.id, service);\n await service.start();\n }\n }\n\n /**\n * The card an operation went through. One journaled before cards: the Cashu card's when the mints made it, else the\n * card made from the network's one source (none, when that source was replaced before cards: nobody shows it).\n */\n owner(op: LightningOp): string {\n if (op.card !== undefined) return op.card;\n return op.providerId === CASHU_MINT_SOURCE ? CASHU_CARD : LEGACY_CARD;\n }\n\n /** The cards a person sees: the Cashu card only while the network has mints. In the order they were added. */\n private shown(): StoredLightningCard[] { return this.stored.cards.filter((c) => c.id !== CASHU_CARD || this.options.hasMints()); }\n\n /** The default for receiving, or the first card shown when that one is not (the Cashu card of a network with no mints). */\n get receivingId(): string {\n const shown = this.shown();\n return shown.find((c) => c.id === this.stored.receive)?.id ?? shown[0]?.id ?? this.stored.receive;\n }\n get receiving(): LightningService { return this.services.get(this.receivingId) ?? [...this.services.values()][0]; }\n\n /** A card by its id; without one, the default for receiving. Throws when it is gone. */\n card(id?: string): LightningService {\n if (id === undefined) { if (!this.services.size) throw new Error(\"Lightning is not open yet\"); return this.receiving; }\n const service = this.services.get(id);\n if (!service) throw new Error(\"That Lightning card is gone: pick another\");\n return service;\n }\n has(id: string) { return this.services.has(id); }\n all(): LightningService[] { return [...this.services.values()]; }\n\n views(): LightningCardView[] {\n const receive = this.receivingId;\n return this.shown().filter((card) => this.services.has(card.id)).map((card) => ({ ...this.services.get(card.id)!.view, card: card.id, name: card.name, receive: card.id === receive }));\n }\n /** The default for receiving's view: what a caller from before cards reads as the network's Lightning. None before start. */\n get view(): LightningCardView | undefined {\n const card = this.stored.cards.find((c) => c.id === this.receivingId) ?? this.stored.cards[0];\n return card && this.services.has(card.id) ? { ...this.services.get(card.id)!.view, card: card.id, name: card.name, receive: true } : undefined;\n }\n\n /** The card holding this quote (each card quotes through its own source). */\n withQuote(quote: string): LightningService | undefined { return this.all().find((s) => s.hasQuote(quote)); }\n\n /**\n * Adds a card with `providerId` and the values of its form: connected and checked first, like any source; only then\n * saved. Nothing is saved when it fails. The same wallet twice (same source, same settings) is refused. A card of the\n * person's own takes receiving over from the mints' card (what a source chosen before cards did); next to another\n * card of their own, the default stays.\n */\n add(providerId: string, values: Record<string, string>): Promise<string> {\n return this.serial(async () => {\n if (providerId === CASHU_MINT_SOURCE) {\n if (this.services.has(CASHU_CARD)) throw new Error(\"Lightning through your Cashu mints is already a card\");\n if (!this.options.hasMints()) throw new Error(\"Create a Cashu wallet first: this card pays and receives through its mints\");\n const card = cashuCard(Date.now());\n const service = this.make(card);\n await service.start();\n await this.save({ ...this.stored, cards: [...this.stored.cards, card] }, card.id);\n this.services.set(card.id, service);\n void service.ensureReady();\n return card.id;\n }\n const id = `ln-${[...crypto.getRandomValues(new Uint8Array(4))].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n const card: StoredLightningCard = { id, name: \"\", providerId, key: id, createdAt: Date.now() };\n const service = this.make(card);\n const same = await this.findSame(providerId, values);\n if (same) throw new Error(`That wallet is already your card “${this.stored.cards.find((c) => c.id === same)?.name}”`);\n await service.start();\n this.adding = service;\n try { await service.sources.set(providerId, values); }\n catch (error) { await service.stop(); throw error; }\n finally { this.adding = undefined; }\n card.name = this.freeName(service.view.alias ? `${service.view.alias.slice(0, 20)} (${shortName(providerId, this.label)})` : shortName(providerId, this.label));\n const receive = this.receivingId === CASHU_CARD ? id : this.stored.receive;\n // The mints' card of a network with no mints is not shown and holds nothing: a card of the person's own takes its\n // place, as the one source did before cards (a Cashu wallet made later does not bring it back).\n const hidden = this.services.has(CASHU_CARD) && !this.options.hasMints();\n const cards = [...this.stored.cards.filter((c) => !hidden || c.id !== CASHU_CARD), card];\n try { await this.save({ cards, receive }, card.id); }\n catch (error) { await service.sources.forget().catch(() => {}); await service.stop(); throw error; }\n this.services.set(id, service);\n if (hidden) { const gone = this.services.get(CASHU_CARD)!; this.services.delete(CASHU_CARD); await gone.stop(); }\n return id;\n });\n }\n\n /** The card that is already this wallet (the same source with the same settings), if one is. Throws what to fix in the form. */\n async findSame(providerId: string, values: Record<string, string>): Promise<string | undefined> {\n const { settings } = this.receiving.sources.settingsOf(providerId, values);\n for (const [id, service] of this.services) if (await service.sources.holds(providerId, settings)) return id;\n return undefined;\n }\n\n /** A name no other card of the network has: \"LND\", then \"LND 2\". */\n private freeName(name: string): string {\n const taken = new Set(this.stored.cards.map((c) => c.name.toLowerCase()));\n if (!taken.has(name.toLowerCase())) return name;\n for (let n = 2; ; n++) if (!taken.has(`${name} ${n}`.toLowerCase())) return `${name} ${n}`;\n }\n\n /**\n * Removes a card: its source and sealed secrets go (refused while a payment through it has not ended). The default\n * for receiving moves to the first card of the person's own left, else the mints' card. A network left with no card gets the Cashu card back, as before cards.\n */\n remove(id: string): Promise<void> {\n return this.serial(async () => {\n const service = this.services.get(id);\n if (!service) throw new Error(\"That Lightning card is gone\");\n await service.sources.forget();\n await service.stop();\n this.services.delete(id);\n let cards = this.stored.cards.filter((c) => c.id !== id);\n let added: LightningService | undefined;\n if (!cards.length) {\n const card = cashuCard(Date.now());\n cards = [card];\n added = this.make(card);\n this.services.set(card.id, added);\n await added.start();\n }\n const shown = cards.filter((c) => c.id !== CASHU_CARD || this.options.hasMints());\n const next = shown.find((c) => c.id !== CASHU_CARD) ?? shown[0] ?? cards[0];\n const receive = this.stored.receive === id || !cards.some((c) => c.id === this.stored.receive) ? next.id : this.stored.receive;\n await this.save({ cards, receive });\n if (added) void added.ensureReady();\n });\n }\n\n /** Makes a card the network's default for receiving. */\n setReceive(id: string): Promise<void> {\n return this.serial(async () => {\n if (!this.shown().some((c) => c.id === id)) throw new Error(\"That Lightning card is gone\");\n await this.save({ ...this.stored, receive: id });\n });\n }\n\n /** Renames a card: 1 to 32 characters, unique on its network. */\n rename(id: string, name: string): Promise<void> {\n return this.serial(async () => {\n const clean = name.replace(/\\s+/g, \" \").trim();\n if (!clean) throw new Error(\"Enter a name\");\n if (clean.length > NAME_MAX) throw new Error(`At most ${NAME_MAX} characters`);\n if (!this.services.has(id)) throw new Error(\"That Lightning card is gone\");\n if (this.stored.cards.some((c) => c.id !== id && c.name.toLowerCase() === clean.toLowerCase())) throw new Error(\"Another card has that name\");\n await this.save({ ...this.stored, cards: this.stored.cards.map((c) => (c.id === id ? { ...c, name: clean } : c)) });\n });\n }\n\n private async save(next: StoredLightningCards, receiveIfNone?: string) {\n const shown = next.cards.filter((c) => c.id !== CASHU_CARD || this.options.hasMints());\n // A first card shown takes the receiving over from one that is not (the Cashu card of a network with no mints).\n const receive = receiveIfNone && !shown.some((c) => c.id === next.receive) ? receiveIfNone : next.receive;\n const stored = { cards: next.cards, receive };\n await transact([STORES.settings], (s) => { s[STORES.settings].put(stored, cardsKey(this.network)); });\n this.stored = stored;\n this.options.events.changed();\n }\n\n // -- the Cashu wallet reports what its mints settled -------------------------------\n\n private async ownerOf(direction: LightningOp[\"direction\"], invoice: string): Promise<LightningService> {\n const op = (await readLightningJournal()).find((o) => o.direction === direction && o.invoice === invoice && o.mode === this.network);\n const owner = op ? this.owner(op) : CASHU_CARD;\n return (owner && this.services.get(owner)) || this.services.get(CASHU_CARD) || this.receiving;\n }\n async reportInvoicePaid(invoice: string, context: { paymentId?: string; mint?: string } = {}) { await (await this.ownerOf(\"in\", invoice)).reportInvoicePaid(invoice, context); }\n async reportPaymentResolved(invoice: string, paid: boolean, context: { paymentId?: string; mint?: string } = {}) { await (await this.ownerOf(\"out\", invoice)).reportPaymentResolved(invoice, paid, context); }\n\n // -- Lightning addresses: resolved by one card, and the invoice asked by that same one --------\n\n resolveDestination(text: string) { return this.receiving.resolveDestination(text); }\n destinationInvoice(id: string, amountSat: number, comment?: string) { return (this.all().find((s) => s.hasDestination(id)) ?? this.receiving).destinationInvoice(id, amountSat, comment); }\n\n // -- every card at once -----------------------------------------------------------\n\n list() { return readLightningJournal(); }\n /**\n * What a handoff needs to know of the cards (WISP 06 § Wallets), from their sealed settings: the LND cards that pin a\n * certificate, and each Breez card's database on this device, by wallet id.\n */\n async handoffFacts(): Promise<{ pinned: string[]; breez: Map<string, string> }> {\n const pinned: string[] = [], breez = new Map<string, string>();\n for (const [id, service] of this.services) {\n const wallet = `lightning:${this.network}:${id}`, providerId = service.view.providerId;\n if (providerId === \"lnd\" && await service.sources.secret(\"certificate\")) pinned.push(wallet);\n if (providerId === \"breez\") {\n const mnemonic = await service.sources.secret(\"mnemonic\");\n if (mnemonic) breez.set(wallet, await breezDatabaseInUse(this.network === \"mainnet\" ? \"bitcoin\" : \"regtest\", normalizePhrase(mnemonic)));\n }\n }\n return { pinned, breez };\n }\n\n /** The cards whose source is opened on this device: not one at home on another device (WISP 06 § Wallets that stay home). */\n private here(): LightningService[] { return [...this.services].filter(([id]) => awayFrom(`lightning:${this.network}:${id}`) === undefined).map(([, s]) => s); }\n async reconcile() { await Promise.all(this.here().map((s) => s.reconcile())); }\n async recover() { for (const s of this.here()) await s.recover(); }\n async ensureReady() { await Promise.all(this.here().map((s) => s.ensureReady())); }\n refreshOffered() { for (const s of this.all()) s.refreshOffered(); }\n wake() { for (const s of this.all()) s.sources.wake(); }\n cutShort() { this.adding?.sources.cutShort(); }\n resume() { this.adding?.sources.resume(); }\n async stop() { this.stopped = true; for (const s of this.all()) await s.stop(); }\n get isStopped() { return this.stopped; }\n}\n","import { FILE_BYTES_STEP, fileBytesOf, type FileBytes } from \"./fileBytes\";\nimport { fileStore, type StoredFile } from \"./idb\";\nimport type { FileTransferView } from \"./types\";\n\n/** The bytes a file holds: its metadata says, and a file stored before that says it with its Blob. */\nexport function storedSize(file: StoredFile): number {\n return file.metadata?.size ?? file.blob?.size ?? 0;\n}\n\n/**\n * A group's file (WISP 503) that is not here, read from its record: when the engine shows no transfer for it, it waits\n * for its person's Download. The engine keeps no view of such a file in the states it builds (one for every file a\n * group ever had would go into each of them), so the app's bubble and the CLI read it here. Null for any other file.\n */\nexport function waitingGroupFile(file: StoredFile | undefined): FileTransferView | null {\n if (!file?.group || file.direction !== \"in\" || file.transfer?.state !== \"transferring\") return null;\n return { state: \"transferring\", stage: \"asking\", transferred: 0, size: file.metadata?.size ?? file.transfer.size, direction: \"in\" };\n}\n\nasync function backend(file: StoredFile): Promise<FileBytes | null> {\n return file.bytes ? fileBytesOf(file.bytes) : null;\n}\n\n/** The file as a Blob backed by storage, or null where it cannot be had as one (see `FileBytes.blob`). */\nexport async function storedBlob(file: StoredFile, type: string): Promise<Blob | null> {\n if (file.blob) return file.blob.slice(0, file.blob.size, type);\n return (await backend(file))?.blob(file.id, type) ?? null;\n}\n\n/** At most `length` bytes from `offset`. */\nexport async function readStored(file: StoredFile, offset: number, length: number): Promise<Uint8Array> {\n if (file.blob) return new Uint8Array(await file.blob.slice(offset, offset + length).arrayBuffer());\n const bytes = await backend(file);\n if (!bytes) throw new Error(\"The file is gone\");\n return bytes.read(file.id, offset, length);\n}\n\n/** The file, a step at a time, from `offset`. */\nexport async function* streamStored(file: StoredFile, offset = 0): AsyncGenerator<Uint8Array> {\n const size = storedSize(file);\n while (offset < size) {\n const part = await readStored(file, offset, Math.min(FILE_BYTES_STEP, size - offset));\n if (!part.length) throw new Error(\"The file is shorter than it says\");\n offset += part.length;\n yield part;\n }\n}\n\n/** A file's record and its bytes, wherever they are. */\nexport async function removeStored(id: string): Promise<void> {\n const file = await fileStore.get(id);\n await fileStore.delete(id);\n if (file?.bytes) await (await backend(file))?.remove(id);\n}\n\n/** Writes are gathered to this size before they go to storage: a chunk from the wire is 16 KiB. */\nconst GATHER = 256 * 1024;\n\n/**\n * Writes one file in order, gathering small writes into larger ones. `written` counts what was handed over,\n * gathered or not; `flush` makes all of it durable.\n */\nexport class FileAppender {\n private buffer = new Uint8Array(GATHER);\n private buffered = 0;\n private stored: number;\n\n constructor(readonly bytes: FileBytes, readonly id: string, offset = 0) { this.stored = offset; }\n\n get written(): number { return this.stored + this.buffered; }\n\n async append(chunk: Uint8Array): Promise<void> {\n if (this.buffered + chunk.length > GATHER) await this.drain();\n if (chunk.length >= GATHER) {\n await this.bytes.append(this.id, this.stored, chunk);\n this.stored += chunk.length;\n return;\n }\n this.buffer.set(chunk, this.buffered);\n this.buffered += chunk.length;\n }\n\n private async drain(): Promise<void> {\n if (!this.buffered) return;\n const part = this.buffer.slice(0, this.buffered);\n await this.bytes.append(this.id, this.stored, part);\n this.stored += part.length;\n this.buffered = 0;\n }\n\n async flush(): Promise<void> {\n await this.drain();\n await this.bytes.flush(this.id);\n }\n\n async close(): Promise<void> {\n await this.drain();\n await this.bytes.close(this.id);\n }\n}\n","import type { DiscoveryStatus, GroupMention, GroupMessageFrame, ImageMeta, LinkPreview, PairingProgress, PaymentMethodName, RoutineCard, StatusCard, TaskCard, TypingKind, UsageCard, VideoMeta, VoiceMeta, WirePin, WireReaction } from \"@ghostly/core\";\nimport type { UsdtWalletView } from \"../engine/paymentAdapters/usdtWallet\";\nimport type { ArkWalletView } from \"../engine/paymentAdapters/arkWallet\";\nimport type { BarkWalletView } from \"../engine/paymentAdapters/barkWallet\";\nimport type { FedimintWalletView } from \"../engine/paymentAdapters/fedimintWallet\";\nimport type { SparkWalletView } from \"../engine/paymentAdapters/sparkWallet\";\nimport type { LightningView } from \"../engine/paymentAdapters/providers/lightningService\";\nimport type { LightningCardView } from \"../engine/paymentAdapters/providers/lightningCards\";\nimport type { BitcoinView } from \"../engine/paymentAdapters/providers/bitcoinService\";\nimport type { PaymentReview, PaymentTarget, VapidKeys, WakeTarget, WalletNetwork } from \"@ghostly/core\";\nimport type { ProviderDescriptorView } from \"../engine/paymentAdapters/providers/types\";\nimport type { CapsState, DeliveryMode, DhtDeliveryState, DhtDeliveryView, HoldKind } from \"@ghostly/core\";\nimport type { S3Config } from \"../backup/s3\";\nimport type { BackupReminders } from \"./backupReminder\";\nimport type { TransportCause, TransportEntry, TransportEvent } from \"../engine/transportLog\";\nimport type { PublicProfile, ProfileChoice } from '../profiles/public';\nimport type { NostrContactCache, NostrContactView, NostrSocialSettings, NostrSocialState } from \"../nostr/types\";\nimport type { ProofLedger, ProofAdapter } from \"@ghostly/core\";\nimport type { IdentityDisplay, IdentityLedger, IdentityStatus, IdentityTimelineEntry, SharedIdentity, VerifiedIdentity } from \"@ghostly/core\";\nimport type { DataLinkState, LinkStatus, LiveAttempt, ServiceAd, PairingState, NativeTransport, PairedTransport, TransportDescriptors, TransportWait } from \"@ghostly/core\";\nimport type { CommunityState, GroupByeFrame, GroupCommit, GroupRole, GroupState, GroupStatus } from \"@ghostly/core\";\n\n/** A link as stored in IndexedDB. Same fields Desktop keeps in its ChatSession. */\nexport interface StoredLink {\n /**\n * Paired 1:1 chats: how to wake the contact's closed web app, as it shared it (`wake/1`, WISP 401 § Wake-up push).\n * Absent until it does, and again once it stops sharing or its push service says the subscription is gone.\n */\n peerWake?: WakeTarget;\n /** Paired 1:1 chats: this side's token for this chat in its own wake-ups; a new one whenever the subscription changes. */\n wakeToken?: string;\n /** Paired 1:1 chats: muted here, so the contact is told to forget this side's subscription until it is unmuted. */\n wakeMuted?: boolean;\n deliveryMode?: DeliveryMode;\n dhtDeliveryState?: DhtDeliveryState;\n /** This side's layer-0 capability record and the contact's last good one (WISP 03). */\n capsState?: CapsState;\n publicProfiles?: PublicProfile[];\n profileChoice?: ProfileChoice;\n peerProofs?: ProofLedger;\n /** Identity proofs shared in this chat, both ways (WISP 300). */\n identities?: IdentityLedger;\n /** The contact's Nostr profile, follows and notes, cached under their proven key, loaded only on request. */\n nostrSocial?: Record<string, NostrContactCache>;\n profile?: \"paired-chat/1\";\n participationSeed?: string;\n transportSeeds?: Partial<Record<NativeTransport, string>>;\n peerDescriptors?: TransportDescriptors;\n peerTransports?: PairedTransport[];\n peerFallback?: boolean;\n /** Absent: no choice for this chat, the app's rule applies (\"Automatic\"). */\n preferredTransport?: PairedTransport;\n transportFallback?: boolean;\n /** The chat's connection story, newest last (see engine/transportLog.ts). Local only. */\n transportLog?: TransportEntry[];\n /** Every connection event, for the connection panel, newest last (see engine/transportLog.ts). Local only. */\n transportHistory?: TransportEvent[];\n pairedPeerKey?: string;\n /** The inviter's participation key, from a `ghostly1` code (the joiner's side): the only key that may be pinned. */\n peerParticipationKeyZ32?: string;\n /** Absent only in old releases, where first pin required an explicit code comparison. */\n peerTrust?: { version: 1; verifiedKey?: string; verifiedAt?: number };\n requireSignedSignals?: boolean;\n id: string;\n seedB64: string;\n peerPubKeyZ32: string;\n encKeyB64: string;\n createdAt: number;\n label?: string;\n /**\n * The name the contact asked to be shown by, as they last said it. `\"\"` means a paired contact said they\n * have none (removed, or not shared); absent means nothing was heard yet.\n */\n peerNick?: string;\n /** The contact's profile picture, as they last sent it (checked, small JPEG data URL). */\n peerAvatar?: string;\n /** Present on the side that created the link, until the peer shows up. */\n inviteCode?: string;\n /** Messages deleted on this device, by id, so a republished one is not stored again. */\n deletedIds?: string[];\n /**\n * Files of mine whose offer went on the DHT floor (WISP 403 § Files) and that were deleted here before they went live,\n * by wire id: the next session that carries files/3 says each is cancelled (`pf-abort`), so the contact's bubble does too.\n */\n withdrawnFiles?: string[];\n /** This side's reactions the contact has not confirmed yet (WISP 401 § Reactions), in the order of their numbers. */\n reactionsOut?: WireReaction[];\n /** The chat's pinned message (WISP 400 § Pinned message): the latest pin, either side's; `id` \"\" once unpinned. */\n pin?: StoredPin;\n /** This side's pin the contact has not confirmed yet (WISP 401 § Pinned message): said on each live session until it does. */\n pinOut?: WirePin;\n /** Ways of paying this device allows in this chat. Absent or true: allowed. */\n paymentMethods?: Partial<Record<PaymentMethodName, boolean>>;\n /**\n * For a way of paying, the networks this chat accepts it on (its cards on the Accept side). Absent for a method:\n * every network. A request, a payment or an ask of a network off here is refused, and the contact is told.\n */\n paymentNetworks?: Partial<Record<PaymentMethodName, WalletNetwork[]>>;\n /** Store-and-forward for this contact (WISP 404, `hold/1`). Absent: off, as for every chat from before it. */\n hold?: HoldState;\n /** An edge of a private group (WISP 900): the group, and the member at the other end. Not a chat. */\n group?: string;\n groupPeer?: string;\n /**\n * Not an edge but an entry session of the group's link (`group-entry/1`): `host` on the admin's\n * side, toward a joiner's member key; `guest` on the joiner's, toward the link's entry key.\n */\n groupEntry?: \"host\" | \"guest\";\n /**\n * An edge whose session is up: set when it opens, cleared when it drops while this app runs (not when the app quits).\n * Still set when the app starts, it was live when the app last ran, and is dialled at once (`resume`). Local only.\n */\n edgeLive?: boolean;\n /** When that session opened: a member's offer from before it is not answered after a restart (`resumeFloor`). Local only. */\n edgeLiveSince?: number;\n}\n\n/** What happened to a group's membership, as a line in its history. */\nexport type GroupEvent = \"created\" | \"joined\" | \"gone\" | \"admin\" | \"rotated\" | \"removed\" | \"left\" | \"forked\" | \"picture\" | \"renamed\";\n\n/** A private group as stored: an invitation not yet answered, or a group I am (or was) in. */\nexport interface StoredGroup {\n id: string;\n createdAt: number;\n /** Pending (or accepted, awaiting the welcome) invitation, on the invitee's side. */\n invitation?: {\n name: string;\n /** The inviter's member key, learned over the contact chat: the admin who must have admitted me. */\n admin: string;\n /** The contact chat it came over. */\n linkId: string;\n e: number;\n n: number;\n /** My member seed, once accepted. */\n seedB64?: string;\n /** Chain pieces that precede the welcome of a long chain. */\n pieces: { t: \"group-chain\"; g: string; commits: GroupCommit[] }[];\n /** Joining through the group's link: its entry key, the admin's side of `linkId` (an entry session, not a contact chat). */\n entry?: string;\n /**\n * Removed and invited again over a contact chat: the hash of the genesis of the chain I held. The id alone names no\n * group (anyone may sign a genesis under it): only a welcome whose chain starts there joins the history I kept.\n */\n genesis?: string;\n /**\n * Taken back by who sent it (`group-decline` over its chat), and when: kept a week so the list says so where the\n * invitation was, instead of it going with no word. Nothing is accepted or owed for it; another invitation replaces it.\n */\n takenBack?: number;\n };\n /**\n * On the admin's side: contact chats invited and not answered yet, and when. Kept so that an accept after my app\n * started again still lets them in.\n */\n invited?: Record<string, number>;\n /** On the admin's side: the group's link is on, with this entry key seed (`group-entry/1`). */\n entry?: { seedB64: string; createdAt: number };\n state?: GroupState;\n /** Member key → the contact chat that invited them (or me): the path for courtesy notices. */\n contacts?: Record<string, string>;\n /**\n * Members whose apps said (`paired-groups` version 3, on their edge or the link that admitted them) that they take\n * rosters past eight: an admin grows the group past eight only when every other member is here (WISP 902 § Bounds).\n */\n large?: string[];\n /**\n * On the admin's side: members I let in over a contact chat or the group's link whose edge has not come up with me\n * yet, so whose app may never have had the group's keys (its welcome lost on the way). A `group-decline` from that\n * chat takes such a member out again, and one that knocks on the link again gets its welcome again; one seen on an\n * edge once is a member, whatever its chat says after.\n */\n unseen?: string[];\n /**\n * Removed, then invited again over a contact chat: the names my app knew members by (member key → name). The history\n * stays while the invitation waits, and still names who wrote it; once in again, the names of members who are gone\n * since go on naming what they wrote.\n */\n formerNames?: Record<string, string>;\n /** Removed, then invited again: the removed group as it was, which a Decline puts back (with the history it kept). */\n previous?: StoredGroup;\n /**\n * Past 16 members, with hubs (WISP 902 · Group Mesh § Hubs): the hubs I kept edges with (as a hub, the other hubs),\n * where my edges go when the app starts again, before the beacon is read.\n */\n hubs?: string[];\n /** Members whose edge came up without `paired-groups` version 4: their apps take no hubs, so hubs keep edges with them. */\n legacy?: string[];\n /**\n * I left: the group is gone from the list and its history from the device. What is kept is the\n * edge to the admin, until the admin's commit removing me arrives or `at` is a week old, so\n * a leave said while the admin was away still reaches it. With hubs, the edges to my hubs too, and my\n * signed leave (`bye`) that they carry to the admin.\n */\n left?: { at: number; admin: string; hubs?: string[]; bye?: GroupByeFrame };\n /**\n * I declined an invitation while its inviter's chat (`linkId`) was down: the answer is said when that chat is up, or\n * dropped once `at` is a week old. Alone when nothing else of the group is kept; beside `state` when the invitation\n * was to a group I was removed from, which stays.\n */\n declined?: { at: number; linkId: string };\n /**\n * On the admin's side: members I removed while they could not be told (their edge was down, and no contact chat\n * carried the notice), by member key. `e`: the epoch of the commit that removed them; `at`: when. The edge to each\n * stays until it opens and that commit goes over it (`told`, the first time), or `at` is a week old: a member removed\n * while its app was closed otherwise never learns it, since every member closes the edge to someone out of the\n * roster. `opened`: when the edge's connection first opened; it closes a while after, told or not.\n */\n farewells?: Record<string, { at: number; e: number; told?: number; answered?: number; opened?: number }>;\n /** The group's pinned message (WISP 400 § Pinned message): the latest pin; `id` \"\" once unpinned. */\n pin?: StoredPin;\n /**\n * When the latest message of another member (`peer`) and the latest one that names me (`mention`) reached this\n * device, where that is later than the message's own time (`cameAt`): what makes the group unread survives a restart.\n */\n came?: { peer?: number; mention?: number };\n /** A community group (`group-community/1`) I am in: its session state. Mesh groups use `state`. */\n community?: CommunityState;\n /**\n * Joining a community group through its link (`group2/…`), or again after my admission lost a\n * race: my member seed, the entry session, and what arrived of the welcome.\n */\n joining?: { g: string; host: string; seedB64: string; linkId: string; name: string; inviter: string; invitedAt?: number; pieces: unknown[]; since: number; check?: boolean };\n}\n\nexport interface GroupMemberView {\n key: string;\n role: GroupRole;\n me: boolean;\n nick?: string;\n /** The pairwise edge to this member is open, or a hub I am connected to reaches them (always true for me). */\n online: boolean;\n /** Reached through a hub, not an edge of mine (WISP 902 · Group Mesh § Hubs). */\n viaHub?: boolean;\n /** A hub of the group now (listed in its beacon), and what the admin said: pinned as a hub, or never one. */\n hub?: boolean;\n hubRole?: \"pin\" | \"exclude\";\n /** Messages of the current epoch known to be missing from this member. */\n missing: number;\n /** The pairwise edge to this member as the engine sees it: absent for me, and until the edge exists. */\n edge?: GroupEdgeView;\n}\n\n/** One edge of a group's mesh (WISP 902): a paired link toward one member, which carries the group to and from them. */\nexport interface GroupEdgeView {\n linkId: string;\n /**\n * `open`: frames flow (the member is reachable). `connecting`: the member answered and the channel is being set up.\n * `waiting`: nothing heard from the member's app yet. `error`: the last attempt failed (`error` says why).\n */\n state: \"open\" | \"connecting\" | \"waiting\" | \"error\";\n /** The transport carrying the edge while it is open: WebRTC, or Iroh or HyperDHT where one side has none. */\n transport?: PairedTransport;\n /**\n * Waiting because this device has no free native slot for it (a member reached over Iroh or HyperDHT, and the\n * device's native listeners held by chats and other group links): it is tried again as one frees up.\n */\n noSlot?: true;\n /** When this device last heard from the member on this edge, in ms (0: never). */\n lastSeenAt: number;\n /** The engine's own words for why the last attempt failed (English, for logs and details); `cause` sums it up. */\n error?: string;\n /**\n * What `error` comes down to, for a short status: `relays`, its packets could not go out or be read (the Pkarr relays\n * failed); `session`, a connection opened and dropped in a way that says nothing about the member (it is dialled\n * again); `other`, anything else (a refused key, no common transport...).\n */\n cause?: GroupEdgeCause;\n}\n\nexport type GroupEdgeCause = \"relays\" | \"session\" | \"other\";\n\n/**\n * How far a join through a group's link got, as the joiner can know it: the knock is being left,\n * it is there for the admin's app to read, that app opened the entry session, it let me in. Or a member's app\n * answered that there is no room yet (`group-full`, WISP 902 § A full group): `full`, or `blocked` while the group\n * waits for every member's app to take more than eight; the joiner goes on knocking.\n */\nexport type GroupJoinStage = \"knocking\" | \"knocked\" | \"answered\" | \"admitted\" | \"full\" | \"blocked\";\n\nexport interface GroupView {\n id: string;\n name: string;\n createdAt: number;\n /** Which kind of group: a private mesh of up to 32 (`group-mesh/1`) or a community (`group-community/1`). */\n profile: \"mesh\" | \"community\";\n /** Absent while it is only an invitation. */\n status?: GroupStatus | \"lost\";\n statusReason?: string;\n epoch?: number;\n myKey?: string;\n isAdmin: boolean;\n /**\n * Admin work is off for this group on this device (WISP 06 § Forced takeover): no commit is signed and no door duty\n * taken until the person turns on \"Manage groups from this device\" (`setGroupManage`).\n */\n adminOff?: true;\n members: GroupMemberView[];\n /** On the invitee's side, until the welcome arrives. */\n /**\n * `waiting`: joining a community through its link, how many others were knocking with me when I last knocked.\n * `since`: joining a community through its link, when the knock started (a group kept here, knocking to get back in,\n * was created long before).\n * `full`: with stage `full` or `blocked`, the members the group has and the most it holds, as the answer said.\n * `unanswered`: accepted over a contact chat, said again as often as it is, and the inviter's app answered nothing\n * while that chat was up: the answer is offered again.\n * `takenBack`: who sent it took it back (the group is gone from its app, or the admin role from it): nothing to answer.\n */\n invitation?: { linkId: string; contact: string; admin: string; members: number; accepted: boolean; unanswered?: boolean; takenBack?: boolean; viaLink?: boolean; stage?: GroupJoinStage; waiting?: number; since?: number; full?: { n: number; max: number } };\n /** The group's link while it is on (`group1/<id>/<entry key>`); only the admin who made it sees it. */\n entryLink?: string;\n /** Contacts (by chat id) invited by me and not yet in. */\n invited: string[];\n /** Contact chats that are members, by chat id → member key. */\n memberLinks: Record<string, string>;\n lastMessageAt: number;\n /** When the latest message from another member arrived (what makes the group unread; mine and membership lines do not); absent for none. */\n lastPeerMessageAt?: number;\n /** When the latest message that names me arrived; absent for none. */\n lastMentionAt?: number;\n /** The latest reaction in the group, for the chat list. */\n lastReaction?: ReactionNote;\n /** The group's pin (WISP 400 § Pinned message): `id` \"\" once unpinned; absent when nothing was ever pinned. */\n pin?: PinView;\n canSend: boolean;\n /** The group's picture (a JPEG data URL the engine checked), set by its admin; absent for none. */\n picture?: string;\n /**\n * A private group past 16 members that runs on hubs (WISP 902 · Group Mesh § Hubs): `hub` when I am one. Absent for\n * the full mesh.\n */\n hubs?: { hub: boolean };\n /** Community groups: how this device is connected (a hub for others, or through hubs). */\n community?: { hub: boolean; hubs: number; connected: number };\n /** Private groups: the members typing now, in the order they started (WISP 902 · Group Mesh § Typing). Never stored. */\n typing?: GroupTypingView[];\n /** Private groups: this side's wake-up tokens, one per member it shared its subscription with (the push worker's table). */\n wakeTokens?: string[];\n /** Private groups: its members were told not to wake this side while the group is muted (`setWakeMuted`). */\n wakeMuted?: boolean;\n /**\n * The names of authors no longer in the roster (removed, left, or back with a new member key), by\n * member key, so their earlier messages still say who wrote them. Absent when there are none.\n */\n formerNames?: Record<string, string>;\n}\n\n/** A member typing in a group: `kind` when it is not plain typing, and a bot's status line, sanitized as in a 1:1 chat. */\nexport interface GroupTypingView { key: string; kind?: Exclude<TypingKind, \"typing\">; status?: string }\n\n/** One item held in this device's storage for the contact, or on its way there. */\nexport interface HeldEntry {\n seq: number;\n /** The id on the wire: the text's wire id, the file's wire id or the payment id. */\n id: string;\n /** The chat message this item is, for its delivery state. */\n messageId: string;\n kind: Exclude<HoldKind, \"manifest\">;\n /** What to rebuild the bundle from: the file's local id or the payment id; text is on the message. */\n ref?: string;\n /** The object's name in storage, chosen before the first upload and kept across retries. */\n name: string;\n bytes: number;\n ts: number;\n expires: number;\n state: \"queued\" | \"held\" | \"failed\";\n error?: string;\n}\n\n/** Everything store-and-forward keeps per chat: the switch, what the contact said, sequences and the outbox. */\nexport interface HoldState {\n /** This device offers `hold/1` in this chat: it accepts held items, and holds items for the contact when it can. */\n enabled: boolean;\n /** The contact's app accepts held items, as it last said (handshake or session). */\n peerAllows?: boolean;\n /** This device's mailbox folder for the contact in its storage, chosen once. */\n mailbox?: string;\n /** Sequence of the last item held for the contact. */\n outSeq: number;\n /** Highest sequence received from the contact and stored. */\n inSeq: number;\n /** Highest sequence the contact said it received of what was held for it. */\n peerAck: number;\n /** This device's pointer revision, and the contact's last seen one. */\n pointerRev: number;\n peerPointerRev: number;\n /** When the manifest and its addresses were last signed (they live seven days). */\n manifestSignedAt?: number;\n outbox: HeldEntry[];\n /** Items from the contact refused on the way in (changed, oversized, not for this chat). */\n refused: number;\n /** Their sequences, the last 32, told to the contact on this device's pointer. */\n refusedSeqs?: number[];\n /** Ways of paying the contact allowed at the last session, for requests held while it is away. */\n peerPaymentMethods?: PaymentMethodName[];\n}\n\n/** A file attached to a message. The bytes live in the `files` store under `id`. */\nexport interface MessageFile {\n id: string;\n name: string;\n size: number;\n mime: string;\n /** A voice message: its length and the shape of its sound. */\n voice?: VoiceMeta;\n /** A video: its length, size and a small first frame, when the sender sent them. */\n video?: VideoMeta;\n /** A picture: its size as it is shown, when the sender sent it. */\n image?: ImageMeta;\n}\n\nexport interface FileTransferView {\n state: \"transferring\" | \"done\" | \"failed\";\n transferred: number;\n size: number;\n error?: string;\n /**\n * files/3 (WISP 501 rev 0.3), while `transferring`: where it stands when it is not moving. `waiting`: no live\n * connection (it goes on by itself); `asking`: the receiver's person has not decided (on the receiving side,\n * this person); `queued`: accepted, waiting its turn; `paused`; `verifying`: every byte there, checking the digest.\n */\n stage?: \"preparing\" | \"waiting\" | \"asking\" | \"queued\" | \"paused\" | \"verifying\";\n /** files/3: the transfer can be paused, resumed and cancelled from here. */\n direction?: \"in\" | \"out\";\n pausedBy?: \"me\" | \"peer\";\n /** Bytes per second lately, while moving. */\n rate?: number;\n /** Receiving, `asking`: bytes this device can still take for files, when it says. */\n room?: number | null;\n /** Failed but the sender can offer it again (files/3). */\n retry?: boolean; /**\n * files/3, unfinished and not moving for a while (or not since the app started): the sender can send it again\n * (`resend`), the receiver ask for it again (`request`). Either goes on from what the receiver holds.\n */\n stalled?: boolean;\n /**\n * A group file (WISP 503) waiting, `waiting`: why. `nobody`: nobody this device is connected to has it; `busy`: its\n * holders answered busy; `damaged`: what came failed the author's digest (`stalled`: its person can ask again).\n */\n wait?: \"nobody\" | \"busy\" | \"damaged\";\n /** The same, in a few words (\"Nobody you are connected to has this file yet\"). */\n note?: string;\n}\n\n/** Ecash held by this peer. One row per proof; `reserved` while an operation is using it. */\nexport interface StoredProof {\n mint: string;\n id: string;\n amount: number;\n secret: string;\n C: string;\n dleq?: unknown;\n /** Handed to the mint for a Lightning payment that has not settled: not spendable, not yet gone. */\n reserved?: boolean;\n /**\n * Brought back by a profile restore: a copy of ecash as it was when the backup was made, which the profile it was\n * copied from (or this one, before the backup was restored) may have spent since. The mint is asked once, and a\n * proof it reads spent is dropped (see `CashuWallet.checkRestored`).\n */\n unchecked?: boolean;\n}\n\n/** A Lightning payment the mint has not settled yet. Its proofs stay in the wallet, reserved, until it does. */\nexport interface PendingMelt {\n /** The melt quote id. */\n quote: string;\n mint: string;\n /** The invoice, so it is not paid a second time while this one is in flight. */\n request: string;\n /** What the invoice pays. */\n amount: number;\n /** The reserved proofs handed to the mint. */\n secrets: string[];\n /** Everything that left the balance for this payment: the proofs above plus any swap fee. */\n outlay: number;\n /** NUT-08 blank outputs (`OutputData.serialize`), to unblind the fee change once the mint returns it. */\n outputs: unknown[];\n note?: string;\n /** Set when the invoice pays a contact's payment request. */\n paymentId?: string;\n createdAt: number;\n}\n\n/**\n * A swap at a mint, written down before the mint is asked, with everything that finishes it: the mint signs the\n * outputs of `swap` and nothing else, so its signatures can be asked for again (NUT-09) for as long as this is kept.\n * Deleted in the transaction that stores what the swap brought, or once the mint says it never happened.\n */\nexport interface PendingSwap {\n id: string;\n mint: string;\n /** `receive`: a token redeemed into this wallet. `send`: this wallet's ecash split, for a token or a Lightning payment. */\n kind: \"receive\" | \"send\";\n /** cashu-ts' `SerializedSwapPreview`: the inputs and the blinded outputs with their secrets. As secret as the ecash itself. */\n swap: unknown;\n createdAt: number;\n /** When the request to the mint ended with no answer. The mint may still act on it for a moment after that. */\n attemptEndedAt?: number;\n /**\n * The inputs were given back (the swap was overdue with nothing signed), and it holds nothing any more. Still asked\n * about for a while: a request that reached the mint late is recovered from what is written here.\n */\n released?: boolean;\n /** What it brought is stored; only the chat of its payment is still to be told (the app closed in between). */\n finished?: boolean;\n /**\n * Its inputs read spent at a mint that cannot say what it signed (no NUT-09): how many times that was the answer.\n * Kept, since the outputs are the only way back to that ecash, and asked about less and less often (`askAt`).\n */\n stuck?: number;\n /** Not asked about before this time. */\n askAt?: number;\n /** Came with a restored copy of the profile: what it brings is as old as the copy, and is checked like the copy's ecash. */\n restored?: boolean;\n /** `receive`: the token being redeemed. Redeeming it again while this is kept finishes this swap, and makes no other. */\n token?: string;\n /** `receive`: the history line of the ecash, written with it. */\n tx?: WalletTx;\n /** `receive`: the payment record written with the ecash (a contact's payment received, or one of ours taken back). */\n payment?: StoredPayment;\n}\n\n/** A Lightning invoice the mint issued for us; paid invoices turn into ecash. */\nexport interface StoredQuote {\n quote: string;\n mint: string;\n amount: number;\n invoice: string;\n createdAt: number;\n expiresAt: number | null;\n /** Set when the invoice belongs to a payment request sent to a peer. */\n paymentId?: string;\n /**\n * The mint says it issued this quote's ecash, but the wallet never stored it. Kept, not polled: it is the\n * only record of sats the user paid for.\n */\n issuedUnclaimed?: boolean;\n /** The mint said this invoice is paid, and its ecash is not claimed yet (the claim is tried again every round). */\n paid?: boolean;\n /**\n * The mint marks its invoices paid by itself (a test mint, `paysItsOwnInvoices`): its \"paid\" says nothing about a\n * payer. Not polled, never minted by itself: a payer saying it paid (`CashuWallet.vouch`) lets it be minted.\n */\n held?: boolean;\n /** Asked for on purpose, as test coins from a test mint (\"Get test coins\"): minted as soon as the mint says paid. */\n testCoins?: boolean;\n /**\n * The blinded outputs the mint is asked to sign for this invoice (`OutputData.serialize`), written down before it\n * is asked: the ecash it issues is for these, and its signatures for them can be asked for again (NUT-09).\n */\n outputs?: unknown[];\n}\n\nexport type PaymentState =\n | \"pending\" // sent, no word from the peer yet / request waiting to be paid\n | \"settled\"\n | \"failed\"\n | \"reclaimed\"; // the peer never took the ecash and it is back in the wallet\n\n/** A payment or payment request in a chat, by id. */\nexport interface StoredPayment {\n /** A request made because the contact asked to pay (its `pay-ask` id), or, on the payer's side, the ask it answers. */\n ask?: string;\n target?: PaymentTarget;\n txid?: string;\n id: string;\n linkId: string;\n kind: \"payment\" | \"request\";\n direction: \"in\" | \"out\";\n amount: number;\n unit: string;\n memo?: string;\n state: PaymentState;\n error?: string;\n createdAt: number;\n mint?: string;\n /** Outgoing ecash, kept until the peer confirms so it can be reclaimed. */\n token?: string;\n /** Requests: how the payer can pay. */\n invoice?: string;\n mints?: string[];\n /**\n * Real money or test coins: only a wallet of this network pays a request, or made a payment. Absent on records\n * from before wallets had their own network: its target, mints or invoice say (see `paymentNetwork`).\n */\n network?: WalletNetwork;\n /** Fedimint requests: the federations the payee takes ecash of (their ids). */\n federations?: string[];\n /** Fedimint payments: the federation of the notes, and the client operation that spent (or redeems) them. */\n federation?: string;\n fedimintOp?: string;\n /** Incoming Fedimint payments: a digest of the notes, kept after they are redeemed or refused, so the same notes sent again under another id are answered from it. */\n notesDigest?: string;\n requestId?: string;\n /** Requests we pay: a Lightning payment is in flight at the mint, so paying again would pay twice. */\n lightningPending?: boolean;\n /** Requests we pay: this device paid it over Lightning (the payee's wallet cannot say who paid an invoice). */\n paidHere?: boolean;\n /**\n * A request to a whole group (WISP 902 § Payments): the group's id. Its `linkId` is `group:<id>`, it went to\n * every member over their edges, and the first member whose payment settles it pays it; later ones are refused.\n */\n group?: string;\n /**\n * A request closed by the one who made it (their wallet for it was removed): it can no longer be paid, and a\n * payment still made to what it named is lost. Set on both sides, with `state` \"failed\".\n */\n closed?: boolean;\n}\n\n/** What pages see of a payment: everything but the token. */\nexport type PaymentView = Omit<StoredPayment, \"token\">;\n\nexport interface MintView {\n url: string;\n name: string;\n balance: number;\n /** Null until the mint answered once. */\n info: MintInfoView | null;\n /** What it still waits for (see `mintAwaiting`), when it does: removing it asks about these first. */\n awaiting?: WalletAwaitingView[];\n}\n\n/** What a mint says about itself and what it charges. */\n/** What a pasted piece of ecash says about itself. Mirrors `CashuInspection` in the UI's platform contract. */\nexport type CashuInspection =\n | { kind: \"token\"; amount: number; unit: string; mint: string; memo?: string; accepted: boolean }\n | { kind: \"request\"; amount: number | null; unit: string; mints: string[]; description?: string };\n\nexport interface MintInfoView {\n version?: string;\n motd?: string;\n /**\n * Fee for spending ecash, in sats per thousand proofs (NUT-02). A swap,\n * a redeem or a Lightning payment spends a handful of proofs and is charged\n * the sum, rounded up. 0 means spending ecash is free.\n */\n inputFeePpk: number;\n /** Lightning in and out: allowed amounts in sats, null when the mint sets no bound. */\n receive: { min: number | null; max: number | null } | null;\n send: { min: number | null; max: number | null } | null;\n}\n\nexport type WalletTxKind =\n | \"lightning-in\"\n | \"lightning-out\"\n | \"ecash-in\" // from a contact, or a pasted token\n | \"ecash-out\"\n | \"reclaimed\"\n | \"fee\"; // a Lightning payment failed, and the mint kept its fee for preparing it (`amount` 0)\n\n/** One movement of the wallet. `fee` is exact: what left the balance beyond `amount`, or what a redeem cost. */\nexport interface WalletTx {\n id: string;\n timestamp: number;\n mint: string;\n kind: WalletTxKind;\n /** Sats that moved, always positive; the kind gives the direction. */\n amount: number;\n fee: number;\n note?: string;\n}\n\n/** The kinds of wallet a profile can have: each is one card of the deck, on one network. */\nexport type WalletType = \"cashu\" | \"lightning\" | \"arkade\" | \"bark\" | \"spark\" | \"bitcoin\" | \"fedimint\" | \"usdt\";\nexport const WALLET_TYPES: readonly WalletType[] = [\"cashu\", \"lightning\", \"arkade\", \"bark\", \"spark\", \"bitcoin\", \"fedimint\", \"usdt\"];\n\n/** One network's wallets, whole: what `WalletView` shows of the network in use, for either network. */\nexport interface NetworkWalletsView {\n ark?: ArkWalletView;\n bark?: BarkWalletView;\n fedimint?: FedimintWalletView;\n spark?: SparkWalletView;\n usdt?: UsdtWalletView;\n /** The network's default Lightning card for receiving (what a caller from before cards reads as its Lightning). */\n lightning?: LightningView;\n /** Every Lightning card of the network, in the order they were added: each its own source, balance and history. */\n lightnings?: LightningCardView[];\n bitcoin?: BitcoinView;\n /** This network's Cashu mints (test mints and mints on this machine are Testnet's). */\n mints: MintView[];\n balance: number;\n /** Cashu: sats held for a payment or a swap the mints have not settled yet. Not in `balance`, and not gone. */\n setAside?: number;\n /** Cashu: swaps at these mints that are not finished yet, and the sats in them. Removing the wallet names them. */\n openSwaps?: number;\n swapsAmount?: number;\n /** Cashu: sats in swaps whose inputs read spent at a mint that cannot say what it gave for them. Not in `setAside`. */\n unconfirmed?: number;\n /** This network's history, newest first. */\n history: WalletTx[];\n feesPaid: number;\n /** What this network's wallets still wait for: removing one reads it (see `walletRemoval`). Always set by the engine. */\n awaiting?: WalletAwaitingView[];\n}\n\n/**\n * Something one wallet still waits for. `request`: a request of ours still open in a chat that only this wallet can\n * be paid through; `invoice`: an invoice of its own, not paid and not expired; `paid`: an invoice the mint says is\n * paid whose ecash is not claimed yet; `unclaimed`: ecash the mint says it issued for an invoice of ours that this\n * wallet never received; `sent`: ecash sent from it that the contact has not taken yet. Removing the wallet loses\n * what is paid to the first four; `sent` Cashu comes back once its mint is added again.\n */\nexport interface WalletAwaitingView {\n /** The wallet it goes through (Lightning through the Cashu mints is the Cashu wallet's). */\n type: WalletType;\n /** `swap`: sats in an exchange with a Cashu mint that the mint has not settled yet (`PendingSwap`). */\n kind: \"request\" | \"invoice\" | \"paid\" | \"unclaimed\" | \"sent\" | \"swap\";\n /** In the wallet's base unit: sats, or the token's smallest unit for USDT. */\n amount: number;\n /** The chat payment it belongs to, when it does. */\n paymentId?: string;\n /** Lightning: the card it goes through. */\n card?: string;\n}\n\n/**\n * A wallet the profile has: one type on one network, one of each but Lightning, which a network can have several\n * cards of. `id` is `<type>:<network>`, and `lightning:<network>:<card>` for a Lightning card. Its seed and secrets\n * stay in the engine; `config` holds only what can be shown (a server, a source).\n */\nexport interface WalletInstanceView {\n id: string;\n type: WalletType;\n network: WalletNetwork;\n config: Record<string, string>;\n /** Lightning: the card's id on its network, its name, and whether it is the network's default for receiving. */\n card?: string;\n name?: string;\n receive?: boolean;\n /**\n * At home on another device (WISP 06 § Wallets that stay home): that device's name (\"\" when this device does not know\n * it), and when its coins expire (Ark, Bark). It is never opened here, and takes no payment here.\n */\n home?: { device: string; expiresAt?: number };\n}\n\n/**\n * What New can make: one type on one network. `available` false: `reason` says why (Mainnet not validated yet,\n * one already there). `needs`: the one thing it asks for, when it cannot be made in one click.\n */\nexport interface WalletOffer {\n type: WalletType;\n network: WalletNetwork;\n available: boolean;\n reason?: string;\n /** Already made: its card is in the deck. */\n exists?: boolean;\n /** A network can have several of it (Lightning): New adds one more, whatever `exists` says. */\n several?: boolean;\n needs?: \"invite\" | \"provider\" | \"apiKey\";\n /** Lightning and on-chain: the sources that can be picked on this network. */\n providers?: ProviderDescriptorView[];\n}\n\n/** What New asks the engine to make (see `walletCreate`). */\nexport interface WalletCreate {\n type: WalletType;\n network: WalletNetwork;\n /** Lightning and on-chain: the source, and the values of its form (blank ones take the network's default). */\n providerId?: string;\n values?: Record<string, string>;\n /** Fedimint: the federation's invite code. */\n invite?: string;\n /** Spark on Mainnet: the person's Breez API key. */\n apiKey?: string;\n}\n\n/** What Remove asks the engine to take away (see `walletRemove`). */\nexport interface WalletRemove {\n type: WalletType;\n network: WalletNetwork;\n /** Lightning: the card to remove; absent, the network's default for receiving. */\n card?: string;\n /** The person confirmed that what it holds on this device becomes unreachable without its backup. */\n acceptLoss?: boolean;\n}\n\n/** \"Get test coins\" on a Testnet wallet: which one asks its faucet. */\nexport interface WalletTestCoins {\n type: WalletType;\n network: WalletNetwork;\n /** Lightning: the card asking (only the Cashu mints' card has a faucet); absent, the default one. */\n card?: string;\n}\n\n/** What the faucet gave: `amount` in the wallet's own unit (\"test sats\", \"TEST-USDT\"). */\nexport interface TestCoinsResult {\n amount: number;\n unit: string;\n /** Asked, and on its way (a transaction the chain has not confirmed): it shows up in a few seconds. */\n pending?: boolean;\n}\n\n/**\n * The first-run wallet setup of a new profile: the Mainnet kinds still to make, and why the last try at each failed.\n * Kept in the settings. Absent on a profile from before it, which is never set up by itself.\n */\nexport interface WalletSetupRecord {\n /** Kinds not made yet, tried again at each start. A kind with no Mainnet wallet here yet (on-chain) waits quietly. */\n left: WalletType[];\n /** Why the last try failed, per kind still left. */\n failed?: Partial<Record<WalletType, string>>;\n}\n\n/** What the Wallet page shows of the first-run setup: whether it runs now, and each kind it could not make. */\nexport interface WalletSetupView {\n running: boolean;\n failed: { type: WalletType; network: WalletNetwork; reason: string }[];\n}\n\nexport interface WalletView {\n /** The first-run setup, while it runs or has a kind it could not make; absent otherwise. */\n setup?: WalletSetupView;\n /** Both networks' wallets, open side by side. The flat fields below are Mainnet's, for a caller naming no network. */\n networks?: Record<WalletNetwork, NetworkWalletsView>;\n /** The wallets this profile has, in the deck's order. */\n wallets?: WalletInstanceView[];\n /** What New can make on each network. */\n offers?: WalletOffer[];\n ark?: ArkWalletView;\n /** Second's Ark (Bark): a different Ark server from `ark`, not interchangeable with it. */\n bark?: BarkWalletView;\n /** Federations joined with an invite code, and their ecash. */\n fedimint?: FedimintWalletView;\n /** Spark, wallet to wallet (the Breez SDK's Spark wallet; it can also be the Breez Lightning source). */\n spark?: SparkWalletView;\n usdt?: UsdtWalletView;\n /** The Lightning source of this mode (the Cashu mints by default) and its latest operations. */\n lightning?: LightningView;\n lightnings?: LightningCardView[];\n /** The on-chain Bitcoin source of this mode, if one is set up. */\n bitcoin?: BitcoinView;\n intents?: PaymentReview[];\n mints: MintView[];\n balance: number;\n /** Cashu: see `NetworkWalletsView`. */\n setAside?: number;\n openSwaps?: number;\n swapsAmount?: number;\n unconfirmed?: number;\n /** Newest first. */\n history: WalletTx[];\n feesPaid: number;\n /** Each Mainnet wallet's backup reminder, by wallet id: the page works out which asks now (`backupDue`). */\n backupReminders?: BackupReminders;\n}\n\nexport interface StoredMessage {\n wireId?: string;\n /**\n * `held`: in this device's storage, waiting for the contact to come back (WISP 404).\n * `queued`: unconfirmed, and sent again by itself under the same id once the chat can carry it.\n * `waiting`: not sent yet; it goes by itself when the chat can carry it (live, or the DHT text before it\n * confirmed), and can be cancelled meanwhile (\"Sends when live\", WISP 400).\n */\n delivery?: \"sending\" | \"sent\" | \"queued\" | \"waiting\" | \"held\" | \"delivered\" | \"failed\";\n deliveryError?: string;\n /** Until when a `queued` message is sent again by itself; after that it waits for Retry. */\n resendUntil?: number;\n /**\n * A file of mine that waits for the live link, said on the DHT floor meanwhile (WISP 403 § Files): `sent` its offer\n * went, `seen` the contact's app took it (its bubble is in place there), `unfit` it cannot go there (it waits as before).\n */\n fileOnFloor?: \"sent\" | \"seen\" | \"unfit\";\n linkId: string;\n id: string;\n text: string;\n sender: \"me\" | \"peer\";\n /**\n * The row's place in its history, which is sorted by it (WISP 400, requirement 10). Mine: when I sent it. Received:\n * when it was first stored on this device (`arrivalKey`), never the sender's clock, so a conversation reads in the\n * order things happened here. A row received before `sentAt` existed keeps the sender's time it was stored under.\n */\n timestamp: number;\n /** Received: when the sender says it sent it, by its own clock. Shown (`shownTime`), never sorted by. */\n sentAt?: number;\n /** `hold`: through the sender's storage while the other side was away (WISP 404). */\n via: \"pkarr\" | \"datalink\" | \"hold\";\n nick?: string;\n file?: MessageFile;\n paymentId?: string;\n /** Group messages: the member key of the sender. */\n member?: string;\n /** Group history lines that are not messages. */\n event?: GroupEvent;\n /** Group history: a payment between members, as the group knows it (WISP 902 § Payments). */\n groupPay?: GroupPayNote;\n /** Group messages: the places of the text that name members, by member key (WISP 902 § Mentions). */\n mentions?: GroupMention[];\n /** A group message someone else sent that names me (or everyone). */\n mentioned?: true;\n /**\n * A private group's message from another member: the frame it came in (sealed, signed by its author), kept on the\n * group's newest rows to hand on to members who were away (`GROUP_LIMITS.relay`, WISP 902 § Catch-up). The engine's\n * own: a row goes to a page without it (`withoutFrame`).\n */\n frame?: GroupMessageFrame;\n /** How this message travelled, as the engine saw it go or come (the message's details view). */\n details?: MessageDetails;\n /** A link preview that came with the text (WISP 401 § Link previews): made by the sender's app, never fetched here. */\n preview?: LinkPreview;\n /** The message this one answers (WISP 400 § Replies). */\n replyTo?: MessageReply;\n /**\n * Reactions to it (WISP 400 § Reactions), one per person: `me`, `peer` (a 1:1 chat's contact) or a group member's\n * key. One taken back stays with an empty emoji, so an older reaction arriving late does not bring it back.\n */\n reactions?: Record<string, MessageReaction>;\n /** A text edited after it was sent (WISP 400 § Edits): `text` is the latest version. */\n edit?: MessageEdit;\n /**\n * A forwarded text or file (WISP 400 § Forwards): how many times it has been forwarded, this hop included. Only the\n * count: never who wrote it first, nor where it came from.\n */\n forwarded?: number;\n /**\n * A bot's status card (WISP 405 · Status Cards): a task or a routine shown instead of `text`, which is its fallback.\n * It belongs to the version it came with: an edit brings its own, or leaves the message a text.\n */\n card?: StatusCard;\n /**\n * A reply that presses a button of this side's own message (WISP 406 · Message Buttons), as the author's engine took\n * it: set only when the button was open for this person. What `button.pressed` reports.\n */\n press?: MessagePress;\n /**\n * A card of mine (WISP 405 · Status Cards, WISP 406 · Message Buttons) whose text went on the DHT floor or into a hold,\n * which carry text alone: `due` until the card goes again live, as an edit of the card alone; `sent` once it did, or\n * once it was given up (too old).\n */\n cardRestore?: \"due\" | \"sent\";\n /** What `cardRestore` was named before it covered every kind of card (buttons only): read for rows kept since. */\n buttonsRestore?: \"due\" | \"sent\";\n}\n\n/**\n * A message that carries a task, a routine or a usage card, as the Tasks board and the chat list read it across every\n * chat and group (`statusCardIndex`): where it is (`linkId`: a chat's id, or `group:<id>`), its message, who sent it (`member`: a group\n * member's key, absent for my own), when it was sent and when its last edit was made. Nothing else of the message.\n */\nexport interface CardIndexRow {\n linkId: string;\n id: string;\n card: TaskCard | RoutineCard | UsageCard;\n sender: \"me\" | \"peer\";\n member?: string;\n timestamp: number;\n editedAt?: number;\n}\n\n/** A page of a chat's history, oldest first, and whether older messages remain (`messagePage`). */\nexport interface MessagePage { messages: StoredMessage[]; more: boolean }\n\n/**\n * A chat's pin as kept (WISP 400 § Pinned message): the message's id both sides know (`id`, \"\" unpinned), the pinner's\n * number (the highest wins), who pinned (`me`, `peer`, or a member's key) and when it came here. `messageId`: the row\n * here, when it was here then. A private group's keeps the pinner's signature (`k`, `sig`), to pass it on.\n */\nexport interface StoredPin extends WirePin { by: string; at: number; messageId?: string; k?: string; sig?: string }\n\n/** A chat's pin, as the pages and the CLI see it. `id`: the id both sides know the message by (\"\" once unpinned); `messageId`: the row here. */\nexport interface PinView { id: string; by: string; at: number; messageId?: string }\n\n/** One person's reaction to a message: the emoji (\"\" once taken back), their number (the highest wins), when it came. */\nexport interface MessageReaction { e: string; n: number; at: number }\n\n/** The latest reaction in a chat, as the chat list says it: \"Ana reacted ❤️ to \"…\"\". */\nexport interface ReactionNote {\n at: number;\n /** Who reacted: `me`, `peer`, or a group member's key. */\n by: string;\n emoji: string;\n /** A line of the message it went to. */\n snippet: string;\n /** The message was mine. */\n mine: boolean;\n /** The message's id here: an edit of it changes `snippet`. */\n message?: string;\n}\n\n/** How a message was edited (WISP 400 § Edits). */\nexport interface MessageEdit {\n /** The edit shown: its number (1 for the first; the highest one wins) and when its author made it. */\n seq: number;\n at: number;\n /** Earlier versions, oldest first (the newest `EDIT_HISTORY_KEEP`): when each was written, and its text. */\n history: MessageVersion[];\n /** Mine: the contact has not confirmed this edit yet. It goes by itself once the chat is live and both sides offer edit/1. */\n pending?: true;\n /**\n * Mine: the engine's own edit that sends a question's buttons again, the text unchanged (WISP 406 · Message Buttons),\n * never one a person or a bot made. The bot's event stream reports none for it; a later edit makes a new one without it.\n */\n restore?: true;\n}\n\nexport interface MessageVersion { at: number; text: string }\n\n/**\n * The message a reply answers, as kept with the reply (WISP 400 § Replies). What the replier's app saw of it goes\n * with the reply; the receiver keeps its own view of the original when it has it (`messageId`), and the line and the\n * author then come from there, not from the wire.\n */\n/** A button press on this side's message (WISP 406 · Message Buttons). */\nexport interface MessagePress {\n /** The message with the buttons, its id here. */\n messageId: string;\n button: string;\n label: string;\n /** Named by its label or id in the text, not by the reply (the DHT floor, or an app without buttons). */\n inferred?: true;\n}\n\nexport interface MessageReply {\n /** The original's id in this chat as both sides know it: a paired chat's wire, file or payment id; a group message id. */\n id: string;\n /** A line of the original, plain and short (`replySnippet`); empty when only its id came (over the DHT). */\n snippet: string;\n /** Who wrote the original; absent when nothing says (only its id came, and it is not here). */\n from?: \"me\" | \"peer\";\n /** A group's original: its author's member key. */\n member?: string;\n /** A button press (WISP 406 · Message Buttons): the original's button this reply presses, its text the label. */\n button?: string;\n /**\n * The original here, when it was found in this chat as the reply was kept (always, for a reply sent here). Without\n * it the line is only what the replier's app said: shown, but marked as not checked.\n */\n messageId?: string;\n}\n\n/**\n * The way a message went, as the engine saw it at that moment: a live transport, the DHT floor (a paired chat's\n * `dht-text/1` envelope), the sender's storage (`hold/1`), or a chat from before pairing (`legacy-*`).\n */\nexport type MessagePath = PairedTransport | \"dht\" | \"hold\" | \"legacy-datalink\" | \"legacy-dht\";\n\n/** One send of a message: when, over what, and how it ended. */\nexport interface MessageSend {\n at: number;\n path: MessagePath;\n /** The live session crossed a relay (WISP 100, \"Relayed\"); the relays' hosts. */\n relayed?: boolean;\n relays?: string[];\n /** The round trip measured on the session at the time. */\n rttMs?: number;\n /** Absent while the send is under way. */\n result?: \"sent\" | \"failed\";\n error?: string;\n}\n\n/**\n * What the engine remembers about one message's travels, for its details view (WISP 400 § Message details): facts\n * as they were when the message went or came, that nothing else keeps. Kept on the message row, bounded by\n * `MESSAGE_DETAILS_MAX_BYTES`. Never a key, a seed or anything else that opens the chat.\n */\nexport interface MessageDetails {\n /** Sends of this message, oldest first: the first and the last ones (`MESSAGE_DETAILS_MAX_SENDS`). */\n sends?: MessageSend[];\n /** Sends there were in all, when more than `sends` keeps. */\n attempts?: number;\n /** A received message: when it came and over what. */\n received?: Omit<MessageSend, \"result\" | \"error\">;\n /** The last successful send was handed to the transport. */\n sentAt?: number;\n /** The contact's receipt came (the message is `delivered`). */\n receiptAt?: number;\n /** Stored for an away contact, sealed for them. */\n heldAt?: number;\n /** A file: its last byte arrived here, or the contact confirmed it. */\n completedAt?: number;\n /** The frame the message travelled in. `wireBytes`: as sent on the channel (a JSON frame, a sealed record, a bundle). */\n wire?: { frame: string; protocol: string; plaintextBytes?: number; wireBytes?: number; chunks?: number; chunkBytes?: number };\n /** The DHT floor: the envelope's sequence and times, the sealed packet's size and nonce, the record read. */\n dht?: { seq?: number; issued?: number; expires?: number; packetBytes?: number; nonce?: string; recordKey?: string; records?: string[] };\n /** Store-and-forward: the item's sequence in the mailbox, its sealed size and expiry. */\n hold?: { seq?: number; bytes?: number; expires?: number; mailbox?: string };\n}\n\n/** A details record larger than this (as JSON) is trimmed: its oldest sends go first. */\nexport const MESSAGE_DETAILS_MAX_BYTES = 2048;\n/** Sends kept on a record: the first, and the last ones. */\nexport const MESSAGE_DETAILS_MAX_SENDS = 6;\n\n/** What the details view is made of: the row, its record, and what the engine knows around it right now. */\nexport interface MessageDetailsView {\n message: Pick<StoredMessage, \"id\" | \"wireId\" | \"linkId\" | \"sender\" | \"timestamp\" | \"sentAt\" | \"via\" | \"delivery\" | \"deliveryError\" | \"resendUntil\" | \"member\" | \"nick\"> & {\n kind: \"text\" | \"file\" | \"voice\" | \"payment\" | \"event\" | \"note\";\n /** UTF-8 bytes of the text. */\n textBytes: number;\n };\n details?: MessageDetails;\n /** The chat the message is in. Keys are the public participation keys (z-base-32), never a seed. */\n link?: {\n id: string;\n profile?: \"paired-chat/1\";\n deliveryMode?: DeliveryMode;\n /** Our own participation key on this link. */\n myKey: string;\n /** The contact's, as pinned; absent before the first contact. */\n peerKey?: string;\n /** The pinned key was compared by the two people (WISP 401 § verification). */\n verified: boolean;\n /** The session carrying the chat right now, if any. */\n transportNow?: PairedTransport;\n relayedNow?: boolean;\n rttNowMs?: number;\n };\n /** The file the message carries (files/2, files/3 or a held item), as stored. */\n file?: {\n id: string; name: string; size: number; mime: string;\n /** SHA-256 of the bytes, hex, once known (the sender's own, or checked on arrival). */\n digest?: string;\n protocol?: \"files/2\" | \"files/3\" | \"hold/1\";\n state?: string;\n stage?: string;\n transferred?: number;\n /** Why a transfer that ended without the file did. */\n error?: string;\n /** files/3: bytes confirmed durably, where a restart resumes. */\n confirmed?: number;\n /** files/3: when the offer was made. */\n since?: number;\n consented?: boolean;\n /** Where the bytes are kept on this device. */\n storage?: string;\n voice?: { duration: number; peaks: number };\n };\n /** The payment or request the message stands for: everything but the money itself. */\n payment?: {\n id: string; kind: \"payment\" | \"request\"; direction: \"in\" | \"out\"; amount: number; unit: string; state: PaymentState;\n method?: string; network?: string; provider?: string; asset?: string; mint?: string; txid?: string; ask?: string;\n createdAt: number; error?: string; memo?: string; group?: string;\n /** SHA-256 of the invoice, hex, when there is one (the invoice itself carries the amount and a payment hash). */\n invoiceDigest?: string;\n lightningPending?: boolean; paidHere?: boolean;\n };\n /** A group message: which group, and the member key of the sender when it is not this device. */\n group?: { id: string; member?: string; profile?: string };\n}\n\n/** The ways of paying a group note can name. */\nexport type GroupPayRail = \"cashu\" | \"lightning\" | \"arkade\" | \"bark\" | \"bitcoin\" | \"spark\" | \"usdt\" | \"fedimint\";\n\n/**\n * A payment between two members as the whole group sees it (WISP 902 § Payments): who pays whom, how much, over\n * what, and how it stands. No money and nothing to pay with: that travels only on the edge between the two.\n */\nexport interface GroupPayNote {\n id: string;\n /** `request`: a member asks to be paid; `payment`: a member sends without a request. */\n kind: \"request\" | \"payment\";\n /** Who pays: a member key, or `*` for a request any member may pay once. */\n from: string;\n /** Who is paid. */\n to: string;\n /** In the unit's smallest denomination (sats, token base units). */\n amount: string;\n unit: string;\n decimals: number;\n rail: GroupPayRail;\n /** Test money (test mints, test networks): worth nothing. */\n test?: boolean;\n memo?: string;\n /** The request answers the payer's own ask (Ark, Bark, on-chain, USDT): the payer started it. */\n ask?: boolean;\n createdAt: number;\n /** `sent`: the payer says it paid, the payee has not confirmed. `closed`: failed, refused or taken back. */\n state: \"open\" | \"sent\" | \"paid\" | \"closed\";\n /** Who paid it: the payee's word, or, when its wallet cannot tell (an invoice), the only member who said so. */\n by?: string;\n /** Members who said they paid it. */\n claims?: string[];\n /** The last note this device said about it, sent again when an edge opens. */\n mine?: GroupPayFrame;\n}\n\n/** `group-pay` on an edge (WISP 902 § Payments). Old apps drop it. */\nexport interface GroupPayFrame {\n t: \"group-pay\";\n g: string;\n id: string;\n k: \"req\" | \"pay\";\n f: string;\n to: string;\n v: string;\n u: string;\n d: number;\n r: GroupPayRail;\n x?: 1;\n m?: string;\n a?: 1;\n ts: number;\n st: GroupPayNote[\"state\"];\n by?: string;\n}\n\n/** A local web application the user chose to share. */\nexport interface StoredService {\n id: string;\n name: string;\n /** Normalized loopback URL, e.g. `http://localhost:3400` */\n target: string;\n enabled: boolean;\n createdAt: number;\n /**\n * The contacts allowed to reach this service, by participation key. Absent or\n * empty means nobody: a service is granted per contact, never to everyone at\n * once, and a service from before this existed starts closed rather than\n * keeping an exposure its owner never chose.\n */\n sharedWith?: string[];\n}\n\nexport interface IceServerSetting {\n urls: string;\n username?: string;\n credential?: string;\n}\n\nexport interface Settings {\n online: boolean;\n nick: string;\n /** This profile's picture, shown to paired contacts: a small square JPEG data URL. */\n avatar?: string;\n /**\n * Whether contacts are told this profile's name and picture (WISP 401 § name and picture). Absent means\n * yes; off, every chat and group member is told there is none, and the join notice names nobody.\n */\n shareProfile?: boolean;\n /** The Pkarr relays. Browsers read and write through them; the Desktop writes to them (so browser contacts see its packets). */\n relays: string[];\n /**\n * Where the DHT is reached directly (the Desktop): whether reads may use the relays too, \"Also use Pkarr relays\"\n * in Settings, Network. Absent means no: reads go to the DHT alone.\n */\n readRelays?: boolean;\n /** Extra ICE servers (typically TURN) on top of the built-in STUN set. */\n iceServers: IceServerSetting[];\n /**\n * The Iroh relays a browser build homes on (WISP 102, relay only): a page cannot send UDP, so every Iroh\n * packet goes through one. Absent or empty means the defaults (n0's public relays, as the desktop app's).\n */\n irohRelays?: string[];\n /**\n * The HyperDHT relay (wss://) this browser reaches the HyperDHT through, so paired chats can use HyperDHT\n * (WISP 103) beside WebRTC. Empty: none. Absent means the default (shared/hyperdhtRelay.ts). The Desktop\n * runs HyperDHT itself and ignores it.\n */\n hyperdhtRelay?: string;\n /** Cashu mints this peer holds ecash at and accepts ecash from. The first is where invoices are created. */\n mints: string[];\n /** False until the default mints were put in place once; after that the list is the user's. */\n mintsInitialized: boolean;\n /** The Nostr social layer: relays, automatic profile loading, publication. Absent means the defaults, everything off. */\n nostr?: NostrSocialSettings;\n /**\n * Identity cards show the public profile (picture, name, bio, counts) of a verified identity, read from its\n * network's public host when the card is on screen (PUBLIC-PROFILES.md). Absent means on; off drops what was kept.\n */\n publicProfiles?: boolean;\n /**\n * Whether paired contacts are told when this profile is typing (WISP 401 § Typing). Absent means yes; off,\n * nothing is said, and a contact's typing is still shown.\n */\n sendTyping?: boolean;\n /**\n * Whether files are downloaded without asking where the rules allow it (WISP 503: a group's voice messages, and its\n * files up to 8 MiB within 256 MiB a group). Absent means on; off, each waits for a Download.\n */\n autoDownloads?: boolean;\n /**\n * Whether this device sends the group files it keeps to members who ask (WISP 503 § Serving limits). Absent means\n * on; off, it answers that it is busy and does not say it holds them.\n */\n serveFiles?: boolean;\n /**\n * Where items are held for away contacts (WISP 404): the profile's S3 storage and its random space\n * (WISP 1000/1002), as set up under Profile → Backups. Kept here for the peer, which may run outside the\n * page; never copied into a backup.\n */\n holdStorage?: { s3: S3Config; space: string } | null;\n /** The first-run wallet setup (see `WalletSetupRecord`). Only the engine writes it: a settings patch cannot. */\n walletSetup?: WalletSetupRecord;\n /**\n * This profile's push subscription (the installed web app, WISP 401 § Wake-up push): shared with each paired\n * contact whose app offers `wake/1`, so it can wake this app while it is closed. Absent: not woken.\n */\n wake?: WakeSubscription;\n /**\n * A contact who held `wake` can no longer be told to forget it (its chat was deleted, or muted: a contact may ignore\n * `w: null`). The app replaces the subscription (a new endpoint and VAPID key pair, new tokens for the others) and\n * `setWakeSubscription` clears this. Set by the engine only.\n */\n wakeRotate?: boolean;\n /**\n * Private groups muted here (WISP 902 · Group Mesh § Wake-up push): their members are told to forget this profile's\n * subscription, as a muted chat's contact is. Only through `setWakeMuted` with the group's `group:<id>`.\n */\n wakeMutedGroups?: string[];\n /**\n * Private groups → members that were given a token on an edge a group on hubs later dropped: they still hold the\n * subscription with no edge to close, so their leaving the group (or this profile leaving it, or muting it) replaces\n * it. Cleared with every new subscription. Set by the engine only.\n */\n wakeHeldBy?: Record<string, string[]>;\n /**\n * In a profile on several devices (WISP 06 § Push and the phone): devices asked for a new subscription over their\n * device link, by signing key, with the endpoint they are to replace. A device that shares another endpoint (or none)\n * has done it. Set by the engine only.\n */\n wakeRenew?: Record<string, string>;\n /**\n * A push relay (https) this app hands a finished wake-up to when it may not post to the contact's push service\n * itself (a browser page: the services answer without CORS). Empty or absent: none; nobody runs one by default.\n */\n pushRelay?: string;\n /** The backup reminder of each Mainnet wallet (see `shared/backupReminder`). Only the engine writes it: a settings patch cannot. */\n backupReminders?: BackupReminders;\n}\n\n/** A browser push subscription and the VAPID key pair it was made with. */\nexport interface WakeSubscription {\n endpoint: string;\n p256dh: string;\n auth: string;\n vapid: VapidKeys;\n /**\n * In a profile on several devices (WISP 06 § Push and the phone): the signing key of the device whose subscription\n * this is. It moves with the profile, so a device with none of its own (a desktop) goes on giving contacts the\n * phone's. Set by the engine only; absent in a profile on one device, and for a subscription made before.\n */\n device?: string;\n}\n\n/**\n * What `updateSettings` takes: any settings, and of the Nostr ones only those that change. The rest of the\n * Nostr settings stay as stored, so a switch flipped from a page that has not seen the last save yet does not\n * put back what that save replaced.\n */\nexport type SettingsPatch = Partial<Omit<Settings, \"nostr\">> & { nostr?: Partial<NostrSocialSettings> };\n\n/** What pages see of store-and-forward in one chat. */\nexport interface LinkHoldView {\n /** The switch of this chat on this device. */\n enabled: boolean;\n /** The contact's app accepts held items, as it last said; undefined until it said anything. */\n peerAllows?: boolean;\n /** This device has storage to hold items in (Profile → Backups → S3 storage). */\n storage: boolean;\n /** Items can be held for the contact right now: switch on, contact allows it, storage set up, contact pinned. */\n canHold: boolean;\n /** Items held for the contact and not yet picked up. */\n outstanding: number;\n bytes: number;\n maxBytes: number;\n maxItems: number;\n ttlMs: number;\n /** Items from the contact refused on the way in. */\n refused: number;\n /** The last problem holding or picking up, for people. */\n error?: string;\n}\n\n/** A proof of this profile (Identities). */\nexport interface IdentityProofView {\n id: string;\n provider: string;\n subject: string;\n /** The proof key the statement authorizes. */\n key: string;\n verified: VerifiedIdentity;\n issuedAt: number;\n /** Seconds: the earlier of the statement's and the evidence's expiry. */\n expiresAt: number;\n createdAt: number;\n /** Chats it is currently shared in (or queued for). */\n sharedWith: number;\n /** The identity as a URI the profile's public DID can list (`alsoKnownAs`); absent when it has none. */\n publicUri?: string;\n /** What the provider checked (public: contacts receive it too). Removal uses it to take down a published proof. */\n evidence?: unknown;\n /** The identity's public profile on its network, once asked for (PUBLIC-PROFILES.md); absent while not asked or turned off. */\n publicProfile?: PublicProfileView;\n}\n\n/**\n * An identity's public profile, as its network publishes it (docs/wisps/PUBLIC-PROFILES.md): self-described by the\n * account, never part of the proof. Only for an identity with a current verified proof, with Settings → Load public\n * profiles on; kept on this device and asked again after a day.\n */\nexport interface PublicProfileView {\n /** False when the network has no profile for this identity (or none was read yet). */\n found: boolean;\n name?: string;\n /** How the network writes the account: \"@alice.bsky.social\", a Nostr `name`. */\n handle?: string;\n /** A short bio, plain text. */\n about?: string;\n /** The website the account names: an https address, shown as a plain link. */\n website?: string;\n /** A sanitized `data:image/jpeg;base64,…` URL, never a remote one. */\n avatar?: string;\n /** The profile names a picture that is not shown: which rule refused it, in words (\"it is a GIF; …\"). */\n avatarMiss?: string;\n followers?: number;\n following?: number;\n /** The hosts it was read from, which saw this device's IP address: \"nexus.pubky.app\". */\n hosts: string[];\n /** Seconds; 0 when it was never read. */\n fetchedAt: number;\n loading?: boolean;\n /** The last attempt failed (a copy read before, if any, is still shown). */\n error?: string;\n}\n\n/**\n * A verified identity's recent posts (docs/wisps/PUBLIC-PROFILES.md, \"Posts and follows\"): `loadPublicPosts`, asked\n * when its card is chosen in a contact's identities panel. Plain text, bounded, never markup.\n */\nexport interface PublicPostView {\n id: string;\n /** Seconds. */\n createdAt: number;\n text: string;\n /** Opens the post in its network's app or site. */\n url?: string;\n reply?: boolean;\n /** Its pictures, loaded only on a tap (`loadPublicPostImage` with the index here). `host`: who is asked. */\n images: { host?: string; alt?: string }[];\n}\n\nexport interface PublicPostsView {\n posts: PublicPostView[];\n /** More can be asked for (`more: true`). */\n more: boolean;\n /** The hosts asked, which saw this device's IP address. */\n hosts: string[];\n /** Seconds. */\n fetchedAt: number;\n /** Notes the person's own Nostr mute list hides. */\n hidden?: number;\n /** The identity's profile in its network's app or site. */\n profileUrl?: string;\n}\n\n/**\n * Who a verified identity follows and who follows it, only as far as it touches people the reader knows: the reader's\n * own identities and the identities contacts shared. Computed on this device from lists read from the same hosts as\n * the profile; the lists themselves are never shown or stored.\n */\nexport interface PublicGraphView {\n /** False when there was nobody to compare with (no identity of the reader's, no contact, on this network): nothing was read. */\n compared: boolean;\n /** Its follow list names one of the reader's identities. */\n followsYou: boolean;\n /** One of the reader's identities follows it (their followers list, or the reader's own follow list). */\n youFollow: boolean;\n /** Contacts whose shared identity it follows (`follows`) or who follow it (`followedBy`). */\n contacts: { linkId: string; follows: boolean; followedBy: boolean }[];\n /** A list was longer than this app reads: someone may be missing, never added. */\n partial?: boolean;\n hosts: string[];\n /** Seconds; 0 when nothing was read. */\n fetchedAt: number;\n}\n\n/** A post's picture, on the reader's tap: a re-encoded `data:image/jpeg` URL, or why there is none. */\nexport interface PublicPostImageView { src?: string; miss?: string; hosts: string[]; width?: number; height?: number }\n\n/** The profile's did:dht (WISP 310-did-dht): its own key, public to everyone, never tied to a chat. */\nexport interface ProfileDidView {\n /** `did:dht:…` */\n id: string;\n /** Identity proof ids the person listed as public, in order (some may not be publishable right now). */\n listed: string[];\n /** What the document says the subject is also known as: the listed identities that are verified and current. */\n alsoKnownAs: string[];\n /** The last packet put on Pkarr: when, and its version (the BEP44 sequence number). */\n published?: { at: number; versionId: string };\n /** The document out there is the current one. */\n upToDate: boolean;\n /** Why the last publish failed. */\n error?: string;\n}\n\n/** A contact's proof, as this app checked it. */\nexport interface ReceivedIdentityView {\n id: string;\n provider: string;\n subject: string;\n verified: VerifiedIdentity;\n /** A public name/picture looked up on request. */\n display?: IdentityDisplay;\n status: IdentityStatus;\n verifiedAt: number;\n checkedAt: number;\n expiresAt: number;\n error?: string;\n /** The provider's checks can go stale and the last one is old enough to repeat. */\n recheckDue: boolean;\n /** Its public profile, while the proof is verified and current (PUBLIC-PROFILES.md). */\n publicProfile?: PublicProfileView;\n}\n\nexport interface LinkIdentitiesView {\n /** Both sides offer identity proofs and the channel is open now. */\n support: boolean;\n /** Providers the contact's app said it can verify. */\n contactProviders?: string[];\n shared: SharedIdentity[];\n received: ReceivedIdentityView[];\n error?: string;\n}\n\nexport interface LinkView {\n /** Identity proofs in this chat; absent for chats that are not paired. */\n identities?: LinkIdentitiesView;\n /** The contact's Nostr data, one entry per key they proved in this chat. */\n nostr?: NostrContactView[];\n discoveryError?: string;\n publicProfiles?: PublicProfile[];\n profileChoice?: ProfileChoice;\n peerProofs?: Pick<ProofLedger, \"local\" | \"remote\">;\n peerProofSupport?: boolean;\n peerProofAdapters?: ProofAdapter[];\n participationKey?: string;\n peerParticipationKey?: string;\n proofError?: string;\n profile?: \"paired-chat/1\";\n pairing?: PairingState;\n /**\n * How far this chat's first pairing got (`@ghostly/core` `PairingProgress`): from the invite being\n * published to `live`. Absent for chats paired before this app start, DHT-only invites and legacy chats.\n */\n pairingProgress?: PairingProgress;\n peerVerified?: boolean;\n /**\n * `methods`: ways of paying both sides allow in this chat right now. `calls` / `services`: both sides offer\n * `calls/1` / `services/1` on the open session (paired chats only; they need a live connection).\n */\n capabilities?: {\n files: boolean; payments: boolean; methods?: Record<PaymentMethodName, boolean>; calls?: boolean; services?: boolean; largeFiles?: boolean;\n /**\n * The networks the contact has a wallet on, per way of paying, as it said on the open session. Absent: it said\n * none (an older app): any network may meet. A way of paying it has no wallet of is not in the map (one it has\n * but turned off here lists no network). A card is offered only where its network is in its list.\n */\n networks?: Partial<Record<PaymentMethodName, readonly WalletNetwork[]>>;\n };\n /** files/3 live in this chat: bytes the contact's device said it can still take for files, when it said. */\n peerFileRoom?: number | null;\n /** Ways of paying this device allows in this chat. */\n paymentMethods?: Record<PaymentMethodName, boolean>;\n /** For each way of paying, the networks this chat accepts it on. */\n paymentNetworks?: Partial<Record<PaymentMethodName, readonly WalletNetwork[]>>;\n /** Both sides announced private groups on the open session: this contact can be invited. */\n groups?: boolean;\n /** Paired chats: what each side offers after the handshake (`paired-capabilities`); `peer` is null until it says. */\n sessionOffers?: { mine: string[]; peer: string[] | null };\n /** Paired chats: why a call cannot be placed right now, or null when it can. */\n callsUnavailable?: string | null;\n /** Paired 1:1 chats: the contact is typing now (`typing/1` on the live session). Absent otherwise. Never stored. */\n peerTyping?: boolean;\n /** Paired 1:1 chats: this side's wake-up token for this chat (the push worker's table), when it shared its subscription. */\n wakeToken?: string;\n /** Paired 1:1 chats: the contact was told not to wake this side while the chat is muted (`setWakeMuted`). */\n wakeMuted?: boolean;\n /** Paired 1:1 chats: the contact shared how to wake its closed app, so a message sent while it is away wakes it. */\n peerWakes?: boolean;\n /** While `peerTyping`: what the contact is doing when it is not plain typing (recording a voice note, thinking). */\n peerTypingKind?: Exclude<TypingKind, \"typing\">;\n /** While `peerTyping`: the contact's (a bot's) status line, sanitized: one line, at most 40 characters, no links. */\n peerTypingStatus?: string;\n availableTransports?: PairedTransport[];\n /**\n * Every transport this app runs for the chat, started or not: DHT only releases the native ones, and an app with no\n * WebRTC (the Linux Desktop) then has none started, yet can still leave DHT only for any of these.\n */\n runnableTransports?: PairedTransport[];\n /** Transports a session with this contact would cross a relay on (a browser's HyperDHT or Iroh): a fallback, and shown as relayed. */\n relayedTransports?: PairedTransport[];\n deliveryMode?: DeliveryMode;\n dhtDelivery?: DhtDeliveryView;\n canSendText?: boolean;\n /** `hold`: the contact is away and what is sent now waits in this device's storage for it. */\n textDelivery?: \"stream\" | \"dht\" | \"hold\" | \"unavailable\";\n hold?: LinkHoldView;\n transportErrors?: Partial<Record<PairedTransport, string>>;\n preferredTransport?: PairedTransport;\n transportFallback?: boolean;\n /** No transport chosen for this chat: the app's rule applies. */\n transportAutomatic?: boolean;\n /** What the contact's app can use on this link, as it last said (a session, or its capability record); unknown before either. */\n peerTransports?: PairedTransport[];\n /**\n * The transport this chat is set to reach and is not on yet, and why (WISP 100, \"A chosen transport not reached\n * yet\"): waited for, never a failure. Absent when the chat is on it, or nothing limits where it goes.\n */\n transportWait?: TransportWait;\n /**\n * The last attempt to go live that did not, as this side saw it (WISP 100, \"Why a chat is not live\"): what it tried\n * and why each did not connect, whether this side dialled or answered, and when it dials again. Absent while live.\n */\n liveAttempt?: LiveAttempt;\n /** Which side dials this chat to go live (the other answers): paired chats. */\n liveDialer?: \"you\" | \"contact\";\n /** Round trip on the live session, once measured. */\n transportRttMs?: number;\n /**\n * The live session goes through relays, never directly (WISP 100, \"Relayed\"): a browser's Iroh. The relays'\n * hosts, this app's first. They see who talks to whom and when, not what is said.\n */\n transportRelayed?: { relays: string[] };\n /** While live: since when the current transport carries the chat, and why it was chosen (see engine/transportLog.ts). */\n transportLive?: { since: number; cause?: TransportCause };\n /** The chat's connection story, newest last; only for the chat on screen (`setActiveLink`). */\n transportLog?: TransportEntry[];\n /** Every connection event of the chat on screen, for its connection panel, newest last. */\n transportHistory?: TransportEvent[];\n /** Identities shared in this chat, both ways, as its timeline shows them, newest last; only for the chat on screen. Local only. */\n identityTimeline?: IdentityTimelineEntry[];\n /** When the contact last shared an identity here (milliseconds): the chat list's preview and order. Paired chats. */\n identitySharedAt?: number;\n /** The latest reaction in this chat, for the chat list; paired chats. */\n lastReaction?: ReactionNote;\n /** The chat's pin (WISP 400 § Pinned message): `id` \"\" once unpinned; absent when nothing was ever pinned. */\n pin?: PinView;\n id: string;\n myPubKeyZ32: string;\n peerPubKeyZ32: string;\n label?: string;\n /**\n * The name the contact asked to be shown by, as they last said it. `\"\"` means a paired contact said they\n * have none (removed, or not shared); absent means nothing was heard yet.\n */\n peerNick?: string;\n /** The contact's profile picture, as they last sent it (checked, small JPEG data URL). */\n peerAvatar?: string;\n inviteCode?: string;\n createdAt: number;\n status: LinkStatus;\n dataLink: DataLinkState;\n peerOnline: boolean;\n peerLastSeenAt: number;\n /** `null` when the peer does not advertise (offline, or a legacy client). */\n peerServices: ServiceAd[] | null;\n lastMessageAt: number;\n /** Newest of my messages the peer acknowledged through Pkarr. */\n peerAck: number;\n lastSyncAt: number;\n poll: { polling: boolean; nextAt: number; interval: number };\n}\n\nexport interface ServiceView extends StoredService {\n /** Requests served since the engine started. */\n requests: number;\n}\n\nexport interface EngineState {\n settings: Settings;\n /**\n * Limited mode (WISP 06 § When a device checks): the device could not read which device is active and was started\n * anyway. The profile is offline whatever `settings.online` says, and no wallet is open. Absent otherwise.\n */\n limited?: true;\n /**\n * Why limited mode is on, when it is a restored copy (WISP 06 § A backup restored where a device set exists): `checking`,\n * the turn is read every 30 seconds to learn whether another device runs the profile; `removed`, the read found the\n * device set moved after a removal, and only the person can start the copy as a profile of its own.\n */\n restoreCheck?: \"checking\" | \"removed\";\n /**\n * Whose push subscription `settings.wake` is, in a profile on several devices (WISP 06 § Push and the phone): `here`,\n * this device's own; `away`, another device's, given to contacts because this device has none of its own (the page\n * neither replaces nor turns off what is not its own). Absent in a profile on one device, or when nobody said.\n */\n wakeOwner?: \"here\" | \"away\";\n transport: {\n protocol: string; relays: string[];\n /** Present where Iroh runs in the browser (web app, extension): the relays it uses and the defaults. */\n iroh?: { relays: string[]; defaults: string[] };\n /** This client reaches the Mainline DHT itself (the Desktop): relays are for writes, and reads only when chosen. */\n direct?: boolean;\n /** How reads go and how each relay is doing, for the connection panel's Details. */\n discovery?: DiscoveryStatus;\n /** False where this client has no WebRTC (Ghostly Desktop on Linux: WebKitGTK has none). Absent where it has. */\n webrtc?: false;\n /**\n * This device's own WebRTC attempts say direct connections do not get through its network (a VPN, a firewall, a\n * carrier's NAT: `DirectPathWatch` in packages/core). Chats still go live through relays. Absent otherwise.\n */\n directBlocked?: true;\n /**\n * This device's clock seems to be off: this clock minus what the relays and several contacts say, in ms (positive\n * when it is ahead). From several sources agreeing, never one contact (`ClockWatch` in packages/core). Absent otherwise.\n */\n clockOffMs?: number;\n /**\n * False where a group's links have no transport at all here: no WebRTC and no native transport (WISP 902 §\n * Transports), so no member of a group can be reached from it. Absent where they have one (the Linux Desktop runs\n * Iroh and HyperDHT).\n */\n groupLinks?: false;\n };\n links: LinkView[];\n services: ServiceView[];\n /** Transfers since the peer started, by file id. */\n transfers: Record<string, FileTransferView>;\n /**\n * The kept transfers are back in `transfers`. False for the first moments after a start: a file with no transfer\n * yet may still be moving, not finished.\n */\n transfersRestored?: boolean;\n /**\n * The kept groups are in `groups`. Unset while the peer starts: until then a group missing from the list may only not\n * be loaded yet, not gone (the app forgets a gone group's draft and read mark only between two states that have it).\n */\n groupsLoaded?: true;\n wallet: WalletView;\n payments: Record<string, PaymentView>;\n /** This profile's identity proofs. */\n identityProofs: IdentityProofView[];\n /** This profile's public DID. Absent until the engine has loaded it. */\n did?: ProfileDidView;\n /** The Nostr social layer: the person's own keys' data and the effective settings. */\n nostr: NostrSocialState;\n /** Private groups and pending invitations (WISP 900). */\n groups: GroupView[];\n /** The links of groups' edges: not chats, but payments with a member travel on them. */\n edges?: LinkView[];\n}\n","import { WALLET_NETWORKS, decodeBolt11, engineText, walletNetworkOf, type PaymentMethodName, type PaymentNetworks, type WalletNetwork } from \"@ghostly/core\";\nimport { mintNetwork } from \"../../shared/mints\";\nimport { WALLET_TYPES, type NetworkWalletsView, type StoredPayment, type WalletInstanceView, type WalletType } from \"../../shared/types\";\nimport { CASHU_MINT_SOURCE } from \"./providers/cashuMint\";\nimport { CASHU_CARD, LEGACY_CARD } from \"./providers/lightningCards\";\nimport { networkLabel } from \"./modeGate\";\nimport { networkReason } from \"./networkReason\";\n\nexport const WALLET_NAMES: Record<WalletType, string> = {\n cashu: \"Cashu\", lightning: \"Lightning\", arkade: \"Ark\", bark: \"Bark\", spark: \"Spark\", bitcoin: \"Bitcoin\", fedimint: \"Fedimint\", usdt: \"USDT\",\n};\n\n/** How long a one-click creation waits on its server before it gives up, saving nothing (tests shorten it). */\nexport const createTiming = { timeoutMs: 60_000 };\n\n/**\n * The wallets a profile has, read from both networks' views: one per type and network, in the deck's order, and one\n * per Lightning card. A wallet exists once it holds something to open (a seed, a source, a mint); Lightning through\n * the Cashu mints comes with that network's Cashu wallet.\n */\nexport function walletInstances(networks: Record<WalletNetwork, NetworkWalletsView>): WalletInstanceView[] {\n const list: WalletInstanceView[] = [];\n for (const type of WALLET_TYPES) for (const network of WALLET_NETWORKS) {\n if (type === \"lightning\") { list.push(...lightningInstances(network, networks[network])); continue; }\n const config = instanceConfig(type, networks[network]);\n if (config) list.push({ id: `${type}:${network}`, type, network, config });\n }\n return list;\n}\n\n/** A network's Lightning cards (a view from before cards: its one source, as one card). */\nfunction lightningInstances(network: WalletNetwork, view: NetworkWalletsView): WalletInstanceView[] {\n const cards = view.lightnings ?? (view.lightning ? [{ ...view.lightning, card: view.lightning.providerId === CASHU_MINT_SOURCE || !view.lightning.providerId ? CASHU_CARD : LEGACY_CARD, name: \"\", receive: true }] : []);\n return cards.flatMap((ln) => {\n if (!ln.providerId || (ln.providerId === CASHU_MINT_SOURCE && !view.mints.length)) return [];\n return [{ id: `lightning:${network}:${ln.card}`, type: \"lightning\" as const, network, config: { providerId: ln.providerId, label: ln.label ?? \"\" }, card: ln.card, name: ln.name || ln.label || \"Lightning\", receive: ln.receive }];\n });\n}\n\nfunction instanceConfig(type: WalletType, view: NetworkWalletsView): Record<string, string> | undefined {\n const text = (value: unknown) => typeof value === \"string\" ? value : \"\";\n switch (type) {\n case \"cashu\": return view.mints.length ? { mint: view.mints[0].url, mints: String(view.mints.length) } : undefined;\n case \"lightning\": return undefined;\n case \"arkade\": return view.ark?.configured ? { chain: text(view.ark.network), provider: text(view.ark.provider) } : undefined;\n case \"bark\": return view.bark?.configured ? { chain: text(view.bark.network), provider: text(view.bark.provider) } : undefined;\n case \"spark\": return view.spark?.configured ? { chain: text(view.spark.network) } : undefined;\n case \"bitcoin\": return view.bitcoin?.providerId ? { providerId: view.bitcoin.providerId, label: text(view.bitcoin.label) } : undefined;\n case \"fedimint\": return view.fedimint?.federations.length ? { federations: String(view.fedimint.federations.length) } : undefined;\n case \"usdt\": return view.usdt?.configured ? { chain: text(view.usdt.network), provider: text(view.usdt.provider) } : undefined;\n }\n}\n\n/** For each way of paying, the networks this profile has a wallet on: what every chat tells its contact. */\nexport function paymentNetworksOf(wallets: readonly WalletInstanceView[]): PaymentNetworks {\n const networks: PaymentNetworks = {};\n for (const wallet of wallets) {\n const method = wallet.type as PaymentMethodName;\n networks[method] = [...new Set([...(networks[method] ?? []), wallet.network])];\n }\n return networks;\n}\n\n/**\n * The network of a payment or a request: written on it since wallets have their own, else read from what it\n * carries (a target's chain, a mint, an invoice's chain). A test mint's invoice looks like a Bitcoin one; its\n * mints say Testnet.\n */\nexport function paymentNetwork(payment: Pick<StoredPayment, \"network\" | \"target\" | \"mints\" | \"mint\" | \"invoice\">): WalletNetwork {\n if (payment.network) return payment.network;\n if (payment.target) return payment.target.method === \"cashu\" ? mintNetwork(payment.target.provider) : walletNetworkOf(payment.target.network);\n const mint = payment.mints?.[0] ?? payment.mint;\n if (mint) return mintNetwork(mint);\n const decoded = payment.invoice ? decodeBolt11(payment.invoice) : null;\n return decoded && decoded.network !== \"bitcoin\" ? \"testnet\" : \"mainnet\";\n}\n\n/** The refusal when a card of one network would pay for the other: test coins and real money never meet. */\nexport const crossNetwork = (card: WalletNetwork, asked: WalletNetwork) =>\n card === asked ? `This is a ${networkLabel(asked)} payment (${asked === \"mainnet\" ? \"real money\" : \"test coins\"}): a ${networkLabel(card)} wallet never pays it. Use a ${networkLabel(asked)} wallet.`\n : engineText(asked === \"mainnet\" ? \"mainnetPaymentOnTestnet\" : \"testnetPaymentOnMainnet\");\n\n/**\n * Real money goes out only once the person confirmed it as real money (\"Send real money\"), whatever screen asked:\n * a spend RPC on Mainnet without `confirmedReal` is refused before anything is spent. Test coins need no second step.\n */\nexport function assertConfirmedReal(network: WalletNetwork, confirmedReal: unknown): void {\n if (network === \"mainnet\" && confirmedReal !== true) throw new Error(REAL_MONEY_UNCONFIRMED);\n}\nexport const REAL_MONEY_UNCONFIRMED = engineText(\"realMoneyUnconfirmed\");\n\n/** One clear message for a creation that failed: nothing was saved, and trying again is safe. */\nexport function createFailure(label: string, error: unknown): string {\n const reason = (networkReason(error) ?? (error instanceof Error ? error.message : String(error))).replace(/\\.$/, \"\");\n return `Could not create the ${label} wallet: ${reason}. Nothing was saved; try again.`;\n}\n","/**\n * Payment ids are chosen by whoever sends them, and every member of a group sees the requests the others make to the\n * group. So a contact's request is kept under the id it came with, unless a record of another chat already holds that\n * id: then under `<id>.<chat>`. A `.` is never in a payment id on the wire (8 to 64 of `A-Z a-z 0-9 _ -`), so no\n * contact can name another's record, and two contacts' requests never share one.\n */\nexport const paymentAlias = (wireId: string, linkId: string): string => `${wireId}.${linkId}`;\n\n/** The id a payment record goes by on the wire, which is also the one its chat and its group's notes know it by. */\nexport const paymentWireId = (key: string): string => key.split(\".\", 1)[0];\n","import { replySnippet, type WireReply } from \"@ghostly/core\";\nimport { paymentWireId } from \"./paymentIds\";\nimport type { MessageReply, StoredMessage } from \"./types\";\n\n/*\n * Replies (WISP 400 § Replies) as the engine and the UI both need them: which id a message goes by on both sides of\n * its chat, and a reply as it is kept and as it goes on the wire.\n */\n\n/** A paired chat's text (`me_`/`peer_` + its 22-character wire id), or a held file (`peer_` + its 16-character one). */\nconst PAIRED_ROW = /^(?:me|peer)_([A-Za-z0-9_-]{22}|[A-Za-z0-9_-]{16})$/;\nconst WIRE_ID = /^[A-Za-z0-9_-]{8,64}$/;\nconst GROUP_LINK = \"group:\";\n\n/** The parts of a message, as the engine or the UI keeps it, that say which id both sides know it by. */\nexport interface ReplyTarget {\n id: string;\n linkId?: string;\n wireId?: string;\n paymentId?: string;\n file?: { id: string };\n event?: unknown;\n groupPay?: unknown;\n /** Set by the UI from the engine's row (`replyRef`), for what the UI's own copy no longer says. */\n ref?: string;\n}\n\n/**\n * The id a message of a chat goes by on both sides, which a reply names; none for what only this side has (a notice,\n * a group payment line, a refused payment). A paired chat: the text's wire id, a payment's id, a file's wire id. A\n * group: the message id, which is the same for every member.\n */\nexport function replyRef(message: ReplyTarget, group = message.linkId?.startsWith(GROUP_LINK) ?? false): string | undefined {\n if (message.ref) return message.ref;\n if (group) return message.paymentId || message.event || message.groupPay || !message.id.includes(\":\") ? undefined : message.id;\n if (message.paymentId) return paymentWireId(message.paymentId);\n if (message.wireId && WIRE_ID.test(message.wireId)) return message.wireId;\n const out = message.file?.id.match(/-out-([A-Za-z0-9_-]{8,64})$/);\n if (out) return out[1];\n const row = message.id.match(PAIRED_ROW);\n return row && !/^\\d+$/.test(row[1]) ? row[1] : undefined;\n}\n\n/** A reply to `original` as this side keeps it: always found, since it is here. The engine's row, or the UI's copy. */\nexport function replyTo(original: Pick<StoredMessage, \"id\" | \"text\" | \"member\"> & { sender: string }, ref: string): MessageReply {\n return { id: ref, snippet: replySnippet(original.text), from: original.sender === \"me\" ? \"me\" : \"peer\",\n ...(original.member && { member: original.member }), messageId: original.id };\n}\n\n/**\n * A reply as it goes on a paired chat's wire, from the side that sends it (`f`: who wrote the original, from the\n * sender's side). For a group, `f` is the author's member key, which the reply keeps.\n */\nexport function pairedWireReply(reply: MessageReply, sender: \"me\" | \"peer\" = \"me\"): WireReply {\n return { i: reply.id, s: reply.snippet, f: reply.from === sender ? \"sender\" : \"recipient\", ...(reply.button && { b: reply.button }) };\n}\n\n/** A paired chat's reply as it arrived (`f` from the sender's side), as this side keeps it. */\nexport function receivedPairedReply(reply: WireReply | { i: string }): MessageReply {\n if (!(\"f\" in reply)) return { id: reply.i, snippet: \"\" };\n return { id: reply.i, snippet: reply.s, from: reply.f === \"sender\" ? \"peer\" : \"me\", ...(reply.b && { button: reply.b }) };\n}\n\n/** A group's reply as it arrived or was sent (`f` the author's member key), as this member keeps it. */\nexport function groupReply(reply: WireReply, myKey: string): MessageReply {\n return { id: reply.i, snippet: reply.s, from: reply.f === myKey ? \"me\" : \"peer\", member: reply.f, ...(reply.b && { button: reply.b }) };\n}\n","import {\n ChatFiles,\n GROUP_FILE_LIMITS,\n GROUP_HAVE_FRAME,\n GROUP_WANT_FRAME,\n GROUP_WANT_NO_FRAME,\n RateWindow,\n fetchesByItself,\n fileMessageText,\n groupFileFallback,\n isGroupMessageId,\n randomBytes,\n readGroupHave,\n readGroupWant,\n readGroupWantNo,\n toBase64Url,\n transferEnded,\n type FileInfo,\n type FileTransferRecord,\n type GroupFileMeta,\n type GroupWantRefusal,\n type IncomingTarget,\n type OfferDecision,\n type OutgoingSource,\n} from \"@ghostly/core\";\nimport { fileBytes, fileBytesOf } from \"../shared/fileBytes\";\nimport { fileStore, type StoredFile } from \"../shared/idb\";\nimport { FileAppender, readStored, removeStored } from \"../shared/storedFiles\";\nimport type { FileTransferView, MessageFile } from \"../shared/types\";\n\n/*\n * Files and voice messages in groups (WISP 503 · Group Files), the engine's side. A file is announced as a group message\n * carrying its description (`fl`); each member's app then fetches the bytes from a member that holds them, over\n * `files/3` on the edge between the two (`ChatFiles`, WISP 501): it sends `group-want`, the holder offers the file with\n * `gm` naming the message, and what arrives is checked against the author's digest. Whoever stored it holds it, and says\n * so (`group-have`), so a member away during the announcement gets it later from whoever is there.\n */\n\n/** A group file's local id: its group's and a random part, never anything a member sends. */\nexport const groupFileId = (groupId: string, direction: \"in\" | \"out\", random = toBase64Url(randomBytes(12))) => `group-${groupId}-${direction}-${random}`;\n/** The group a local file id names, among `groupIds`. */\nexport const groupOfFile = (fileId: string, groupIds: Iterable<string>): string | undefined =>\n [...groupIds].find(g => fileId.startsWith(`group-${g}-in-`) || fileId.startsWith(`group-${g}-out-`));\n\n/** What a file's message says: its caption, or (when the author wrote none) the file itself, not the line older apps show. */\nexport const groupFileText = (text: string, meta: GroupFileMeta): string => text === groupFileFallback(meta) ? fileMessageText(meta) : text;\n\n/** Where a group's files are kept: the profile's file store and file storage (`storedGroupFiles`), or a test's memory. */\nexport interface GroupFileStore {\n /** The records of a group's files (`linkId` `group:<id>`). */\n list(groupId: string): Promise<StoredFile[]>;\n /** Every group a stored file is of, whether or not this device still has the group. */\n groups(): Promise<string[]>;\n get(id: string): Promise<StoredFile | undefined>;\n put(file: StoredFile): Promise<void>;\n patch(id: string, fields: Pick<StoredFile, \"transfer\" | \"group\">): Promise<void>;\n /** The record and its bytes. */\n remove(id: string): Promise<void>;\n /**\n * Writes a file in order: from its first byte (whatever was there goes), or with `resume` after what an earlier\n * transfer stored (the writer's `offset`).\n */\n write(id: string, resume?: boolean): Promise<GroupFileWriter>;\n /** At most `length` bytes of a kept file from `offset`. */\n read(file: StoredFile, offset: number, length: number): Promise<Uint8Array>;\n /** Bytes this device can still take for files, or null when the platform does not say. */\n room(): Promise<number | null>;\n}\n\nexport interface GroupFileWriter {\n /** Where it writes from: the bytes already stored. */\n offset: number;\n append(bytes: Uint8Array): Promise<void>;\n flush(): Promise<void>;\n /** SHA-256 of what was stored, read back from storage, base64url. */\n digest(): Promise<string>;\n /** Done writing for now: what is stored stays. */\n close(): Promise<void>;\n discard(): Promise<void>;\n}\n\n/** The profile's files store and file storage, as a 1:1 chat's files use them. */\nexport const storedGroupFiles: GroupFileStore = {\n list: groupId => fileStore.listForLink(`group:${groupId}`),\n groups: () => fileStore.groupIds(),\n get: id => fileStore.get(id),\n put: file => fileStore.put(file),\n patch: (id, fields) => fileStore.patch(id, fields),\n remove: id => removeStored(id),\n async write(id, resume = false) {\n const stored = await fileStore.get(id);\n const bytes = (stored?.bytes && await fileBytesOf(stored.bytes)) || await fileBytes();\n const size = await bytes.size(id);\n const offset = resume && size !== null ? size : 0;\n if (size !== null && size !== offset) await bytes.truncate(id, offset);\n if (stored && stored.bytes !== bytes.kind) await fileStore.patch(id, { bytes: bytes.kind });\n const appender = new FileAppender(bytes, id, offset);\n return {\n offset,\n append: chunk => appender.append(chunk),\n flush: () => appender.flush(),\n digest: async () => { await appender.close(); return bytes.digest(id); },\n close: () => appender.close(),\n discard: async () => { await appender.close().catch(() => {}); await bytes.remove(id); },\n };\n },\n read: (file, offset, length) => readStored(file, offset, length),\n room: async () => (await fileBytes()).room(),\n};\n\n/** What the engine gives the group files: where they are kept, their views, the profile's settings, an edge's files/3. */\nexport interface GroupFilesHost {\n store: GroupFileStore;\n transfers: Map<string, FileTransferView>;\n /** The file's view goes in the next state the engine sends, and then away: the file's record says the rest. */\n brief(fileId: string): void;\n settings(): { autoDownloads: boolean; serveFiles: boolean };\n /** A files/3 frame on an edge's open session; false when it could not go. */\n sendFiles(linkId: string, frame: Record<string, unknown>): boolean;\n /** A file's bytes on an edge, in the form its session agreed (`GhostLink.sendFileData`, files/4). Absent: `pf-data` frames. */\n sendData?(linkId: string, id: string, offset: number, data: Uint8Array): boolean;\n /** Resolves once the edge's session can take another data frame. */\n writable?(linkId: string): Promise<void> | undefined;\n}\n\n/** Me in a group I am active in, and who could read one of its messages. */\nexport interface GroupFileMembership {\n me: string;\n community: boolean;\n inRoster(key: string): boolean;\n /** In the roster of the message's epoch and in the roster now (WISP 503 § Asking). */\n couldRead(key: string, messageId: string): boolean;\n}\n\nexport interface GroupFileDeps extends GroupFilesHost {\n /** A group frame on an edge; false when it could not go. */\n send(linkId: string, frame: Record<string, unknown>): boolean;\n /** Member key → edge link id, for the group's edges. */\n edges(groupId: string): Map<string, string>;\n /** The edge is up, and the app at its other end announced group files (`GROUP_VERSION_FILES`). */\n ready(linkId: string): boolean;\n membership(groupId: string): GroupFileMembership | undefined;\n /** An application frame to everyone in a community. */\n sendApp(groupId: string, frame: Record<string, unknown>): Promise<void>;\n changed(): void;\n now(): number;\n}\n\n/** A group file this device knows of: one it announced, or one a member announced. */\ninterface Kept {\n id: string;\n groupId: string;\n message: string;\n author: string;\n meta: GroupFileMeta;\n /** I announced it: my copy is its first. */\n mine: boolean;\n /** Fetched without asking: it counts toward the group's automatic limit. */\n auto: boolean;\n /** Its person asked for it (Download). */\n asked: boolean;\n /** Its person paused it: what came stays, and it reads paused after a restart too, until Resume. */\n paused?: boolean;\n /** Every byte is here, checked against the author's digest: this device holds it. */\n held: boolean;\n /** Members said to hold it (`group-have`), or that offered it, newest first. The author is asked whether or not it is here. */\n holders: string[];\n /** A discard of what an earlier transfer stored, which the next one waits for. */\n idle?: Promise<void>;\n /** Its transfer's write under way to storage, which a Leave waits for before it deletes the file (`drop`). */\n writing?: Promise<unknown>;\n /** Bytes a transfer stored here that the next one goes on from, shown while it waits for a holder. */\n stored?: number;\n /** The transfer (its id on the edge) its person cancelled: what it stored is deleted, not kept for the next one. */\n dropped?: string;\n /**\n * The bytes kept from an earlier holder did not check out with the next one's tail: who served the bad part is not\n * known, so the next transfer starts from 0 (`fresh`) and the holder whose tail failed is not counted as damaged\n * (`unclear`, read once by `transferChanged`).\n */\n fresh?: boolean;\n unclear?: boolean;\n /**\n * The holder every byte stored here came from, in this run (none once two holders' bytes are mixed): its tail failing\n * the check is its own bytes failing, so it counts as damaged, however many transfers it split them into.\n */\n storedBy?: string;\n /**\n * Holders whose bytes failed the author's digest: not asked again for it, whatever the round, until its person asks\n * for it again (Download). Once every holder known is here, it is fetched no more (`DAMAGED`).\n */\n damaged: Set<string>;\n /** The holder its last transfer was left with (it stalled, or its person paused it): asked last, not first, next time. */\n left?: string;\n}\n\n/** A file being asked for: of one holder at a time. */\ninterface Want {\n /** The holder asked now and the edge it was asked on; none while nobody reachable has it. */\n holder?: string;\n linkId?: string;\n at: number;\n /** Holders asked in this round that had nothing to give: the next ask goes to another. */\n tried: Set<string>;\n /**\n * The holder's transfer, once it offered: its id on the edge, and the bytes stored when it last moved by\n * `wantMoveBytes` and when (-1 until its first change: a resumed transfer starts at what was stored before).\n */\n wire?: string;\n moved: number;\n movedAt: number;\n /** What the last holder asked in this round answered, when it was not the file: shown while the want waits. */\n why?: \"busy\" | \"damaged\";\n /**\n * Holders that answered `busy` in this round: one of them in reach tells this device when its place frees (its\n * `group-have`), so members never heard of as holders are not asked meanwhile.\n */\n busy?: Set<string>;\n}\n\n/** One edge's files/3, both ways. */\ninterface Edge {\n linkId: string;\n groupId: string;\n peer: string;\n files: ChatFiles;\n /** Transfers this side serves on the edge (their wire id) → the local file. */\n serving: Map<string, string>;\n /** Of each transfer served: the bytes the asker confirmed, when that last moved, and when it was offered. */\n servedMoved: Map<string, { confirmed: number; at: number; from: number }>;\n /** Transfers this side takes on the edge (the holder's wire id) → the local file. */\n taking: Map<string, string>;\n /** Transfers that ended on the edge, oldest first: the last `ENDED_KEPT` keep their record, the rest are forgotten. */\n ended: [\"in\" | \"out\", string][];\n /** Message ids to say in the next `group-have`, and the frames of it that went lately (`haveFrames` in `haveWindowMs`). */\n have: Set<string>;\n haveSent: RateWindow;\n /** Its member's `group-have` frames taken lately: no more than it may send (`haveFrames` in `haveWindowMs`); past that, dropped. */\n haveTaken: RateWindow;\n /** Message ids its member asked for while this device served all it serves at once: said in a `group-have` once a slot frees. */\n busy: Set<string>;\n /** Message ids its member said it does not hold (`gone`): not asked of it again until it says it does, or the edge's session closes or opens. */\n gone: Set<string>;\n}\n\n/**\n * Ended transfers an edge keeps the record of. A late frame for one of them is still answered as files/3 says (an abort\n * to an asker going on with a transfer cancelled here); older ones are forgotten, so an edge's records stay few\n * however many transfers it has seen.\n */\nconst ENDED_KEPT = 8;\n\n/** Why a want waits, as the person reads it (the app shows its own words for each `wait`). */\nconst WAITING_NOTES = {\n nobody: \"Nobody you are connected to has this file yet\",\n busy: \"Its holders are busy; asking again soon\",\n damaged: \"It arrived damaged; ask again\",\n} as const;\n/** Why a want stopped: every holder served bytes that are not the author's file. */\nconst DAMAGED = \"Every copy of this file arrived damaged and was deleted. Ask for it again later.\";\n\nexport class GroupFileDesk {\n private readonly kept = new Map<string, Kept>();\n /** `<group>\\n<message id>` → local id. */\n private readonly byMessage = new Map<string, string>();\n private readonly wants = new Map<string, Want>();\n /**\n * The want last asked on each edge (by link id), and its file: whether a file is asked of an edge's member is read here,\n * not by going through every want each time (`askedOf`). An entry the want has moved on from is dropped when read.\n */\n private readonly askedOn = new Map<string, { id: string; want: Want }>();\n private readonly edgesById = new Map<string, Edge>();\n /** My file about to be announced, by `<group>\\n<digest>`: the message that announces it takes this local id. */\n private readonly own = new Map<string, string>();\n /** Announcements sent per group, and taken per member of a group (WISP 503 § Rules for both, Pace). */\n private readonly sentPace = new Map<string, RateWindow>();\n private readonly sentTaken = new Map<string, RateWindow>();\n private readonly takenPace = new Map<string, RateWindow>();\n /** `<group>\\n<message id>` a `group-have` named before its message came → its holders, newest first. */\n private readonly ahead = new Map<string, string[]>();\n /** A community's ids for its next `group-have`, and when one last went. */\n private readonly communityHave = new Map<string, { ids: Set<string>; at: number }>();\n /** Bytes served today, and the day (UTC days since the epoch). */\n private served = { day: 0, bytes: 0 };\n\n constructor(private readonly deps: GroupFileDeps) {}\n\n // -- what the groups hand over -------------------------------------------------------------------------------\n\n /**\n * At start: each group's files as they were kept. One fetched by itself, or asked for, and not here yet is asked for\n * again; with automatic downloads off, one chosen by itself before waits for a Download instead.\n */\n async load(groupIds: string[]): Promise<void> {\n for (const groupId of groupIds) {\n for (const file of await this.deps.store.list(groupId).catch(() => [])) {\n if (!file.group || !file.metadata || !file.digest || this.kept.has(file.id)) continue;\n const { name, size, mime, voice, video, image } = file.metadata;\n const kept: Kept = { id: file.id, groupId, message: file.group.message, author: file.group.author,\n meta: { name, size, mime, d: file.digest, ...(voice && { voice }), ...(video && { video }), ...(image && { image }) },\n mine: file.direction === \"out\", auto: !!file.group.auto, asked: !!file.group.asked, held: file.transfer?.state === \"done\", holders: [], damaged: new Set() };\n this.add(kept);\n // All here: it shows no transfer (a file with none reads as finished; its record says done). A view for each\n // would go into every state the engine builds, so each state would cost more with every file the groups ever had.\n if (kept.held) continue;\n // Every holder served it damaged: it waits for its person to ask again, not fetched again by itself.\n if (file.transfer?.state === \"failed\") this.show(kept, { state: \"failed\", transferred: 0, size, direction: \"in\", retry: true, error: file.transfer.error ?? DAMAGED });\n // Paused by its person: still paused, with the bytes that came (what the next transfer goes on from).\n else if (file.group.paused) {\n kept.paused = true;\n kept.stored = await this.keptBytes(kept.id);\n this.show(kept, { state: \"transferring\", stage: \"paused\", pausedBy: \"me\", transferred: kept.stored, size, direction: \"in\" });\n }\n else if (kept.asked || (kept.auto && this.deps.settings().autoDownloads)) this.wants.set(kept.id, this.newWant());\n else if (kept.auto) await this.stop(kept, false);\n // Waiting for a Download: no transfer either. Its record says it is not here (`waitingGroupFile`).\n }\n }\n for (const [id] of this.wants) this.ask(this.kept.get(id)!);\n this.deps.changed();\n }\n\n /**\n * At start, once every group was read: the files of a group this device no longer has (`groupIds` are the groups it\n * has, a tombstone or a join under way among them) go, with their bytes. A group left before Leave took its files\n * left them behind, where nothing lists them.\n */\n async sweep(groupIds: string[]): Promise<void> {\n const here = new Set(groupIds);\n for (const groupId of await this.deps.store.groups().catch(() => [])) {\n if (here.has(groupId)) continue;\n for (const file of await this.deps.store.list(groupId).catch(() => [])) await this.deps.store.remove(file.id).catch(() => {});\n }\n }\n\n /**\n * The person stops a file that is coming, or that this device would fetch (Cancel, Decline): nobody is asked for it\n * any more, the transfer under way ends, what came of it is deleted, and it waits for a Download. Paused (`keep`),\n * what came stays, and Resume (`download`) goes on from there.\n */\n async halt(fileId: string, keep = false): Promise<void> {\n const kept = this.kept.get(fileId);\n if (!kept) throw new Error(\"No such file\");\n // Only a file that is coming pauses: one waiting for a Download has nothing to go on with.\n if (keep && !this.wants.has(fileId)) return;\n await this.stop(kept, keep);\n }\n\n /** Automatic downloads were turned off: files chosen by themselves and not here yet stop coming, and wait for a Download. */\n async autoDownloadsOff(): Promise<void> {\n for (const kept of [...this.kept.values()]) if (kept.auto && !kept.asked && this.wants.has(kept.id)) await this.stop(kept, false);\n }\n\n private async stop(kept: Kept, keep: boolean): Promise<void> {\n if (kept.held || kept.mine) return;\n const want = this.wants.get(kept.id);\n // Gone first: the transfer's end below asks nobody else (`transferChanged`), and a late offer is refused.\n this.wants.delete(kept.id);\n // Paused on a holder's transfer: Resume asks the others first.\n if (want?.wire && want.holder) kept.left = want.holder;\n const edge = want?.wire && want.linkId ? this.edgesById.get(want.linkId) : undefined;\n const record = edge?.files.get(\"in\", want!.wire!);\n const { size } = kept.meta;\n // One every holder served damaged waits for a Download as any other: its record no longer says it failed.\n const failed = !keep && this.deps.transfers.get(kept.id)?.state === \"failed\";\n if (!keep) { kept.stored = 0; kept.fresh = true; }\n if (edge && record && !transferEnded(record)) {\n // The transfer's own writer deletes what it stored (`openTarget`), or keeps it when paused.\n if (!keep) kept.dropped = record.id;\n edge.files.cancel(\"in\", record.id);\n edge.taking.delete(record.id);\n } else if (!keep) {\n // No transfer under way: what an earlier one stored goes, before the next one writes.\n kept.idle = (kept.idle ?? Promise.resolve()).then(() => this.deps.store.write(kept.id)).then(writer => writer.discard()).catch(() => {});\n }\n if (keep) this.show(kept, { state: \"transferring\", stage: \"paused\", pausedBy: \"me\", transferred: kept.stored ?? 0, size, direction: \"in\" });\n else this.waiting(kept);\n // Not fetched by itself any more, nor asked for: it stays stopped (or paused) after a restart, and counts toward no limit.\n const wasPaused = !!kept.paused;\n kept.paused = keep;\n if (kept.auto || kept.asked || wasPaused !== keep || failed) {\n kept.auto = kept.asked = false;\n await this.deps.store.patch(kept.id, { group: this.groupField(kept), ...(failed && { transfer: { state: \"transferring\", transferred: 0, size } }) }).catch(() => {});\n }\n }\n\n /**\n * An error when this group took as many announcements of mine as it takes in a minute, or as its members take from\n * one member in `announceWindowMs` (past that they would not fetch the file by themselves); null when one may go now.\n */\n mayAnnounce(groupId: string): string | null {\n const wait = Math.max(...this.sentPaces(groupId).map(pace => pace.wait()));\n if (!wait) return null;\n const minutes = Math.ceil(wait / 60_000);\n return `You sent many files to this group just now. Wait ${minutes > 1 ? `${minutes} minutes` : \"a minute\"}.`;\n }\n\n /** My file, stored under `fileId` with the author's digest, is about to be announced in the group: its message takes it. */\n outgoing(groupId: string, fileId: string, meta: GroupFileMeta): void {\n this.own.set(`${groupId}\\n${meta.d}`, fileId);\n }\n /** The announcement did not go: the file is not this group's after all. */\n notSent(groupId: string, meta: GroupFileMeta): void { this.own.delete(`${groupId}\\n${meta.d}`); }\n\n /**\n * A message of the group announces a file (mine, or a member's): the file its row shows. A member's is kept here\n * unfetched, and fetched now when it is one this device takes by itself and the member announced no more than the\n * group fetches that way. The same message again (a catch-up, a whole copy) is the same file.\n */\n async announced(groupId: string, m: { id: string; sender: string; file: GroupFileMeta }, me: string): Promise<MessageFile | undefined> {\n const known = this.byMessage.get(`${groupId}\\n${m.id}`);\n if (known) return messageFile(this.kept.get(known)!);\n const ownId = m.sender === me ? this.own.get(`${groupId}\\n${m.file.d}`) : undefined;\n if (ownId) {\n this.own.delete(`${groupId}\\n${m.file.d}`);\n for (const pace of this.sentPaces(groupId)) pace.take();\n const kept: Kept = { id: ownId, groupId, message: m.id, author: me, meta: m.file, mine: true, auto: false, asked: false, held: true, holders: [], damaged: new Set() };\n this.add(kept);\n const transfer = { state: \"done\" as const, transferred: m.file.size, size: m.file.size };\n await this.deps.store.patch(ownId, { group: { message: m.id, author: me }, transfer }).catch(() => {});\n this.finished(kept, \"out\");\n this.queueHave(kept);\n return messageFile(kept);\n }\n // More from one member than a group fetches by itself: past it, the file is kept and waits for a Download (WISP 503,\n // Pace). Counted as they come, so a catch-up of files sent over hours is one burst: none of them is lost to it.\n const paced = this.pace(this.takenPace, `${groupId}\\n${m.sender}`, GROUP_FILE_LIMITS.announceTaken, GROUP_FILE_LIMITS.announceWindowMs).take();\n const id = groupFileId(groupId, \"in\");\n const auto = paced && this.deps.settings().autoDownloads && fetchesByItself(m.file, this.keptAutomatically(groupId));\n const holders = this.ahead.get(`${groupId}\\n${m.id}`) ?? [];\n this.ahead.delete(`${groupId}\\n${m.id}`);\n const kept: Kept = { id, groupId, message: m.id, author: m.sender, meta: m.file, mine: false, auto, asked: false, held: false, holders, damaged: new Set() };\n const { name, size, mime, voice, video, image } = m.file;\n // Known at once: the same message coming again meanwhile (another copy of it) is this file.\n this.add(kept);\n // No bytes yet: where they go is decided when they come (`GroupFileStore.write`).\n await this.deps.store.put({ id, linkId: `group:${groupId}`, direction: \"in\", digest: m.file.d, createdAt: this.deps.now(),\n metadata: { name, size, mime, timestamp: this.deps.now(), voice, video, image },\n transfer: { state: \"transferring\", transferred: 0, size }, group: { message: m.id, author: m.sender, ...(auto && { auto: true }) } });\n if (auto) { this.wants.set(id, this.newWant()); this.ask(kept); }\n else this.waiting(kept);\n return messageFile(kept);\n }\n\n /** The person asks for a file this device did not fetch by itself (Download), or for one that stopped coming. */\n async download(fileId: string): Promise<void> {\n const kept = this.kept.get(fileId);\n if (!kept) throw new Error(\"No such file\");\n if (kept.held) return;\n const room = await this.deps.store.room().catch(() => null);\n if (room !== null && kept.meta.size > room) throw new Error(\"Not enough space on this device for this file\");\n if (!kept.asked || kept.paused) {\n kept.asked = true; kept.paused = false;\n await this.deps.store.patch(kept.id, { group: this.groupField(kept) }).catch(() => {});\n }\n // Asked again: every holder is worth asking again, one that served it damaged too.\n kept.damaged.clear();\n if (this.deps.transfers.get(kept.id)?.state === \"failed\") {\n await this.deps.store.patch(kept.id, { transfer: { state: \"transferring\", transferred: 0, size: kept.meta.size } }).catch(() => {});\n }\n const want = this.wants.get(kept.id);\n if (!want) this.wants.set(kept.id, this.newWant());\n else if (!want.wire) { want.tried.clear(); want.why = want.busy = undefined; }\n this.ask(kept);\n }\n\n /** The group is gone from this device: its files go too. */\n async drop(groupId: string): Promise<void> {\n for (const kept of [...this.kept.values()].filter(k => k.groupId === groupId)) {\n this.kept.delete(kept.id);\n this.byMessage.delete(`${groupId}\\n${kept.message}`);\n this.wants.delete(kept.id);\n this.deps.transfers.delete(kept.id);\n // A write under way goes first; after it, the file's transfer writes nothing more (`openTarget`).\n await kept.writing?.catch(() => {});\n await this.deps.store.remove(kept.id).catch(() => {});\n }\n for (const [linkId, edge] of this.edgesById) if (edge.groupId === groupId) { edge.files.detach(); this.edgesById.delete(linkId); }\n for (const key of [...this.ahead.keys()]) if (key.startsWith(`${groupId}\\n`)) this.ahead.delete(key);\n this.communityHave.delete(groupId);\n this.deps.changed();\n }\n\n // -- edges ---------------------------------------------------------------------------------------------------\n\n /** An edge's files/3 session opened (both sides agreed it) or closed. */\n session(groupId: string, peer: string, linkId: string, open: boolean): void {\n const edge = this.edge(groupId, peer, linkId);\n if (open) edge.files.attach(); else edge.files.detach();\n // Another session, or none: its member may have fetched since what it did not hold, and is asked again.\n edge.gone.clear();\n if (open) this.retry(groupId);\n }\n\n /** An edge came up and both sides said where they are: what this device holds goes to it, and wants waiting for a holder go. */\n edgeReady(groupId: string, peer: string, linkId: string): void {\n if (!this.deps.ready(linkId)) return;\n const edge = this.edge(groupId, peer, linkId);\n if (this.deps.settings().serveFiles) {\n const held = [...this.kept.values()].filter(k => k.groupId === groupId && k.held).map(k => k.message).slice(-GROUP_FILE_LIMITS.haveIds);\n if (held.length) this.sendHave(edge, held);\n }\n this.retry(groupId);\n }\n\n /** A `group-want`, `group-want-no` or `group-have` on an edge, from the member it is pinned to. */\n async frame(groupId: string, peer: string, linkId: string, frame: Record<string, unknown>): Promise<void> {\n if (frame.t === GROUP_WANT_FRAME) {\n const want = readGroupWant(frame, groupId);\n if (want) await this.serve(this.edge(groupId, peer, linkId), want.id);\n } else if (frame.t === GROUP_WANT_NO_FRAME) {\n const no = readGroupWantNo(frame, groupId);\n if (no) this.refused(groupId, peer, linkId, no.id, no.why);\n } else if (frame.t === GROUP_HAVE_FRAME) {\n const ids = readGroupHave(frame, groupId);\n // Each one makes its member worth asking again: taken at the pace it may send them, not faster.\n if (ids && this.edge(groupId, peer, linkId).haveTaken.take()) this.heard(groupId, peer, ids);\n }\n }\n\n /** A community's `group-have`, an application frame its signer sent the group. */\n communityApp(groupId: string, sender: string, frame: Record<string, unknown>): void {\n const ids = readGroupHave(frame);\n if (ids) this.heard(groupId, sender, ids);\n }\n\n /**\n * A files/3 frame on an edge. An offer is taken only for a file this device asked that member for (`gm` naming the\n * message): nobody can push a file into another's storage. What it brings belongs to the group's message, not to the\n * edge as a chat.\n */\n filesFrame(groupId: string, peer: string, linkId: string, frame: Record<string, unknown>): Promise<void> {\n const edge = this.edge(groupId, peer, linkId);\n if (frame.t === \"pf-offer\" && typeof frame.id === \"string\" && /^[A-Za-z0-9_-]{8,64}$/.test(frame.id) && !edge.taking.has(frame.id)) {\n const kept = isGroupMessageId(frame.gm) ? this.kept.get(this.byMessage.get(`${groupId}\\n${frame.gm}`) ?? \"\") : undefined;\n const want = kept && this.wants.get(kept.id);\n if (!kept || kept.held || !want || want.linkId !== linkId || want.wire) {\n this.deps.sendFiles(linkId, { t: \"pf-refuse\", id: frame.id, why: \"invalid\" });\n return Promise.resolve();\n }\n edge.taking.set(frame.id, kept.id);\n // It offers the file: it holds it, whether or not it ever said so.\n kept.holders = newestHolder(peer, kept.holders);\n want.wire = frame.id;\n want.why = undefined;\n want.moved = -1;\n want.movedAt = this.deps.now();\n }\n return edge.files.handle(frame);\n }\n\n /** Wants that waited too long for an offer, or for bytes, go to the next holder; `group-have`s gathered go. */\n tick(): void {\n const now = this.deps.now();\n for (const [id, want] of this.wants) {\n const kept = this.kept.get(id);\n if (!kept) { this.wants.delete(id); continue; }\n if (now - (want.wire ? want.movedAt : want.at) < GROUP_FILE_LIMITS.wantWaitMs) continue;\n if (want.wire) {\n // A holder that stalls, or sends too little to count (`wantMoveBytes`), is left: the transfer ends here, and the\n // next holder is asked (`transferChanged`).\n kept.left = want.holder;\n const edge = want.linkId ? this.edgesById.get(want.linkId) : undefined;\n if (edge?.files.get(\"in\", want.wire) && !transferEnded(edge.files.get(\"in\", want.wire)!)) { edge.files.cancel(\"in\", want.wire); continue; }\n want.wire = undefined;\n }\n if (want.holder) want.tried.add(want.holder);\n // Everyone asked in turn: a new round, as holders come and go.\n else { want.tried.clear(); want.why = want.busy = undefined; }\n this.ask(kept);\n }\n this.letGo();\n for (const edge of this.edgesById.values()) {\n if (edge.have.size && this.deps.ready(edge.linkId)) this.sendHave(edge, [...edge.have]);\n }\n for (const [groupId, have] of this.communityHave) {\n if (!have.ids.size || now - have.at < GROUP_FILE_LIMITS.communityHaveEveryMs) continue;\n const ids = [...have.ids].slice(-GROUP_FILE_LIMITS.haveIds);\n have.ids.clear();\n have.at = now;\n void this.deps.sendApp(groupId, { t: GROUP_HAVE_FRAME, ids }).catch(() => {});\n }\n }\n\n // -- asking --------------------------------------------------------------------------------------------------\n\n private newWant(): Want { return { at: this.deps.now(), tried: new Set(), moved: 0, movedAt: 0 }; }\n\n /**\n * Wants of this group that wait for a holder: asked now (an edge came up or freed, or a member said it holds\n * something). One queued behind an edge's file is asked only while an edge of the group is free to take it, and free\n * for it (`freeFor`): once every edge has a file asked of it, or the free ones are of members it already asked, it would\n * only queue again. So a file that ends asks the few wants it frees an edge for, not every want waiting (a member back\n * from away with hundreds of files to fetch asked each of them once per file, more so with a member in the group that\n * holds none of them, or serves none).\n */\n private retry(groupId: string): void {\n let free: [string, string][] | undefined;\n for (const [id, want] of this.wants) {\n const kept = this.kept.get(id);\n if (kept?.groupId !== groupId || want.holder) continue;\n if (this.deps.transfers.get(id)?.stage === \"queued\" && !this.freeFor(kept, want, free ??= this.freeEdges(groupId))) continue;\n this.ask(kept);\n // That ask may have taken a free edge.\n free = undefined;\n }\n }\n\n /** The group's edges that are up, with files/3 live and no file asked of their member: a want could be asked on them. */\n private freeEdges(groupId: string): [string, string][] {\n return [...this.deps.edges(groupId)].filter(([, linkId]) => this.deps.ready(linkId) && this.edgesById.get(linkId)?.files.live && !this.askedOf(linkId));\n }\n\n /** One of these free edges (member key, link id) is one `ask` would ask this want on: asked now, it would not only queue again. */\n private freeFor(kept: Kept, want: Want, free: [string, string][]): boolean {\n const membership = this.deps.membership(kept.groupId);\n if (!membership) return false;\n const known = this.knownHolders(kept, membership);\n // As in `ask`: the others only while no known holder is worth waiting for.\n const others = !this.waitForKnown(kept, want, known, this.deps.edges(kept.groupId));\n return free.some(([key, linkId]) => this.mayAsk(kept, want, key, linkId)\n && (known.includes(key) || (others && key !== membership.me && membership.couldRead(key, kept.message))));\n }\n\n /** The members known to hold the file (its author first), in the group's roster, but this device. */\n private knownHolders(kept: Kept, membership: GroupFileMembership | undefined): string[] {\n return [kept.author, ...kept.holders.filter(key => key !== kept.author)]\n .filter(key => membership && key !== membership.me && membership.inRoster(key));\n }\n\n /**\n * The author, its edge up, that answered busy or whose edge another file of this device's is asked of, serves when its\n * place frees: the others are asked only when it is not worth waiting for (out of reach), as after a restart here.\n * Only the author holds them back: any member can say it holds files, and one that answers busy, or serves the file\n * asked of it slowly, would keep every file it named here from coming while the author is away. Its busy only skips\n * it for the round.\n */\n private waitForKnown(kept: Kept, want: Want, known: string[], edges: Map<string, string>): boolean {\n if (!known.includes(kept.author)) return false;\n const linkId = edges.get(kept.author) ?? \"\";\n return this.deps.ready(linkId) && (!!want.busy?.has(kept.author) || this.askedOf(linkId, want));\n }\n\n /** The want may ask this member in this round: not asked yet, it did not serve it damaged, and did not say it is gone. */\n private mayAsk(kept: Kept, want: Want, key: string, linkId: string | undefined): boolean {\n return !want.tried.has(key) && !kept.damaged.has(key) && !this.edgesById.get(linkId ?? \"\")?.gone.has(kept.message);\n }\n\n /**\n * Asks the next holder for a file: the author first while its edge is up, then the holders whose edge is up, then every\n * other member whose edge is up and that could read its message. Who holds what is heard, not kept: a holder names few\n * of its files to an edge that opens, and none it was told of is known after a restart here, so a member in reach is\n * asked whether or not it said so (one that does not hold it answers `gone`, and is not asked again). Only over an\n * edge this device has. (A private group past 16 members, or a community, reaches a holder it has no edge with\n * over a file session of the two members, WISP 503 § The file session; that session is not built yet, so such a\n * holder is not asked, and the file waits for one that is reachable.)\n */\n private ask(kept: Kept): void {\n const want = this.wants.get(kept.id);\n if (!want || kept.held || want.wire) return;\n const membership = this.deps.membership(kept.groupId);\n const edges = this.deps.edges(kept.groupId);\n const known = this.knownHolders(kept, membership);\n // Every holder known served it damaged: asking again would fetch the same bytes, all of them, round after round.\n if (known.length && known.every(key => kept.damaged.has(key))) { this.stopDamaged(kept); return; }\n const others = this.waitForKnown(kept, want, known, edges) ? [] : [...edges.keys()].filter(key => !known.includes(key) && membership && key !== membership.me && membership.couldRead(key, kept.message));\n const order = [...known, ...others].filter(key => this.mayAsk(kept, want, key, edges.get(key)));\n // The holder last left for this file is asked once the others were.\n if (kept.left && order.includes(kept.left)) order.push(...order.splice(order.indexOf(kept.left), 1));\n let behind = false;\n for (const key of order) {\n const linkId = edges.get(key);\n if (!linkId || !this.deps.ready(linkId) || !this.edgesById.get(linkId)?.files.live) continue;\n // A holder serves this device one file at a time (WISP 503 § Serving limits): asked now, it would answer `busy`.\n // This one waits its turn, and is asked when the file before it ends (`transferChanged`).\n if (this.askedOf(linkId, want)) { behind = true; continue; }\n // Asked of it before the frame goes: its answer is this want's, however soon it comes.\n Object.assign(want, { holder: key, linkId, at: this.deps.now() });\n this.askedOn.set(linkId, { id: kept.id, want });\n if (!this.deps.send(linkId, { t: GROUP_WANT_FRAME, g: kept.groupId, id: kept.message })) { want.tried.add(key); continue; }\n if (want.holder === key) this.show(kept, { state: \"transferring\", stage: \"queued\", transferred: kept.stored ?? 0, size: kept.meta.size, direction: \"in\" });\n return;\n }\n Object.assign(want, { holder: undefined, linkId: undefined, at: this.deps.now() });\n if (behind) { this.show(kept, { state: \"transferring\", stage: \"queued\", transferred: kept.stored ?? 0, size: kept.meta.size, direction: \"in\" }); return; }\n // Why it waits: a holder busy, bytes that came damaged (its person can ask again), or nobody in reach has it.\n const wait = want.why ?? \"nobody\";\n this.show(kept, { state: \"transferring\", stage: \"waiting\", transferred: kept.stored ?? 0, size: kept.meta.size, direction: \"in\",\n wait, note: WAITING_NOTES[wait], ...(wait === \"damaged\" && { stalled: true }) });\n }\n\n /**\n * Another file is asked of, or coming from, the member at the other end of this edge. A want gets its edge only in\n * `ask`, which notes it in `askedOn`, and one edge has one want at a time: the want noted there is it, while it still\n * waits and is still on that edge.\n */\n private askedOf(linkId: string, except?: Want): boolean {\n const on = this.askedOn.get(linkId);\n if (!on || on.want === except) return false;\n if (on.want.linkId === linkId && this.wants.get(on.id) === on.want) return true;\n this.askedOn.delete(linkId);\n return false;\n }\n\n /** No holder serves the author's bytes: the want stops, and stays stopped after a restart, until Download. */\n private stopDamaged(kept: Kept): void {\n this.wants.delete(kept.id);\n const { size } = kept.meta;\n void this.deps.store.patch(kept.id, { transfer: { state: \"failed\", transferred: 0, size, error: DAMAGED } }).catch(() => {});\n this.show(kept, { state: \"failed\", transferred: 0, size, direction: \"in\", retry: true, error: DAMAGED });\n }\n\n /** A holder's no: the next one is asked. One that deleted the file is no holder any more. */\n private refused(groupId: string, peer: string, linkId: string, messageId: string, why: GroupWantRefusal): void {\n const kept = this.kept.get(this.byMessage.get(`${groupId}\\n${messageId}`) ?? \"\");\n const want = kept && this.wants.get(kept.id);\n if (!kept || !want || want.linkId !== linkId || want.wire) return;\n if (why === \"gone\") {\n kept.holders = kept.holders.filter(key => key !== peer);\n this.edgesById.get(linkId)?.gone.add(messageId);\n }\n if (why === \"busy\") { want.why = \"busy\"; (want.busy ??= new Set()).add(peer); }\n want.tried.add(peer);\n want.holder = want.linkId = undefined;\n this.ask(kept);\n // The edge is free for a file waiting its turn there.\n this.retry(groupId);\n }\n\n /** A member says it holds these files: remembered as a holder of each (newest first), and asked when a want waits. */\n private heard(groupId: string, from: string, ids: string[]): void {\n const membership = this.deps.membership(groupId);\n if (!membership || from === membership.me || !membership.inRoster(from)) return;\n const newest = (holders: string[]) => newestHolder(from, holders);\n const edge = this.edgesById.get(this.deps.edges(groupId).get(from) ?? \"\");\n // Its edge, while free: a want queued behind other edges' files is asked again only if it can be asked there.\n let free: [string, string][] | undefined;\n for (const message of ids) {\n const key = `${groupId}\\n${message}`, kept = this.kept.get(this.byMessage.get(key) ?? \"\");\n if (!kept) {\n // Its message has not come yet (it is on its way in a catch-up): who holds it is kept for when it does.\n this.ahead.set(key, newest(this.ahead.get(key) ?? []));\n if (this.ahead.size > GROUP_FILE_LIMITS.heardAhead) this.ahead.delete(this.ahead.keys().next().value!);\n continue;\n }\n if (kept.mine) continue;\n kept.holders = newest(kept.holders);\n edge?.gone.delete(message);\n const want = this.wants.get(kept.id);\n if (!want) continue;\n want.tried.delete(from);\n want.busy?.delete(from);\n if (want.holder) continue;\n if (this.deps.transfers.get(kept.id)?.stage === \"queued\" && !this.freeFor(kept, want, free ??= this.freeEdges(groupId).filter(([key]) => key === from))) continue;\n this.ask(kept);\n free = undefined;\n }\n }\n\n // -- serving -------------------------------------------------------------------------------------------------\n\n /** A member asks for a file: offered over files/3 on its edge, or a `group-want-no` saying why not. */\n private async serve(edge: Edge, messageId: string): Promise<void> {\n const kept = this.kept.get(this.byMessage.get(`${edge.groupId}\\n${messageId}`) ?? \"\");\n const here = !!kept?.held && !!(await this.deps.store.get(kept.id).catch(() => undefined));\n // Decided and offered in one step: two wants at once never both find the last free place.\n const why = this.mayServe(edge, messageId, here ? kept : undefined);\n if (why || !kept) { this.deps.send(edge.linkId, { t: GROUP_WANT_NO_FRAME, g: edge.groupId, id: messageId, why: why ?? \"gone\" }); return; }\n const wire = toBase64Url(randomBytes(12));\n edge.serving.set(wire, kept.id);\n edge.servedMoved.set(wire, { confirmed: 0, at: this.deps.now(), from: this.deps.now() });\n const { name, mime, size, voice, video, image } = kept.meta;\n const file: FileInfo = { id: wire, name, mime, size, timestamp: this.deps.now(), ...(voice && { voice }), ...(video && { video }), ...(image && { image }) };\n edge.files.offer(file, kept.meta.d);\n }\n\n /**\n * Why this device does not serve a file now, or null when it does (WISP 503 § Asking, Serving limits): `refused` to\n * someone who could not read the announcement, `gone` when it is not here (`kept` undefined), `busy` when serving is\n * off or full.\n */\n private mayServe(edge: Edge, messageId: string, kept: Kept | undefined): GroupWantRefusal | null {\n const membership = this.deps.membership(edge.groupId);\n if (!membership || !membership.couldRead(edge.peer, messageId)) return \"refused\";\n if (!kept) return \"gone\";\n if (!this.deps.settings().serveFiles || !edge.files.live) return \"busy\";\n this.letGo();\n const active = this.servingNow();\n // One place per member, whatever it confirms. One that has all its bytes, lately, waits only for the member's word\n // that its check passed, which may come after this want: that one alone is not its place.\n const theirs = active.filter(a => a.peer === edge.peer);\n const placed = theirs.length > 1 || theirs.some(a => a.sending || this.deps.now() - a.since >= GROUP_FILE_LIMITS.doneWaitMs);\n // All places taken: a member holding none gets the one held longest past `servePlaceMs`, if there is one.\n if (active.length >= GROUP_FILE_LIMITS.serveAtOnce && (placed || !this.makeRoom())) { edge.busy.add(messageId); return \"busy\"; }\n if (placed) return \"busy\";\n const today = Math.floor(this.deps.now() / 86_400_000);\n if (this.served.day !== today) this.served = { day: today, bytes: 0 };\n const reserved = this.servingNow().reduce((sum, a) => sum + a.size, 0);\n return this.served.bytes + reserved + kept.meta.size > GROUP_FILE_LIMITS.serveBytesPerDay ? \"busy\" : null;\n }\n\n /** Ends the served transfer that has held its place longest, once past `servePlaceMs`: false when none has. */\n private makeRoom(): boolean {\n const now = this.deps.now();\n let oldest: { edge: Edge; wire: string; from: number } | undefined;\n for (const edge of this.edgesById.values()) for (const wire of edge.serving.keys()) {\n const record = edge.files.get(\"out\", wire), from = edge.servedMoved.get(wire)?.from ?? now;\n if (record && !transferEnded(record) && now - from >= GROUP_FILE_LIMITS.servePlaceMs && (!oldest || from < oldest.from)) oldest = { edge, wire, from };\n }\n if (!oldest) return false;\n oldest.edge.files.cancel(\"out\", oldest.wire);\n return true;\n }\n\n /**\n * Transfers served now, all groups together: to whom, what each takes of the day (its size, or what it sent when that\n * is more), whether bytes are still to go, and since when it has not gone further.\n */\n private servingNow(): { peer: string; size: number; sending: boolean; since: number }[] {\n const active: { peer: string; size: number; sending: boolean; since: number }[] = [];\n for (const edge of this.edgesById.values()) for (const wire of edge.serving.keys()) {\n const record = edge.files.get(\"out\", wire);\n if (record && !transferEnded(record)) {\n active.push({ peer: edge.peer, size: Math.max(record.file.size, edge.files.sent(wire)), sending: record.confirmed < record.file.size,\n since: edge.servedMoved.get(wire)?.at ?? this.deps.now() });\n }\n }\n return active;\n }\n\n /**\n * An asker has a served transfer send parts again (WISP 503 § Serving limits): every byte sent counts toward the day,\n * so it goes on only while the day has room for it.\n */\n private mayResend(): boolean {\n const today = Math.floor(this.deps.now() / 86_400_000);\n if (this.served.day !== today) this.served = { day: today, bytes: 0 };\n return this.served.bytes + this.servingNow().reduce((sum, a) => sum + a.size, 0) < GROUP_FILE_LIMITS.serveBytesPerDay;\n }\n\n /**\n * Served transfers that no longer count end, their places free for others (WISP 503 § Serving limits): the asker's\n * session closed, it is not in the roster any more, it has not had `serveMoveBytes` more confirmed (or the rest of\n * its file) for `wantWaitMs` (the asker has moved on by then), or it has all its bytes and the member's next want has\n * overtaken its word for `doneWaitMs`.\n */\n private letGo(): void {\n const now = this.deps.now();\n const theirs = new Map<string, number>();\n for (const a of this.servingNow()) theirs.set(a.peer, (theirs.get(a.peer) ?? 0) + 1);\n for (const edge of this.edgesById.values()) for (const wire of [...edge.serving.keys()]) {\n const record = edge.files.get(\"out\", wire);\n if (!record || transferEnded(record)) continue;\n let moved = edge.servedMoved.get(wire);\n if (!moved) edge.servedMoved.set(wire, (moved = { confirmed: record.confirmed, at: now, from: now }));\n const gone = !edge.files.live || !this.deps.membership(edge.groupId)?.inRoster(edge.peer);\n const overtaken = record.confirmed === record.file.size && theirs.get(edge.peer)! > 1 && now - moved.at >= GROUP_FILE_LIMITS.doneWaitMs;\n if (gone || overtaken || now - moved.at >= GROUP_FILE_LIMITS.wantWaitMs) {\n edge.files.cancel(\"out\", wire);\n theirs.set(edge.peer, theirs.get(edge.peer)! - 1);\n }\n }\n }\n\n /**\n * Says on an edge which files this device holds, as many frames as the edge takes now (`haveFrames` in\n * `haveWindowMs`); the ids past that, and those of a frame that could not go, wait for the next tick with room.\n */\n private sendHave(edge: Edge, ids: string[]): void {\n for (const id of ids) edge.have.add(id);\n for (let i = 0; i < ids.length; i += GROUP_FILE_LIMITS.haveIds) {\n const frame = ids.slice(i, i + GROUP_FILE_LIMITS.haveIds);\n if (edge.haveSent.wait() || !this.deps.send(edge.linkId, { t: GROUP_HAVE_FRAME, g: edge.groupId, ids: frame })) return;\n edge.haveSent.take();\n for (const id of frame) edge.have.delete(id);\n }\n }\n\n /**\n * This device holds a new file: the group hears it with the next `group-have`, which gathers what there is to say\n * until the next tick.\n */\n private queueHave(kept: Kept): void {\n if (!this.deps.settings().serveFiles) return;\n if (this.deps.membership(kept.groupId)?.community) {\n let have = this.communityHave.get(kept.groupId);\n if (!have) this.communityHave.set(kept.groupId, (have = { ids: new Set(), at: 0 }));\n have.ids.add(kept.message);\n return;\n }\n for (const [key, linkId] of this.deps.edges(kept.groupId)) this.edge(kept.groupId, key, linkId).have.add(kept.message);\n }\n\n // -- files/3 on an edge --------------------------------------------------------------------------------------\n\n private edge(groupId: string, peer: string, linkId: string): Edge {\n const known = this.edgesById.get(linkId);\n if (known) return known;\n const edge: Edge = { linkId, groupId, peer, files: undefined as unknown as ChatFiles, serving: new Map(), servedMoved: new Map(), taking: new Map(), ended: [], have: new Set(),\n haveSent: new RateWindow(GROUP_FILE_LIMITS.haveFrames, GROUP_FILE_LIMITS.haveWindowMs, () => this.deps.now()),\n haveTaken: new RateWindow(GROUP_FILE_LIMITS.haveFrames, GROUP_FILE_LIMITS.haveWindowMs, () => this.deps.now()), busy: new Set(), gone: new Set() };\n edge.files = new ChatFiles({\n // An offer names the group message it serves (`gm`), which a 1:1 chat never carries.\n send: frame => {\n const kept = frame.t === \"pf-offer\" ? this.kept.get(edge.serving.get(frame.id as string) ?? \"\") : undefined;\n return this.deps.sendFiles(linkId, kept ? { ...frame, gm: kept.message } : frame);\n },\n decide: file => this.decide(edge, file),\n openTarget: record => this.openTarget(edge, record),\n openSource: record => this.openSource(edge, record),\n changed: (record, transferred) => this.transferChanged(edge, record, transferred),\n room: () => this.deps.store.room(),\n ...(this.deps.sendData && { sendData: (id: string, offset: number, data: Uint8Array) => this.deps.sendData!(linkId, id, offset, data) }),\n writable: () => this.deps.writable?.(linkId),\n resend: record => !edge.serving.has(record.id) || this.mayResend(),\n });\n this.edgesById.set(linkId, edge);\n return edge;\n }\n\n /** An offer that answers my want (`filesFrame` checked that): taken, unless it is not the file announced or there is no room. */\n private async decide(edge: Edge, file: FileInfo): Promise<OfferDecision> {\n const kept = this.kept.get(edge.taking.get(file.id) ?? \"\"), want = kept && this.wants.get(kept.id);\n if (!kept || kept.held || want?.linkId !== edge.linkId || want.wire !== file.id || file.size !== kept.meta.size) return { refuse: \"invalid\" };\n const room = await this.deps.store.room().catch(() => null);\n return room !== null && file.size > room ? { refuse: \"no-room\", room } : \"accept\";\n }\n\n private async openTarget(edge: Edge, record: FileTransferRecord): Promise<IncomingTarget> {\n const kept = this.kept.get(edge.taking.get(record.id) ?? \"\");\n if (!kept) throw new Error(\"No such file\");\n // What an earlier holder's transfer stored stays, and this one goes on from there (WISP 503: the asker accepts from\n // the offset it stored): every holder serves the author's bytes, and the whole file is checked at the end.\n await kept.idle;\n const writer = await this.deps.store.write(kept.id, !kept.fresh);\n kept.fresh = false;\n const resumed = writer.offset > 0;\n if (!resumed) kept.storedBy = edge.peer;\n else if (kept.storedBy !== edge.peer) kept.storedBy = undefined;\n let damaged = false;\n // Its group was left (`drop`): what the transfer still has to write (its checkpoint as the edge closes) is deleted,\n // not stored as a file nothing knows of.\n const gone = () => this.kept.get(kept.id) !== kept;\n const write = async <T>(op: () => Promise<T>): Promise<T> => {\n if (gone()) { await writer.discard(); throw new Error(\"No such file\"); }\n return await (kept.writing = op());\n };\n return {\n offset: writer.offset,\n append: bytes => write(() => writer.append(bytes)),\n flush: () => write(() => writer.flush()),\n // Against the author's digest, read back from storage, not the holder's word: a holder can withhold a file, never\n // serve another one.\n verify: async () => {\n damaged = (await write(() => writer.digest())) !== kept.meta.d;\n const want = this.wants.get(kept.id);\n if (damaged && want) want.why = \"damaged\";\n return !damaged;\n },\n // Only damaged bytes go, and those of a file its person cancelled. Left with a holder that stalled or stopped, what\n // came is kept for the next holder.\n discard: () => {\n // Whole from this holder and damaged: its bytes are not the author's, and it is not asked for them again.\n if (damaged) {\n kept.stored = 0;\n if (resumed && kept.storedBy !== edge.peer) kept.fresh = kept.unclear = true; else kept.damaged.add(edge.peer);\n kept.storedBy = undefined;\n }\n return (kept.idle = (damaged || kept.dropped === record.id || gone() ? writer.discard() : writer.close()).catch(() => {}));\n },\n };\n }\n\n private async openSource(edge: Edge, record: FileTransferRecord): Promise<OutgoingSource> {\n const kept = this.kept.get(edge.serving.get(record.id) ?? \"\");\n const stored = kept && await this.deps.store.get(kept.id);\n if (!kept || !stored) throw new Error(\"The file is gone\");\n return { read: (offset, length) => this.deps.store.read(stored, offset, length), digest: async () => kept.meta.d };\n }\n\n /** A transfer on the edge ended: its record stays among the last `ENDED_KEPT`, and the oldest past that is forgotten. */\n private transferOver(edge: Edge, record: FileTransferRecord): void {\n if (edge.ended.some(([direction, id]) => direction === record.direction && id === record.id)) return;\n edge.ended.push([record.direction, record.id]);\n while (edge.ended.length > ENDED_KEPT) edge.files.forget(...edge.ended.shift()!);\n }\n\n private transferChanged(edge: Edge, record: FileTransferRecord, transferred: number): void {\n if (transferEnded(record)) this.transferOver(edge, record);\n if (record.direction === \"out\") {\n // Only bytes confirmed for the first time are progress, and only `serveMoveBytes` of them (or the rest of the\n // file): one accepted back and forth stays where it was, and a byte now and then does not keep a place.\n const moved = edge.servedMoved.get(record.id);\n if (moved && record.confirmed > moved.confirmed && (record.confirmed - moved.confirmed >= GROUP_FILE_LIMITS.serveMoveBytes || record.confirmed === record.file.size)) {\n edge.servedMoved.set(record.id, { ...moved, confirmed: record.confirmed, at: this.deps.now() });\n }\n if (transferEnded(record) && edge.serving.has(record.id)) {\n // What was sent to a member counts toward the day's serving, done or not, and whatever it confirmed.\n const today = Math.floor(this.deps.now() / 86_400_000);\n if (this.served.day !== today) this.served = { day: today, bytes: 0 };\n this.served.bytes += edge.files.sent(record.id);\n edge.serving.delete(record.id);\n edge.servedMoved.delete(record.id);\n // A slot is free: those turned away as busy hear this device holds their file (with the next `group-have`), and\n // ask it again rather than wait out their round.\n const serving = this.deps.settings().serveFiles;\n for (const other of this.edgesById.values()) {\n if (serving) for (const id of other.busy) other.have.add(id);\n other.busy.clear();\n }\n }\n return;\n }\n const kept = this.kept.get(edge.taking.get(record.id) ?? \"\"), want = kept && this.wants.get(kept.id);\n // A transfer this device moved on from (the holder stalled, then went on) changes nothing.\n if (!kept || !want || want.wire !== record.id || want.linkId !== edge.linkId) return;\n // Moving: `wantMoveBytes` more since it last moved (or the rest of the file), not any byte.\n if (want.moved < 0) want.moved = transferred;\n else if (transferred - want.moved >= Math.min(GROUP_FILE_LIMITS.wantMoveBytes, kept.meta.size - want.moved)) { want.moved = transferred; want.movedAt = this.deps.now(); }\n if (record.state !== \"failed\" && record.state !== \"declined\" && record.state !== \"cancelled\") kept.stored = transferred;\n const { size } = kept.meta;\n switch (record.state) {\n case \"done\": {\n kept.held = true;\n this.wants.delete(kept.id);\n edge.taking.delete(record.id);\n void this.deps.store.patch(kept.id, { transfer: { state: \"done\", transferred: size, size } }).catch(() => {});\n this.finished(kept, \"in\");\n this.queueHave(kept);\n // The holder is free for the next file this device waits for.\n this.retry(kept.groupId);\n return;\n }\n case \"failed\": case \"declined\": case \"cancelled\":\n // Damaged (refused, its bytes deleted), stopped by the holder or left here: the next holder is asked.\n edge.taking.delete(record.id);\n // A resumed file that failed its check may owe its bad bytes to the earlier holder: this one is asked again, from 0.\n if (want.holder && !kept.unclear) want.tried.add(want.holder);\n kept.unclear = false;\n Object.assign(want, { wire: undefined, holder: undefined, linkId: undefined, moved: 0 });\n this.ask(kept);\n this.retry(kept.groupId);\n return;\n case \"verifying\":\n this.show(kept, { state: \"transferring\", stage: \"verifying\", transferred, size, direction: \"in\" });\n return;\n default:\n this.show(kept, { state: \"transferring\", transferred, size, direction: \"in\" });\n }\n }\n\n // -- bookkeeping ---------------------------------------------------------------------------------------------\n\n private add(kept: Kept): void {\n this.kept.set(kept.id, kept);\n this.byMessage.set(`${kept.groupId}\\n${kept.message}`, kept.id);\n }\n\n /** Bytes this device fetched by itself from the group and keeps (or is fetching): what `autoBytesPerGroup` bounds. */\n private keptAutomatically(groupId: string): number {\n let sum = 0;\n for (const kept of this.kept.values()) if (kept.groupId === groupId && kept.auto && !kept.mine) sum += kept.meta.size;\n return sum;\n }\n\n private groupField(kept: Kept): NonNullable<StoredFile[\"group\"]> {\n return { message: kept.message, author: kept.author, ...(kept.auto && { auto: true }), ...(kept.asked && { asked: true }), ...(kept.paused && { paused: true }) };\n }\n\n /** The bytes of a file kept here from transfers that did not finish. */\n private async keptBytes(id: string): Promise<number> {\n const writer = await this.deps.store.write(id, true).catch(() => undefined);\n if (!writer) return 0;\n await writer.close().catch(() => {});\n return writer.offset;\n }\n\n private pace(windows: Map<string, RateWindow>, key: string, limit: number, ms: number): RateWindow {\n let window = windows.get(key);\n if (!window) windows.set(key, (window = new RateWindow(limit, ms, () => this.deps.now())));\n return window;\n }\n\n /** My announcements in a group, in a minute and in the window the members count them over. */\n private sentPaces(groupId: string): RateWindow[] {\n return [this.pace(this.sentPace, groupId, GROUP_FILE_LIMITS.announcePerMinute, 60_000),\n this.pace(this.sentTaken, groupId, GROUP_FILE_LIMITS.announceTaken, GROUP_FILE_LIMITS.announceWindowMs)];\n }\n\n /**\n * The file waits for its person's Download: said in one state (the CLI's `file.offered`), then left to its record,\n * which says it is not here (`waitingGroupFile`). A view kept for each such file would go into every state the engine\n * builds, so each would cost more with every file the groups ever had.\n */\n private waiting(kept: Kept): void {\n this.show(kept, { state: \"transferring\", stage: \"asking\", transferred: 0, size: kept.meta.size, direction: \"in\" });\n this.deps.brief(kept.id);\n }\n\n /**\n * All here: said in one state (the CLI's `file.done`), then left to its record, which says it is done. A view kept for\n * each file fetched or sent would go into every state the engine builds until the next start.\n */\n private finished(kept: Kept, direction: \"in\" | \"out\"): void {\n this.show(kept, { state: \"done\", transferred: kept.meta.size, size: kept.meta.size, direction });\n this.deps.brief(kept.id);\n }\n\n /** A file's row shows `view`; the engine hears of it only when that is not what the row shows already. */\n private show(kept: Kept, view: FileTransferView): void {\n if (sameView(this.deps.transfers.get(kept.id), view)) return;\n this.deps.transfers.set(kept.id, view);\n this.deps.changed();\n }\n}\n\n/** Two views of a transfer that say the same: their fields are all plain values. */\nfunction sameView(a: FileTransferView | undefined, b: FileTransferView): boolean {\n if (!a) return false;\n const keys = new Set([...Object.keys(a), ...Object.keys(b)] as (keyof FileTransferView)[]);\n for (const key of keys) if (a[key] !== b[key]) return false;\n return true;\n}\n\n/** A file's holders with `key` the newest of them, the `holders` newest kept. */\nconst newestHolder = (key: string, holders: string[]): string[] => [key, ...holders.filter(other => other !== key)].slice(0, GROUP_FILE_LIMITS.holders);\n\nfunction messageFile(kept: Kept): MessageFile {\n const { name, size, mime, voice, video, image } = kept.meta;\n return { id: kept.id, name, size, mime, ...(voice && { voice }), ...(video && { video }), ...(image && { image }) };\n}\n"],"mappings":"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"}