@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.
- package/README.md +25 -9
- package/dist/assets/api-Dq2QXQDk.js +4172 -0
- package/dist/assets/api-Dq2QXQDk.js.map +1 -0
- package/dist/assets/api-TiB5QSG9.js +3 -0
- package/dist/assets/apiKit-CA2x5GJF.js +200 -0
- package/dist/assets/apiKit-CA2x5GJF.js.map +1 -0
- package/dist/assets/apps-BxV9i_Y4.js +786 -0
- package/dist/assets/apps-BxV9i_Y4.js.map +1 -0
- package/dist/assets/apps-C-T8jeji.js +2 -0
- package/dist/assets/{arkade-CdnKGDbg.js → arkade-C_qDmHnm.js} +2 -2
- package/dist/assets/{arkade-CdnKGDbg.js.map → arkade-C_qDmHnm.js.map} +1 -1
- package/dist/assets/backup-Bugnn3Ue.js +2 -0
- package/dist/assets/backup-CnMN2oUK.js +731 -0
- package/dist/assets/backup-CnMN2oUK.js.map +1 -0
- package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
- package/dist/assets/bitcoind-CZchoh43.js +349 -0
- package/dist/assets/bitcoind-CZchoh43.js.map +1 -0
- package/dist/assets/bitcoindProvider-DGw3xBSa.js +97 -0
- package/dist/assets/bitcoindProvider-DGw3xBSa.js.map +1 -0
- package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -1
- package/dist/assets/buttons-Bj7Vf7Pn.js +2 -0
- package/dist/assets/buttons-CvNBzh2S.js +496 -0
- package/dist/assets/buttons-CvNBzh2S.js.map +1 -0
- package/dist/assets/{endpoint-pgFkR2AS.js → endpoint-BOTFMpfS.js} +56 -18
- package/dist/assets/endpoint-BOTFMpfS.js.map +1 -0
- package/dist/assets/envelope-WjingYSc.js +90 -0
- package/dist/assets/envelope-WjingYSc.js.map +1 -0
- package/dist/assets/errors-DO5z6h_l.js +105 -0
- package/dist/assets/errors-DO5z6h_l.js.map +1 -0
- package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -1
- package/dist/assets/{fileBytes-Dbifu5I5.js → fileBytes-CfSXjTFa.js} +2 -2
- package/dist/assets/{fileBytes-Dbifu5I5.js.map → fileBytes-CfSXjTFa.js.map} +1 -1
- package/dist/assets/fileBytes-CgbibpbV.js +2 -0
- package/dist/assets/{fileBytes-BcYZhNgF.js → fileBytes-Di7VZtmU.js} +6 -19
- package/dist/assets/fileBytes-Di7VZtmU.js.map +1 -0
- package/dist/assets/fileBytesIdb-CFP-DSkZ.js +2 -0
- package/dist/assets/{fileBytesIdb-DzSaqI0A.js → fileBytesIdb-D5u6H8X_.js} +4 -4
- package/dist/assets/{fileBytesIdb-DzSaqI0A.js.map → fileBytesIdb-D5u6H8X_.js.map} +1 -1
- package/dist/assets/{fileBytesOpfs-Dx8RZiV5.js → fileBytesOpfs-Dd1N4ERH.js} +3 -3
- package/dist/assets/{fileBytesOpfs-Dx8RZiV5.js.map → fileBytesOpfs-Dd1N4ERH.js.map} +1 -1
- package/dist/assets/groupFiles-IEUW3DQz.js +3202 -0
- package/dist/assets/groupFiles-IEUW3DQz.js.map +1 -0
- package/dist/assets/host-CWt-jtqz.js +4673 -0
- package/dist/assets/host-CWt-jtqz.js.map +1 -0
- package/dist/assets/hyperdhtRelay-BT_wRxab.js +21 -0
- package/dist/assets/hyperdhtRelay-BT_wRxab.js.map +1 -0
- package/dist/assets/{hyperdhtRelay-C1p0nkle.js → hyperdhtRelay-CPYwSLLS.js} +25 -10
- package/dist/assets/hyperdhtRelay-CPYwSLLS.js.map +1 -0
- package/dist/assets/{idb-CTF6LF5B.js → idb-B5L8wuOm.js} +7 -5
- package/dist/assets/idb-B5L8wuOm.js.map +1 -0
- package/dist/assets/irohWeb-4oChIOIp.js +544 -0
- package/dist/assets/irohWeb-4oChIOIp.js.map +1 -0
- package/dist/assets/{joinTrace-CO8mlhNO.js → joinTrace-CjRDFy85.js} +2 -7
- package/dist/assets/{joinTrace-CO8mlhNO.js.map → joinTrace-CjRDFy85.js.map} +1 -1
- package/dist/assets/joinTrace-D-fnt2p3.js +2 -0
- package/dist/assets/main-DbqcdShN.js +4409 -0
- package/dist/assets/main-DbqcdShN.js.map +1 -0
- package/dist/assets/{mainline-BH6s8HVi.js → mainline-ZkiOnevs.js} +11 -6
- package/dist/assets/mainline-ZkiOnevs.js.map +1 -0
- package/dist/assets/mints-BrlY773j.js +74 -0
- package/dist/assets/mints-BrlY773j.js.map +1 -0
- package/dist/assets/mints-CqTdA4Gk.js +2 -0
- package/dist/assets/networkReason-CetKkTPo.js +154 -0
- package/dist/assets/networkReason-CetKkTPo.js.map +1 -0
- package/dist/assets/nwc-CgkqyvsE.js +612 -0
- package/dist/assets/nwc-CgkqyvsE.js.map +1 -0
- package/dist/assets/nwcProvider-4eVz_8HP.js +77 -0
- package/dist/assets/nwcProvider-4eVz_8HP.js.map +1 -0
- package/dist/assets/privateFolder-Bk9C9oFv.js +38 -0
- package/dist/assets/privateFolder-Bk9C9oFv.js.map +1 -0
- package/dist/assets/profiles-BHc9pEYg.js +139 -0
- package/dist/assets/profiles-BHc9pEYg.js.map +1 -0
- package/dist/assets/registry-DlAKO0Fy.js +3793 -0
- package/dist/assets/registry-DlAKO0Fy.js.map +1 -0
- package/dist/assets/registry-OBJBnHVQ.js +2 -0
- package/dist/assets/resolve-CouHhCpJ.js +591 -0
- package/dist/assets/resolve-CouHhCpJ.js.map +1 -0
- package/dist/assets/{restoredRows-C_84q0Pd.js → restoredRows-Db3oUTko.js} +2 -2
- package/dist/assets/{restoredRows-C_84q0Pd.js.map → restoredRows-Db3oUTko.js.map} +1 -1
- package/dist/assets/{server-0zfNpABQ.js → server-Ss0gJ-nw.js} +1275 -1404
- package/dist/assets/server-Ss0gJ-nw.js.map +1 -0
- package/dist/assets/{src-CbLkHfz8.js → src-DDzSERyc.js} +882 -134
- package/dist/assets/src-DDzSERyc.js.map +1 -0
- package/dist/assets/turnRefs-B87dtrHM.js +323 -0
- package/dist/assets/turnRefs-B87dtrHM.js.map +1 -0
- package/dist/assets/types-C4_KafL_.js +122 -0
- package/dist/assets/types-C4_KafL_.js.map +1 -0
- package/dist/assets/{usdt-Qf4UuygS.js → usdt-Fj8CQO2z.js} +3 -3
- package/dist/assets/{usdt-Qf4UuygS.js.map → usdt-Fj8CQO2z.js.map} +1 -1
- package/dist/ghostly.mjs +4 -22077
- package/dist/ghostly.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/assets/endpoint-pgFkR2AS.js.map +0 -1
- package/dist/assets/fileBytes-BcYZhNgF.js.map +0 -1
- package/dist/assets/hyperdhtRelay-C1p0nkle.js.map +0 -1
- package/dist/assets/idb-CTF6LF5B.js.map +0 -1
- package/dist/assets/mainline-BH6s8HVi.js.map +0 -1
- package/dist/assets/rolldown-runtime-8H4AJuhK.js +0 -14
- package/dist/assets/server-0zfNpABQ.js.map +0 -1
- package/dist/assets/src-CbLkHfz8.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Jc as RelayTransport, al as newerPacket, dl as publicKeyFromZ32, el as isDiscoveryBudgetError, sl as parseRelayPayload } from "./src-DDzSERyc.js";
|
|
2
2
|
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import DHT from "bittorrent-dht";
|
|
@@ -115,17 +115,22 @@ var RelaysAndDht = class {
|
|
|
115
115
|
this.relays = new RelayTransport(relays);
|
|
116
116
|
}
|
|
117
117
|
async publish(identity, records, options = {}) {
|
|
118
|
-
await this.relays.publishDated(identity, records, (key, payload,
|
|
118
|
+
await this.relays.publishDated(identity, records, (key, payload, timestamp, conflict) => this.putBoth(key, payload, options, conflict, timestamp), options, false, this.dht !== null);
|
|
119
119
|
}
|
|
120
120
|
publishPayload(pubKeyZ32, payload, options = {}) {
|
|
121
121
|
return this.putBoth(pubKeyZ32, payload, options);
|
|
122
122
|
}
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
/**
|
|
124
|
+
* `timestamp`: the payload was just signed here (`publish`), dated so; its signature is not checked again on the way
|
|
125
|
+
* to the relays. Without it (`publishPayload`, signed elsewhere) the relays' side checks it to read its date.
|
|
126
|
+
*/
|
|
127
|
+
async putBoth(pubKeyZ32, payload, options, conflict, timestamp) {
|
|
128
|
+
const toRelays = () => timestamp === void 0 ? this.relays.publishPayload(pubKeyZ32, payload, options, conflict) : this.relays.putSigned(pubKeyZ32, payload, timestamp, options, conflict);
|
|
129
|
+
if (!this.dht) return toRelays();
|
|
125
130
|
const onDht = this.dht.put(pubKeyZ32, payload);
|
|
126
131
|
onDht.catch(() => {});
|
|
127
132
|
try {
|
|
128
|
-
await
|
|
133
|
+
await toRelays();
|
|
129
134
|
} catch (error) {
|
|
130
135
|
if (isDiscoveryBudgetError(error)) throw error;
|
|
131
136
|
await onDht.catch(() => {
|
|
@@ -190,4 +195,4 @@ var RelaysAndDht = class {
|
|
|
190
195
|
//#endregion
|
|
191
196
|
export { Mainline, RelaysAndDht };
|
|
192
197
|
|
|
193
|
-
//# sourceMappingURL=mainline-
|
|
198
|
+
//# sourceMappingURL=mainline-ZkiOnevs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mainline-ZkiOnevs.js","names":[],"sources":["../../src/runtime/mainline.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport DHT from \"bittorrent-dht\";\nimport { ed25519 } from \"@noble/curves/ed25519.js\";\nimport {\n RelayTransport, isDiscoveryBudgetError, newerPacket, parseRelayPayload, publicKeyFromZ32,\n type DiscoveryStatus, type GhostRecord, type Identity, type PkarrRequestOptions, type PkarrTransport, type RelayTransportOptions, type SignedPacket,\n} from \"@ghostly/core\";\n\n/**\n * Pkarr over the Mainline DHT (BEP44), from Node, as the Desktop reaches it from Rust: the relays are HTTP bridges to\n * this same DHT, so a headless Ghostly that speaks it directly keeps finding its contacts while the relays fail (they\n * answered HTTP 500 for more than ten minutes on 2026-09-27, and a group's edges stayed down all that while).\n *\n * A Pkarr packet is a BEP44 mutable item: the key is the Ed25519 public key, the value the DNS packet, the sequence\n * number its timestamp in microseconds, and the signature the one the relay payload already carries (over\n * `3:seqi<seq>e1:v<len>:<v>`). So one signed payload goes to the relays and to the DHT unchanged.\n */\n\nexport interface MainlineOptions {\n /** `host:port` nodes to start from; the public routers when absent; none with `false` (a testnet's first node). */\n bootstrap?: string[] | false;\n /** Address to bind (a loopback testnet binds 127.0.0.1). */\n host?: string;\n /** How long a lookup may take before it is given up, in ms. */\n timeoutMs?: number;\n}\n\nconst SIGNATURE = 64, TIMESTAMP = 8;\nconst verify = (sig: Uint8Array, message: Uint8Array, key: Uint8Array) => { try { return ed25519.verify(sig, message, key); } catch { return false; } };\n\n/** A DHT node of this process: puts and gets Pkarr packets. */\nexport class Mainline {\n private readonly dht: DHT;\n /** Its UDP socket is bound. */\n readonly listening: Promise<void>;\n private readonly timeoutMs: number;\n\n constructor(options: MainlineOptions = {}) {\n this.timeoutMs = options.timeoutMs ?? 20_000;\n const bootstrap = options.bootstrap === false ? false : options.bootstrap?.length ? options.bootstrap : undefined;\n this.dht = new DHT({ verify, ...(bootstrap !== undefined ? { bootstrap } : {}), ...(options.host ? { host: options.host } : {}) });\n this.listening = new Promise((resolve) => this.dht.listen(0, options.host, () => resolve()));\n // A node of the table that errors must not end the process.\n this.dht.on(\"error\", () => {});\n }\n\n /** Adds a node (a testnet's peers). */\n addNode(host: string, port: number): void { this.dht.addNode({ host, port }); }\n address(): { port: number } { return this.dht.address() as { port: number }; }\n\n /** Puts a relay payload (`<signature><timestamp><DNS packet>`) as the mutable item of `pubKeyZ32`; resolves with the nodes that took it. */\n put(pubKeyZ32: string, payload: Uint8Array): Promise<number> {\n const k = Buffer.from(publicKeyFromZ32(pubKeyZ32));\n const sig = Buffer.from(payload.subarray(0, SIGNATURE));\n const seq = Number(new DataView(payload.buffer, payload.byteOffset + SIGNATURE, TIMESTAMP).getBigUint64(0));\n const v = Buffer.from(payload.subarray(SIGNATURE + TIMESTAMP));\n return this.bounded(new Promise((resolve, reject) => {\n this.dht.put({ k, v, seq, sig }, (error, _hash, nodes) => {\n if (error) reject(error); else if (!nodes) reject(new Error(\"No DHT node took the packet\")); else resolve(nodes);\n });\n }));\n }\n\n /** The newest packet of `pubKeyZ32` the DHT holds, checked; null when nobody has one. */\n get(pubKeyZ32: string): Promise<SignedPacket | null> {\n const k = Buffer.from(publicKeyFromZ32(pubKeyZ32));\n // A mutable item without salt is found under the SHA-1 of its public key (BEP44).\n const target = createHash(\"sha1\").update(k).digest();\n return this.bounded(new Promise((resolve, reject) => {\n this.dht.get(target, { cache: false }, (error, found) => {\n if (error) return reject(error);\n if (!found?.v || !found.sig) return resolve(null);\n const timestamp = new Uint8Array(TIMESTAMP);\n new DataView(timestamp.buffer).setBigUint64(0, BigInt(found.seq));\n const payload = new Uint8Array(SIGNATURE + TIMESTAMP + found.v.length);\n payload.set(found.sig, 0); payload.set(timestamp, SIGNATURE); payload.set(found.v, SIGNATURE + TIMESTAMP);\n try { resolve(parseRelayPayload(pubKeyZ32, payload)); } catch { resolve(null); }\n });\n }));\n }\n\n private bounded<T>(work: Promise<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(\"DHT lookup timed out\")), this.timeoutMs);\n work.then((value) => { clearTimeout(timer); resolve(value); }, (error) => { clearTimeout(timer); reject(error); });\n });\n }\n\n destroy(): Promise<void> { return new Promise((resolve) => this.dht.destroy(() => resolve())); }\n}\n\n/**\n * The relays first, as the web app reaches Pkarr (its contacts read only the relays), and the DHT beside them: every\n * packet also goes to the DHT, and a read goes there when every relay is failing or the read failed for anything but\n * this app's own request budget (that is a wait, not an outage).\n *\n * `pinned`: the relays given are the only ones (`GHOSTLY_PKARR_RELAYS`, a private network): the profile's `relays`\n * setting does not replace them. With no DHT (`dht` null), the relays alone carry every packet.\n */\nexport class RelaysAndDht implements PkarrTransport {\n readonly relays: RelayTransport;\n private lastVia: \"relay\" | \"dht\" = \"relay\";\n\n constructor(private readonly dht: Mainline | null, relays: RelayTransportOptions = {}, private readonly pinned = false) {\n this.relays = new RelayTransport(relays);\n }\n\n async publish(identity: Identity, records: GhostRecord[], options: PkarrRequestOptions = {}): Promise<void> {\n // BEP44 sequence numbers must strictly increase, on the relays and on the DHT alike: one payload for both, dated\n // past every packet put or read under the key (`RelayTransport.publishDated`), whichever clock dated those.\n // With the DHT, the packet goes there whatever the relays' budgets say: signed every time. Without it (a private\n // network's relays only), a publish none of them takes now is not signed at all.\n await this.relays.publishDated(identity, records, (key, payload, timestamp, conflict) => this.putBoth(key, payload, options, conflict, timestamp), options, false, this.dht !== null);\n }\n\n publishPayload(pubKeyZ32: string, payload: Uint8Array, options: PkarrRequestOptions = {}): Promise<void> {\n return this.putBoth(pubKeyZ32, payload, options);\n }\n\n /**\n * `timestamp`: the payload was just signed here (`publish`), dated so; its signature is not checked again on the way\n * to the relays. Without it (`publishPayload`, signed elsewhere) the relays' side checks it to read its date.\n */\n private async putBoth(pubKeyZ32: string, payload: Uint8Array, options: PkarrRequestOptions, conflict?: (relay: string) => void, timestamp?: bigint): Promise<void> {\n const toRelays = () => timestamp === undefined\n ? this.relays.publishPayload(pubKeyZ32, payload, options, conflict)\n : this.relays.putSigned(pubKeyZ32, payload, timestamp, options, conflict);\n if (!this.dht) return toRelays();\n const onDht = this.dht.put(pubKeyZ32, payload);\n onDht.catch(() => {});\n try {\n await toRelays();\n } catch (error) {\n // Held back by this app's budget: the relays take it when it frees (browser contacts read only them).\n if (isDiscoveryBudgetError(error)) throw error;\n // The relays failed: out on the DHT is out (a Pkarr relay reads the DHT for a key it has not got).\n await onDht.catch(() => { throw error; });\n }\n }\n\n async resolve(pubKeyZ32: string, options: PkarrRequestOptions = {}): Promise<SignedPacket | null> {\n if (!this.dht) return this.relays.resolve(pubKeyZ32, options);\n let fromRelays: SignedPacket | null = null, failed: unknown = null;\n // Every relay left alone for failing: the relays answer from what they last saw, if anything; the DHT is asked.\n const down = () => this.relays.discovery().relays.every((relay) => relay.state === \"failing\");\n if (!down()) {\n try { fromRelays = await this.relays.resolve(pubKeyZ32, options); this.lastVia = \"relay\"; } catch (error) {\n if (isDiscoveryBudgetError(error)) throw error;\n failed = error;\n }\n if (!failed && !down()) return fromRelays;\n }\n try {\n const fromDht = await this.dht.get(pubKeyZ32);\n this.lastVia = \"dht\";\n if (fromDht) this.relays.sawTimestamp(pubKeyZ32, fromDht.timestampMicros);\n return newerPacket(fromRelays, fromDht);\n } catch (error) {\n if (fromRelays) return fromRelays;\n throw failed ?? error;\n }\n }\n\n describe(): { protocol: string; relays: string[] } {\n return { protocol: this.dht ? \"Pkarr relays (HTTP), and the Mainline DHT (BEP44) directly\" : \"Pkarr relays (HTTP)\", relays: this.relays.describe().relays };\n }\n discovery(): DiscoveryStatus {\n const status = this.relays.discovery();\n return this.lastVia === \"dht\" ? { ...status, path: { via: \"dht\" } } : status;\n }\n subscribe(listener: Parameters<RelayTransport[\"subscribe\"]>[0]): () => void { return this.relays.subscribe(listener); }\n /** A relay's answer only: a packet the DHT hands back when the relays are down may be older than one already read. */\n readAnsweredAt(pubKeyZ32: string): number | undefined { return this.relays.readAnsweredAt(pubKeyZ32); }\n readHeldBackFor(pubKeyZ32: string): number | undefined { return this.relays.readHeldBackFor(pubKeyZ32); }\n onServerTime(listener: Parameters<RelayTransport[\"onServerTime\"]>[0]): () => void { return this.relays.onServerTime(listener); }\n configure({ relays }: { relays: string[]; readRelays: boolean }): void { if (!this.pinned) this.relays.setRelays(relays); }\n}\n"],"mappings":";;;;;AA2BA,IAAM,YAAY;AAAI,IAAA,YAAY;AAClC,IAAM,UAAU,KAAiB,SAAqB,QAAoB;CAAE,IAAI;EAAE,OAAO,QAAQ,OAAO,KAAK,SAAS,GAAG;CAAG,QAAQ;EAAE,OAAO;CAAO;AAAE;;AAGtJ,IAAa,WAAb,MAAsB;CACpB;;CAEA;CACA;CAEA,YAAY,UAA2B,CAAC,GAAG;EACzC,KAAK,YAAY,QAAQ,aAAa;EACtC,MAAM,YAAY,QAAQ,cAAc,QAAQ,QAAQ,QAAQ,WAAW,SAAS,QAAQ,YAAY,KAAA;EACxG,KAAK,MAAM,IAAI,IAAI;GAAE;GAAQ,GAAI,cAAc,KAAA,IAAY,EAAE,UAAU,IAAI,CAAC;GAAI,GAAI,QAAQ,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;EAAG,CAAC;EACjI,KAAK,YAAY,IAAI,SAAS,YAAY,KAAK,IAAI,OAAO,GAAG,QAAQ,YAAY,QAAQ,CAAC,CAAC;EAE3F,KAAK,IAAI,GAAG,eAAe,CAAC,CAAC;CAC/B;;CAGA,QAAQ,MAAc,MAAoB;EAAE,KAAK,IAAI,QAAQ;GAAE;GAAM;EAAK,CAAC;CAAG;CAC9E,UAA4B;EAAE,OAAO,KAAK,IAAI,QAAQ;CAAuB;;CAG7E,IAAI,WAAmB,SAAsC;EAC3D,MAAM,IAAI,OAAO,KAAK,iBAAiB,SAAS,CAAC;EACjD,MAAM,MAAM,OAAO,KAAK,QAAQ,SAAS,GAAG,SAAS,CAAC;EACtD,MAAM,MAAM,OAAO,IAAI,SAAS,QAAQ,QAAQ,QAAQ,aAAa,WAAW,SAAS,CAAC,CAAC,aAAa,CAAC,CAAC;EAC1G,MAAM,IAAI,OAAO,KAAK,QAAQ,SAAS,EAAqB,CAAC;EAC7D,OAAO,KAAK,QAAQ,IAAI,SAAS,SAAS,WAAW;GACnD,KAAK,IAAI,IAAI;IAAE;IAAG;IAAG;IAAK;GAAI,IAAI,OAAO,OAAO,UAAU;IACxD,IAAI,OAAO,OAAO,KAAK;SAAQ,IAAI,CAAC,OAAO,uBAAO,IAAI,MAAM,6BAA6B,CAAC;SAAQ,QAAQ,KAAK;GACjH,CAAC;EACH,CAAC,CAAC;CACJ;;CAGA,IAAI,WAAiD;EACnD,MAAM,IAAI,OAAO,KAAK,iBAAiB,SAAS,CAAC;EAEjD,MAAM,SAAS,WAAW,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO;EACnD,OAAO,KAAK,QAAQ,IAAI,SAAS,SAAS,WAAW;GACnD,KAAK,IAAI,IAAI,QAAQ,EAAE,OAAO,MAAM,IAAI,OAAO,UAAU;IACvD,IAAI,OAAO,OAAO,OAAO,KAAK;IAC9B,IAAI,CAAC,OAAO,KAAK,CAAC,MAAM,KAAK,OAAO,QAAQ,IAAI;IAChD,MAAM,YAAY,IAAI,WAAW,SAAS;IAC1C,IAAI,SAAS,UAAU,MAAM,CAAC,CAAC,aAAa,GAAG,OAAO,MAAM,GAAG,CAAC;IAChE,MAAM,UAAU,IAAI,WAAW,KAAwB,MAAM,EAAE,MAAM;IACrE,QAAQ,IAAI,MAAM,KAAK,CAAC;IAAG,QAAQ,IAAI,WAAW,SAAS;IAAG,QAAQ,IAAI,MAAM,GAAG,EAAqB;IACxG,IAAI;KAAE,QAAQ,kBAAkB,WAAW,OAAO,CAAC;IAAG,QAAQ;KAAE,QAAQ,IAAI;IAAG;GACjF,CAAC;EACH,CAAC,CAAC;CACJ;CAEA,QAAmB,MAA8B;EAC/C,OAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,QAAQ,iBAAiB,uBAAO,IAAI,MAAM,sBAAsB,CAAC,GAAG,KAAK,SAAS;GACxF,KAAK,MAAM,UAAU;IAAE,aAAa,KAAK;IAAG,QAAQ,KAAK;GAAG,IAAI,UAAU;IAAE,aAAa,KAAK;IAAG,OAAO,KAAK;GAAG,CAAC;EACnH,CAAC;CACH;CAEA,UAAyB;EAAE,OAAO,IAAI,SAAS,YAAY,KAAK,IAAI,cAAc,QAAQ,CAAC,CAAC;CAAG;AACjG;;;;;;;;;AAUA,IAAa,eAAb,MAAoD;CAIrB;CAA2E;CAHxG;CACA,UAAmC;CAEnC,YAAY,KAAuC,SAAgC,CAAC,GAAG,SAA0B,OAAO;EAA3F,KAAA,MAAA;EAA2E,KAAA,SAAA;EACtG,KAAK,SAAS,IAAI,eAAe,MAAM;CACzC;CAEA,MAAM,QAAQ,UAAoB,SAAwB,UAA+B,CAAC,GAAkB;EAK1G,MAAM,KAAK,OAAO,aAAa,UAAU,UAAU,KAAK,SAAS,WAAW,aAAa,KAAK,QAAQ,KAAK,SAAS,SAAS,UAAU,SAAS,GAAG,SAAS,OAAO,KAAK,QAAQ,IAAI;CACtL;CAEA,eAAe,WAAmB,SAAqB,UAA+B,CAAC,GAAkB;EACvG,OAAO,KAAK,QAAQ,WAAW,SAAS,OAAO;CACjD;;;;;CAMA,MAAc,QAAQ,WAAmB,SAAqB,SAA8B,UAAoC,WAAmC;EACjK,MAAM,iBAAiB,cAAc,KAAA,IACjC,KAAK,OAAO,eAAe,WAAW,SAAS,SAAS,QAAQ,IAChE,KAAK,OAAO,UAAU,WAAW,SAAS,WAAW,SAAS,QAAQ;EAC1E,IAAI,CAAC,KAAK,KAAK,OAAO,SAAS;EAC/B,MAAM,QAAQ,KAAK,IAAI,IAAI,WAAW,OAAO;EAC7C,MAAM,YAAY,CAAC,CAAC;EACpB,IAAI;GACF,MAAM,SAAS;EACjB,SAAS,OAAO;GAEd,IAAI,uBAAuB,KAAK,GAAG,MAAM;GAEzC,MAAM,MAAM,YAAY;IAAE,MAAM;GAAO,CAAC;EAC1C;CACF;CAEA,MAAM,QAAQ,WAAmB,UAA+B,CAAC,GAAiC;EAChG,IAAI,CAAC,KAAK,KAAK,OAAO,KAAK,OAAO,QAAQ,WAAW,OAAO;EAC5D,IAAI,aAAkC,MAAM,SAAkB;EAE9D,MAAM,aAAa,KAAK,OAAO,UAAU,CAAC,CAAC,OAAO,OAAO,UAAU,MAAM,UAAU,SAAS;EAC5F,IAAI,CAAC,KAAK,GAAG;GACX,IAAI;IAAE,aAAa,MAAM,KAAK,OAAO,QAAQ,WAAW,OAAO;IAAG,KAAK,UAAU;GAAS,SAAS,OAAO;IACxG,IAAI,uBAAuB,KAAK,GAAG,MAAM;IACzC,SAAS;GACX;GACA,IAAI,CAAC,UAAU,CAAC,KAAK,GAAG,OAAO;EACjC;EACA,IAAI;GACF,MAAM,UAAU,MAAM,KAAK,IAAI,IAAI,SAAS;GAC5C,KAAK,UAAU;GACf,IAAI,SAAS,KAAK,OAAO,aAAa,WAAW,QAAQ,eAAe;GACxE,OAAO,YAAY,YAAY,OAAO;EACxC,SAAS,OAAO;GACd,IAAI,YAAY,OAAO;GACvB,MAAM,UAAU;EAClB;CACF;CAEA,WAAmD;EACjD,OAAO;GAAE,UAAU,KAAK,MAAM,+DAA+D;GAAuB,QAAQ,KAAK,OAAO,SAAS,CAAC,CAAC;EAAO;CAC5J;CACA,YAA6B;EAC3B,MAAM,SAAS,KAAK,OAAO,UAAU;EACrC,OAAO,KAAK,YAAY,QAAQ;GAAE,GAAG;GAAQ,MAAM,EAAE,KAAK,MAAM;EAAE,IAAI;CACxE;CACA,UAAU,UAAkE;EAAE,OAAO,KAAK,OAAO,UAAU,QAAQ;CAAG;;CAEtH,eAAe,WAAuC;EAAE,OAAO,KAAK,OAAO,eAAe,SAAS;CAAG;CACtG,gBAAgB,WAAuC;EAAE,OAAO,KAAK,OAAO,gBAAgB,SAAS;CAAG;CACxG,aAAa,UAAqE;EAAE,OAAO,KAAK,OAAO,aAAa,QAAQ;CAAG;CAC/H,UAAU,EAAE,UAA2D;EAAE,IAAI,CAAC,KAAK,QAAQ,KAAK,OAAO,UAAU,MAAM;CAAG;AAC5H"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
//#region ../browser/src/shared/mints.ts
|
|
2
|
+
/**
|
|
3
|
+
* Mints a new wallet starts with, so nobody has to know what a mint is before
|
|
4
|
+
* receiving sats. The first one that answers is where Lightning invoices are
|
|
5
|
+
* created; ecash from any of them is accepted from contacts.
|
|
6
|
+
*
|
|
7
|
+
* A mint is a custodian: it holds the sats. This list is a product decision,
|
|
8
|
+
* to be reviewed regularly. It was picked on 2026-09-18 from the community
|
|
9
|
+
* auditor (https://audit.8333.space): mints with thousands of audited swaps,
|
|
10
|
+
* the highest success rates and an OK state at the time.
|
|
11
|
+
*/
|
|
12
|
+
var DEFAULT_MINTS = [
|
|
13
|
+
"https://mint.minibits.cash/Bitcoin",
|
|
14
|
+
"https://21mint.me",
|
|
15
|
+
"https://mint.mountainlake.io"
|
|
16
|
+
];
|
|
17
|
+
/** Public test mint: worthless sats, invoices settle by themselves. */
|
|
18
|
+
var TEST_MINT = "https://testnut.cashu.space";
|
|
19
|
+
/**
|
|
20
|
+
* Mints whose sats are worth nothing: never counted as money, never mixed with real sats.
|
|
21
|
+
* Mutinynet's mint (cashu.mutinynet.com) would belong here, but on 2026-09-22 it answered with a
|
|
22
|
+
* duplicated Access-Control-Allow-Origin header that every browser and WebView rejects.
|
|
23
|
+
*/
|
|
24
|
+
var TEST_MINTS = [TEST_MINT];
|
|
25
|
+
var isTestMint = (url) => TEST_MINTS.includes(url.replace(/\/+$/, ""));
|
|
26
|
+
/** A mint on this very machine (a local test server): whatever it issues is not money anyone else holds. */
|
|
27
|
+
function isLocalMint(url) {
|
|
28
|
+
try {
|
|
29
|
+
return [
|
|
30
|
+
"localhost",
|
|
31
|
+
"127.0.0.1",
|
|
32
|
+
"[::1]"
|
|
33
|
+
].includes(new URL(url).hostname);
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Mints whose sats are worth nothing: the public test mints and mints on this machine. They belong to
|
|
40
|
+
* the Testnet mode, and their ecash never settles a request for real sats. Only the public test mints
|
|
41
|
+
* are ever added by themselves (a contact must not make this app contact a port on this machine).
|
|
42
|
+
*/
|
|
43
|
+
var isWorthlessMint = (url) => isTestMint(url) || isLocalMint(url);
|
|
44
|
+
/**
|
|
45
|
+
* A mint whose word "this invoice is paid" proves nobody paid it. The public test mint runs a fake Lightning backend
|
|
46
|
+
* that marks every invoice paid by itself, and a mint on this machine is a test server that may do the same. Its
|
|
47
|
+
* invoices are still shown to be paid, but the wallet mints one only once a payer says it paid it (see `held` on
|
|
48
|
+
* StoredQuote); asking it for test coins on purpose is `CashuWallet.testCoins`.
|
|
49
|
+
*/
|
|
50
|
+
var paysItsOwnInvoices = isWorthlessMint;
|
|
51
|
+
/**
|
|
52
|
+
* Mints on this machine the operator declared test servers with a fake Lightning backend (the CLI's
|
|
53
|
+
* `GHOSTLY_TEST_MINTS`, for regtest and e2e stacks). Nothing else declares one: the apps never do.
|
|
54
|
+
*/
|
|
55
|
+
var declaredTestMints = /* @__PURE__ */ new Set();
|
|
56
|
+
var bare = (url) => url.trim().replace(/\/+$/, "");
|
|
57
|
+
function declareTestMints(urls) {
|
|
58
|
+
for (const url of urls) if (isLocalMint(bare(url))) declaredTestMints.add(bare(url));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Whether a Testnet wallet may pay a Bitcoin (`lnbc`) invoice through this mint. Test mints issue `lnbc` invoices,
|
|
62
|
+
* but so does a real node: only a mint known to fake its Lightning moves nothing real when it pays one. That is the
|
|
63
|
+
* public test mint, or a mint on this machine the operator declared a test server; never any other mint on this
|
|
64
|
+
* machine, which may be a self-hosted mint with real sats behind it.
|
|
65
|
+
*/
|
|
66
|
+
var fakesLightning = (url) => isTestMint(url) || declaredTestMints.has(bare(url));
|
|
67
|
+
/** Refused before any mint is asked: see `fakesLightning`. */
|
|
68
|
+
var BITCOIN_INVOICE_ON_TESTNET = "This is a Bitcoin invoice (real money): test sats pay one only through the public test mint. Nothing was sent.";
|
|
69
|
+
/** The network a Cashu mint's ecash belongs to: test mints and mints on this machine are Testnet. */
|
|
70
|
+
var mintNetwork = (url) => isWorthlessMint(url) ? "testnet" : "mainnet";
|
|
71
|
+
//#endregion
|
|
72
|
+
export { declareTestMints as a, isTestMint as c, paysItsOwnInvoices as d, TEST_MINTS as i, isWorthlessMint as l, DEFAULT_MINTS as n, fakesLightning as o, TEST_MINT as r, isLocalMint as s, BITCOIN_INVOICE_ON_TESTNET as t, mintNetwork as u };
|
|
73
|
+
|
|
74
|
+
//# sourceMappingURL=mints-BrlY773j.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mints-BrlY773j.js","names":[],"sources":["../../../browser/src/shared/mints.ts"],"sourcesContent":["import type { WalletNetwork } from \"@ghostly/core\";\n/**\n * Mints a new wallet starts with, so nobody has to know what a mint is before\n * receiving sats. The first one that answers is where Lightning invoices are\n * created; ecash from any of them is accepted from contacts.\n *\n * A mint is a custodian: it holds the sats. This list is a product decision,\n * to be reviewed regularly. It was picked on 2026-09-18 from the community\n * auditor (https://audit.8333.space): mints with thousands of audited swaps,\n * the highest success rates and an OK state at the time.\n */\nexport const DEFAULT_MINTS = [\n \"https://mint.minibits.cash/Bitcoin\",\n \"https://21mint.me\",\n \"https://mint.mountainlake.io\",\n];\n\n/** Public test mint: worthless sats, invoices settle by themselves. */\nexport const TEST_MINT = \"https://testnut.cashu.space\";\n\n/**\n * Mints whose sats are worth nothing: never counted as money, never mixed with real sats.\n * Mutinynet's mint (cashu.mutinynet.com) would belong here, but on 2026-09-22 it answered with a\n * duplicated Access-Control-Allow-Origin header that every browser and WebView rejects.\n */\nexport const TEST_MINTS: readonly string[] = [TEST_MINT];\nexport const isTestMint = (url: string) => TEST_MINTS.includes(url.replace(/\\/+$/, \"\"));\n\n/** A mint on this very machine (a local test server): whatever it issues is not money anyone else holds. */\nexport function isLocalMint(url: string): boolean {\n try { return [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(new URL(url).hostname); } catch { return false; }\n}\n\n/**\n * Mints whose sats are worth nothing: the public test mints and mints on this machine. They belong to\n * the Testnet mode, and their ecash never settles a request for real sats. Only the public test mints\n * are ever added by themselves (a contact must not make this app contact a port on this machine).\n */\nexport const isWorthlessMint = (url: string) => isTestMint(url) || isLocalMint(url);\n\n/**\n * A mint whose word \"this invoice is paid\" proves nobody paid it. The public test mint runs a fake Lightning backend\n * that marks every invoice paid by itself, and a mint on this machine is a test server that may do the same. Its\n * invoices are still shown to be paid, but the wallet mints one only once a payer says it paid it (see `held` on\n * StoredQuote); asking it for test coins on purpose is `CashuWallet.testCoins`.\n */\nexport const paysItsOwnInvoices = isWorthlessMint;\n\n/**\n * Mints on this machine the operator declared test servers with a fake Lightning backend (the CLI's\n * `GHOSTLY_TEST_MINTS`, for regtest and e2e stacks). Nothing else declares one: the apps never do.\n */\nconst declaredTestMints = new Set<string>();\nconst bare = (url: string) => url.trim().replace(/\\/+$/, \"\");\nexport function declareTestMints(urls: readonly string[]): void {\n for (const url of urls) if (isLocalMint(bare(url))) declaredTestMints.add(bare(url));\n}\n\n/**\n * Whether a Testnet wallet may pay a Bitcoin (`lnbc`) invoice through this mint. Test mints issue `lnbc` invoices,\n * but so does a real node: only a mint known to fake its Lightning moves nothing real when it pays one. That is the\n * public test mint, or a mint on this machine the operator declared a test server; never any other mint on this\n * machine, which may be a self-hosted mint with real sats behind it.\n */\nexport const fakesLightning = (url: string) => isTestMint(url) || declaredTestMints.has(bare(url));\n\n/** Refused before any mint is asked: see `fakesLightning`. */\nexport const BITCOIN_INVOICE_ON_TESTNET = \"This is a Bitcoin invoice (real money): test sats pay one only through the public test mint. Nothing was sent.\";\n\n/** Real money, or test networks: each wallet has its own (see WalletNetwork in @ghostly/core). */\nexport type WalletMode = WalletNetwork;\n/** The network a Cashu mint's ecash belongs to: test mints and mints on this machine are Testnet. */\nexport const mintNetwork = (url: string): WalletNetwork => isWorthlessMint(url) ? \"testnet\" : \"mainnet\";\n"],"mappings":";;;;;;;;;;;AAWA,IAAa,gBAAgB;CAC3B;CACA;CACA;AACF;;AAGA,IAAa,YAAY;;;;;;AAOzB,IAAa,aAAgC,CAAC,SAAS;AACvD,IAAa,cAAc,QAAgB,WAAW,SAAS,IAAI,QAAQ,QAAQ,EAAE,CAAC;;AAGtF,SAAgB,YAAY,KAAsB;CAChD,IAAI;EAAE,OAAO;GAAC;GAAa;GAAa;EAAO,CAAC,CAAC,SAAS,IAAI,IAAI,GAAG,CAAC,CAAC,QAAQ;CAAG,QAAQ;EAAE,OAAO;CAAO;AAC5G;;;;;;AAOA,IAAa,mBAAmB,QAAgB,WAAW,GAAG,KAAK,YAAY,GAAG;;;;;;;AAQlF,IAAa,qBAAqB;;;;;AAMlC,IAAM,oCAAoB,IAAI,IAAY;AAC1C,IAAM,QAAQ,QAAgB,IAAI,KAAK,CAAC,CAAC,QAAQ,QAAQ,EAAE;AAC3D,SAAgB,iBAAiB,MAA+B;CAC9D,KAAK,MAAM,OAAO,MAAM,IAAI,YAAY,KAAK,GAAG,CAAC,GAAG,kBAAkB,IAAI,KAAK,GAAG,CAAC;AACrF;;;;;;;AAQA,IAAa,kBAAkB,QAAgB,WAAW,GAAG,KAAK,kBAAkB,IAAI,KAAK,GAAG,CAAC;;AAGjG,IAAa,6BAA6B;;AAK1C,IAAa,eAAe,QAA+B,gBAAgB,GAAG,IAAI,YAAY"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Xn as engineText, Yn as engineError } from "./src-DDzSERyc.js";
|
|
2
|
+
import { f as store, i as STORES, m as wrap, p as transact, u as openDb } from "./idb-B5L8wuOm.js";
|
|
3
|
+
//#region ../browser/src/engine/paymentAdapters/persistence.ts
|
|
4
|
+
/**
|
|
5
|
+
* A USDT payment is one transaction at one account nonce. Another payment from the same account and chain that is
|
|
6
|
+
* submitted or of unknown outcome holds its nonce until it confirms. A review made after it was sent took the next
|
|
7
|
+
* nonce and may go at once. One made before (or read from an RPC that had not seen it yet) has the same or a lower
|
|
8
|
+
* nonce: sent, it would replace the earlier transaction or be dropped, so it is refused. A row with no nonce refuses.
|
|
9
|
+
*/
|
|
10
|
+
function nonceTaken(review, intents) {
|
|
11
|
+
if (review.method !== "usdt") return false;
|
|
12
|
+
const from = review.evm?.from?.toLowerCase(), nonce = review.evm?.nonce;
|
|
13
|
+
return intents.some(({ review: other }) => other.id !== review.id && other.method === "usdt" && other.chainId === review.chainId && other.evm?.from?.toLowerCase() === from && ["submitted", "unknown"].includes(other.state) && !(typeof nonce === "number" && typeof other.evm?.nonce === "number" && nonce > other.evm.nonce));
|
|
14
|
+
}
|
|
15
|
+
var intentRepository = {
|
|
16
|
+
async get(id) {
|
|
17
|
+
return wrap((await store(STORES.intents, "readonly")).get(id));
|
|
18
|
+
},
|
|
19
|
+
async list() {
|
|
20
|
+
return wrap((await store(STORES.intents, "readonly")).getAll());
|
|
21
|
+
},
|
|
22
|
+
async put(intent) {
|
|
23
|
+
await transact([STORES.intents], (s) => {
|
|
24
|
+
s[STORES.intents].put(intent);
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
async claim(id) {
|
|
28
|
+
const tx = (await openDb()).transaction(STORES.intents, "readwrite");
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
let saved;
|
|
31
|
+
let reason = engineText("alreadySubmitted");
|
|
32
|
+
const request = tx.objectStore(STORES.intents).getAll();
|
|
33
|
+
request.onsuccess = () => {
|
|
34
|
+
const intents = request.result;
|
|
35
|
+
saved = intents.find((item) => item.review.id === id);
|
|
36
|
+
if (!saved || saved.review.state !== "pending") {
|
|
37
|
+
tx.abort();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (saved.review.requestId && intents.some(({ review }) => review.id !== id && review.requestId === saved.review.requestId && review.linkId === saved.review.linkId && [
|
|
41
|
+
"submitted",
|
|
42
|
+
"settled",
|
|
43
|
+
"unknown"
|
|
44
|
+
].includes(review.state))) {
|
|
45
|
+
tx.abort();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (nonceTaken(saved.review, intents)) {
|
|
49
|
+
reason = engineText("sentAfterReview");
|
|
50
|
+
tx.abort();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
saved.review = {
|
|
54
|
+
...saved.review,
|
|
55
|
+
state: "submitted",
|
|
56
|
+
error: void 0,
|
|
57
|
+
submittedAt: Date.now()
|
|
58
|
+
};
|
|
59
|
+
tx.objectStore(STORES.intents).put(saved);
|
|
60
|
+
};
|
|
61
|
+
tx.oncomplete = () => resolve(saved);
|
|
62
|
+
tx.onabort = tx.onerror = () => reject(new Error(reason));
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
async cancel(id) {
|
|
66
|
+
const tx = (await openDb()).transaction(STORES.intents, "readwrite");
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
let saved;
|
|
69
|
+
const request = tx.objectStore(STORES.intents).get(id);
|
|
70
|
+
request.onsuccess = () => {
|
|
71
|
+
saved = request.result;
|
|
72
|
+
if (!saved || saved.review.state !== "pending") {
|
|
73
|
+
tx.abort();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
saved.review = {
|
|
77
|
+
...saved.review,
|
|
78
|
+
state: "cancelled"
|
|
79
|
+
};
|
|
80
|
+
tx.objectStore(STORES.intents).put(saved);
|
|
81
|
+
};
|
|
82
|
+
tx.oncomplete = () => resolve(saved);
|
|
83
|
+
tx.onabort = tx.onerror = () => reject(engineError("cannotCancelSubmitted"));
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
/** Key for a wallet that needs no password: its seed is protected like the Cashu proofs in the same database. */
|
|
88
|
+
function newDeviceKey() {
|
|
89
|
+
return btoa(String.fromCharCode(...crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32)))).replace(/=+$/, "");
|
|
90
|
+
}
|
|
91
|
+
async function passwordKey(password, salt) {
|
|
92
|
+
const material = await crypto.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveKey"]);
|
|
93
|
+
return crypto.subtle.deriveKey({
|
|
94
|
+
name: "PBKDF2",
|
|
95
|
+
hash: "SHA-256",
|
|
96
|
+
salt,
|
|
97
|
+
iterations: 6e5
|
|
98
|
+
}, material, {
|
|
99
|
+
name: "AES-GCM",
|
|
100
|
+
length: 256
|
|
101
|
+
}, false, ["encrypt", "decrypt"]);
|
|
102
|
+
}
|
|
103
|
+
async function sealSeed(seed, password) {
|
|
104
|
+
if (password.length < 12) throw new Error("Use at least 12 characters for the wallet password");
|
|
105
|
+
const salt = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16)), iv = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(12));
|
|
106
|
+
const ciphertext = await crypto.subtle.encrypt({
|
|
107
|
+
name: "AES-GCM",
|
|
108
|
+
iv
|
|
109
|
+
}, await passwordKey(password, salt), new TextEncoder().encode(seed));
|
|
110
|
+
return {
|
|
111
|
+
version: 1,
|
|
112
|
+
salt: [...salt],
|
|
113
|
+
iv: [...iv],
|
|
114
|
+
ciphertext: [...new Uint8Array(ciphertext)]
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
async function unsealSeed(seed, password) {
|
|
118
|
+
if (seed.version !== 1) throw new Error("Unsupported wallet backup version");
|
|
119
|
+
try {
|
|
120
|
+
return new TextDecoder().decode(await crypto.subtle.decrypt({
|
|
121
|
+
name: "AES-GCM",
|
|
122
|
+
iv: new Uint8Array(seed.iv)
|
|
123
|
+
}, await passwordKey(password, new Uint8Array(seed.salt)), new Uint8Array(seed.ciphertext)));
|
|
124
|
+
} catch {
|
|
125
|
+
throw new Error("Could not unlock the wallet. Check the password and backup.");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region ../browser/src/engine/paymentAdapters/networkReason.ts
|
|
130
|
+
/**
|
|
131
|
+
* A request's failure as a person says it, when the browser's own words say nothing: a request that timed out or was
|
|
132
|
+
* given up ("Fetch is aborted" in Safari, "signal timed out" in Chromium) did not answer in time; one that never left
|
|
133
|
+
* ("Failed to fetch", "Load failed") could not reach it. `host` names what was asked, when known. Anything else is
|
|
134
|
+
* `null`: its own words are kept.
|
|
135
|
+
*/
|
|
136
|
+
function networkReason(error, host) {
|
|
137
|
+
const name = error instanceof Error ? error.name : "";
|
|
138
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
139
|
+
if (name === "AbortError" || name === "TimeoutError" || /^(fetch is aborted|signal timed out|signal is aborted without reason|the (user|operation) (aborted a request|was aborted)\.?|this operation was aborted|the operation timed out\.?)$/i.test(text.trim())) return host ? engineText("hostTimedOut", { host }) : engineText("networkTimedOut");
|
|
140
|
+
if (/^(failed to fetch|load failed|fetch failed|networkerror when attempting to fetch resource\.?|network request failed)$/i.test(text.trim())) return host ? engineText("hostUnreachable", { host }) : engineText("networkUnreachable");
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
/** A request URL's host, for a reason that names it; the URL itself when it does not parse. */
|
|
144
|
+
function rpcHost(url) {
|
|
145
|
+
try {
|
|
146
|
+
return new URL(url).host;
|
|
147
|
+
} catch {
|
|
148
|
+
return url;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
export { sealSeed as a, newDeviceKey as i, rpcHost as n, unsealSeed as o, intentRepository as r, networkReason as t };
|
|
153
|
+
|
|
154
|
+
//# sourceMappingURL=networkReason-CetKkTPo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"networkReason-CetKkTPo.js","names":[],"sources":["../../../browser/src/engine/paymentAdapters/persistence.ts","../../../browser/src/engine/paymentAdapters/networkReason.ts"],"sourcesContent":["import { engineError, engineText } from \"@ghostly/core\";\nimport { STORES, openDb, store, transact, wrap } from \"../../shared/idb\";\nimport type { IntentRepository, SavedIntent } from \"./coordinator\";\n/**\n * A USDT payment is one transaction at one account nonce. Another payment from the same account and chain that is\n * submitted or of unknown outcome holds its nonce until it confirms. A review made after it was sent took the next\n * nonce and may go at once. One made before (or read from an RPC that had not seen it yet) has the same or a lower\n * nonce: sent, it would replace the earlier transaction or be dropped, so it is refused. A row with no nonce refuses.\n */\nfunction nonceTaken(review:SavedIntent[\"review\"],intents:SavedIntent[]):boolean {\n if(review.method!==\"usdt\")return false;\n const from=review.evm?.from?.toLowerCase(), nonce=review.evm?.nonce;\n return intents.some(({review:other})=>other.id!==review.id && other.method===\"usdt\" && other.chainId===review.chainId && other.evm?.from?.toLowerCase()===from && [\"submitted\",\"unknown\"].includes(other.state)\n && !(typeof nonce===\"number\" && typeof other.evm?.nonce===\"number\" && nonce>other.evm.nonce));\n}\nexport const intentRepository: IntentRepository = {\n async get(id) { return wrap<SavedIntent | undefined>((await store(STORES.intents,\"readonly\")).get(id)); },\n async list() { return wrap<SavedIntent[]>((await store(STORES.intents,\"readonly\")).getAll()); },\n async put(intent) { await transact([STORES.intents],s=>{s[STORES.intents].put(intent);}); },\n async claim(id) {\n const tx=(await openDb()).transaction(STORES.intents,\"readwrite\");\n return new Promise<SavedIntent>((resolve,reject)=>{\n let saved:SavedIntent;\n let reason=engineText(\"alreadySubmitted\");\n const request=tx.objectStore(STORES.intents).getAll();\n request.onsuccess=()=>{\n const intents:SavedIntent[]=request.result;\n saved=intents.find(item=>item.review.id===id)!;\n if(!saved || saved.review.state!==\"pending\") {tx.abort();return;}\n const duplicate=saved.review.requestId && intents.some(({review})=>review.id!==id && review.requestId===saved.review.requestId && review.linkId===saved.review.linkId && [\"submitted\",\"settled\",\"unknown\"].includes(review.state));\n if(duplicate){tx.abort();return;}\n if(nonceTaken(saved.review,intents)){reason=engineText(\"sentAfterReview\");tx.abort();return;}\n saved.review={...saved.review,state:\"submitted\",error:undefined,submittedAt:Date.now()};\n tx.objectStore(STORES.intents).put(saved);\n };\n tx.oncomplete=()=>resolve(saved);\n tx.onabort=tx.onerror=()=>reject(new Error(reason));\n });\n },\n async cancel(id) {\n const tx=(await openDb()).transaction(STORES.intents,\"readwrite\");\n return new Promise<SavedIntent>((resolve,reject)=>{\n let saved:SavedIntent;\n const request=tx.objectStore(STORES.intents).get(id);\n request.onsuccess=()=>{\n saved=request.result;\n if(!saved || saved.review.state!==\"pending\"){tx.abort();return;}\n saved.review={...saved.review,state:\"cancelled\"};\n tx.objectStore(STORES.intents).put(saved);\n };\n tx.oncomplete=()=>resolve(saved);\n tx.onabort=tx.onerror=()=>reject(engineError(\"cannotCancelSubmitted\"));\n });\n },\n};\n/** Key for a wallet that needs no password: its seed is protected like the Cashu proofs in the same database. */\nexport function newDeviceKey():string {return btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32)))).replace(/=+$/,\"\");}\nexport interface EncryptedSeed {version:1;salt:number[];iv:number[];ciphertext:number[]}\nasync function passwordKey(password:string,salt:Uint8Array<ArrayBuffer>) {\n const material=await crypto.subtle.importKey(\"raw\",new TextEncoder().encode(password),\"PBKDF2\",false,[\"deriveKey\"]);\n return crypto.subtle.deriveKey({name:\"PBKDF2\",hash:\"SHA-256\",salt,iterations:600000},material,{name:\"AES-GCM\",length:256},false,[\"encrypt\",\"decrypt\"]);\n}\nexport async function sealSeed(seed:string,password:string):Promise<EncryptedSeed> {\n if(password.length<12)throw new Error(\"Use at least 12 characters for the wallet password\");\n const salt=crypto.getRandomValues(new Uint8Array(16)),iv=crypto.getRandomValues(new Uint8Array(12));\n const ciphertext=await crypto.subtle.encrypt({name:\"AES-GCM\",iv},await passwordKey(password,salt),new TextEncoder().encode(seed));\n return {version:1,salt:[...salt],iv:[...iv],ciphertext:[...new Uint8Array(ciphertext)]};\n}\nexport async function unsealSeed(seed:EncryptedSeed,password:string):Promise<string> {\n if(seed.version!==1)throw new Error(\"Unsupported wallet backup version\");\n try {return new TextDecoder().decode(await crypto.subtle.decrypt({name:\"AES-GCM\",iv:new Uint8Array(seed.iv)},await passwordKey(password,new Uint8Array(seed.salt)),new Uint8Array(seed.ciphertext)));}\n catch {throw new Error(\"Could not unlock the wallet. Check the password and backup.\");}\n}\n","import { engineText } from \"@ghostly/core\";\n\n/**\n * A request's failure as a person says it, when the browser's own words say nothing: a request that timed out or was\n * given up (\"Fetch is aborted\" in Safari, \"signal timed out\" in Chromium) did not answer in time; one that never left\n * (\"Failed to fetch\", \"Load failed\") could not reach it. `host` names what was asked, when known. Anything else is\n * `null`: its own words are kept.\n */\nexport function networkReason(error: unknown, host?: string): string | null {\n const name = error instanceof Error ? error.name : \"\";\n const text = error instanceof Error ? error.message : String(error);\n if (name === \"AbortError\" || name === \"TimeoutError\" || /^(fetch is aborted|signal timed out|signal is aborted without reason|the (user|operation) (aborted a request|was aborted)\\.?|this operation was aborted|the operation timed out\\.?)$/i.test(text.trim()))\n return host ? engineText(\"hostTimedOut\", { host }) : engineText(\"networkTimedOut\");\n if (/^(failed to fetch|load failed|fetch failed|networkerror when attempting to fetch resource\\.?|network request failed)$/i.test(text.trim()))\n return host ? engineText(\"hostUnreachable\", { host }) : engineText(\"networkUnreachable\");\n return null;\n}\n\n/** A request URL's host, for a reason that names it; the URL itself when it does not parse. */\nexport function rpcHost(url: string): string {\n try { return new URL(url).host; } catch { return url; }\n}\n"],"mappings":"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"}
|