@ghostlytools/cli 1.2.4 → 1.3.1

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 (100) hide show
  1. package/README.md +25 -9
  2. package/dist/assets/api-Dq2QXQDk.js +4172 -0
  3. package/dist/assets/api-Dq2QXQDk.js.map +1 -0
  4. package/dist/assets/api-TiB5QSG9.js +3 -0
  5. package/dist/assets/apiKit-CA2x5GJF.js +200 -0
  6. package/dist/assets/apiKit-CA2x5GJF.js.map +1 -0
  7. package/dist/assets/apps-BxV9i_Y4.js +786 -0
  8. package/dist/assets/apps-BxV9i_Y4.js.map +1 -0
  9. package/dist/assets/apps-C-T8jeji.js +2 -0
  10. package/dist/assets/{arkade-CdnKGDbg.js → arkade-C_qDmHnm.js} +2 -2
  11. package/dist/assets/{arkade-CdnKGDbg.js.map → arkade-C_qDmHnm.js.map} +1 -1
  12. package/dist/assets/backup-Bugnn3Ue.js +2 -0
  13. package/dist/assets/backup-CnMN2oUK.js +731 -0
  14. package/dist/assets/backup-CnMN2oUK.js.map +1 -0
  15. package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
  16. package/dist/assets/bitcoind-CZchoh43.js +349 -0
  17. package/dist/assets/bitcoind-CZchoh43.js.map +1 -0
  18. package/dist/assets/bitcoindProvider-DGw3xBSa.js +97 -0
  19. package/dist/assets/bitcoindProvider-DGw3xBSa.js.map +1 -0
  20. package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -1
  21. package/dist/assets/buttons-Bj7Vf7Pn.js +2 -0
  22. package/dist/assets/buttons-CvNBzh2S.js +496 -0
  23. package/dist/assets/buttons-CvNBzh2S.js.map +1 -0
  24. package/dist/assets/{endpoint-pgFkR2AS.js → endpoint-BOTFMpfS.js} +56 -18
  25. package/dist/assets/endpoint-BOTFMpfS.js.map +1 -0
  26. package/dist/assets/envelope-WjingYSc.js +90 -0
  27. package/dist/assets/envelope-WjingYSc.js.map +1 -0
  28. package/dist/assets/errors-DO5z6h_l.js +105 -0
  29. package/dist/assets/errors-DO5z6h_l.js.map +1 -0
  30. package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -1
  31. package/dist/assets/{fileBytes-Dbifu5I5.js → fileBytes-CfSXjTFa.js} +2 -2
  32. package/dist/assets/{fileBytes-Dbifu5I5.js.map → fileBytes-CfSXjTFa.js.map} +1 -1
  33. package/dist/assets/fileBytes-CgbibpbV.js +2 -0
  34. package/dist/assets/{fileBytes-BcYZhNgF.js → fileBytes-Di7VZtmU.js} +6 -19
  35. package/dist/assets/fileBytes-Di7VZtmU.js.map +1 -0
  36. package/dist/assets/fileBytesIdb-CFP-DSkZ.js +2 -0
  37. package/dist/assets/{fileBytesIdb-DzSaqI0A.js → fileBytesIdb-D5u6H8X_.js} +4 -4
  38. package/dist/assets/{fileBytesIdb-DzSaqI0A.js.map → fileBytesIdb-D5u6H8X_.js.map} +1 -1
  39. package/dist/assets/{fileBytesOpfs-Dx8RZiV5.js → fileBytesOpfs-Dd1N4ERH.js} +3 -3
  40. package/dist/assets/{fileBytesOpfs-Dx8RZiV5.js.map → fileBytesOpfs-Dd1N4ERH.js.map} +1 -1
  41. package/dist/assets/groupFiles-IEUW3DQz.js +3202 -0
  42. package/dist/assets/groupFiles-IEUW3DQz.js.map +1 -0
  43. package/dist/assets/host-CWt-jtqz.js +4673 -0
  44. package/dist/assets/host-CWt-jtqz.js.map +1 -0
  45. package/dist/assets/hyperdhtRelay-BT_wRxab.js +21 -0
  46. package/dist/assets/hyperdhtRelay-BT_wRxab.js.map +1 -0
  47. package/dist/assets/{hyperdhtRelay-C1p0nkle.js → hyperdhtRelay-CPYwSLLS.js} +25 -10
  48. package/dist/assets/hyperdhtRelay-CPYwSLLS.js.map +1 -0
  49. package/dist/assets/{idb-CTF6LF5B.js → idb-B5L8wuOm.js} +7 -5
  50. package/dist/assets/idb-B5L8wuOm.js.map +1 -0
  51. package/dist/assets/irohWeb-4oChIOIp.js +544 -0
  52. package/dist/assets/irohWeb-4oChIOIp.js.map +1 -0
  53. package/dist/assets/{joinTrace-CO8mlhNO.js → joinTrace-CjRDFy85.js} +2 -7
  54. package/dist/assets/{joinTrace-CO8mlhNO.js.map → joinTrace-CjRDFy85.js.map} +1 -1
  55. package/dist/assets/joinTrace-D-fnt2p3.js +2 -0
  56. package/dist/assets/main-DbqcdShN.js +4409 -0
  57. package/dist/assets/main-DbqcdShN.js.map +1 -0
  58. package/dist/assets/{mainline-BH6s8HVi.js → mainline-ZkiOnevs.js} +11 -6
  59. package/dist/assets/mainline-ZkiOnevs.js.map +1 -0
  60. package/dist/assets/mints-BrlY773j.js +74 -0
  61. package/dist/assets/mints-BrlY773j.js.map +1 -0
  62. package/dist/assets/mints-CqTdA4Gk.js +2 -0
  63. package/dist/assets/networkReason-CetKkTPo.js +154 -0
  64. package/dist/assets/networkReason-CetKkTPo.js.map +1 -0
  65. package/dist/assets/nwc-CgkqyvsE.js +612 -0
  66. package/dist/assets/nwc-CgkqyvsE.js.map +1 -0
  67. package/dist/assets/nwcProvider-4eVz_8HP.js +77 -0
  68. package/dist/assets/nwcProvider-4eVz_8HP.js.map +1 -0
  69. package/dist/assets/privateFolder-Bk9C9oFv.js +38 -0
  70. package/dist/assets/privateFolder-Bk9C9oFv.js.map +1 -0
  71. package/dist/assets/profiles-BHc9pEYg.js +139 -0
  72. package/dist/assets/profiles-BHc9pEYg.js.map +1 -0
  73. package/dist/assets/registry-DlAKO0Fy.js +3793 -0
  74. package/dist/assets/registry-DlAKO0Fy.js.map +1 -0
  75. package/dist/assets/registry-OBJBnHVQ.js +2 -0
  76. package/dist/assets/resolve-CouHhCpJ.js +591 -0
  77. package/dist/assets/resolve-CouHhCpJ.js.map +1 -0
  78. package/dist/assets/{restoredRows-C_84q0Pd.js → restoredRows-Db3oUTko.js} +2 -2
  79. package/dist/assets/{restoredRows-C_84q0Pd.js.map → restoredRows-Db3oUTko.js.map} +1 -1
  80. package/dist/assets/{server-0zfNpABQ.js → server-Ss0gJ-nw.js} +1275 -1404
  81. package/dist/assets/server-Ss0gJ-nw.js.map +1 -0
  82. package/dist/assets/{src-CbLkHfz8.js → src-DDzSERyc.js} +882 -134
  83. package/dist/assets/src-DDzSERyc.js.map +1 -0
  84. package/dist/assets/turnRefs-B87dtrHM.js +323 -0
  85. package/dist/assets/turnRefs-B87dtrHM.js.map +1 -0
  86. package/dist/assets/types-C4_KafL_.js +122 -0
  87. package/dist/assets/types-C4_KafL_.js.map +1 -0
  88. package/dist/assets/{usdt-Qf4UuygS.js → usdt-Fj8CQO2z.js} +3 -3
  89. package/dist/assets/{usdt-Qf4UuygS.js.map → usdt-Fj8CQO2z.js.map} +1 -1
  90. package/dist/ghostly.mjs +4 -22077
  91. package/dist/ghostly.mjs.map +1 -1
  92. package/package.json +1 -1
  93. package/dist/assets/endpoint-pgFkR2AS.js.map +0 -1
  94. package/dist/assets/fileBytes-BcYZhNgF.js.map +0 -1
  95. package/dist/assets/hyperdhtRelay-C1p0nkle.js.map +0 -1
  96. package/dist/assets/idb-CTF6LF5B.js.map +0 -1
  97. package/dist/assets/mainline-BH6s8HVi.js.map +0 -1
  98. package/dist/assets/rolldown-runtime-8H4AJuhK.js +0 -14
  99. package/dist/assets/server-0zfNpABQ.js.map +0 -1
  100. package/dist/assets/src-CbLkHfz8.js.map +0 -1
@@ -0,0 +1,544 @@
1
+ import { aa as irohRelayUrl, gl as fromBase64Url, ia as irohDescriptor } from "./src-DDzSERyc.js";
2
+ //#region ../browser/src/engine/paymentAdapters/fedimintSdk.ts
3
+ var fedimintDatabase = (id) => `ghostly-fedimint-${id}.db`;
4
+ var MODULE_WASM = () => import("./fedimint_client_wasm_bg-BtHHlFpp.js");
5
+ /** The SDK checks it with an assert: any other length kills the worker. One client per database, always this name. */
6
+ var CLIENT_NAME = "dd5135b2-c228-41b7-a4f9-3b6e7afe3088";
7
+ var NETWORKS = {
8
+ bitcoin: "bitcoin",
9
+ signet: "signet",
10
+ testnet: "testnet",
11
+ testnet4: "testnet",
12
+ regtest: "regtest",
13
+ mutinynet: "mutinynet"
14
+ };
15
+ /**
16
+ * A v1 wallet (or Lightning) module's client config comes consensus-encoded as hex (`config` from a joined client,
17
+ * `unknown_module_hex` in a preview); its network is the chain's magic, a u32 as a compact-size integer (`fe` +
18
+ * four bytes, little-endian). Exactly one network must be found, or it stays unknown.
19
+ */
20
+ var MAGIC = [
21
+ ["fed9b4bef9", "bitcoin"],
22
+ ["fe0709110b", "testnet"],
23
+ ["fe283f161c", "testnet"],
24
+ ["fe40cf030a", "signet"],
25
+ ["fecb2ddfa5", "mutinynet"],
26
+ ["fedab5bffa", "regtest"]
27
+ ];
28
+ function moduleNetwork(module) {
29
+ if (typeof module.network === "string") return NETWORKS[module.network];
30
+ const encoded = typeof module.config === "string" ? module.config : module.unknown_module_hex;
31
+ if (typeof encoded !== "string" || !/^[0-9a-f]*$/i.test(encoded)) return void 0;
32
+ const hex = encoded.toLowerCase();
33
+ const found = [...new Set(MAGIC.filter(([magic]) => hex.includes(magic)).map(([, network]) => network))];
34
+ return found.length === 1 ? found[0] : void 0;
35
+ }
36
+ var hex = (value) => typeof value === "string" ? value : Array.isArray(value) ? value.map((b) => Number(b).toString(16).padStart(2, "0")).join("") : void 0;
37
+ var compiling;
38
+ /** Compiled once per page (11 MB): each worker is handed the module, not the bytes. */
39
+ function compiled() {
40
+ return compiling ??= (async () => {
41
+ const url = (await MODULE_WASM()).default;
42
+ const response = await fetch(url);
43
+ try {
44
+ return await WebAssembly.compileStreaming(response.clone());
45
+ } catch {
46
+ return WebAssembly.compile(await response.arrayBuffer());
47
+ }
48
+ })().catch((error) => {
49
+ compiling = void 0;
50
+ throw error;
51
+ });
52
+ }
53
+ /** A transport of `@fedimint/core` onto our own worker (fedimintWorker.ts). */
54
+ async function browserTransport() {
55
+ const [{ Transport }, module] = await Promise.all([import("@fedimint/types"), compiled()]);
56
+ class WorkerTransport extends Transport {
57
+ logger = {
58
+ debug() {},
59
+ info() {},
60
+ warn() {},
61
+ error() {}
62
+ };
63
+ worker = new Worker(new URL("./fedimintWorker.ts", import.meta.url), { type: "module" });
64
+ constructor() {
65
+ super();
66
+ this.worker.onmessage = (event) => this.messageHandler(event.data);
67
+ this.worker.onerror = (event) => {
68
+ event.preventDefault();
69
+ this.messageHandler({
70
+ type: "error",
71
+ error: `The Fedimint client stopped: ${event.message || "unknown error"}`
72
+ });
73
+ };
74
+ }
75
+ postMessage(message) {
76
+ this.worker.postMessage(message.type === "init" ? {
77
+ ...message,
78
+ payload: {
79
+ ...message.payload,
80
+ module
81
+ }
82
+ } : message);
83
+ }
84
+ terminate() {
85
+ this.worker.terminate();
86
+ }
87
+ }
88
+ return new WorkerTransport();
89
+ }
90
+ var text = (value, max = 200) => typeof value === "string" ? value.slice(0, max) : void 0;
91
+ var errorText = (error) => error instanceof Error ? error.message : typeof error === "string" ? error : JSON.stringify(error);
92
+ function federationInfo(federationId, config) {
93
+ const c = config;
94
+ const endpoints = Object.entries(c?.global?.api_endpoints ?? {}).sort(([a], [b]) => Number(a) - Number(b));
95
+ const modules = Object.values(c?.modules ?? {});
96
+ const network = modules.filter((m) => m?.kind === "wallet").map(moduleNetwork).find(Boolean) ?? modules.filter((m) => m?.kind === "ln").map(moduleNetwork).find(Boolean);
97
+ const version = c?.global?.consensus_version;
98
+ return {
99
+ federationId,
100
+ name: text(c?.global?.meta?.federation_name, 80),
101
+ guardians: endpoints.slice(0, 64).map(([peer, e]) => ({
102
+ name: text(e?.name, 80) || `Guardian ${peer}`,
103
+ url: text(e?.url, 300) ?? ""
104
+ })),
105
+ consensusVersion: `${Number(version?.major ?? 0)}.${Number(version?.minor ?? 0)}`,
106
+ network,
107
+ modules: [...new Set(modules.map((m) => typeof m?.kind === "string" ? m.kind.slice(0, 32) : "").filter(Boolean))],
108
+ welcome: text(c?.global?.meta?.welcome_message, 280)
109
+ };
110
+ }
111
+ /** The first value a subscription gives that is final, or the last one within `waitMs`. */
112
+ function watch(subscribe, final, waitMs) {
113
+ return new Promise((resolve) => {
114
+ let last, done = false, cancel = () => {};
115
+ const finish = () => {
116
+ if (done) return;
117
+ done = true;
118
+ clearTimeout(timer);
119
+ cancel();
120
+ resolve(last);
121
+ };
122
+ const timer = setTimeout(finish, waitMs);
123
+ cancel = subscribe((state) => {
124
+ last = state;
125
+ if (final(state)) finish();
126
+ }, () => finish());
127
+ });
128
+ }
129
+ var SPEND = {
130
+ Created: "created",
131
+ UserCanceledProcessing: "canceling",
132
+ UserCanceledSuccess: "canceled",
133
+ UserCanceledFailure: "taken",
134
+ Success: "taken",
135
+ Refunded: "refunded"
136
+ };
137
+ var spendFinal = (s) => s === "canceled" || s === "taken" || s === "refunded";
138
+ var REDEEM = {
139
+ Created: "created",
140
+ Issuing: "issuing",
141
+ Done: "done"
142
+ };
143
+ var receiveOf = (s) => s === "claimed" ? "claimed" : s === "funded" || s === "awaiting_funds" ? "funded" : typeof s === "object" && s && "canceled" in s ? "canceled" : "open";
144
+ var RealClient = class {
145
+ federationId;
146
+ wallet;
147
+ director;
148
+ end;
149
+ constructor(federationId, wallet, director, end) {
150
+ this.federationId = federationId;
151
+ this.wallet = wallet;
152
+ this.director = director;
153
+ this.end = end;
154
+ }
155
+ async info() {
156
+ return federationInfo(this.federationId, await this.wallet.federation.getConfig());
157
+ }
158
+ balance() {
159
+ return this.wallet.balance.getBalance();
160
+ }
161
+ onBalance(listener) {
162
+ return this.wallet.balance.subscribeBalance(listener, () => {});
163
+ }
164
+ async parseNotes(notes) {
165
+ const parsed = await this.director.parseOobNotes(notes);
166
+ return {
167
+ amountMsats: Number(parsed.total_amount),
168
+ federationIdPrefix: String(parsed.federation_id_prefix),
169
+ federationId: parsed.federation_id ?? void 0
170
+ };
171
+ }
172
+ async spend(amountMsats, { cancelAfterSecs, ghostly }) {
173
+ const { notes, operation_id } = await this.wallet.mint.spendNotes(amountMsats, cancelAfterSecs, false, { ghostly });
174
+ return {
175
+ notes: String(notes),
176
+ operationId: String(operation_id)
177
+ };
178
+ }
179
+ cancelSpend(operationId) {
180
+ return this.wallet.mint.tryCancelSpendNotes(operationId);
181
+ }
182
+ spendState(operationId, waitMs) {
183
+ return watch((ok, fail) => this.wallet.mint.subscribeSpendNotes(operationId, (s) => ok(SPEND[s] ?? "created"), fail), spendFinal, waitMs);
184
+ }
185
+ redeem(notes, ghostly) {
186
+ return this.wallet.mint.reissueExternalNotes(notes, { ghostly });
187
+ }
188
+ redeemState(operationId, waitMs) {
189
+ return watch((ok, fail) => this.wallet.mint.subscribeReissueExternalNotes(operationId, (s) => ok(typeof s === "string" ? REDEEM[s] ?? "created" : s && typeof s === "object" && "Failed" in s ? "failed" : "created"), (error) => {
190
+ ok("failed");
191
+ fail(error);
192
+ }), (s) => s === "done" || s === "failed", waitMs);
193
+ }
194
+ async createInvoice(amountMsats, description, expirySecs, ghostly) {
195
+ const { operation_id, invoice } = await this.wallet.lightning.createInvoice(amountMsats, description, expirySecs, void 0, { ghostly });
196
+ return {
197
+ operationId: String(operation_id),
198
+ invoice: String(invoice)
199
+ };
200
+ }
201
+ receiveState(operationId, waitMs) {
202
+ return watch((ok, fail) => this.wallet.lightning.subscribeLnReceive(operationId, (s) => ok(receiveOf(s)), fail), (s) => s === "claimed" || s === "canceled", waitMs);
203
+ }
204
+ async gatewayFee(amountMsats) {
205
+ await this.wallet.lightning.updateGatewayCache().catch(() => {});
206
+ const fees = (await this.wallet.lightning.getAvailableGateway().catch(() => null))?.fees;
207
+ if (!fees) return void 0;
208
+ return Number(fees.base_msat ?? 0) + Math.ceil(amountMsats * Number(fees.proportional_millionths ?? 0) / 1e6);
209
+ }
210
+ async payInvoice(invoice, ghostly) {
211
+ const paid = await this.wallet.lightning.payInvoice(invoice, void 0, { ghostly });
212
+ const internal = "internal" in paid.payment_type;
213
+ return {
214
+ operationId: String(internal ? paid.payment_type.internal : paid.payment_type.lightning),
215
+ feeMsats: Number(paid.fee ?? 0),
216
+ internal
217
+ };
218
+ }
219
+ async payState(operationId, internal, waitMs) {
220
+ const state = await watch((ok, fail) => internal ? this.wallet.lightning.subscribeInternalPayment(operationId, ok, fail) : this.wallet.lightning.subscribeLnPay(operationId, ok, fail), (s) => typeof s === "object" && !!s && ("success" in s || "preimage" in s || "refunded" in s || "refund_success" in s || "unexpected_error" in s || "funding_failed" in s) || s === "canceled", waitMs);
221
+ if (state && typeof state === "object") {
222
+ const s = state;
223
+ if (s.success?.preimage || s.preimage) return {
224
+ state: "paid",
225
+ preimage: hex(s.success?.preimage ?? s.preimage)
226
+ };
227
+ if ("refunded" in s || "refund_success" in s || "funding_failed" in s) return {
228
+ state: "failed",
229
+ error: errorText(s.refunded?.gateway_error ?? s.funding_failed?.error ?? "The gateway gave the payment back")
230
+ };
231
+ if ("unexpected_error" in s) return {
232
+ state: "pending",
233
+ error: errorText(s.unexpected_error)
234
+ };
235
+ }
236
+ if (state === "canceled") return {
237
+ state: "failed",
238
+ error: "Canceled before it was funded"
239
+ };
240
+ return { state: "pending" };
241
+ }
242
+ async operations(limit) {
243
+ return (await this.wallet.federation.listOperations(limit)).map(([key, log]) => {
244
+ const variant = log.meta?.variant && typeof log.meta.variant === "object" ? Object.keys(log.meta.variant)[0] : void 0;
245
+ const detail = variant ? log.meta.variant[variant] : void 0;
246
+ return {
247
+ id: String(key.operation_id ?? ""),
248
+ kind: String(log.operation_module_kind ?? ""),
249
+ variant,
250
+ createdAt: Number(key.creation_time?.secs_since_epoch ?? 0) * 1e3,
251
+ amountMsats: typeof log.meta?.amount === "number" ? log.meta.amount : typeof detail?.requested_amount === "number" ? detail.requested_amount : void 0,
252
+ outcome: log.outcome?.outcome,
253
+ ghostly: typeof log.meta?.extra_meta?.ghostly === "string" ? log.meta.extra_meta.ghostly : void 0,
254
+ invoice: typeof detail?.invoice === "string" ? detail.invoice : void 0,
255
+ feeMsats: typeof detail?.fee === "number" ? detail.fee : void 0
256
+ };
257
+ });
258
+ }
259
+ async close() {
260
+ try {
261
+ await this.wallet.cleanup();
262
+ } catch {}
263
+ this.end();
264
+ }
265
+ };
266
+ var BROWSER = {
267
+ transport: browserTransport,
268
+ async exists(database) {
269
+ const root = await navigator.storage.getDirectory();
270
+ try {
271
+ return (await (await root.getFileHandle(database)).getFile()).size > 0;
272
+ } catch {
273
+ return false;
274
+ }
275
+ },
276
+ async remove(database) {
277
+ await (await navigator.storage.getDirectory()).removeEntry(database).catch((error) => {
278
+ if (error?.name !== "NotFoundError") throw error;
279
+ });
280
+ }
281
+ };
282
+ var loading;
283
+ /** Loads the SDK on first use: profiles that never join a federation never fetch its 11 MB. */
284
+ function loadFedimintSdk() {
285
+ return loading ??= fedimintSdkOn(BROWSER).catch((error) => {
286
+ loading = void 0;
287
+ throw error;
288
+ });
289
+ }
290
+ /** The SDK on a platform's workers and files. */
291
+ async function fedimintSdkOn(platform) {
292
+ const { WalletDirector } = await import("@fedimint/core");
293
+ /** A director on its own worker and database, with the mnemonic in place. */
294
+ const director = async (database, mnemonic) => {
295
+ const t = await platform.transport();
296
+ const d = new WalletDirector(t, database, true);
297
+ try {
298
+ await d.initialize(database);
299
+ if (mnemonic && !await d.hasMnemonicSet()) await d.setMnemonic(mnemonic.trim().split(/\s+/));
300
+ return {
301
+ director: d,
302
+ end: () => t.terminate()
303
+ };
304
+ } catch (error) {
305
+ t.terminate();
306
+ throw error;
307
+ }
308
+ };
309
+ const client = async (database, mnemonic, start) => {
310
+ const { director: d, end } = await director(database, mnemonic);
311
+ try {
312
+ const wallet = await d.createWallet();
313
+ await start(wallet);
314
+ return new RealClient(await wallet.federation.getFederationId(), wallet, d, end);
315
+ } catch (error) {
316
+ end();
317
+ throw error;
318
+ }
319
+ };
320
+ const sdk = {
321
+ async preview(invite) {
322
+ const database = fedimintDatabase(`preview-${crypto.randomUUID()}`);
323
+ const { director: d, end } = await director(database);
324
+ try {
325
+ const preview = await d.previewFederation(invite.trim());
326
+ return federationInfo(preview.federation_id, preview.config);
327
+ } finally {
328
+ end();
329
+ await sdk.remove(database).catch(() => {});
330
+ }
331
+ },
332
+ join: ({ database, mnemonic, invite, recover }) => client(database, mnemonic, async (wallet) => {
333
+ if (!await wallet.joinFederation(invite.trim(), {
334
+ clientName: CLIENT_NAME,
335
+ forceRecover: recover
336
+ })) throw new Error("Could not join the federation: its guardians did not answer, or the invite code is not valid");
337
+ }),
338
+ open: ({ database, mnemonic }) => client(database, mnemonic, (wallet) => wallet.open(CLIENT_NAME)),
339
+ exists: (database) => platform.exists(database),
340
+ remove: (database) => platform.remove(database)
341
+ };
342
+ return sdk;
343
+ }
344
+ //#endregion
345
+ //#region ../browser/src/platform/irohWeb.ts
346
+ /**
347
+ * Iroh (WISP 102) in a browser: the same QUIC/TLS session and binding as the
348
+ * Desktop's native Iroh, but relay only. A page cannot send UDP, so every
349
+ * packet goes through an Iroh relay over a WebSocket; the relay sees who talks
350
+ * to whom and when, never the frames.
351
+ *
352
+ * The wasm (about 1 MB gzip) loads on the first chat that starts an endpoint,
353
+ * never on the app's first load.
354
+ */
355
+ /**
356
+ * n0's public relays, the same ones the Desktop's Iroh homes on by default. Iroh itself names them with the trailing
357
+ * dot of a full domain name (`use1-1.relay.n0.iroh.link.`), and compares relay URLs as text; here they have none,
358
+ * because WebKit opens no host name that ends in a dot. `BROWSER_SPELLING` is what keeps the two one relay.
359
+ */
360
+ var DEFAULT_IROH_RELAYS = [
361
+ "https://use1-1.relay.n0.iroh.link/",
362
+ "https://euc1-1.relay.n0.iroh.link/",
363
+ "https://aps1-1.relay.n0.iroh.link/",
364
+ "https://usw1-1.relay.n0.iroh.link/"
365
+ ];
366
+ /**
367
+ * The spelling of every relay URL handed to the wasm Iroh (`irohRelayUrl`): the relays it homes on and a contact's
368
+ * relay before a dial. One spelling, so a contact on this endpoint's relay is reached over the connection already
369
+ * open to it; without the dot, so Safari can open it (a Desktop's record names its relay with the dot).
370
+ */
371
+ var BROWSER_SPELLING = "plain";
372
+ /**
373
+ * Why a relay URL cannot be used, or null. TLS, except on this machine (a local `iroh-relay --dev`): a page
374
+ * may not open plain WebSockets elsewhere, and the relay's operator would see traffic metadata in the clear.
375
+ */
376
+ function irohRelayProblem(value) {
377
+ let url;
378
+ try {
379
+ url = new URL(value);
380
+ } catch {
381
+ return `Not a relay address: ${value}`;
382
+ }
383
+ const local = [
384
+ "localhost",
385
+ "127.0.0.1",
386
+ "[::1]"
387
+ ].includes(url.hostname);
388
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && local)) return `Use an https:// relay address: ${value}`;
389
+ if (url.username || url.password || url.search || url.hash || value.length > 256) return `Not a relay address: ${value}`;
390
+ return null;
391
+ }
392
+ var loaded = null;
393
+ /** Loads the wasm once per page. Tests pass their own `load` (Node reads the file). */
394
+ function loadIrohWasm() {
395
+ loaded ??= import("./ghostly_iroh_web-Dvzh3Y6E.js").then(async (module) => {
396
+ await module.default();
397
+ return module;
398
+ }).catch((error) => {
399
+ loaded = null;
400
+ throw error;
401
+ });
402
+ return loaded;
403
+ }
404
+ var SEND_BUDGET = 122880;
405
+ var MAX_FRAME = 61440;
406
+ /** One paired channel over one QUIC stream. Sends are queued in order. */
407
+ var IrohWebChannel = class {
408
+ conn;
409
+ forget;
410
+ reader = null;
411
+ buffered = [];
412
+ queue = Promise.resolve();
413
+ closed = false;
414
+ bufferedAmount = 0;
415
+ sendBudget = SEND_BUDGET;
416
+ onClose = null;
417
+ constructor(conn, forget) {
418
+ this.conn = conn;
419
+ this.forget = forget;
420
+ this.read();
421
+ }
422
+ get onMessage() {
423
+ return this.reader;
424
+ }
425
+ set onMessage(reader) {
426
+ this.reader = reader;
427
+ if (reader) for (const text of this.buffered.splice(0)) reader(text);
428
+ }
429
+ async read() {
430
+ try {
431
+ for (;;) {
432
+ const text = await this.conn.recv();
433
+ if (text === void 0 || this.closed) break;
434
+ if (this.reader) this.reader(text);
435
+ else if (this.buffered.length < 64) this.buffered.push(text);
436
+ else break;
437
+ }
438
+ } catch {}
439
+ this.close();
440
+ }
441
+ send(data) {
442
+ if (typeof data !== "string" || this.closed || !data.length || data.length > MAX_FRAME || this.bufferedAmount + data.length > SEND_BUDGET) throw new Error("Iroh channel is closed or its send budget is full");
443
+ this.bufferedAmount += data.length;
444
+ this.queue = this.queue.then(() => this.closed ? void 0 : this.conn.send(data)).catch(() => this.close()).finally(() => {
445
+ this.bufferedAmount -= data.length;
446
+ });
447
+ }
448
+ drained() {
449
+ return this.queue;
450
+ }
451
+ get rttMs() {
452
+ const rtt = this.conn.rtt();
453
+ return rtt >= 0 ? rtt : void 0;
454
+ }
455
+ close() {
456
+ if (this.closed) return;
457
+ this.closed = true;
458
+ this.buffered = [];
459
+ try {
460
+ this.conn.close();
461
+ } catch {}
462
+ this.forget();
463
+ this.onClose?.();
464
+ }
465
+ };
466
+ /** A NativeEndpoint for `iroh/1`, as the Desktop's is, for one chat's transport seed. */
467
+ async function createIrohWebEndpoint(seedB64, options = {}) {
468
+ const seed = fromBase64Url(seedB64);
469
+ if (seed.length !== 32) throw new Error("Invalid transport seed");
470
+ const relays = [...new Set((options.relays?.length ? options.relays : DEFAULT_IROH_RELAYS).map((relay) => irohRelayUrl(relay, BROWSER_SPELLING)))];
471
+ const node = await (await (options.load ?? loadIrohWasm)()).IrohNode.start(seed, relays, options.onlineMs ?? 1e4);
472
+ const channels = /* @__PURE__ */ new Set();
473
+ let stopped = false;
474
+ let handler = null;
475
+ const early = [];
476
+ const bind = (conn) => {
477
+ const channel = new IrohWebChannel(conn, () => channels.delete(channel));
478
+ channels.add(channel);
479
+ return {
480
+ channel,
481
+ binding: conn.binding()
482
+ };
483
+ };
484
+ const endpoint = {
485
+ transport: "iroh/1",
486
+ descriptor: {
487
+ ...node.address(),
488
+ relayed: true
489
+ },
490
+ onDescriptor: null,
491
+ get onConnection() {
492
+ return handler;
493
+ },
494
+ set onConnection(value) {
495
+ handler = value;
496
+ if (value) for (const bound of early.splice(0)) value(bound);
497
+ },
498
+ async connect(descriptor) {
499
+ if (stopped) throw new Error("Iroh endpoint is stopped");
500
+ const conn = await node.connect(irohDescriptor(descriptor, BROWSER_SPELLING), options.connectMs ?? 2e4);
501
+ if (stopped) {
502
+ conn.close();
503
+ throw new Error("Iroh endpoint is stopped");
504
+ }
505
+ return bind(conn);
506
+ },
507
+ async close() {
508
+ if (stopped) return;
509
+ stopped = true;
510
+ for (const channel of [...channels]) channel.close();
511
+ for (const bound of early.splice(0)) bound.channel.close();
512
+ await node.close().catch(() => {});
513
+ }
514
+ };
515
+ (async () => {
516
+ while (!stopped) {
517
+ let conn;
518
+ try {
519
+ conn = await node.accept();
520
+ } catch {
521
+ break;
522
+ }
523
+ if (!conn) break;
524
+ if (stopped) {
525
+ conn.close();
526
+ break;
527
+ }
528
+ const bound = bind(conn);
529
+ const accept = endpoint.onConnection;
530
+ if (accept) accept(bound);
531
+ else if (early.length < 4) early.push(bound);
532
+ else bound.channel.close();
533
+ }
534
+ if (!stopped) {
535
+ endpoint.onUnavailable?.();
536
+ await endpoint.close();
537
+ }
538
+ })();
539
+ return endpoint;
540
+ }
541
+ //#endregion
542
+ export { fedimintSdkOn as a, fedimintDatabase as i, createIrohWebEndpoint as n, loadFedimintSdk as o, irohRelayProblem as r, DEFAULT_IROH_RELAYS as t };
543
+
544
+ //# sourceMappingURL=irohWeb-4oChIOIp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"irohWeb-4oChIOIp.js","names":[],"sources":["../../../browser/src/engine/paymentAdapters/fedimintSdk.ts","../../../browser/src/platform/irohWeb.ts"],"sourcesContent":["import type { BitcoinNetwork } from \"@ghostly/core\";\n\n/**\n * The part of the Fedimint web SDK (`@fedimint/core`, WebAssembly client in a worker) Ghostly uses, behind an\n * interface small enough to fake in tests. The client runs in a worker, on a file it reads and writes through a sync\n * access handle: in a browser, a module worker and the origin-private file system; on Node, the CLI's\n * `worker_threads` worker and a file of the profile (`FedimintPlatform`).\n *\n * One client database per joined federation (`ghostly-fedimint-<id>.db`, one file): the SDK's services are bound to one client per database, and each database is held by one worker.\n * Every database of a profile and mode is given the same mnemonic; the client derives a different secret per\n * federation from it. Amounts are millisatoshis, as the SDK counts them.\n */\nexport interface FederationGuardian { name: string; url: string }\nexport interface FederationInfo {\n federationId: string;\n /** `meta.federation_name`, when the guardians set one. */\n name?: string;\n guardians: FederationGuardian[];\n /** Core consensus version, `major.minor`. */\n consensusVersion: string;\n /** The Bitcoin network of its wallet module. Unknown when the federation has no v1 wallet module. */\n network?: BitcoinNetwork;\n /** Module kinds (`mint`, `ln`, `wallet`, `meta`…). The SDK uses `mint`, `ln` and `wallet` (v1). */\n modules: string[];\n /** `meta.welcome_message`, shortened. */\n welcome?: string;\n}\n\n/** Out-of-band notes, parsed offline (signatures and prefix only: whether they are spent is the federation's to say). */\nexport interface ParsedNotes { amountMsats: number; federationIdPrefix: string; federationId?: string }\n\n/** Where a spend of notes stands. `canceled` = taken back (reissued to us); `taken` = the recipient redeemed them. */\nexport type SpendState = \"created\" | \"canceling\" | \"canceled\" | \"taken\" | \"refunded\";\n/** A redeem (reissue) of notes we were given. */\nexport type RedeemState = \"created\" | \"issuing\" | \"done\" | \"failed\";\nexport type ReceiveState = \"open\" | \"funded\" | \"claimed\" | \"canceled\";\nexport interface PayOutcome { state: \"pending\" | \"paid\" | \"failed\"; preimage?: string; error?: string }\n\nexport interface FedimintOperation {\n id: string;\n /** Module: `mint`, `ln`, `wallet`. */\n kind: string;\n /** What it was: `spend_oob`, `reissuance`, `receive`, `pay`… as the SDK names it. */\n variant?: string;\n createdAt: number;\n amountMsats?: number;\n /** The SDK's final outcome, once there is one. */\n outcome?: unknown;\n /** What Ghostly asked to be kept with it (`extra_meta`): the payment id it belongs to. */\n ghostly?: string;\n /** Lightning: the invoice it paid or was paid on, and the fee it paid (msats). */\n invoice?: string;\n feeMsats?: number;\n}\n\nexport interface FedimintClient {\n readonly federationId: string;\n info(): Promise<FederationInfo>;\n balance(): Promise<number>;\n /** Called with every new balance, until the returned function is called. */\n onBalance(listener: (msats: number) => void): () => void;\n parseNotes(notes: string): Promise<ParsedNotes>;\n /** Takes notes out of the wallet, as a bearer string. Unredeemed after `cancelAfterSecs`, they come back by themselves. */\n spend(amountMsats: number, options: { cancelAfterSecs: number; ghostly: string }): Promise<{ notes: string; operationId: string }>;\n /** Asks to take notes back: they are reissued to us unless the recipient redeemed them first. */\n cancelSpend(operationId: string): Promise<void>;\n /** The latest state within `waitMs` (it returns early on a final one). Undefined: the client knows no such operation. */\n spendState(operationId: string, waitMs: number): Promise<SpendState | undefined>;\n redeem(notes: string, ghostly: string): Promise<string>;\n redeemState(operationId: string, waitMs: number): Promise<RedeemState | undefined>;\n createInvoice(amountMsats: number, description: string, expirySecs: number, ghostly: string): Promise<{ operationId: string; invoice: string }>;\n receiveState(operationId: string, waitMs: number): Promise<ReceiveState | undefined>;\n /** What the federation's gateway would charge to pay this amount, in msats. Undefined: no gateway. */\n gatewayFee(amountMsats: number): Promise<number | undefined>;\n payInvoice(invoice: string, ghostly: string): Promise<{ operationId: string; feeMsats: number; internal: boolean }>;\n payState(operationId: string, internal: boolean, waitMs: number): Promise<PayOutcome>;\n operations(limit: number): Promise<FedimintOperation[]>;\n /** Ends the worker; the database stays. */\n close(): Promise<void>;\n}\n\nexport interface FedimintSdk {\n preview(invite: string): Promise<FederationInfo>;\n /** Joins (or, `recover`, restores from the federation's backup of this mnemonic) into a new database. */\n join(params: { database: string; mnemonic: string; invite: string; recover: boolean }): Promise<FedimintClient>;\n /** Opens a database joined before. */\n open(params: { database: string; mnemonic: string }): Promise<FedimintClient>;\n /** Deletes a database: only one that never held money (a failed join). */\n remove(database: string): Promise<void>;\n /** Whether this device has the database (a profile restored from a backup has the records, not the files). */\n exists(database: string): Promise<boolean>;\n}\n\nexport const fedimintDatabase = (id: string) => `ghostly-fedimint-${id}.db`;\n\n// ── The real SDK ───────────────────────────────────────────────────────────────────────────────────────────\n\ntype Json = null | boolean | number | string | Json[] | { [key: string]: Json };\nconst MODULE_WASM = () => import(\"@fedimint/fedimint-client-wasm-bundler/fedimint_client_wasm_bg.wasm?url\");\n/** The SDK checks it with an assert: any other length kills the worker. One client per database, always this name. */\nconst CLIENT_NAME = \"dd5135b2-c228-41b7-a4f9-3b6e7afe3088\";\nconst NETWORKS: Record<string, BitcoinNetwork> = { bitcoin: \"bitcoin\", signet: \"signet\", testnet: \"testnet\", testnet4: \"testnet\", regtest: \"regtest\", mutinynet: \"mutinynet\" };\n/**\n * A v1 wallet (or Lightning) module's client config comes consensus-encoded as hex (`config` from a joined client,\n * `unknown_module_hex` in a preview); its network is the chain's magic, a u32 as a compact-size integer (`fe` +\n * four bytes, little-endian). Exactly one network must be found, or it stays unknown.\n */\nconst MAGIC: [string, BitcoinNetwork][] = [[\"fed9b4bef9\", \"bitcoin\"], [\"fe0709110b\", \"testnet\"], [\"fe283f161c\", \"testnet\"], [\"fe40cf030a\", \"signet\"], [\"fecb2ddfa5\", \"mutinynet\"], [\"fedab5bffa\", \"regtest\"]];\nfunction moduleNetwork(module: { network?: unknown; config?: unknown; unknown_module_hex?: unknown }): BitcoinNetwork | undefined {\n if (typeof module.network === \"string\") return NETWORKS[module.network];\n const encoded = typeof module.config === \"string\" ? module.config : module.unknown_module_hex;\n if (typeof encoded !== \"string\" || !/^[0-9a-f]*$/i.test(encoded)) return undefined;\n const hex = encoded.toLowerCase();\n const found = [...new Set(MAGIC.filter(([magic]) => hex.includes(magic)).map(([, network]) => network))];\n return found.length === 1 ? found[0] : undefined;\n}\nconst hex = (value: unknown) => typeof value === \"string\" ? value : Array.isArray(value) ? value.map((b) => Number(b).toString(16).padStart(2, \"0\")).join(\"\") : undefined;\n\nlet compiling: Promise<WebAssembly.Module> | undefined;\n/** Compiled once per page (11 MB): each worker is handed the module, not the bytes. */\nfunction compiled(): Promise<WebAssembly.Module> {\n return compiling ??= (async () => {\n const url = (await MODULE_WASM()).default;\n const response = await fetch(url);\n // A server that sends the wrong MIME type cannot stream: compile from the bytes instead.\n try { return await WebAssembly.compileStreaming(response.clone()); } catch { return WebAssembly.compile(await response.arrayBuffer()); }\n })().catch((error) => { compiling = undefined; throw error; });\n}\n\n/** A transport of `@fedimint/core` onto our own worker (fedimintWorker.ts). */\nasync function browserTransport() {\n const [{ Transport }, module] = await Promise.all([import(\"@fedimint/types\"), compiled()]);\n class WorkerTransport extends Transport {\n readonly logger = { debug() {}, info() {}, warn() {}, error() {} };\n private readonly worker = new Worker(new URL(\"./fedimintWorker.ts\", import.meta.url), { type: \"module\" });\n constructor() {\n super();\n this.worker.onmessage = (event) => this.messageHandler(event.data);\n this.worker.onerror = (event) => { event.preventDefault(); this.messageHandler({ type: \"error\", error: `The Fedimint client stopped: ${event.message || \"unknown error\"}` }); };\n }\n postMessage(message: { type: string; payload?: Json; requestId?: number }) {\n // The worker gets the compiled module with its init, and nothing to log.\n this.worker.postMessage(message.type === \"init\" ? { ...message, payload: { ...(message.payload as object), module } } : message);\n }\n terminate() { this.worker.terminate(); }\n }\n return new WorkerTransport();\n}\n\nconst text = (value: unknown, max = 200) => typeof value === \"string\" ? value.slice(0, max) : undefined;\nconst errorText = (error: unknown) => error instanceof Error ? error.message : typeof error === \"string\" ? error : JSON.stringify(error);\n\nexport function federationInfo(federationId: string, config: unknown): FederationInfo {\n const c = config as { global?: { api_endpoints?: Record<string, { url?: unknown; name?: unknown }>; consensus_version?: { major?: unknown; minor?: unknown }; meta?: Record<string, unknown> }; modules?: Record<string, { kind?: unknown; network?: unknown; config?: unknown; unknown_module_hex?: unknown }> };\n const endpoints = Object.entries(c?.global?.api_endpoints ?? {}).sort(([a], [b]) => Number(a) - Number(b));\n const modules = Object.values(c?.modules ?? {});\n // The wallet module says it; a federation without one (Lightning and ecash only) says it through its Lightning module.\n const network = modules.filter((m) => m?.kind === \"wallet\").map(moduleNetwork).find(Boolean) ?? modules.filter((m) => m?.kind === \"ln\").map(moduleNetwork).find(Boolean);\n const version = c?.global?.consensus_version;\n return {\n federationId,\n name: text(c?.global?.meta?.federation_name, 80),\n guardians: endpoints.slice(0, 64).map(([peer, e]) => ({ name: text(e?.name, 80) || `Guardian ${peer}`, url: text(e?.url, 300) ?? \"\" })),\n consensusVersion: `${Number(version?.major ?? 0)}.${Number(version?.minor ?? 0)}`,\n network,\n modules: [...new Set(modules.map((m) => (typeof m?.kind === \"string\" ? m.kind.slice(0, 32) : \"\")).filter(Boolean))],\n welcome: text(c?.global?.meta?.welcome_message, 280),\n };\n}\n\n/** The first value a subscription gives that is final, or the last one within `waitMs`. */\nfunction watch<S>(subscribe: (onState: (state: S) => void, onError: (error: string) => void) => () => void, final: (state: S) => boolean, waitMs: number): Promise<S | undefined> {\n return new Promise((resolve) => {\n let last: S | undefined, done = false, cancel: () => void = () => {};\n const finish = () => { if (done) return; done = true; clearTimeout(timer); cancel(); resolve(last); };\n const timer = setTimeout(finish, waitMs);\n cancel = subscribe((state) => { last = state; if (final(state)) finish(); }, () => finish());\n });\n}\n\nconst SPEND: Record<string, SpendState> = { Created: \"created\", UserCanceledProcessing: \"canceling\", UserCanceledSuccess: \"canceled\", UserCanceledFailure: \"taken\", Success: \"taken\", Refunded: \"refunded\" };\nconst spendFinal = (s: SpendState) => s === \"canceled\" || s === \"taken\" || s === \"refunded\";\nconst REDEEM: Record<string, RedeemState> = { Created: \"created\", Issuing: \"issuing\", Done: \"done\" };\nconst receiveOf = (s: unknown): ReceiveState => s === \"claimed\" ? \"claimed\" : s === \"funded\" || s === \"awaiting_funds\" ? \"funded\" : typeof s === \"object\" && s && \"canceled\" in s ? \"canceled\" : \"open\";\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- the SDK's own types are loose unions of JSON; this is where they are narrowed. */\nclass RealClient implements FedimintClient {\n constructor(readonly federationId: string, private wallet: any, private director: any, private end: () => void) {}\n async info() { return federationInfo(this.federationId, await this.wallet.federation.getConfig()); }\n balance(): Promise<number> { return this.wallet.balance.getBalance(); }\n onBalance(listener: (msats: number) => void) { return this.wallet.balance.subscribeBalance(listener, () => {}); }\n async parseNotes(notes: string): Promise<ParsedNotes> {\n const parsed = await this.director.parseOobNotes(notes);\n return { amountMsats: Number(parsed.total_amount), federationIdPrefix: String(parsed.federation_id_prefix), federationId: parsed.federation_id ?? undefined };\n }\n async spend(amountMsats: number, { cancelAfterSecs, ghostly }: { cancelAfterSecs: number; ghostly: string }) {\n const { notes, operation_id } = await this.wallet.mint.spendNotes(amountMsats, cancelAfterSecs, false, { ghostly });\n return { notes: String(notes), operationId: String(operation_id) };\n }\n cancelSpend(operationId: string): Promise<void> { return this.wallet.mint.tryCancelSpendNotes(operationId); }\n spendState(operationId: string, waitMs: number) {\n return watch<SpendState>((ok, fail) => this.wallet.mint.subscribeSpendNotes(operationId, (s: string) => ok(SPEND[s] ?? \"created\"), fail), spendFinal, waitMs);\n }\n redeem(notes: string, ghostly: string): Promise<string> { return this.wallet.mint.reissueExternalNotes(notes, { ghostly }); }\n redeemState(operationId: string, waitMs: number) {\n return watch<RedeemState>((ok, fail) => this.wallet.mint.subscribeReissueExternalNotes(operationId, (s: unknown) => ok(typeof s === \"string\" ? REDEEM[s] ?? \"created\" : s && typeof s === \"object\" && \"Failed\" in s ? \"failed\" : \"created\"), (error: string) => { ok(\"failed\"); fail(error); }), (s) => s === \"done\" || s === \"failed\", waitMs);\n }\n async createInvoice(amountMsats: number, description: string, expirySecs: number, ghostly: string) {\n const { operation_id, invoice } = await this.wallet.lightning.createInvoice(amountMsats, description, expirySecs, undefined, { ghostly });\n return { operationId: String(operation_id), invoice: String(invoice) };\n }\n receiveState(operationId: string, waitMs: number) {\n return watch<ReceiveState>((ok, fail) => this.wallet.lightning.subscribeLnReceive(operationId, (s: unknown) => ok(receiveOf(s)), fail), (s) => s === \"claimed\" || s === \"canceled\", waitMs);\n }\n async gatewayFee(amountMsats: number) {\n await this.wallet.lightning.updateGatewayCache().catch(() => {});\n const gateway = await this.wallet.lightning.getAvailableGateway().catch(() => null);\n const fees = gateway?.fees;\n if (!fees) return undefined;\n return Number(fees.base_msat ?? 0) + Math.ceil(amountMsats * Number(fees.proportional_millionths ?? 0) / 1_000_000);\n }\n async payInvoice(invoice: string, ghostly: string) {\n const paid = await this.wallet.lightning.payInvoice(invoice, undefined, { ghostly });\n const internal = \"internal\" in paid.payment_type;\n return { operationId: String(internal ? paid.payment_type.internal : paid.payment_type.lightning), feeMsats: Number(paid.fee ?? 0), internal };\n }\n async payState(operationId: string, internal: boolean, waitMs: number): Promise<PayOutcome> {\n const state = await watch<unknown>((ok, fail) => internal ? this.wallet.lightning.subscribeInternalPayment(operationId, ok, fail) : this.wallet.lightning.subscribeLnPay(operationId, ok, fail),\n (s) => typeof s === \"object\" && !!s && (\"success\" in s || \"preimage\" in s || \"refunded\" in s || \"refund_success\" in s || \"unexpected_error\" in s || \"funding_failed\" in s) || s === \"canceled\", waitMs);\n if (state && typeof state === \"object\") {\n const s = state as Record<string, any>;\n // A payment out through the gateway ends in `success`; one to the same federation (internal) in `preimage` (bytes).\n if (s.success?.preimage || s.preimage) return { state: \"paid\", preimage: hex(s.success?.preimage ?? s.preimage) };\n if (\"refunded\" in s || \"refund_success\" in s || \"funding_failed\" in s) return { state: \"failed\", error: errorText(s.refunded?.gateway_error ?? s.funding_failed?.error ?? \"The gateway gave the payment back\") };\n if (\"unexpected_error\" in s) return { state: \"pending\", error: errorText(s.unexpected_error) };\n }\n if (state === \"canceled\") return { state: \"failed\", error: \"Canceled before it was funded\" };\n return { state: \"pending\" };\n }\n async operations(limit: number): Promise<FedimintOperation[]> {\n const list: [{ creation_time?: { secs_since_epoch?: number }; operation_id?: string }, { operation_module_kind?: string; meta?: any; outcome?: any }][] = await this.wallet.federation.listOperations(limit);\n return list.map(([key, log]) => {\n const variant = log.meta?.variant && typeof log.meta.variant === \"object\" ? Object.keys(log.meta.variant)[0] : undefined;\n const detail = variant ? log.meta.variant[variant] : undefined;\n return {\n id: String(key.operation_id ?? \"\"), kind: String(log.operation_module_kind ?? \"\"), variant,\n createdAt: Number(key.creation_time?.secs_since_epoch ?? 0) * 1000,\n amountMsats: typeof log.meta?.amount === \"number\" ? log.meta.amount : typeof detail?.requested_amount === \"number\" ? detail.requested_amount : undefined,\n outcome: log.outcome?.outcome, ghostly: typeof log.meta?.extra_meta?.ghostly === \"string\" ? log.meta.extra_meta.ghostly : undefined,\n invoice: typeof detail?.invoice === \"string\" ? detail.invoice : undefined,\n feeMsats: typeof detail?.fee === \"number\" ? detail.fee : undefined,\n };\n });\n }\n async close() { try { await this.wallet.cleanup(); } catch { /* already gone */ } this.end(); }\n}\n\n/**\n * Where the client runs: a worker per database, and the files the databases are. The browser's is a module worker on\n * the origin-private file system (below); a headless Ghostly on Node brings its own (packages/cli/src/runtime/fedimint.ts).\n */\nexport interface FedimintPlatform {\n /** A `@fedimint/types` Transport onto a new worker of its own, which one database will be opened in. */\n transport(): Promise<{ terminate(): void }>;\n exists(database: string): Promise<boolean>;\n /** Deletes a database, and is done when there is none. */\n remove(database: string): Promise<void>;\n}\n\nconst BROWSER: FedimintPlatform = {\n transport: browserTransport,\n async exists(database) {\n const root = await navigator.storage.getDirectory();\n try { const file = await (await root.getFileHandle(database)).getFile(); return file.size > 0; } catch { return false; }\n },\n async remove(database) {\n const root = await navigator.storage.getDirectory();\n await root.removeEntry(database).catch((error: unknown) => { if ((error as { name?: string })?.name !== \"NotFoundError\") throw error; });\n },\n};\n\nlet loading: Promise<FedimintSdk> | undefined;\n/** Loads the SDK on first use: profiles that never join a federation never fetch its 11 MB. */\nexport function loadFedimintSdk(): Promise<FedimintSdk> {\n return loading ??= fedimintSdkOn(BROWSER).catch((error) => { loading = undefined; throw error; });\n}\n\n/** The SDK on a platform's workers and files. */\nexport async function fedimintSdkOn(platform: FedimintPlatform): Promise<FedimintSdk> {\n const { WalletDirector } = await import(\"@fedimint/core\");\n /** A director on its own worker and database, with the mnemonic in place. */\n const director = async (database: string, mnemonic?: string) => {\n const t = await platform.transport();\n const d = new WalletDirector(t as any, database, true);\n try {\n await d.initialize(database);\n if (mnemonic && !(await d.hasMnemonicSet())) await d.setMnemonic(mnemonic.trim().split(/\\s+/));\n return { director: d, end: () => t.terminate() };\n } catch (error) { t.terminate(); throw error; }\n };\n const client = async (database: string, mnemonic: string, start: (wallet: any) => Promise<void>) => {\n const { director: d, end } = await director(database, mnemonic);\n try {\n const wallet = await d.createWallet();\n await start(wallet);\n return new RealClient(await wallet.federation.getFederationId(), wallet, d, end);\n } catch (error) { end(); throw error; }\n };\n const sdk: FedimintSdk = {\n async preview(invite) {\n // A scratch database of its own: previewing joins nothing.\n const database = fedimintDatabase(`preview-${crypto.randomUUID()}`);\n const { director: d, end } = await director(database);\n try { const preview = await d.previewFederation(invite.trim()); return federationInfo(preview.federation_id, preview.config); }\n finally { end(); await sdk.remove(database).catch(() => {}); }\n },\n join: ({ database, mnemonic, invite, recover }) => client(database, mnemonic, async (wallet) => {\n // joinFederation says false instead of throwing; its reason goes to the (silenced) logger.\n if (!(await wallet.joinFederation(invite.trim(), { clientName: CLIENT_NAME, forceRecover: recover }))) throw new Error(\"Could not join the federation: its guardians did not answer, or the invite code is not valid\");\n }),\n open: ({ database, mnemonic }) => client(database, mnemonic, (wallet) => wallet.open(CLIENT_NAME)),\n exists: (database) => platform.exists(database),\n remove: (database) => platform.remove(database),\n };\n return sdk;\n}\n/* eslint-enable @typescript-eslint/no-explicit-any */\n","import { fromBase64Url, irohDescriptor, irohRelayUrl, type BoundChannel, type FrameChannel, type NativeBinding, type NativeEndpoint } from \"@ghostly/core\";\n\n/**\n * Iroh (WISP 102) in a browser: the same QUIC/TLS session and binding as the\n * Desktop's native Iroh, but relay only. A page cannot send UDP, so every\n * packet goes through an Iroh relay over a WebSocket; the relay sees who talks\n * to whom and when, never the frames.\n *\n * The wasm (about 1 MB gzip) loads on the first chat that starts an endpoint,\n * never on the app's first load.\n */\n\n/**\n * n0's public relays, the same ones the Desktop's Iroh homes on by default. Iroh itself names them with the trailing\n * dot of a full domain name (`use1-1.relay.n0.iroh.link.`), and compares relay URLs as text; here they have none,\n * because WebKit opens no host name that ends in a dot. `BROWSER_SPELLING` is what keeps the two one relay.\n */\nexport const DEFAULT_IROH_RELAYS = [\n \"https://use1-1.relay.n0.iroh.link/\",\n \"https://euc1-1.relay.n0.iroh.link/\",\n \"https://aps1-1.relay.n0.iroh.link/\",\n \"https://usw1-1.relay.n0.iroh.link/\",\n] as const;\n\n/**\n * The spelling of every relay URL handed to the wasm Iroh (`irohRelayUrl`): the relays it homes on and a contact's\n * relay before a dial. One spelling, so a contact on this endpoint's relay is reached over the connection already\n * open to it; without the dot, so Safari can open it (a Desktop's record names its relay with the dot).\n */\nconst BROWSER_SPELLING = \"plain\";\n\n/**\n * Why a relay URL cannot be used, or null. TLS, except on this machine (a local `iroh-relay --dev`): a page\n * may not open plain WebSockets elsewhere, and the relay's operator would see traffic metadata in the clear.\n */\nexport function irohRelayProblem(value: string): string | null {\n let url: URL;\n try { url = new URL(value); } catch { return `Not a relay address: ${value}`; }\n const local = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname);\n if (url.protocol !== \"https:\" && !(url.protocol === \"http:\" && local)) return `Use an https:// relay address: ${value}`;\n if (url.username || url.password || url.search || url.hash || value.length > 256) return `Not a relay address: ${value}`;\n return null;\n}\n\n/** A browser endpoint's descriptor: no direct addresses, only its relay. */\nexport interface IrohWebDescriptor { id: string; relay: string | null; addresses: string[] }\n\ninterface WasmConn {\n binding(): NativeBinding;\n send(text: string): Promise<void>;\n recv(): Promise<string | undefined>;\n rtt(): number;\n close(): void;\n free(): void;\n}\ninterface WasmNode {\n address(): IrohWebDescriptor;\n connect(descriptor: unknown, timeoutMs: number): Promise<WasmConn>;\n accept(): Promise<WasmConn | undefined>;\n close(): Promise<void>;\n free(): void;\n}\nexport interface IrohWasm {\n IrohNode: { start(seed: Uint8Array, relays: string[], onlineMs: number): Promise<WasmNode> };\n}\n\nlet loaded: Promise<IrohWasm> | null = null;\n/** Loads the wasm once per page. Tests pass their own `load` (Node reads the file). */\nexport function loadIrohWasm(): Promise<IrohWasm> {\n loaded ??= import(\"@ghostly/iroh-web\").then(async module => {\n await module.default();\n return module as unknown as IrohWasm;\n }).catch(error => { loaded = null; throw error; });\n return loaded;\n}\n\nexport interface IrohWebOptions {\n /** Relay URLs, first preferred. Default: n0's public relays. */\n relays?: readonly string[];\n /** How long to wait for the first relay to answer. */\n onlineMs?: number;\n connectMs?: number;\n load?: () => Promise<IrohWasm>;\n}\n\nconst SEND_BUDGET = 120 * 1024;\nconst MAX_FRAME = 60 * 1024;\n\n/** One paired channel over one QUIC stream. Sends are queued in order. */\nclass IrohWebChannel implements FrameChannel {\n private reader: FrameChannel[\"onMessage\"] = null;\n private buffered: string[] = [];\n private queue = Promise.resolve();\n private closed = false;\n bufferedAmount = 0;\n readonly sendBudget = SEND_BUDGET;\n onClose: FrameChannel[\"onClose\"] = null;\n constructor(private conn: WasmConn, private forget: () => void) { void this.read(); }\n get onMessage() { return this.reader; }\n set onMessage(reader: FrameChannel[\"onMessage\"]) {\n this.reader = reader;\n if (reader) for (const text of this.buffered.splice(0)) reader(text);\n }\n private async read() {\n try {\n for (;;) {\n const text = await this.conn.recv();\n if (text === undefined || this.closed) break;\n if (this.reader) this.reader(text);\n else if (this.buffered.length < 64) this.buffered.push(text);\n else break;\n }\n } catch { /* A broken stream ends the channel. */ }\n this.close();\n }\n send(data: string | Uint8Array) {\n if (typeof data !== \"string\" || this.closed || !data.length || data.length > MAX_FRAME || this.bufferedAmount + data.length > SEND_BUDGET)\n throw new Error(\"Iroh channel is closed or its send budget is full\");\n this.bufferedAmount += data.length;\n this.queue = this.queue.then(() => this.closed ? undefined : this.conn.send(data))\n .catch(() => this.close()).finally(() => { this.bufferedAmount -= data.length; });\n }\n drained() { return this.queue; }\n get rttMs() { const rtt = this.conn.rtt(); return rtt >= 0 ? rtt : undefined; }\n close() {\n if (this.closed) return;\n this.closed = true; this.buffered = [];\n try { this.conn.close(); } catch { /* Already gone. */ }\n this.forget();\n this.onClose?.();\n }\n}\n\n/** A NativeEndpoint for `iroh/1`, as the Desktop's is, for one chat's transport seed. */\nexport async function createIrohWebEndpoint(seedB64: string, options: IrohWebOptions = {}): Promise<NativeEndpoint> {\n const seed = fromBase64Url(seedB64);\n if (seed.length !== 32) throw new Error(\"Invalid transport seed\");\n // Either spelling of a relay (with or without the trailing dot) homes on it under the one a browser opens.\n const relays = [...new Set((options.relays?.length ? options.relays : DEFAULT_IROH_RELAYS).map(relay => irohRelayUrl(relay, BROWSER_SPELLING)))];\n const wasm = await (options.load ?? loadIrohWasm)();\n const node = await wasm.IrohNode.start(seed, relays, options.onlineMs ?? 10_000);\n const channels = new Set<IrohWebChannel>();\n let stopped = false;\n let handler: NativeEndpoint[\"onConnection\"] = null;\n const early: BoundChannel[] = [];\n const bind = (conn: WasmConn): BoundChannel => {\n const channel: IrohWebChannel = new IrohWebChannel(conn, () => channels.delete(channel));\n channels.add(channel);\n return { channel, binding: conn.binding() };\n };\n const endpoint: NativeEndpoint = {\n transport: \"iroh/1\",\n // Relay only: the rank puts it after every direct path (WISP 100, \"Relayed\").\n descriptor: { ...node.address(), relayed: true },\n onDescriptor: null,\n get onConnection() { return handler; },\n set onConnection(value) { handler = value; if (value) for (const bound of early.splice(0)) value(bound); },\n async connect(descriptor) {\n if (stopped) throw new Error(\"Iroh endpoint is stopped\");\n const conn = await node.connect(irohDescriptor(descriptor, BROWSER_SPELLING), options.connectMs ?? 20_000);\n if (stopped) { conn.close(); throw new Error(\"Iroh endpoint is stopped\"); }\n return bind(conn);\n },\n async close() {\n if (stopped) return;\n stopped = true;\n for (const channel of [...channels]) channel.close();\n for (const bound of early.splice(0)) bound.channel.close();\n await node.close().catch(() => {});\n },\n };\n void (async () => {\n while (!stopped) {\n let conn: WasmConn | undefined;\n try { conn = await node.accept(); } catch { break; }\n if (!conn) break;\n if (stopped) { conn.close(); break; }\n const bound = bind(conn);\n const accept = endpoint.onConnection;\n if (accept) accept(bound); else if (early.length < 4) early.push(bound); else bound.channel.close();\n }\n // The relay went away for good (or the endpoint was closed): stop offering Iroh.\n if (!stopped) { endpoint.onUnavailable?.(); await endpoint.close(); }\n })();\n return endpoint;\n}\n"],"mappings":"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"}
@@ -1,9 +1,4 @@
1
- import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
1
  //#region ../browser/src/engine/joinTrace.ts
3
- var joinTrace_exports = /* @__PURE__ */ __exportAll({
4
- setJoinTraceSink: () => setJoinTraceSink,
5
- traceJoin: () => traceJoin
6
- });
7
2
  var sink = null;
8
3
  /** Where the lines go besides the console; `null` removes it. */
9
4
  function setJoinTraceSink(next) {
@@ -24,6 +19,6 @@ function traceJoin(group, step, detail) {
24
19
  } catch {}
25
20
  }
26
21
  //#endregion
27
- export { traceJoin as n, joinTrace_exports as t };
22
+ export { traceJoin as n, setJoinTraceSink as t };
28
23
 
29
- //# sourceMappingURL=joinTrace-CO8mlhNO.js.map
24
+ //# sourceMappingURL=joinTrace-CjRDFy85.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"joinTrace-CO8mlhNO.js","names":[],"sources":["../../../browser/src/engine/joinTrace.ts"],"sourcesContent":["/**\n * Timestamps along a join through a group's link, for measuring where the time goes. Silent unless\n * the page sets `globalThis.__ghostlyJoinTrace` (the e2e measurement does): each step is then one\n * `console.debug` line, `[ghostly:join] {\"t\":…,\"g\":…,\"step\":…}`, read back by the test. A host may also\n * install a sink (the CLI writes the lines beside its link trace), which gets every line whether or not\n * the global is set.\n */\ntype JoinTraceSink = (line: string) => void;\nlet sink: JoinTraceSink | null = null;\n\n/** Where the lines go besides the console; `null` removes it. */\nexport function setJoinTraceSink(next: JoinTraceSink | null): void {\n sink = next;\n}\n\nexport function traceJoin(group: string, step: string, detail?: Record<string, unknown>): void {\n const console_ = (globalThis as { __ghostlyJoinTrace?: boolean }).__ghostlyJoinTrace;\n if (!console_ && !sink) return;\n const line = JSON.stringify({ t: Date.now(), g: group, step, ...detail });\n if (console_) console.debug(`[ghostly:join] ${line}`);\n try { sink?.(line); } catch { /* a sink that fails must not fail the join */ }\n}\n"],"mappings":";;;;;;AAQA,IAAI,OAA6B;;AAGjC,SAAgB,iBAAiB,MAAkC;CACjE,OAAO;AACT;AAEA,SAAgB,UAAU,OAAe,MAAc,QAAwC;CAC7F,MAAM,WAAY,WAAgD;CAClE,IAAI,CAAC,YAAY,CAAC,MAAM;CACxB,MAAM,OAAO,KAAK,UAAU;EAAE,GAAG,KAAK,IAAI;EAAG,GAAG;EAAO;EAAM,GAAG;CAAO,CAAC;CACxE,IAAI,UAAU,QAAQ,MAAM,kBAAkB,MAAM;CACpD,IAAI;EAAE,OAAO,IAAI;CAAG,QAAQ,CAAiD;AAC/E"}
1
+ {"version":3,"file":"joinTrace-CjRDFy85.js","names":[],"sources":["../../../browser/src/engine/joinTrace.ts"],"sourcesContent":["/**\n * Timestamps along a join through a group's link, for measuring where the time goes. Silent unless\n * the page sets `globalThis.__ghostlyJoinTrace` (the e2e measurement does): each step is then one\n * `console.debug` line, `[ghostly:join] {\"t\":…,\"g\":…,\"step\":…}`, read back by the test. A host may also\n * install a sink (the CLI writes the lines beside its link trace), which gets every line whether or not\n * the global is set.\n */\ntype JoinTraceSink = (line: string) => void;\nlet sink: JoinTraceSink | null = null;\n\n/** Where the lines go besides the console; `null` removes it. */\nexport function setJoinTraceSink(next: JoinTraceSink | null): void {\n sink = next;\n}\n\nexport function traceJoin(group: string, step: string, detail?: Record<string, unknown>): void {\n const console_ = (globalThis as { __ghostlyJoinTrace?: boolean }).__ghostlyJoinTrace;\n if (!console_ && !sink) return;\n const line = JSON.stringify({ t: Date.now(), g: group, step, ...detail });\n if (console_) console.debug(`[ghostly:join] ${line}`);\n try { sink?.(line); } catch { /* a sink that fails must not fail the join */ }\n}\n"],"mappings":";AAQA,IAAI,OAA6B;;AAGjC,SAAgB,iBAAiB,MAAkC;CACjE,OAAO;AACT;AAEA,SAAgB,UAAU,OAAe,MAAc,QAAwC;CAC7F,MAAM,WAAY,WAAgD;CAClE,IAAI,CAAC,YAAY,CAAC,MAAM;CACxB,MAAM,OAAO,KAAK,UAAU;EAAE,GAAG,KAAK,IAAI;EAAG,GAAG;EAAO;EAAM,GAAG;CAAO,CAAC;CACxE,IAAI,UAAU,QAAQ,MAAM,kBAAkB,MAAM;CACpD,IAAI;EAAE,OAAO,IAAI;CAAG,QAAQ,CAAiD;AAC/E"}