@ghostlytools/cli 1.3.0 → 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 +9 -3
- package/dist/assets/{api-CrxVxtY1.js → api-Dq2QXQDk.js} +141 -40
- package/dist/assets/api-Dq2QXQDk.js.map +1 -0
- package/dist/assets/{api-BiaX6l6f.js → api-TiB5QSG9.js} +2 -2
- package/dist/assets/{apiKit-CajiDxdK.js → apiKit-CA2x5GJF.js} +7 -3
- package/dist/assets/apiKit-CA2x5GJF.js.map +1 -0
- package/dist/assets/{apps-CbfiCSIw.js → apps-BxV9i_Y4.js} +3 -4
- package/dist/assets/{apps-CbfiCSIw.js.map → apps-BxV9i_Y4.js.map} +1 -1
- package/dist/assets/{apps-t23gkc28.js → apps-C-T8jeji.js} +1 -1
- package/dist/assets/{arkade-D4BVJFEQ.js → arkade-C_qDmHnm.js} +2 -2
- package/dist/assets/{arkade-D4BVJFEQ.js.map → arkade-C_qDmHnm.js.map} +1 -1
- package/dist/assets/{backup-Cg52WeQi.js → backup-Bugnn3Ue.js} +1 -1
- package/dist/assets/{backup-BAMi2lAq.js → backup-CnMN2oUK.js} +8 -8
- 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-CX1pKB0j.js → buttons-CvNBzh2S.js} +4 -5
- package/dist/assets/{buttons-CX1pKB0j.js.map → buttons-CvNBzh2S.js.map} +1 -1
- package/dist/assets/{endpoint-Dn7H8qE1.js → endpoint-BOTFMpfS.js} +44 -12
- package/dist/assets/endpoint-BOTFMpfS.js.map +1 -0
- package/dist/assets/{envelope-BvU0fLEt.js → envelope-WjingYSc.js} +2 -2
- package/dist/assets/{envelope-BvU0fLEt.js.map → envelope-WjingYSc.js.map} +1 -1
- package/dist/assets/{errors-my6VJR_0.js → errors-DO5z6h_l.js} +29 -1
- package/dist/assets/errors-DO5z6h_l.js.map +1 -0
- package/dist/assets/groupFiles-IEUW3DQz.js +3202 -0
- package/dist/assets/groupFiles-IEUW3DQz.js.map +1 -0
- package/dist/assets/{host-Cmd205Dc.js → host-CWt-jtqz.js} +41 -16
- package/dist/assets/host-CWt-jtqz.js.map +1 -0
- package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js → hyperdhtRelay-CPYwSLLS.js} +2 -3
- package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js.map → hyperdhtRelay-CPYwSLLS.js.map} +1 -1
- package/dist/assets/{irohWeb--YyEA-hX.js → irohWeb-4oChIOIp.js} +2 -3
- package/dist/assets/{irohWeb--YyEA-hX.js.map → irohWeb-4oChIOIp.js.map} +1 -1
- package/dist/assets/{main-BzNN6bkx.js → main-DbqcdShN.js} +142 -35
- package/dist/assets/main-DbqcdShN.js.map +1 -0
- package/dist/assets/{mainline-D5D5C-jT.js → mainline-ZkiOnevs.js} +2 -2
- package/dist/assets/{mainline-D5D5C-jT.js.map → mainline-ZkiOnevs.js.map} +1 -1
- package/dist/assets/{networkReason-DxZWHw6v.js → networkReason-CetKkTPo.js} +2 -2
- package/dist/assets/{networkReason-DxZWHw6v.js.map → networkReason-CetKkTPo.js.map} +1 -1
- 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/{profiles-B6F4oa9K.js → profiles-BHc9pEYg.js} +2 -2
- package/dist/assets/{profiles-B6F4oa9K.js.map → profiles-BHc9pEYg.js.map} +1 -1
- 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/{server-CBCEYKMN.js → server-Ss0gJ-nw.js} +184 -1181
- package/dist/assets/server-Ss0gJ-nw.js.map +1 -0
- package/dist/assets/{src-DhfGgAUu.js → src-DDzSERyc.js} +2249 -72
- package/dist/assets/src-DDzSERyc.js.map +1 -0
- package/dist/assets/{turnRefs-ChEfgbSX.js → turnRefs-B87dtrHM.js} +2 -2
- package/dist/assets/{turnRefs-ChEfgbSX.js.map → turnRefs-B87dtrHM.js.map} +1 -1
- package/dist/assets/types-C4_KafL_.js +122 -0
- package/dist/assets/types-C4_KafL_.js.map +1 -0
- package/dist/assets/{usdt-C1Xao2xH.js → usdt-Fj8CQO2z.js} +3 -3
- package/dist/assets/{usdt-C1Xao2xH.js.map → usdt-Fj8CQO2z.js.map} +1 -1
- package/dist/ghostly.mjs +1 -1
- package/package.json +1 -1
- package/dist/assets/api-CrxVxtY1.js.map +0 -1
- package/dist/assets/apiKit-CajiDxdK.js.map +0 -1
- package/dist/assets/backup-BAMi2lAq.js.map +0 -1
- package/dist/assets/buttons-CWl2yNJu.js +0 -2
- package/dist/assets/endpoint-Dn7H8qE1.js.map +0 -1
- package/dist/assets/errors-my6VJR_0.js.map +0 -1
- package/dist/assets/groupFiles-BNTSDaoq.js +0 -7685
- package/dist/assets/groupFiles-BNTSDaoq.js.map +0 -1
- package/dist/assets/groupFiles-DBbjOLuD.js +0 -2088
- package/dist/assets/groupFiles-DBbjOLuD.js.map +0 -1
- package/dist/assets/host-Cmd205Dc.js.map +0 -1
- package/dist/assets/main-BzNN6bkx.js.map +0 -1
- package/dist/assets/server-CBCEYKMN.js.map +0 -1
- package/dist/assets/src-DhfGgAUu.js.map +0 -1
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { n as traceJoin } from "./joinTrace-CjRDFy85.js";
|
|
2
|
-
import { $ as
|
|
3
|
-
import {
|
|
2
|
+
import { $c as budgetRetryMs, $i as PeerProofs, $o as HTTP_SERVICE_PROTO, $r as deviceWakeRequest, $s as enrollCancelFrame, $t as didDhtFromPublicKey, A as CommunitySession, Aa as COMMUNITY_REACTION_FRAME, Ao as DirectPathWatch, Ar as rankHubs, As as handoffStreamKey, B as knockRecords, Ba as wireReaction, Bc as signTurnPacket, Bn as isFederationId, Bo as LIMITS, Bs as readHandoffVerified, C as sha256Paced, Ca as EDIT_BUFFER_MS, Cc as deviceEchoNonce, Co as GROUP_MEMBER_CAP, Cr as freshHubs, Cs as handoffEphemeral, D as openGroupTyping, Da as parseLinkPreview, Dc as newDeviceSetSecret, Do as rosterHas, Dr as mergeLobby, Ds as handoffPakeFrame, E as groupTypingEpoch, Ea as dhtEditId, Ec as devicePingFrame, Eo as commitHash, Er as mergeBeacon, Es as handoffOfferFrame, Et as normalizeAtprotoHandle, Fa as queueReaction, Fc as TURN_LAST_SEQUENCE, Fn as decodeBolt11, Fo as presenceSeenAt, Fr as ChatFiles, Fs as readHandoffDone, G as groupWakeFrame, Gc as turnStatedVerifier, Gn as LIVENESS_PING_MS, Gr as PIN_LIMITS, Gs as setAckFrame, H as readKnocks, Ha as readReply, Hc as turnInstance, Hi as IDENTITY_REVOCATION_LABEL, Hn as parseFedimintRequestPayload, Ho as encodeControl, I as decodeGroupEntryLink, Ia as reactionEmoji, Ic as TURN_MAX, Ii as CapsExchange, In as ENDPOINT, Io as GhostlyHttpError, Ir as FILE_LIMITS, Is as readHandoffHave, J as GROUP_LIMITS, Jc as RelayTransport, Ji as identityStatement, Jn as pairedMessageFrame, Jr as pinIsNewer, Js as verifySetUpdate, K as groupWakes, Kc as turnVerifierMark, Ki as emptyIdentityLedger, Kn as PAIRED_FRAME_TOO_LARGE, Kr as mayPin, Ks as setUpdateFrame, L as encodeGroupEntryLink, La as reactionIsNewer, Lc as firstDeviceSetSecret, Li as DHT_TEXT_CAPABILITY, Ln as cashuRequestPayload, Lo as formatLocalTarget, Lr as transferEnded, Ls as readHandoffHello, M as communityHasFrame, Ma as REACTION_LIMITS, Mc as TURN_SETTLE_MS, Mo as DHT_POLL_INTERVALS, Mr as readBeaconHead, Ms as newHandoffId, N as decodeCommunityLink, Na as ReactionWindow, Nc as classifyTurnRead, Nr as readLobby, Ns as readHandoffAck, O as sealGroupTyping, Oa as forwardedAgain, Oc as seedSigner, Oo as verifyCommitSignature, Or as newerHead, Os as handoffReleaseFrame, P as encodeCommunityLink, Pa as nextReactionNumber, Pc as turnPutSummary, Po as RELAY_POLL_INTERVALS, Pr as shouldBeHub, Ps as readHandoffCancel, Q as frameBytes, Qc as traceLink, Qi as lastSharedWithMe, Qo as emptyLinkRecords, Qr as WakeLimiter, Qt as didDhtDocument, R as entryParams, Ra as readReaction, Rc as nextTurnPosition, Ri as capsDescriptors, Rn as fedimintRequestPayload, Ro as parseLocalTarget, Rr as transferStalled, Rs as readHandoffPake, S as readAppBundleAsync, Sa as parseVideoMeta, Sc as deviceEchoFrame, Sn as isBitcoinAddress, So as readCanonicalJson, Sr as doorHubs, Ss as handoffDigest, T as GroupTypingBoard, Ta as RateWindow, Tc as deviceLinkPairing, To as MEMBER_KEY, Tr as lobbyRecords, Ts as handoffManifestFrames, Tt as isAtprotoDid, Ua as replySnippet, Uc as turnKeys, Un as GhostLink, Ur as typingActivity, V as mergeKnocks, Va as pairedReplyAuthor, Vc as signTurnRelease, Vn as parseCashuRequestPayload, Vo as decodeControl, Vr as TypingSender, Vs as sealHandoffPiece, W as GroupWakeLimiter, Wa as BUTTON_ID, Wc as turnName, Wi as IdentityExchange, Wn as INVITE_TAKEN, Wo as RTC_CONFIG, Ws as readSetUpdate, X as GROUP_READ_NOTE, Xc as normalizeRelayUrl, Xi as receivedIdentityStatus, Xn as engineText, Xo as parseCallSignal, Xr as readPin, Ya as STATUS_CARD_LIMITS, Yc as currentRelays, Yi as newIdentityBinding, Yn as engineError, Yr as pinNumberHolds, Z as GroupSession, Zi as revokesIdentity, Zn as GROUP_FILE_LIMITS, Zr as WAKE_CALL_INTERVAL_MS, _c as inviteOwnership, _i as HoldKeys, _l as randomBytes, _n as walletNetworkOf, _r as meshHubs, _s as handoffBusyFrame, a as planAppUpdate, ac as enrollProofFrame, ai as vapidKeysMatch, an as EVM_TEST_CHAINS, ao as heardTime, as as HANDOFF_HAVE, at as groupName, b as pacer, ba as parseVoiceMeta, bc as ENROLL_CAPABILITY, bi as newMailbox, bl as utf8Decode, bn as sparkAddressKind, bo as canonicalJson, br as beaconKeys, bs as handoffContextProof, ca as peekDhtMailbox, cc as verifyEnrollProof, ci as isAppVersion, cl as createIdentity, cn as SEPOLIA_TEST_USDT, cr as CarriedTransport, cs as HANDOFF_PAKE, d as APP_BUNDLE_LIMITS, da as parseImageMeta, dc as readDeviceInvite, di as EDIT_CAPABILITY, dl as publicKeyFromZ32, dn as USDT_PUBLIC_RPC, do as appRef, ds as HANDOFF_TIMINGS, ea as emptyProofLedger, ec as enrollCancelReason, ei as newWakeToken, el as isDiscoveryBudgetError, en as encodeDidDhtDeactivated, eo as checkStatusCard, es as LEGACY_SERVICES, f as APP_BUNDLE_MAGIC, fc as OwnInviteError, fi as STATUS_CARD_CAPABILITY, fl as verify, fn as WALLET_NETWORKS, fs as HANDOFF_VERIFIED, g as appViewOf, gi as HOLD_LIMITS, gl as fromBase64Url, gn as validatePaymentTarget, go as readAppRevocation, gr as mayBeHub, gs as handoffAttemptSucceeded, h as appDigest, ha as sanitizeMime, hc as encodeInviteCode, hi as HOLD_DATED_BACK_MS, hl as bytesEqual, hn as formatPaymentAmount, ho as isAppRef, hr as MESH_HUBS, hs as handoffAttemptFailed, i as checkAppBeforeRun, ic as enrollHelloFrame, ii as checkPushEndpoint, il as createRelayPayload, in as ETHEREUM_USDT, io as claimedTime, ir as readGroupFileMeta, is as HANDOFF_DONE, it as mentionsMember, j as GROUP_READ_NOTE_COMMUNITY, ja as GROUP_REACTION_FRAME, jc as TURN_RAISE_READ_TIMEOUT_MS, jr as readBeacon, js as handoffVersions, k as COMMUNITY_LIMITS, ka as readForwarded, kc as webCryptoSigner, ko as edgeParams, kr as pickHubs, ks as handoffRetryAfter, l as readAppStoreSteps, la as DHT_FILE_CAPABILITY, lc as createDeviceInvite, li as APPS_CAPABILITY, ll as identityFromSeed, lo as APP_STATEMENT_LIMITS, ls as HANDOFF_PIECE_BYTES, ma as sanitizeFileName, mc as decodeInviteCode, mi as HOLD_CAPABILITY, ml as sanitizeAvatar, mn as assertWholeSats, mo as isAppKey, mr as readGroupSignal, ms as handoffAttemptAllowed, n as appStoreDecision, na as TRANSPORTS, nc as enrollDoneFrame, ni as relayRequest, nl as PacketTooLargeError, no as statusCardText, nr as groupFileFallback, ns as HANDOFF_ACK, nt as carryMentions, o as APP_STORE_LIMITS, oc as readEnrollGrant, on as ONCHAIN_PROVIDER, oo as receivedTimestamp, os as HANDOFF_HELLO, p as APP_ICON_PATH, pa as formatFileSize, pc as createChatInvite, pi as UPGRADE_CAPABILITY, pn as assertTokenUnits, po as isAppHash, pr as groupSignalFrame, ps as filePartName, q as parseGroupWakeFrame, qc as DEFAULT_RELAYS, qi as identityRevocationValue, qn as pairedMessageFits, qr as nextPinNumber, qs as signTombstone, r as appUpdateDecision, ra as automaticTransport, rc as enrollGrantFrame, ri as wakeRequest, rn as signDidDhtPacket, ro as arrivalKey, rs as HANDOFF_CANCEL, rt as parseCommunityEdit, sa as DHT_MESSAGE_TTL, sc as readEnrollHello, si as chatAppId, sn as PaymentPreflightError, so as shownTime, ss as HANDOFF_OFFER, t as appRevocationFor, ta as validExternalKey, tc as enrollDigits, ti as parseWakeFrame, tl as withRequestOptions, tn as encodeDidDhtPacket, to as sameButtonText, ts as serviceIdFromName, tt as GROUP_EDIT_TEXT_BYTES, ua as dhtFileId, uc as deviceInviteJoinerParams, ui as BUTTONS_CAPABILITY, ul as identityFromSeedB64, un as TEST_USDT_FAUCET_AMOUNT, uo as appFingerprint, us as HANDOFF_REQUEST, vi as HoldRefusedError, vl as toBase64Url, vn as SPARK_NETWORKS, vr as pickMeshHubs, vs as handoffCancelFrame, wa as EDIT_RATE_WINDOW_MS, wc as deviceKeyZ32, wn as isPubkyKey, wr as lobbyKeys, ws as handoffHelloFrame, xc as HANDOFF_CAPABILITY, xi as readManifest, xl as utf8Encode, xn as sparkInvoiceDetails, xr as beaconRecords, xs as handoffDataFrame, y as isAppUrl, ya as fileMessageText, yc as DEVICES_CAPABILITY, yi as isHoldUrl, yl as toZ32, yn as SPARK_PROVIDER, yr as COMMUNITY_TOPOLOGY, ys as handoffContext, z as knockIdentity, za as validReactionNumber, zc as readTurnPacket, zi as dialDescriptors, zn as findEndpoint, zo as webLocalFetch, zs as readHandoffTurnFrame } from "./src-DDzSERyc.js";
|
|
3
|
+
import { i as SourceUnreachableError, n as PROVIDER_PLATFORMS, o as isNothingSpentError, r as SourceConfigError, t as NothingSpentError, u as redact } from "./types-C4_KafL_.js";
|
|
4
|
+
import { t as bitcoindRpc } from "./bitcoindProvider-DGw3xBSa.js";
|
|
5
|
+
import { t as nwc } from "./nwcProvider-4eVz_8HP.js";
|
|
4
6
|
import { a as clearProfileStores, c as fileStore, d as profileOpenFailure, f as store, i as STORES$1, l as newestWalletTx, m as wrap, n as APP_STORAGE_SCOPE_INDEX, o as databaseName, p as transact, r as CARD_INDEX, s as deleteDatabase, t as APP_STORAGE_CHAT_INDEX, u as openDb } from "./idb-B5L8wuOm.js";
|
|
5
|
-
import { a as sealSeed, i as newDeviceKey, o as unsealSeed, r as intentRepository } from "./networkReason-
|
|
6
|
-
import { $ as
|
|
7
|
-
import { i as fedimintDatabase, n as createIrohWebEndpoint, o as loadFedimintSdk, r as irohRelayProblem, t as DEFAULT_IROH_RELAYS } from "./irohWeb
|
|
7
|
+
import { a as sealSeed, i as newDeviceKey, o as unsealSeed, r as intentRepository } from "./networkReason-CetKkTPo.js";
|
|
8
|
+
import { $ as setSingleWriterGate, A as LightningCards, B as breezDatabaseInUse, C as FileAppender, D as streamStored, E as storedSize, F as ProviderSources, G as ModeChanged, H as dropBreezDatabase, I as sourceKey, J as networkLabel, K as ModeGate, L as CASHU_MINT_SOURCE, M as isRecoveryPhrase, N as normalizePhrase, P as lightningPaying, Q as setAwayWallets, R as cashuMint, S as WALLET_TYPES, T as removeStored, U as iceServerProblem, V as breezDatabasesFor, W as iceServerSetting, X as refuseAway, Y as awayFrom, Z as requireTurn, _ as crossNetwork, a as storedGroupFiles, b as walletInstances, c as receivedPairedReply, d as paymentAlias, f as paymentWireId, g as createTiming, h as createFailure, i as groupOfFile, j as cardsKey, k as CASHU_CARD, l as replyRef, m as assertConfirmedReal, n as groupFileId, o as groupReply, p as WALLET_NAMES, q as WrongNetworkError, r as groupFileText, s as pairedWireReply, t as GroupFileDesk, u as replyTo, v as paymentNetwork, w as readStored$1, y as paymentNetworksOf, z as breezDatabase } from "./groupFiles-IEUW3DQz.js";
|
|
9
|
+
import { i as fedimintDatabase, n as createIrohWebEndpoint, o as loadFedimintSdk, r as irohRelayProblem, t as DEFAULT_IROH_RELAYS } from "./irohWeb-4oChIOIp.js";
|
|
8
10
|
import { c as isTestMint, d as paysItsOwnInvoices, l as isWorthlessMint, n as DEFAULT_MINTS, o as fakesLightning, r as TEST_MINT, t as BITCOIN_INVOICE_ON_TESTNET, u as mintNetwork } from "./mints-BrlY773j.js";
|
|
11
|
+
import { _ as reserveAdapterIds, f as setOwnDidSource, g as registeredOnchainProviders, h as registeredLightningProviders, l as chosenResolver, n as resolveAtprotoDid, p as onAdaptersChanged, t as assertPublicServer, v as boundedIdentityFetch, y as verifyIdentity } from "./resolve-CouHhCpJ.js";
|
|
9
12
|
import { c as fileSpace, f as FILE_BYTES_STEP, o as fileBytes, r as blobDigest, s as fileBytesOf, u as removeFileBytes } from "./fileBytes-Di7VZtmU.js";
|
|
10
|
-
import { n as MAX_BACKUP_BYTES, t as BACKUP_MEDIA_TYPE } from "./envelope-
|
|
13
|
+
import { n as MAX_BACKUP_BYTES, t as BACKUP_MEDIA_TYPE } from "./envelope-WjingYSc.js";
|
|
11
14
|
import { t as hyperdhtRelayProblem } from "./hyperdhtRelay-BT_wRxab.js";
|
|
12
15
|
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
13
16
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
@@ -15,13 +18,10 @@ import { base64, base64url, base64urlnopad, bech32, hex } from "@scure/base";
|
|
|
15
18
|
import { bytesToHex, concatBytes, hexToBytes, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
16
19
|
import { chacha20poly1305 } from "@noble/ciphers/chacha.js";
|
|
17
20
|
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
21
|
+
import { verifyEvent } from "nostr-tools/pure";
|
|
18
22
|
import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from "@scure/bip39";
|
|
19
23
|
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
20
24
|
import { Amount, MeltChangeError, OutputData, Wallet, decodePaymentRequest, deserializeSwapPreview, getEncodedToken, getTokenMetadata, isMintOperationError, serializeSwapPreview } from "@cashu/cashu-ts";
|
|
21
|
-
import { Address, OutScript, Transaction } from "@scure/btc-signer";
|
|
22
|
-
import * as nip04 from "nostr-tools/nip04";
|
|
23
|
-
import * as nip44 from "nostr-tools/nip44";
|
|
24
|
-
import { finalizeEvent, getPublicKey, verifyEvent } from "nostr-tools/pure";
|
|
25
25
|
import { decode, noteEncode, npubEncode } from "nostr-tools/nip19";
|
|
26
26
|
/** Evidence older than this says nothing about the clock now. */
|
|
27
27
|
var CLOCK_EVIDENCE_MS = 18e5;
|
|
@@ -183,9 +183,23 @@ async function migrateWalletNetworks(now = Date.now()) {
|
|
|
183
183
|
return report;
|
|
184
184
|
}
|
|
185
185
|
//#endregion
|
|
186
|
+
//#region ../browser/src/engine/paymentAdapters/viewNews.ts
|
|
187
|
+
/**
|
|
188
|
+
* Whether a wallet's view reads otherwise after a poll: only then is the poll news to the engine, which makes the whole
|
|
189
|
+
* wallet view again for it. Compared as written, not by reference: a poll builds its history and expiry anew each time.
|
|
190
|
+
* A view that cannot be written out is news.
|
|
191
|
+
*/
|
|
192
|
+
function viewNews(before, after) {
|
|
193
|
+
try {
|
|
194
|
+
return JSON.stringify(before) !== JSON.stringify(after);
|
|
195
|
+
} catch {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//#endregion
|
|
186
200
|
//#region ../browser/src/engine/paymentAdapters/usdtWallet.ts
|
|
187
201
|
/** The Ethereum libraries, a large part of the app: loaded once a USDT wallet is made, opened or restored, not with the engine. */
|
|
188
|
-
var loadUsdt = () => import("./usdt-
|
|
202
|
+
var loadUsdt = () => import("./usdt-Fj8CQO2z.js");
|
|
189
203
|
/** Every new profile starts with this wallet: an address to receive on, nothing to set up. */
|
|
190
204
|
var DEFAULT_USDT = {
|
|
191
205
|
network: "ethereum",
|
|
@@ -222,6 +236,7 @@ var UsdtWallet = class {
|
|
|
222
236
|
balance: "0",
|
|
223
237
|
gasBalance: "0"
|
|
224
238
|
};
|
|
239
|
+
/** `changed(false)`: a balance read that found nothing new; the receipts are checked, the wallet view stays. */
|
|
225
240
|
constructor(network, changed) {
|
|
226
241
|
this.network = network;
|
|
227
242
|
this.changed = changed;
|
|
@@ -408,6 +423,7 @@ var UsdtWallet = class {
|
|
|
408
423
|
clearTimeout(this.timer);
|
|
409
424
|
const adapter = this.adapter;
|
|
410
425
|
if (!adapter) return;
|
|
426
|
+
const before = this.view;
|
|
411
427
|
try {
|
|
412
428
|
const address = await this.gate.within(adapter.address()), balances = await this.gate.within(adapter.balances());
|
|
413
429
|
if (adapter !== this.adapter) return;
|
|
@@ -427,7 +443,7 @@ var UsdtWallet = class {
|
|
|
427
443
|
error: "RPC unavailable. Balance may be stale."
|
|
428
444
|
};
|
|
429
445
|
}
|
|
430
|
-
this.changed();
|
|
446
|
+
this.changed(viewNews(before, this.view));
|
|
431
447
|
if (this.adapter) this.timer = setTimeout(() => void this.refresh(), 8e3);
|
|
432
448
|
}
|
|
433
449
|
/** Testnet on Sepolia: 1,000 TEST-USDT from Aave's faucet, paid with this wallet's own test ETH. */
|
|
@@ -632,7 +648,7 @@ async function restoreArkDatabase(walletId, snapshot) {
|
|
|
632
648
|
//#endregion
|
|
633
649
|
//#region ../browser/src/engine/paymentAdapters/arkWallet.ts
|
|
634
650
|
/** The Ark SDK, a large part of the app: loaded once an Ark wallet is made, opened or restored, not with the engine. */
|
|
635
|
-
var loadArkade = () => import("./arkade-
|
|
651
|
+
var loadArkade = () => import("./arkade-C_qDmHnm.js");
|
|
636
652
|
/** How long a Testnet wallet about to make way for another waits for its server to say what it holds. */
|
|
637
653
|
var RETIRE_READ_MS = 1e4;
|
|
638
654
|
/** Every new profile starts with this wallet: nothing to set up before receiving. */
|
|
@@ -683,6 +699,7 @@ var ArkWallet = class {
|
|
|
683
699
|
locked: true,
|
|
684
700
|
balance: 0
|
|
685
701
|
};
|
|
702
|
+
/** `changed(false)`: a balance read that found nothing new; the receipts are checked, the wallet view stays. */
|
|
686
703
|
constructor(network, changed) {
|
|
687
704
|
this.network = network;
|
|
688
705
|
this.changed = changed;
|
|
@@ -991,6 +1008,7 @@ var ArkWallet = class {
|
|
|
991
1008
|
});
|
|
992
1009
|
if (this.adapter !== adapter) return;
|
|
993
1010
|
const balancesRead = this.view.read || !failed.some((what) => what === "balance" || what === "incoming" || what === "recoverable");
|
|
1011
|
+
const before = this.view;
|
|
994
1012
|
this.view = {
|
|
995
1013
|
configured: true,
|
|
996
1014
|
locked: false,
|
|
@@ -1008,7 +1026,7 @@ var ArkWallet = class {
|
|
|
1008
1026
|
...expiresAt !== void 0 ? { expiresAt } : {},
|
|
1009
1027
|
error: failed.length ? `Could not read the ${failed.join(", ")} from the Ark provider. Last values may be stale.` : void 0
|
|
1010
1028
|
};
|
|
1011
|
-
this.changed();
|
|
1029
|
+
this.changed(viewNews(before, this.view));
|
|
1012
1030
|
if (this.adapter) this.timer = setTimeout(() => void this.refresh(), 1e4);
|
|
1013
1031
|
}
|
|
1014
1032
|
/** Swept outputs back into the balance (refused while expired ones wait for the sweep); the next refresh shows them. */
|
|
@@ -1076,10 +1094,10 @@ var NETWORK = {
|
|
|
1076
1094
|
signet: "Signet",
|
|
1077
1095
|
regtest: "Regtest"
|
|
1078
1096
|
};
|
|
1079
|
-
var loading$
|
|
1097
|
+
var loading$4;
|
|
1080
1098
|
/** Loads the WebAssembly once, on first use: profiles that never open a Bark wallet never fetch its 7.7 MB. */
|
|
1081
1099
|
function loadBarkSdk() {
|
|
1082
|
-
return loading$
|
|
1100
|
+
return loading$4 ??= (async () => {
|
|
1083
1101
|
const [bark, wasm] = await Promise.all([import("@secondts/bark/web"), import("./bark_ffi_wasm_bg-EY2uc0Bx.js")]);
|
|
1084
1102
|
await bark.default({ module_or_path: wasm.default });
|
|
1085
1103
|
return {
|
|
@@ -1118,7 +1136,7 @@ function loadBarkSdk() {
|
|
|
1118
1136
|
}
|
|
1119
1137
|
};
|
|
1120
1138
|
})().catch((error) => {
|
|
1121
|
-
loading$
|
|
1139
|
+
loading$4 = void 0;
|
|
1122
1140
|
throw error;
|
|
1123
1141
|
});
|
|
1124
1142
|
}
|
|
@@ -1483,6 +1501,7 @@ var BarkWallet = class {
|
|
|
1483
1501
|
locked: true,
|
|
1484
1502
|
balance: 0
|
|
1485
1503
|
};
|
|
1504
|
+
/** `changed(false)`: a balance read that found nothing new; the receipts are checked, the wallet view stays. */
|
|
1486
1505
|
constructor(network, changed, sdk = loadBarkSdk) {
|
|
1487
1506
|
this.network = network;
|
|
1488
1507
|
this.changed = changed;
|
|
@@ -1768,6 +1787,7 @@ var BarkWallet = class {
|
|
|
1768
1787
|
}
|
|
1769
1788
|
if (this.adapter !== adapter) return;
|
|
1770
1789
|
const balancesRead = this.view.read || balance !== void 0 && onchain !== void 0;
|
|
1790
|
+
const before = this.view;
|
|
1771
1791
|
this.view = {
|
|
1772
1792
|
...this.idle(adapter.config),
|
|
1773
1793
|
locked: false,
|
|
@@ -1782,7 +1802,7 @@ var BarkWallet = class {
|
|
|
1782
1802
|
expiry,
|
|
1783
1803
|
error: failed.length ? `Could not read the ${failed.join(", ")} from the Bark server. Last values may be stale.` : void 0
|
|
1784
1804
|
};
|
|
1785
|
-
this.changed();
|
|
1805
|
+
this.changed(viewNews(before, this.view));
|
|
1786
1806
|
if (this.adapter) this.timer = setTimeout(() => void this.refresh(), 1e4);
|
|
1787
1807
|
}
|
|
1788
1808
|
/** On-chain coins into Ark: the board confirms on-chain, then they are spendable. */
|
|
@@ -2639,10 +2659,10 @@ var FedimintAdapter = class {
|
|
|
2639
2659
|
};
|
|
2640
2660
|
//#endregion
|
|
2641
2661
|
//#region ../browser/src/engine/paymentAdapters/providers/breezSdk.ts
|
|
2642
|
-
var loading$
|
|
2662
|
+
var loading$3;
|
|
2643
2663
|
/** Loads the WebAssembly once, on first use: profiles that never pick Breez never fetch its 12.8 MB. */
|
|
2644
2664
|
function loadBreezSdk() {
|
|
2645
|
-
return loading$
|
|
2665
|
+
return loading$3 ??= (async () => {
|
|
2646
2666
|
const breez = await import("./web-DwgdhfcY.js");
|
|
2647
2667
|
await breez.default();
|
|
2648
2668
|
return { async connect({ network, mnemonic, apiKey, storage }) {
|
|
@@ -2658,11 +2678,11 @@ function loadBreezSdk() {
|
|
|
2658
2678
|
});
|
|
2659
2679
|
} };
|
|
2660
2680
|
})().catch((error) => {
|
|
2661
|
-
loading$
|
|
2681
|
+
loading$3 = void 0;
|
|
2662
2682
|
throw error;
|
|
2663
2683
|
});
|
|
2664
2684
|
}
|
|
2665
|
-
var hex$
|
|
2685
|
+
var hex$8 = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
2666
2686
|
var breezNetwork = (network) => network === "bitcoin" ? "mainnet" : "regtest";
|
|
2667
2687
|
var DISCONNECT_MS = 1e4;
|
|
2668
2688
|
/** One SDK instance per wallet storage: two over one database would each think the leaves are theirs. */
|
|
@@ -2674,7 +2694,7 @@ var open = /* @__PURE__ */ new Map();
|
|
|
2674
2694
|
*/
|
|
2675
2695
|
async function openBreez(params, sdk = loadBreezSdk, storage = breezDatabase) {
|
|
2676
2696
|
const name = await storage(params.network, params.mnemonic);
|
|
2677
|
-
const key = `${name}|${params.apiKey ? hex$
|
|
2697
|
+
const key = `${name}|${params.apiKey ? hex$8(sha256(new TextEncoder().encode(params.apiKey))).slice(0, 8) : ""}`;
|
|
2678
2698
|
let entry = open.get(key);
|
|
2679
2699
|
if (!entry) {
|
|
2680
2700
|
const wallet = sdk().then((module) => module.connect({
|
|
@@ -3008,6 +3028,7 @@ var SparkWallet = class {
|
|
|
3008
3028
|
locked: true,
|
|
3009
3029
|
balance: 0
|
|
3010
3030
|
};
|
|
3031
|
+
/** `changed(false)`: a balance read that found nothing new; the receipts are checked, the wallet view stays. */
|
|
3011
3032
|
constructor(network, changed, sdk = loadBreezSdk) {
|
|
3012
3033
|
this.network = network;
|
|
3013
3034
|
this.changed = changed;
|
|
@@ -3223,6 +3244,7 @@ var SparkWallet = class {
|
|
|
3223
3244
|
const history = await read("history", () => adapter.history(), this.view.history);
|
|
3224
3245
|
if (this.adapter !== adapter) return;
|
|
3225
3246
|
const balancesRead = this.view.read || !failed.includes("balance");
|
|
3247
|
+
const before = this.view;
|
|
3226
3248
|
this.view = {
|
|
3227
3249
|
...this.idle(this.saved),
|
|
3228
3250
|
locked: false,
|
|
@@ -3232,7 +3254,7 @@ var SparkWallet = class {
|
|
|
3232
3254
|
history,
|
|
3233
3255
|
error: failed.length ? `Could not read the ${failed.join(", ")} from Spark. Last values may be stale.` : void 0
|
|
3234
3256
|
};
|
|
3235
|
-
this.changed();
|
|
3257
|
+
this.changed(viewNews(before, this.view));
|
|
3236
3258
|
if (this.adapter) this.timer = setTimeout(() => void this.refresh(), sparkTiming.pollMs);
|
|
3237
3259
|
}
|
|
3238
3260
|
/** Where a contact pays one request: a Spark invoice made for it (its amount, its memo, a quarter of an hour). */
|
|
@@ -3344,6 +3366,17 @@ var PaymentCoordinator = class {
|
|
|
3344
3366
|
adapters;
|
|
3345
3367
|
changed;
|
|
3346
3368
|
active = /* @__PURE__ */ new Map();
|
|
3369
|
+
/**
|
|
3370
|
+
* The reviews still pending (an Approve may send them), by id: what `dropStale` looks at, so that its minute's sweep
|
|
3371
|
+
* does not read every review ever made. `null` until the store was read once. An id leaves when the sweep finds its
|
|
3372
|
+
* review no longer pending, or gone.
|
|
3373
|
+
*/
|
|
3374
|
+
pending = null;
|
|
3375
|
+
/** Reviews read from the store past the coordinator (`refreshWallet`'s list): the pending ones are swept too. */
|
|
3376
|
+
notePending(reviews) {
|
|
3377
|
+
const pending = this.pending ??= /* @__PURE__ */ new Set();
|
|
3378
|
+
for (const review of reviews) if (review.state === "pending") pending.add(review.id);
|
|
3379
|
+
}
|
|
3347
3380
|
constructor(repository, adapters, changed = () => {}) {
|
|
3348
3381
|
this.repository = repository;
|
|
3349
3382
|
this.adapters = adapters;
|
|
@@ -3374,6 +3407,7 @@ var PaymentCoordinator = class {
|
|
|
3374
3407
|
review,
|
|
3375
3408
|
prepared
|
|
3376
3409
|
});
|
|
3410
|
+
this.pending?.add(review.id);
|
|
3377
3411
|
this.changed(review);
|
|
3378
3412
|
return review;
|
|
3379
3413
|
}
|
|
@@ -3444,12 +3478,20 @@ var PaymentCoordinator = class {
|
|
|
3444
3478
|
*/
|
|
3445
3479
|
async dropStale(now = Date.now(), unpayable = () => false) {
|
|
3446
3480
|
const dropped = [];
|
|
3447
|
-
|
|
3448
|
-
|
|
3481
|
+
if (!this.pending) this.notePending((await this.repository.list()).map(({ review }) => review));
|
|
3482
|
+
const pending = this.pending;
|
|
3483
|
+
for (const id of [...pending]) {
|
|
3484
|
+
const review = (await this.repository.get(id))?.review;
|
|
3485
|
+
if (!review || review.state !== "pending") {
|
|
3486
|
+
pending.delete(id);
|
|
3487
|
+
continue;
|
|
3488
|
+
}
|
|
3489
|
+
if (review.expiresAt > now && !unpayable(review)) continue;
|
|
3449
3490
|
try {
|
|
3450
3491
|
const cancelled = await this.cancel(review.id);
|
|
3451
3492
|
if (cancelled.state === "cancelled") dropped.push(cancelled);
|
|
3452
3493
|
} catch {}
|
|
3494
|
+
pending.delete(id);
|
|
3453
3495
|
}
|
|
3454
3496
|
return dropped;
|
|
3455
3497
|
}
|
|
@@ -3781,7 +3823,7 @@ var PAGE = 100;
|
|
|
3781
3823
|
var PAGES = 10;
|
|
3782
3824
|
/** Without any record of a payment, it is called failed only this long after its invoice expired. */
|
|
3783
3825
|
var LOST_AFTER_MS = 36e5;
|
|
3784
|
-
var hex$
|
|
3826
|
+
var hex$7 = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
3785
3827
|
var sats$4 = (value) => {
|
|
3786
3828
|
const n = Number(value);
|
|
3787
3829
|
if (!Number.isSafeInteger(n) || n < 0) throw new Error("Breez returned an invalid amount");
|
|
@@ -3796,7 +3838,7 @@ function idempotencyKey(paymentHash, attempt) {
|
|
|
3796
3838
|
const b = sha256(new TextEncoder().encode(`ghostly-breez-pay:${paymentHash}:${attempt}`)).slice(0, 16);
|
|
3797
3839
|
b[6] = b[6] & 15 | 64;
|
|
3798
3840
|
b[8] = b[8] & 63 | 128;
|
|
3799
|
-
const h = hex$
|
|
3841
|
+
const h = hex$7(b);
|
|
3800
3842
|
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
3801
3843
|
}
|
|
3802
3844
|
/** The payment hash a Breez payment is about, when it is one (Lightning, or a Spark HTLC). */
|
|
@@ -4039,7 +4081,7 @@ var appLoader = async () => {
|
|
|
4039
4081
|
wasm: await fetch(url.default)
|
|
4040
4082
|
};
|
|
4041
4083
|
};
|
|
4042
|
-
var loading$
|
|
4084
|
+
var loading$2;
|
|
4043
4085
|
/**
|
|
4044
4086
|
* Loads the WebAssembly once, on first use: profiles that never set up a BDK wallet never fetch its 2 MB.
|
|
4045
4087
|
* The package is wasm-pack's `bundler` build, whose entry imports the `.wasm` as an ES module (which
|
|
@@ -4047,7 +4089,7 @@ var loading$1;
|
|
|
4047
4089
|
* that entry would.
|
|
4048
4090
|
*/
|
|
4049
4091
|
function loadBdk(loader = appLoader) {
|
|
4050
|
-
return loading$
|
|
4092
|
+
return loading$2 ??= (async () => {
|
|
4051
4093
|
const { glue, wasm } = await loader();
|
|
4052
4094
|
const imports = { [WASM_IMPORTS]: glue };
|
|
4053
4095
|
let instance;
|
|
@@ -4061,7 +4103,7 @@ function loadBdk(loader = appLoader) {
|
|
|
4061
4103
|
instance.exports.__wbindgen_start?.();
|
|
4062
4104
|
return glue;
|
|
4063
4105
|
})().catch((error) => {
|
|
4064
|
-
loading$
|
|
4106
|
+
loading$2 = void 0;
|
|
4065
4107
|
throw error;
|
|
4066
4108
|
});
|
|
4067
4109
|
}
|
|
@@ -4127,7 +4169,7 @@ var idbBdkStore = {
|
|
|
4127
4169
|
});
|
|
4128
4170
|
}
|
|
4129
4171
|
};
|
|
4130
|
-
var hex$
|
|
4172
|
+
var hex$6 = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4131
4173
|
var unhex = (text) => {
|
|
4132
4174
|
if (!/^([0-9a-f]{2})+$/.test(text)) throw new Error("Not hex");
|
|
4133
4175
|
return Uint8Array.from(text.match(/../g), (h) => parseInt(h, 16));
|
|
@@ -4341,7 +4383,7 @@ var BdkOnchain = class BdkOnchain {
|
|
|
4341
4383
|
throw new SourceConfigError(`bad descriptor: the recovery phrase does not make a ${SCRIPT[config.script].label} wallet (${message$3(error)})`, error);
|
|
4342
4384
|
}
|
|
4343
4385
|
const fingerprint = /\[([0-9a-f]{8})\//.exec(publicDescriptor)?.[1] ?? "";
|
|
4344
|
-
const key = `bdkWallet-${config.network}-${hex$
|
|
4386
|
+
const key = `bdkWallet-${config.network}-${hex$6(sha256(new TextEncoder().encode(publicDescriptor))).slice(0, 32)}`;
|
|
4345
4387
|
const walletStore = deps.store ?? idbBdkStore;
|
|
4346
4388
|
let stored = await walletStore.load(key);
|
|
4347
4389
|
if (stored) try {
|
|
@@ -4439,7 +4481,7 @@ var BdkOnchain = class BdkOnchain {
|
|
|
4439
4481
|
amount,
|
|
4440
4482
|
fee,
|
|
4441
4483
|
feeRate,
|
|
4442
|
-
signed: hex$
|
|
4484
|
+
signed: hex$6(tx.to_bytes())
|
|
4443
4485
|
};
|
|
4444
4486
|
}
|
|
4445
4487
|
/** What was signed is what was reviewed: one output of exactly the amount to the address, the rest ours. */
|
|
@@ -4692,435 +4734,6 @@ var bdk = {
|
|
|
4692
4734
|
}
|
|
4693
4735
|
};
|
|
4694
4736
|
//#endregion
|
|
4695
|
-
//#region ../browser/src/engine/paymentAdapters/providers/bitcoind.ts
|
|
4696
|
-
var BitcoindRpcError = class extends Error {
|
|
4697
|
-
kind;
|
|
4698
|
-
code;
|
|
4699
|
-
constructor(kind, message, code) {
|
|
4700
|
-
super(message);
|
|
4701
|
-
this.kind = kind;
|
|
4702
|
-
this.code = code;
|
|
4703
|
-
this.name = "BitcoindRpcError";
|
|
4704
|
-
}
|
|
4705
|
-
/** The node never ran the call: it was not sent, could not connect, or the credentials were refused. */
|
|
4706
|
-
get neverRan() {
|
|
4707
|
-
return this.kind === "refused" || this.kind === "connect" || this.kind === "auth";
|
|
4708
|
-
}
|
|
4709
|
-
};
|
|
4710
|
-
/** The Tauri command, through the Desktop host. */
|
|
4711
|
-
function tauriTransport$1(invoke) {
|
|
4712
|
-
if (!invoke) throw new Error("Bitcoin Core needs the Ghostly desktop app: a browser cannot reach its RPC");
|
|
4713
|
-
return (call) => invoke("bitcoind_rpc", { ...call });
|
|
4714
|
-
}
|
|
4715
|
-
/** Bitcoin Core's JSON-RPC error codes this module acts on (src/rpc/protocol.h). */
|
|
4716
|
-
var RPC = {
|
|
4717
|
-
INVALID_ADDRESS_OR_KEY: -5,
|
|
4718
|
-
VERIFY_ERROR: -25,
|
|
4719
|
-
VERIFY_REJECTED: -26,
|
|
4720
|
-
VERIFY_ALREADY_IN_CHAIN: -27,
|
|
4721
|
-
DESERIALIZATION_ERROR: -22
|
|
4722
|
-
};
|
|
4723
|
-
var CHAINS = {
|
|
4724
|
-
main: "bitcoin",
|
|
4725
|
-
test: "testnet",
|
|
4726
|
-
testnet4: "testnet",
|
|
4727
|
-
signet: "signet",
|
|
4728
|
-
regtest: "regtest"
|
|
4729
|
-
};
|
|
4730
|
-
/** A fee rate for the test networks when the node has too few blocks to estimate one (regtest always). */
|
|
4731
|
-
var TEST_FALLBACK_FEE_RATE = 2;
|
|
4732
|
-
var TXID = /^[0-9a-f]{64}$/;
|
|
4733
|
-
var WALLET_NAME = /^[A-Za-z0-9._-]{0,64}$/;
|
|
4734
|
-
/** Address encodings, for btc-signer to read the outputs back as addresses. */
|
|
4735
|
-
var TESTNET_ENCODING = {
|
|
4736
|
-
bech32: "tb",
|
|
4737
|
-
pubKeyHash: 111,
|
|
4738
|
-
scriptHash: 196,
|
|
4739
|
-
wif: 239
|
|
4740
|
-
};
|
|
4741
|
-
var BTC_NETWORKS = {
|
|
4742
|
-
bitcoin: {
|
|
4743
|
-
bech32: "bc",
|
|
4744
|
-
pubKeyHash: 0,
|
|
4745
|
-
scriptHash: 5,
|
|
4746
|
-
wif: 128
|
|
4747
|
-
},
|
|
4748
|
-
testnet: TESTNET_ENCODING,
|
|
4749
|
-
signet: TESTNET_ENCODING,
|
|
4750
|
-
mutinynet: TESTNET_ENCODING,
|
|
4751
|
-
regtest: {
|
|
4752
|
-
...TESTNET_ENCODING,
|
|
4753
|
-
bech32: "bcrt"
|
|
4754
|
-
}
|
|
4755
|
-
};
|
|
4756
|
-
var TX_OPTS = {
|
|
4757
|
-
allowUnknownOutputs: true,
|
|
4758
|
-
allowUnknownInputs: true,
|
|
4759
|
-
disableScriptCheck: true,
|
|
4760
|
-
allowLegacyWitnessUtxo: true
|
|
4761
|
-
};
|
|
4762
|
-
/** Whole sats as the exact decimal string bitcoind reads as BTC (never a float). */
|
|
4763
|
-
var satsToBtc = (sats) => {
|
|
4764
|
-
if (!Number.isSafeInteger(sats) || sats < 0) throw new Error("Amounts are whole sats");
|
|
4765
|
-
const digits = sats.toString().padStart(9, "0");
|
|
4766
|
-
return `${digits.slice(0, -8)}.${digits.slice(-8)}`;
|
|
4767
|
-
};
|
|
4768
|
-
/** A BTC amount from bitcoind (a JSON number with at most 8 decimals) as whole sats. */
|
|
4769
|
-
var btcToSats = (btc) => {
|
|
4770
|
-
if (typeof btc !== "number" || !Number.isFinite(btc)) throw new Error("The node returned an amount that is not a number");
|
|
4771
|
-
return Math.round(btc * 1e8);
|
|
4772
|
-
};
|
|
4773
|
-
var object = (value, what) => {
|
|
4774
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`The node returned no ${what}`);
|
|
4775
|
-
return value;
|
|
4776
|
-
};
|
|
4777
|
-
var text$2 = (value, what) => {
|
|
4778
|
-
if (typeof value !== "string" || !value) throw new Error(`The node returned no ${what}`);
|
|
4779
|
-
return value;
|
|
4780
|
-
};
|
|
4781
|
-
var BitcoindOnchain = class BitcoindOnchain {
|
|
4782
|
-
config;
|
|
4783
|
-
transport;
|
|
4784
|
-
closed = false;
|
|
4785
|
-
network;
|
|
4786
|
-
constructor(config, transport) {
|
|
4787
|
-
this.config = config;
|
|
4788
|
-
this.transport = transport;
|
|
4789
|
-
}
|
|
4790
|
-
/** Connects and checks the wallet is there and the node is on a network of `mode`. */
|
|
4791
|
-
static async connect(config, mode, transport, signal) {
|
|
4792
|
-
const provider = new BitcoindOnchain(config, transport);
|
|
4793
|
-
signal?.addEventListener("abort", () => void provider.close(), { once: true });
|
|
4794
|
-
const { network } = await provider.info();
|
|
4795
|
-
if (networkMode(network) !== mode) throw new Error(mode === "mainnet" ? `This node is on ${network}, not Bitcoin mainnet` : "This node is on Bitcoin mainnet: set it up as a Mainnet wallet instead");
|
|
4796
|
-
if (object(await provider.call("getwalletinfo"), "wallet information").private_keys_enabled === false) throw new Error("This wallet has no private keys: it cannot sign a payment");
|
|
4797
|
-
return provider;
|
|
4798
|
-
}
|
|
4799
|
-
async call(method, ...params) {
|
|
4800
|
-
if (this.closed) throw new BitcoindRpcError("refused", "This Bitcoin Core source is closed");
|
|
4801
|
-
try {
|
|
4802
|
-
return await this.transport({
|
|
4803
|
-
...this.config,
|
|
4804
|
-
method,
|
|
4805
|
-
params
|
|
4806
|
-
});
|
|
4807
|
-
} catch (error) {
|
|
4808
|
-
if (error instanceof BitcoindRpcError) throw error;
|
|
4809
|
-
const failure = error;
|
|
4810
|
-
if (failure && typeof failure === "object" && typeof failure.kind === "string" && typeof failure.message === "string") throw new BitcoindRpcError(failure.kind, failure.message, failure.code);
|
|
4811
|
-
throw new BitcoindRpcError("transport", error instanceof Error ? error.message : String(error));
|
|
4812
|
-
}
|
|
4813
|
-
}
|
|
4814
|
-
async info() {
|
|
4815
|
-
const chain = object(await this.call("getblockchaininfo"), "chain information").chain;
|
|
4816
|
-
const network = typeof chain === "string" ? CHAINS[chain] : void 0;
|
|
4817
|
-
if (!network) throw new Error(`Ghostly does not know the chain "${String(chain).slice(0, 20)}"`);
|
|
4818
|
-
this.network = network;
|
|
4819
|
-
return {
|
|
4820
|
-
network,
|
|
4821
|
-
alias: this.config.wallet || "Bitcoin Core"
|
|
4822
|
-
};
|
|
4823
|
-
}
|
|
4824
|
-
async receiveAddress() {
|
|
4825
|
-
return text$2(await this.call("getnewaddress", "Ghostly"), "address");
|
|
4826
|
-
}
|
|
4827
|
-
async balance() {
|
|
4828
|
-
const mine = object(object(await this.call("getbalances"), "balances").mine, "balances");
|
|
4829
|
-
return {
|
|
4830
|
-
confirmed: btcToSats(mine.trusted),
|
|
4831
|
-
unconfirmed: btcToSats(mine.untrusted_pending)
|
|
4832
|
-
};
|
|
4833
|
-
}
|
|
4834
|
-
async feeRate(request) {
|
|
4835
|
-
if (request.feeRate !== void 0) {
|
|
4836
|
-
if (!Number.isFinite(request.feeRate) || request.feeRate < 1 || request.feeRate > 1e4) throw new Error("The fee rate must be between 1 and 10,000 sat/vB");
|
|
4837
|
-
return request.feeRate;
|
|
4838
|
-
}
|
|
4839
|
-
const estimate = object(await this.call("estimatesmartfee", 6), "fee estimate");
|
|
4840
|
-
if (typeof estimate.feerate === "number" && estimate.feerate > 0) return Math.max(1, Math.ceil(estimate.feerate * 1e5 * 1e3) / 1e3);
|
|
4841
|
-
if ((this.network ?? (await this.info()).network) === "bitcoin") throw new Error("Your node cannot estimate a fee yet. Nothing was sent.");
|
|
4842
|
-
return TEST_FALLBACK_FEE_RATE;
|
|
4843
|
-
}
|
|
4844
|
-
async prepareSend(request) {
|
|
4845
|
-
if (!Number.isSafeInteger(request.amount) || request.amount <= 0) throw new Error("Amounts are whole sats");
|
|
4846
|
-
const network = this.network ?? (await this.info()).network;
|
|
4847
|
-
const feeRate = await this.feeRate(request);
|
|
4848
|
-
const funded = object(await this.call("walletcreatefundedpsbt", [], [{ [request.address]: satsToBtc(request.amount) }], 0, {
|
|
4849
|
-
fee_rate: feeRate,
|
|
4850
|
-
lockUnspents: true,
|
|
4851
|
-
replaceable: true
|
|
4852
|
-
}), "funded transaction");
|
|
4853
|
-
const inputs = this.outpoints(Transaction.fromPSBT(base64.decode(text$2(funded.psbt, "funded transaction")), TX_OPTS));
|
|
4854
|
-
try {
|
|
4855
|
-
const fee = btcToSats(funded.fee);
|
|
4856
|
-
if (fee > request.feeCap) throw new Error(`The fee (${fee} sats) is above your limit of ${request.feeCap}`);
|
|
4857
|
-
const signed = object(await this.call("walletprocesspsbt", funded.psbt, true, "ALL"), "signed transaction");
|
|
4858
|
-
if (signed.complete !== true) throw new Error("The wallet could not sign this payment (is it locked, or watch-only?)");
|
|
4859
|
-
const final = object(await this.call("finalizepsbt", signed.psbt, false), "finalized transaction");
|
|
4860
|
-
if (final.complete !== true) throw new Error("The wallet could not finalize this payment");
|
|
4861
|
-
return this.check(Transaction.fromPSBT(base64.decode(text$2(final.psbt, "finalized transaction")), TX_OPTS), request, inputs, feeRate, network);
|
|
4862
|
-
} catch (error) {
|
|
4863
|
-
await this.unlock(inputs).catch(() => {});
|
|
4864
|
-
throw error;
|
|
4865
|
-
}
|
|
4866
|
-
}
|
|
4867
|
-
/** The transaction the node signed is exactly the one asked for, before anyone reviews it. */
|
|
4868
|
-
check(tx, request, funded, feeRate, network) {
|
|
4869
|
-
const same = (a, b) => a.length === b.length && a.every((o, i) => o.txid === b[i].txid && o.vout === b[i].vout);
|
|
4870
|
-
if (!same(this.outpoints(tx), funded)) throw new Error("The node signed other coins than it funded the payment with");
|
|
4871
|
-
const script = (() => {
|
|
4872
|
-
try {
|
|
4873
|
-
return hex.encode(OutScript.encode(Address(BTC_NETWORKS[network]).decode(request.address)));
|
|
4874
|
-
} catch {
|
|
4875
|
-
return;
|
|
4876
|
-
}
|
|
4877
|
-
})();
|
|
4878
|
-
let paid = 0;
|
|
4879
|
-
for (let i = 0; i < tx.outputsLength; i++) {
|
|
4880
|
-
const output = tx.getOutput(i);
|
|
4881
|
-
if (script !== void 0 && output.script && hex.encode(output.script) === script) paid += Number(output.amount ?? 0n);
|
|
4882
|
-
}
|
|
4883
|
-
if (paid !== request.amount) throw new Error("The node built a transaction that does not pay the address and amount asked for");
|
|
4884
|
-
const fee = Number(tx.fee);
|
|
4885
|
-
if (!Number.isSafeInteger(fee) || fee < 0 || fee > request.feeCap) throw new Error(`The fee (${fee} sats) is above your limit of ${request.feeCap}`);
|
|
4886
|
-
const txid = tx.id;
|
|
4887
|
-
if (!TXID.test(txid)) throw new Error("The node returned a transaction without a valid id");
|
|
4888
|
-
return {
|
|
4889
|
-
txid,
|
|
4890
|
-
address: request.address,
|
|
4891
|
-
amount: request.amount,
|
|
4892
|
-
fee,
|
|
4893
|
-
feeRate,
|
|
4894
|
-
signed: hex.encode(tx.extract())
|
|
4895
|
-
};
|
|
4896
|
-
}
|
|
4897
|
-
outpoints(tx) {
|
|
4898
|
-
const out = [];
|
|
4899
|
-
for (let i = 0; i < tx.inputsLength; i++) {
|
|
4900
|
-
const input = tx.getInput(i);
|
|
4901
|
-
if (!input.txid || input.index === void 0) throw new Error("The node returned a transaction with an unreadable input");
|
|
4902
|
-
out.push({
|
|
4903
|
-
txid: hex.encode(input.txid),
|
|
4904
|
-
vout: input.index
|
|
4905
|
-
});
|
|
4906
|
-
}
|
|
4907
|
-
return out;
|
|
4908
|
-
}
|
|
4909
|
-
async unlock(outpoints) {
|
|
4910
|
-
if (outpoints.length) await this.call("lockunspent", true, outpoints);
|
|
4911
|
-
}
|
|
4912
|
-
signedTx(prepared) {
|
|
4913
|
-
if (!/^[0-9a-f]+$/.test(prepared.signed)) throw new Error("This prepared payment has no signed transaction");
|
|
4914
|
-
const tx = Transaction.fromRaw(hex.decode(prepared.signed), TX_OPTS);
|
|
4915
|
-
if (tx.id !== prepared.txid) throw new Error("This prepared payment's transaction is not the one reviewed");
|
|
4916
|
-
return tx;
|
|
4917
|
-
}
|
|
4918
|
-
async broadcast(prepared) {
|
|
4919
|
-
const tx = this.signedTx(prepared);
|
|
4920
|
-
let txid;
|
|
4921
|
-
try {
|
|
4922
|
-
txid = await this.call("sendrawtransaction", prepared.signed);
|
|
4923
|
-
} catch (error) {
|
|
4924
|
-
if (!(error instanceof BitcoindRpcError)) throw error;
|
|
4925
|
-
if (error.kind === "rpc" && error.code === RPC.VERIFY_ALREADY_IN_CHAIN) return prepared.txid;
|
|
4926
|
-
const rejected = error.kind === "rpc" && [
|
|
4927
|
-
RPC.VERIFY_ERROR,
|
|
4928
|
-
RPC.VERIFY_REJECTED,
|
|
4929
|
-
RPC.DESERIALIZATION_ERROR
|
|
4930
|
-
].includes(error.code ?? 0);
|
|
4931
|
-
if (!error.neverRan && !rejected) throw error;
|
|
4932
|
-
const seen = rejected ? await this.known(prepared.txid) : await this.known(prepared.txid).catch(() => void 0);
|
|
4933
|
-
if (seen) {
|
|
4934
|
-
if (["mempool", "confirmed"].includes((await this.status(prepared)).state)) return prepared.txid;
|
|
4935
|
-
throw error;
|
|
4936
|
-
}
|
|
4937
|
-
if (seen === false) await this.unlock(this.outpoints(tx)).catch(() => {});
|
|
4938
|
-
throw new NothingSpentError(`Your node did not send it: ${error.message}`);
|
|
4939
|
-
}
|
|
4940
|
-
if (txid !== prepared.txid) throw new Error("The node broadcast a different transaction than the one reviewed");
|
|
4941
|
-
return prepared.txid;
|
|
4942
|
-
}
|
|
4943
|
-
/** Whether the wallet knows this transaction (it does once its node accepted it, even if it was dropped since). */
|
|
4944
|
-
async known(txid) {
|
|
4945
|
-
try {
|
|
4946
|
-
await this.call("gettransaction", txid);
|
|
4947
|
-
return true;
|
|
4948
|
-
} catch (error) {
|
|
4949
|
-
if (error instanceof BitcoindRpcError && error.kind === "rpc" && error.code === RPC.INVALID_ADDRESS_OR_KEY) return false;
|
|
4950
|
-
throw error;
|
|
4951
|
-
}
|
|
4952
|
-
}
|
|
4953
|
-
async status(prepared) {
|
|
4954
|
-
if (!TXID.test(prepared.txid)) throw new Error("Not a transaction id");
|
|
4955
|
-
let tx;
|
|
4956
|
-
try {
|
|
4957
|
-
tx = object(await this.call("gettransaction", prepared.txid), "transaction");
|
|
4958
|
-
} catch (error) {
|
|
4959
|
-
if (error instanceof BitcoindRpcError && error.kind === "rpc" && error.code === RPC.INVALID_ADDRESS_OR_KEY) return {
|
|
4960
|
-
state: "missing",
|
|
4961
|
-
confirmations: 0
|
|
4962
|
-
};
|
|
4963
|
-
throw error;
|
|
4964
|
-
}
|
|
4965
|
-
const confirmations = typeof tx.confirmations === "number" ? tx.confirmations : 0;
|
|
4966
|
-
if (confirmations > 0) return {
|
|
4967
|
-
state: "confirmed",
|
|
4968
|
-
confirmations
|
|
4969
|
-
};
|
|
4970
|
-
if (confirmations < 0) return {
|
|
4971
|
-
state: "conflicted",
|
|
4972
|
-
confirmations: 0
|
|
4973
|
-
};
|
|
4974
|
-
if (Array.isArray(tx.mempoolconflicts) && tx.mempoolconflicts.length > 0) return {
|
|
4975
|
-
state: "conflicted",
|
|
4976
|
-
confirmations: 0
|
|
4977
|
-
};
|
|
4978
|
-
try {
|
|
4979
|
-
await this.call("getmempoolentry", prepared.txid);
|
|
4980
|
-
return {
|
|
4981
|
-
state: "mempool",
|
|
4982
|
-
confirmations: 0
|
|
4983
|
-
};
|
|
4984
|
-
} catch (error) {
|
|
4985
|
-
if (error instanceof BitcoindRpcError && error.kind === "rpc" && error.code === RPC.INVALID_ADDRESS_OR_KEY) return {
|
|
4986
|
-
state: "missing",
|
|
4987
|
-
confirmations: 0
|
|
4988
|
-
};
|
|
4989
|
-
throw error;
|
|
4990
|
-
}
|
|
4991
|
-
}
|
|
4992
|
-
async release(prepared) {
|
|
4993
|
-
await this.unlock(this.outpoints(this.signedTx(prepared)));
|
|
4994
|
-
}
|
|
4995
|
-
async history(limit) {
|
|
4996
|
-
const count = Math.max(1, Math.min(200, Math.floor(limit) * 3));
|
|
4997
|
-
const entries = await this.call("listtransactions", "*", count, 0, true);
|
|
4998
|
-
if (!Array.isArray(entries)) throw new Error("The node returned no transaction list");
|
|
4999
|
-
const byTxid = /* @__PURE__ */ new Map();
|
|
5000
|
-
for (const raw of entries) {
|
|
5001
|
-
if (!raw || typeof raw.txid !== "string" || !TXID.test(raw.txid) || raw.category === "orphan") continue;
|
|
5002
|
-
const tx = byTxid.get(raw.txid) ?? {
|
|
5003
|
-
txid: raw.txid,
|
|
5004
|
-
amount: 0,
|
|
5005
|
-
confirmations: typeof raw.confirmations === "number" ? Math.max(0, raw.confirmations) : 0
|
|
5006
|
-
};
|
|
5007
|
-
tx.amount += btcToSats(raw.amount);
|
|
5008
|
-
if (raw.category === "send" && typeof raw.fee === "number" && tx.fee === void 0) tx.fee = Math.abs(btcToSats(raw.fee));
|
|
5009
|
-
if (typeof raw.time === "number") tx.timestamp = raw.time * 1e3;
|
|
5010
|
-
byTxid.delete(raw.txid);
|
|
5011
|
-
byTxid.set(raw.txid, tx);
|
|
5012
|
-
}
|
|
5013
|
-
return [...byTxid.values()].reverse().slice(0, Math.max(0, limit));
|
|
5014
|
-
}
|
|
5015
|
-
/** The wallet's receives on this address, newest first: only `receive` entries paying exactly it count. */
|
|
5016
|
-
async received(address) {
|
|
5017
|
-
const entries = await this.call("listtransactions", "*", 200, 0, true);
|
|
5018
|
-
if (!Array.isArray(entries)) throw new Error("The node returned no transaction list");
|
|
5019
|
-
const byTxid = /* @__PURE__ */ new Map();
|
|
5020
|
-
for (const raw of entries) {
|
|
5021
|
-
if (!raw || raw.category !== "receive" || raw.address !== address || typeof raw.txid !== "string" || !TXID.test(raw.txid)) continue;
|
|
5022
|
-
const tx = byTxid.get(raw.txid) ?? {
|
|
5023
|
-
txid: raw.txid,
|
|
5024
|
-
amount: 0,
|
|
5025
|
-
confirmations: typeof raw.confirmations === "number" ? Math.max(0, raw.confirmations) : 0
|
|
5026
|
-
};
|
|
5027
|
-
tx.amount += btcToSats(raw.amount);
|
|
5028
|
-
byTxid.delete(raw.txid);
|
|
5029
|
-
byTxid.set(raw.txid, tx);
|
|
5030
|
-
}
|
|
5031
|
-
return [...byTxid.values()].reverse();
|
|
5032
|
-
}
|
|
5033
|
-
async close() {
|
|
5034
|
-
this.closed = true;
|
|
5035
|
-
}
|
|
5036
|
-
};
|
|
5037
|
-
/** `user:password` in the password field (the .cookie file's contents) when no user is given. */
|
|
5038
|
-
function credentials({ config, secrets }) {
|
|
5039
|
-
const password = secrets.password ?? "";
|
|
5040
|
-
const user = config.user?.trim() ?? "";
|
|
5041
|
-
if (!user && password.includes(":")) {
|
|
5042
|
-
const at = password.indexOf(":");
|
|
5043
|
-
return {
|
|
5044
|
-
user: password.slice(0, at),
|
|
5045
|
-
password: password.slice(at + 1)
|
|
5046
|
-
};
|
|
5047
|
-
}
|
|
5048
|
-
return {
|
|
5049
|
-
user,
|
|
5050
|
-
password
|
|
5051
|
-
};
|
|
5052
|
-
}
|
|
5053
|
-
var bitcoindRpc = {
|
|
5054
|
-
id: "bitcoind",
|
|
5055
|
-
label: "Bitcoin Core",
|
|
5056
|
-
kind: "onchain",
|
|
5057
|
-
description: "A wallet on your own Bitcoin Core node, over its RPC. Your node holds the keys and signs. Needs the desktop app.",
|
|
5058
|
-
networks: [
|
|
5059
|
-
"bitcoin",
|
|
5060
|
-
"testnet",
|
|
5061
|
-
"signet",
|
|
5062
|
-
"regtest"
|
|
5063
|
-
],
|
|
5064
|
-
platforms: ["desktop"],
|
|
5065
|
-
experimental: true,
|
|
5066
|
-
fields: [
|
|
5067
|
-
{
|
|
5068
|
-
name: "url",
|
|
5069
|
-
label: "RPC address",
|
|
5070
|
-
kind: "url",
|
|
5071
|
-
placeholder: "http://127.0.0.1:8332",
|
|
5072
|
-
defaults: {
|
|
5073
|
-
mainnet: "http://127.0.0.1:8332",
|
|
5074
|
-
testnet: "http://127.0.0.1:38332"
|
|
5075
|
-
},
|
|
5076
|
-
help: "Your node's rpcbind and rpcport: 8332 mainnet, 38332 signet, 48332 testnet4, 18443 regtest."
|
|
5077
|
-
},
|
|
5078
|
-
{
|
|
5079
|
-
name: "wallet",
|
|
5080
|
-
label: "Wallet name",
|
|
5081
|
-
kind: "text",
|
|
5082
|
-
optional: true,
|
|
5083
|
-
placeholder: "ghostly",
|
|
5084
|
-
help: "A wallet loaded in the node (bitcoin-cli listwallets). Empty: its default wallet."
|
|
5085
|
-
},
|
|
5086
|
-
{
|
|
5087
|
-
name: "user",
|
|
5088
|
-
label: "RPC user",
|
|
5089
|
-
kind: "text",
|
|
5090
|
-
optional: true,
|
|
5091
|
-
help: "Leave empty to paste the .cookie file below."
|
|
5092
|
-
},
|
|
5093
|
-
{
|
|
5094
|
-
name: "password",
|
|
5095
|
-
label: "RPC password or cookie",
|
|
5096
|
-
kind: "secret",
|
|
5097
|
-
help: "rpcpassword, or the contents of the node's .cookie file (it changes when bitcoind restarts)."
|
|
5098
|
-
}
|
|
5099
|
-
],
|
|
5100
|
-
validate(settings) {
|
|
5101
|
-
const url = (() => {
|
|
5102
|
-
try {
|
|
5103
|
-
return new URL(settings.config.url?.trim() ?? "");
|
|
5104
|
-
} catch {
|
|
5105
|
-
return;
|
|
5106
|
-
}
|
|
5107
|
-
})();
|
|
5108
|
-
if (!url || !["http:", "https:"].includes(url.protocol)) throw new Error("Enter the node's RPC address, like http://127.0.0.1:8332");
|
|
5109
|
-
if (url.username || url.password) throw new Error("Put the RPC user and password in their own fields, not in the address");
|
|
5110
|
-
if (url.search || url.hash) throw new Error("The RPC address cannot have a query or fragment");
|
|
5111
|
-
if (!WALLET_NAME.test(settings.config.wallet?.trim() ?? "") || [".", ".."].includes(settings.config.wallet?.trim() ?? "")) throw new Error("A wallet name has only letters, digits, '-', '_' and '.'");
|
|
5112
|
-
const { user, password } = credentials(settings);
|
|
5113
|
-
if (!user || !password) throw new Error("Enter the RPC user and password, or paste the .cookie file");
|
|
5114
|
-
},
|
|
5115
|
-
async create(settings, host) {
|
|
5116
|
-
return BitcoindOnchain.connect({
|
|
5117
|
-
url: settings.config.url.trim(),
|
|
5118
|
-
wallet: settings.config.wallet?.trim() ?? "",
|
|
5119
|
-
...credentials(settings)
|
|
5120
|
-
}, host.mode, tauriTransport$1(host.invoke), host.signal);
|
|
5121
|
-
}
|
|
5122
|
-
};
|
|
5123
|
-
//#endregion
|
|
5124
4737
|
//#region ../browser/src/engine/paymentAdapters/providers/fedimint.ts
|
|
5125
4738
|
var FEDIMINT_SOURCE = "fedimint";
|
|
5126
4739
|
/** How long an invoice made here stays payable. */
|
|
@@ -5692,15 +5305,15 @@ var CLN_RUNE_COMMAND = `lightning-cli createrune restrictions='[[${CLN_METHODS.m
|
|
|
5692
5305
|
var PAY_TIMEOUT_S$1 = 60;
|
|
5693
5306
|
var CALL_TIMEOUT_MS = 3e4;
|
|
5694
5307
|
var PAY_CALL_TIMEOUT_MS = 9e4;
|
|
5695
|
-
var INVOICE_EXPIRY_S$
|
|
5696
|
-
var NETWORKS$
|
|
5308
|
+
var INVOICE_EXPIRY_S$1 = 3600;
|
|
5309
|
+
var NETWORKS$1 = {
|
|
5697
5310
|
bitcoin: "bitcoin",
|
|
5698
5311
|
testnet: "testnet",
|
|
5699
5312
|
testnet4: "testnet",
|
|
5700
5313
|
signet: "signet",
|
|
5701
5314
|
regtest: "regtest"
|
|
5702
5315
|
};
|
|
5703
|
-
var HEX64$
|
|
5316
|
+
var HEX64$3 = /^[0-9a-f]{64}$/;
|
|
5704
5317
|
var NODE_ID = /^0[23][0-9a-f]{64}$/;
|
|
5705
5318
|
var msatToSat = (msat) => {
|
|
5706
5319
|
const value = typeof msat === "number" ? msat : typeof msat === "string" ? Number(msat.replace(/msat$/, "")) : NaN;
|
|
@@ -5778,7 +5391,7 @@ var CoreLightning = class CoreLightning {
|
|
|
5778
5391
|
}
|
|
5779
5392
|
async info() {
|
|
5780
5393
|
const info = await this.rpc.call("getinfo", {}, { timeoutMs: CALL_TIMEOUT_MS });
|
|
5781
|
-
const network = NETWORKS$
|
|
5394
|
+
const network = NETWORKS$1[info?.network];
|
|
5782
5395
|
if (!network) throw new Error(`The node is on a network Ghostly does not know (${String(info?.network).slice(0, 20)})`);
|
|
5783
5396
|
if (this.nodeId && info.id !== this.nodeId) throw new Error("The node answered with another id");
|
|
5784
5397
|
return {
|
|
@@ -5804,7 +5417,7 @@ var CoreLightning = class CoreLightning {
|
|
|
5804
5417
|
amount_msat: amount * 1e3,
|
|
5805
5418
|
label,
|
|
5806
5419
|
description: (memo || "Ghostly").slice(0, 200),
|
|
5807
|
-
expiry: INVOICE_EXPIRY_S$
|
|
5420
|
+
expiry: INVOICE_EXPIRY_S$1
|
|
5808
5421
|
}, { timeoutMs: CALL_TIMEOUT_MS });
|
|
5809
5422
|
const decoded = typeof created?.bolt11 === "string" && created.bolt11.length < 8e3 ? decodeBolt11(created.bolt11) : null;
|
|
5810
5423
|
if (!decoded || decoded.paymentHash !== created.payment_hash || decoded.amountSat !== amount) throw new Error("The node returned an invoice that does not match the request");
|
|
@@ -5904,7 +5517,7 @@ var CoreLightning = class CoreLightning {
|
|
|
5904
5517
|
}
|
|
5905
5518
|
/** What the node's own record of this payment hash says: `none` when it never tried. */
|
|
5906
5519
|
async lookup(paymentHash) {
|
|
5907
|
-
if (!HEX64$
|
|
5520
|
+
if (!HEX64$3.test(paymentHash)) throw new Error("Invalid payment hash");
|
|
5908
5521
|
const { pays } = await this.rpc.call("listpays", { payment_hash: paymentHash }, { timeoutMs: CALL_TIMEOUT_MS });
|
|
5909
5522
|
const mine = (pays ?? []).filter((p) => p.payment_hash === paymentHash);
|
|
5910
5523
|
const complete = mine.find((p) => p.status === "complete");
|
|
@@ -5977,672 +5590,6 @@ var coreLightning = {
|
|
|
5977
5590
|
return CoreLightning.connect(settingsOf(settings), host.signal);
|
|
5978
5591
|
}
|
|
5979
5592
|
};
|
|
5980
|
-
//#endregion
|
|
5981
|
-
//#region ../browser/src/engine/paymentAdapters/providers/nwc.ts
|
|
5982
|
-
/**
|
|
5983
|
-
* Lightning through a wallet reached over Nostr Wallet Connect (NIP-47): Alby Hub, a node's NWC service, a
|
|
5984
|
-
* hosted wallet. The person pastes the wallet's `nostr+walletconnect://` URI; requests are signed with the
|
|
5985
|
-
* key it carries, encrypted to the wallet (NIP-44, or NIP-04 when its info event offers nothing else) and
|
|
5986
|
-
* sent through its relay; the answers come back the same way. That wallet holds the sats, applies its own
|
|
5987
|
-
* fee limit, and is custodial or not depending on what it is.
|
|
5988
|
-
*
|
|
5989
|
-
* Money safety (PROVIDERS.md): a request that never reached the relay, or an error code the wallet answers
|
|
5990
|
-
* before it tries to pay, is `NothingSpentError`. A `pay_invoice` whose answer does not come back in time,
|
|
5991
|
-
* or any other error, is an unknown outcome, and `paymentStatus` asks `lookup_invoice` until it ends.
|
|
5992
|
-
*/
|
|
5993
|
-
var KIND = {
|
|
5994
|
-
info: 13194,
|
|
5995
|
-
request: 23194,
|
|
5996
|
-
response: 23195
|
|
5997
|
-
};
|
|
5998
|
-
/** A relay message, an encrypted answer, an info event, an invoice: nothing bigger is read. */
|
|
5999
|
-
var MAX_MESSAGE = 262144;
|
|
6000
|
-
var MAX_CONTENT = 65536;
|
|
6001
|
-
var MAX_INFO = 4096;
|
|
6002
|
-
var MAX_INVOICE = 7089;
|
|
6003
|
-
var MAX_URI = 2048;
|
|
6004
|
-
var MAX_RELAYS = 5;
|
|
6005
|
-
var CONNECT_MS = 1e4;
|
|
6006
|
-
var ACK_MS = 1e4;
|
|
6007
|
-
var REQUEST_MS$1 = 2e4;
|
|
6008
|
-
/** A payment may take a while to route; an answer after this is an unknown outcome, looked up later. */
|
|
6009
|
-
var PAY_MS = 9e4;
|
|
6010
|
-
/** The wallet must not start a payment it receives after this (NIP-47 `expiration`). */
|
|
6011
|
-
var PAY_EXPIRY_S = 60;
|
|
6012
|
-
/** A payment the wallet never heard of is failed once its invoice can no longer be paid, with room for clocks. */
|
|
6013
|
-
var EXPIRED_MARGIN_MS = 6e5;
|
|
6014
|
-
var INVOICE_EXPIRY_S$1 = 3600;
|
|
6015
|
-
/** Error codes a wallet answers before it tries to pay: nothing can have left it. (`BAD_REQUEST`: Alby Hub, an invoice it cannot read.) */
|
|
6016
|
-
var REFUSED_BEFORE_PAYING = /* @__PURE__ */ new Set([
|
|
6017
|
-
"RATE_LIMITED",
|
|
6018
|
-
"NOT_IMPLEMENTED",
|
|
6019
|
-
"INSUFFICIENT_BALANCE",
|
|
6020
|
-
"QUOTA_EXCEEDED",
|
|
6021
|
-
"RESTRICTED",
|
|
6022
|
-
"UNAUTHORIZED",
|
|
6023
|
-
"UNSUPPORTED_ENCRYPTION",
|
|
6024
|
-
"BAD_REQUEST"
|
|
6025
|
-
]);
|
|
6026
|
-
var NETWORKS$1 = {
|
|
6027
|
-
mainnet: "bitcoin",
|
|
6028
|
-
bitcoin: "bitcoin",
|
|
6029
|
-
testnet: "testnet",
|
|
6030
|
-
testnet3: "testnet",
|
|
6031
|
-
testnet4: "testnet",
|
|
6032
|
-
signet: "signet",
|
|
6033
|
-
mutinynet: "mutinynet",
|
|
6034
|
-
regtest: "regtest"
|
|
6035
|
-
};
|
|
6036
|
-
var HEX64$3 = /^[0-9a-f]{64}$/;
|
|
6037
|
-
var hex$6 = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
6038
|
-
var bytes$2 = (text) => Uint8Array.from(text.match(/../g), (b) => parseInt(b, 16));
|
|
6039
|
-
var now$1 = () => Math.floor(Date.now() / 1e3);
|
|
6040
|
-
var isMsat = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
6041
|
-
/** Only secure relays, except on this machine (a local test relay). */
|
|
6042
|
-
function checkRelay(address) {
|
|
6043
|
-
let relay;
|
|
6044
|
-
try {
|
|
6045
|
-
relay = new URL(address);
|
|
6046
|
-
} catch {
|
|
6047
|
-
throw new Error("The connection URI has a relay that is not a URL");
|
|
6048
|
-
}
|
|
6049
|
-
if (relay.username || relay.password || relay.hash) throw new Error("The connection URI has a relay with credentials in it");
|
|
6050
|
-
const loopback = [
|
|
6051
|
-
"127.0.0.1",
|
|
6052
|
-
"localhost",
|
|
6053
|
-
"[::1]"
|
|
6054
|
-
].includes(relay.hostname);
|
|
6055
|
-
if (!(relay.protocol === "wss:" || relay.protocol === "ws:" && loopback)) throw new Error("Use a wallet whose relay is secure (wss://)");
|
|
6056
|
-
return relay.href;
|
|
6057
|
-
}
|
|
6058
|
-
/** Reads a `nostr+walletconnect://` URI. Its errors never repeat it: it holds a secret. */
|
|
6059
|
-
function parseNwcUri(input) {
|
|
6060
|
-
const text = (input ?? "").trim();
|
|
6061
|
-
if (text.length > MAX_URI) throw new Error("That connection URI is too long");
|
|
6062
|
-
const match = /^(?:nostr\+walletconnect|nostrwalletconnect):(?:\/\/)?([0-9a-fA-F]{64})\/?\?([^#]*)$/.exec(text);
|
|
6063
|
-
if (!match) throw new Error("That is not a Nostr Wallet Connect URI (nostr+walletconnect://…)");
|
|
6064
|
-
const params = new URLSearchParams(match[2]);
|
|
6065
|
-
const secret = (params.get("secret") ?? "").toLowerCase();
|
|
6066
|
-
if (!HEX64$3.test(secret) || /^0+$/.test(secret)) throw new Error("The connection URI has no valid secret");
|
|
6067
|
-
const relays = [...new Set(params.getAll("relay").map(checkRelay))];
|
|
6068
|
-
if (!relays.length) throw new Error("The connection URI names no relay");
|
|
6069
|
-
if (relays.length > MAX_RELAYS) throw new Error("The connection URI names too many relays");
|
|
6070
|
-
return {
|
|
6071
|
-
walletPubkey: match[1].toLowerCase(),
|
|
6072
|
-
relays,
|
|
6073
|
-
secret: bytes$2(secret)
|
|
6074
|
-
};
|
|
6075
|
-
}
|
|
6076
|
-
/** The request never reached the relay (not connected, refused by it): the wallet cannot have seen it. */
|
|
6077
|
-
var NotDelivered = class extends Error {
|
|
6078
|
-
constructor(message) {
|
|
6079
|
-
super(message);
|
|
6080
|
-
this.name = "NotDelivered";
|
|
6081
|
-
}
|
|
6082
|
-
};
|
|
6083
|
-
/** An error the wallet answered, with its NIP-47 code. */
|
|
6084
|
-
var NwcError = class extends Error {
|
|
6085
|
-
code;
|
|
6086
|
-
constructor(code, message) {
|
|
6087
|
-
super(message);
|
|
6088
|
-
this.code = code;
|
|
6089
|
-
this.name = "NwcError";
|
|
6090
|
-
}
|
|
6091
|
-
};
|
|
6092
|
-
/** No answer in time. The request may or may not have been acted on. */
|
|
6093
|
-
var NoAnswer = class extends Error {
|
|
6094
|
-
constructor(method) {
|
|
6095
|
-
super(`The wallet did not answer ${method} in time`);
|
|
6096
|
-
this.name = "NoAnswer";
|
|
6097
|
-
}
|
|
6098
|
-
};
|
|
6099
|
-
/**
|
|
6100
|
-
* A minimal NIP-01 client for one relay: it knows exactly whether an event was written to an open socket
|
|
6101
|
-
* and whether the relay acknowledged it, which is what tells "nothing sent" from "unknown" apart.
|
|
6102
|
-
* It reconnects when asked to send again, and subscriptions follow it.
|
|
6103
|
-
*/
|
|
6104
|
-
var RelayConnection = class {
|
|
6105
|
-
url;
|
|
6106
|
-
WebSocketImpl;
|
|
6107
|
-
socket;
|
|
6108
|
-
opening;
|
|
6109
|
-
subs = /* @__PURE__ */ new Map();
|
|
6110
|
-
acks = /* @__PURE__ */ new Map();
|
|
6111
|
-
closed = false;
|
|
6112
|
-
constructor(url, WebSocketImpl = globalThis.WebSocket) {
|
|
6113
|
-
this.url = url;
|
|
6114
|
-
this.WebSocketImpl = WebSocketImpl;
|
|
6115
|
-
}
|
|
6116
|
-
open() {
|
|
6117
|
-
if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("The wallet connection is closed"));
|
|
6118
|
-
if (this.socket?.readyState === 1) return Promise.resolve(this.socket);
|
|
6119
|
-
if (!this.opening) {
|
|
6120
|
-
const opening = this.connectSocket();
|
|
6121
|
-
this.opening = opening;
|
|
6122
|
-
opening.catch(() => {}).finally(() => {
|
|
6123
|
-
if (this.opening === opening) this.opening = void 0;
|
|
6124
|
-
});
|
|
6125
|
-
}
|
|
6126
|
-
return this.opening;
|
|
6127
|
-
}
|
|
6128
|
-
connectSocket() {
|
|
6129
|
-
return new Promise((resolve, reject) => {
|
|
6130
|
-
let settled = false;
|
|
6131
|
-
const socket = new this.WebSocketImpl(this.url);
|
|
6132
|
-
const done = (error) => {
|
|
6133
|
-
if (settled) return;
|
|
6134
|
-
settled = true;
|
|
6135
|
-
clearTimeout(timer);
|
|
6136
|
-
if (error) {
|
|
6137
|
-
try {
|
|
6138
|
-
socket.close();
|
|
6139
|
-
} catch {}
|
|
6140
|
-
reject(error);
|
|
6141
|
-
return;
|
|
6142
|
-
}
|
|
6143
|
-
if (this.closed) {
|
|
6144
|
-
socket.close();
|
|
6145
|
-
reject(/* @__PURE__ */ new Error("The wallet connection is closed"));
|
|
6146
|
-
return;
|
|
6147
|
-
}
|
|
6148
|
-
this.socket = socket;
|
|
6149
|
-
for (const [id, sub] of this.subs) this.write([
|
|
6150
|
-
"REQ",
|
|
6151
|
-
id,
|
|
6152
|
-
sub.filter
|
|
6153
|
-
]);
|
|
6154
|
-
resolve(socket);
|
|
6155
|
-
};
|
|
6156
|
-
const timer = setTimeout(() => done(/* @__PURE__ */ new Error("The wallet's relay did not answer")), CONNECT_MS);
|
|
6157
|
-
socket.onopen = () => done();
|
|
6158
|
-
socket.onerror = () => done(/* @__PURE__ */ new Error("Could not reach the wallet's relay"));
|
|
6159
|
-
socket.onclose = () => {
|
|
6160
|
-
done(/* @__PURE__ */ new Error("The wallet's relay closed the connection"));
|
|
6161
|
-
if (this.socket !== socket) return;
|
|
6162
|
-
this.socket = void 0;
|
|
6163
|
-
for (const [id, ack] of this.acks) {
|
|
6164
|
-
this.acks.delete(id);
|
|
6165
|
-
ack.fail(/* @__PURE__ */ new Error("The relay connection dropped before it confirmed the request"));
|
|
6166
|
-
}
|
|
6167
|
-
};
|
|
6168
|
-
socket.onmessage = (message) => {
|
|
6169
|
-
if (this.socket === socket) this.receive(message.data);
|
|
6170
|
-
};
|
|
6171
|
-
});
|
|
6172
|
-
}
|
|
6173
|
-
subscribe(id, filter, sub) {
|
|
6174
|
-
this.subs.set(id, {
|
|
6175
|
-
...sub,
|
|
6176
|
-
filter
|
|
6177
|
-
});
|
|
6178
|
-
this.write([
|
|
6179
|
-
"REQ",
|
|
6180
|
-
id,
|
|
6181
|
-
filter
|
|
6182
|
-
]);
|
|
6183
|
-
}
|
|
6184
|
-
unsubscribe(id) {
|
|
6185
|
-
if (this.subs.delete(id)) this.write(["CLOSE", id]);
|
|
6186
|
-
}
|
|
6187
|
-
/**
|
|
6188
|
-
* Events matching `filter` the relay has stored, until it says that is all (or `ms` pass). With
|
|
6189
|
-
* `waitForOne`, a relay that has none yet is listened to until one arrives live, within `ms`.
|
|
6190
|
-
*/
|
|
6191
|
-
async fetch(filter, { ms = CONNECT_MS, waitForOne = false } = {}) {
|
|
6192
|
-
await this.open();
|
|
6193
|
-
const id = `q${hex$6(crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(6)))}`, events = [];
|
|
6194
|
-
try {
|
|
6195
|
-
await new Promise((resolve) => {
|
|
6196
|
-
const timer = setTimeout(resolve, ms);
|
|
6197
|
-
let stored = false;
|
|
6198
|
-
const finish = () => {
|
|
6199
|
-
clearTimeout(timer);
|
|
6200
|
-
resolve();
|
|
6201
|
-
};
|
|
6202
|
-
this.subscribe(id, filter, {
|
|
6203
|
-
event: (e) => {
|
|
6204
|
-
if (events.length < 20) events.push(e);
|
|
6205
|
-
if (stored) finish();
|
|
6206
|
-
},
|
|
6207
|
-
eose: () => {
|
|
6208
|
-
stored = true;
|
|
6209
|
-
if (events.length || !waitForOne) finish();
|
|
6210
|
-
}
|
|
6211
|
-
});
|
|
6212
|
-
});
|
|
6213
|
-
} finally {
|
|
6214
|
-
this.unsubscribe(id);
|
|
6215
|
-
}
|
|
6216
|
-
return events;
|
|
6217
|
-
}
|
|
6218
|
-
/**
|
|
6219
|
-
* Resolves once the relay accepted the event. `NotDelivered` when it certainly did not reach it (no open
|
|
6220
|
-
* socket, refused); any other rejection means it may have.
|
|
6221
|
-
*/
|
|
6222
|
-
async publish(event) {
|
|
6223
|
-
let socket;
|
|
6224
|
-
try {
|
|
6225
|
-
socket = await this.open();
|
|
6226
|
-
} catch (error) {
|
|
6227
|
-
throw new NotDelivered(error instanceof Error ? error.message : "Could not reach the wallet's relay");
|
|
6228
|
-
}
|
|
6229
|
-
return new Promise((resolve, reject) => {
|
|
6230
|
-
const timer = setTimeout(() => {
|
|
6231
|
-
this.acks.delete(event.id);
|
|
6232
|
-
reject(/* @__PURE__ */ new Error("The relay did not confirm the request"));
|
|
6233
|
-
}, ACK_MS);
|
|
6234
|
-
this.acks.set(event.id, {
|
|
6235
|
-
ok: () => {
|
|
6236
|
-
clearTimeout(timer);
|
|
6237
|
-
resolve();
|
|
6238
|
-
},
|
|
6239
|
-
fail: (error) => {
|
|
6240
|
-
clearTimeout(timer);
|
|
6241
|
-
reject(error);
|
|
6242
|
-
}
|
|
6243
|
-
});
|
|
6244
|
-
if (socket.readyState !== 1 || socket !== this.socket) {
|
|
6245
|
-
this.acks.delete(event.id);
|
|
6246
|
-
clearTimeout(timer);
|
|
6247
|
-
reject(new NotDelivered("The relay connection closed"));
|
|
6248
|
-
return;
|
|
6249
|
-
}
|
|
6250
|
-
socket.send(JSON.stringify(["EVENT", event]));
|
|
6251
|
-
});
|
|
6252
|
-
}
|
|
6253
|
-
close() {
|
|
6254
|
-
this.closed = true;
|
|
6255
|
-
for (const [id, ack] of this.acks) {
|
|
6256
|
-
this.acks.delete(id);
|
|
6257
|
-
ack.fail(/* @__PURE__ */ new Error("The wallet connection is closed"));
|
|
6258
|
-
}
|
|
6259
|
-
this.subs.clear();
|
|
6260
|
-
try {
|
|
6261
|
-
this.socket?.close();
|
|
6262
|
-
} catch {}
|
|
6263
|
-
this.socket = void 0;
|
|
6264
|
-
}
|
|
6265
|
-
write(message) {
|
|
6266
|
-
if (this.socket?.readyState === 1) this.socket.send(JSON.stringify(message));
|
|
6267
|
-
}
|
|
6268
|
-
receive(data) {
|
|
6269
|
-
if (typeof data !== "string" || data.length > MAX_MESSAGE) return;
|
|
6270
|
-
let message;
|
|
6271
|
-
try {
|
|
6272
|
-
message = JSON.parse(data);
|
|
6273
|
-
} catch {
|
|
6274
|
-
return;
|
|
6275
|
-
}
|
|
6276
|
-
if (!Array.isArray(message) || typeof message[1] !== "string") return;
|
|
6277
|
-
const [type, id] = message;
|
|
6278
|
-
if (type === "EVENT") this.subs.get(id)?.event(message[2]);
|
|
6279
|
-
else if (type === "EOSE") this.subs.get(id)?.eose?.();
|
|
6280
|
-
else if (type === "OK") {
|
|
6281
|
-
const ack = this.acks.get(id);
|
|
6282
|
-
if (!ack) return;
|
|
6283
|
-
this.acks.delete(id);
|
|
6284
|
-
const reason = typeof message[3] === "string" ? message[3].slice(0, 120) : "";
|
|
6285
|
-
if (message[2] === true || reason.startsWith("duplicate:")) ack.ok();
|
|
6286
|
-
else ack.fail(new NotDelivered(`The relay refused the request${reason ? ` (${reason})` : ""}`));
|
|
6287
|
-
}
|
|
6288
|
-
}
|
|
6289
|
-
};
|
|
6290
|
-
var NwcLightning = class NwcLightning {
|
|
6291
|
-
connection;
|
|
6292
|
-
relay;
|
|
6293
|
-
encryption;
|
|
6294
|
-
options;
|
|
6295
|
-
/** From the info event, then narrowed to what this connection may do (`get_info`). */
|
|
6296
|
-
capabilities;
|
|
6297
|
-
clientPubkey;
|
|
6298
|
-
subscription = `nwc${hex$6(crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(6)))}`;
|
|
6299
|
-
pending = /* @__PURE__ */ new Map();
|
|
6300
|
-
conversationKey;
|
|
6301
|
-
network = "bitcoin";
|
|
6302
|
-
alias;
|
|
6303
|
-
closed = false;
|
|
6304
|
-
constructor(connection, relay, encryption, methods, options) {
|
|
6305
|
-
this.connection = connection;
|
|
6306
|
-
this.relay = relay;
|
|
6307
|
-
this.encryption = encryption;
|
|
6308
|
-
this.options = options;
|
|
6309
|
-
this.clientPubkey = getPublicKey(connection.secret);
|
|
6310
|
-
if (encryption === "nip44_v2") this.conversationKey = nip44.v2.utils.getConversationKey(connection.secret, connection.walletPubkey);
|
|
6311
|
-
this.capabilities = this.capabilitiesOf(methods);
|
|
6312
|
-
}
|
|
6313
|
-
/**
|
|
6314
|
-
* Reaches the wallet through the first relay that answers, reads its info event (the methods and the
|
|
6315
|
-
* encryption it offers), listens for its answers, and asks `get_info` for its network.
|
|
6316
|
-
*/
|
|
6317
|
-
static async connect(uri, options = {}) {
|
|
6318
|
-
const connection = parseNwcUri(uri);
|
|
6319
|
-
options.signal?.throwIfAborted();
|
|
6320
|
-
let relay, lastError;
|
|
6321
|
-
for (const url of connection.relays) {
|
|
6322
|
-
const candidate = new RelayConnection(url, options.WebSocket);
|
|
6323
|
-
try {
|
|
6324
|
-
await candidate.open();
|
|
6325
|
-
relay = candidate;
|
|
6326
|
-
break;
|
|
6327
|
-
} catch (error) {
|
|
6328
|
-
candidate.close();
|
|
6329
|
-
lastError = error;
|
|
6330
|
-
}
|
|
6331
|
-
}
|
|
6332
|
-
if (!relay) throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("Could not reach the wallet's relay");
|
|
6333
|
-
let wallet;
|
|
6334
|
-
try {
|
|
6335
|
-
const info = NwcLightning.newestInfo(await relay.fetch({
|
|
6336
|
-
kinds: [KIND.info],
|
|
6337
|
-
authors: [connection.walletPubkey],
|
|
6338
|
-
limit: 5
|
|
6339
|
-
}, {
|
|
6340
|
-
waitForOne: true,
|
|
6341
|
-
ms: options.infoMs
|
|
6342
|
-
}), connection.walletPubkey);
|
|
6343
|
-
if (!info) throw new Error(`The wallet has not published its NWC info on ${new URL(relay.url).host}: check the URI, or that the wallet is running`);
|
|
6344
|
-
const offered = (info.tags.find((t) => t[0] === "encryption")?.slice(1).join(" ") ?? "").split(/\s+/).filter(Boolean);
|
|
6345
|
-
const encryption = offered.includes("nip44_v2") ? "nip44_v2" : !offered.length || offered.includes("nip04") ? "nip04" : void 0;
|
|
6346
|
-
if (!encryption) throw new Error("The wallet offers no encryption Ghostly speaks (NIP-44 or NIP-04)");
|
|
6347
|
-
const methods = new Set(info.content.split(/\s+/).filter(Boolean));
|
|
6348
|
-
wallet = new NwcLightning(connection, relay, encryption, methods, options);
|
|
6349
|
-
wallet.listen();
|
|
6350
|
-
if (options.signal) {
|
|
6351
|
-
const opened = wallet;
|
|
6352
|
-
if (options.signal.aborted) throw options.signal.reason;
|
|
6353
|
-
options.signal.addEventListener("abort", () => void opened.close(), { once: true });
|
|
6354
|
-
}
|
|
6355
|
-
await wallet.readInfo(methods);
|
|
6356
|
-
return wallet;
|
|
6357
|
-
} catch (error) {
|
|
6358
|
-
if (wallet) await wallet.close();
|
|
6359
|
-
else relay.close();
|
|
6360
|
-
throw error;
|
|
6361
|
-
}
|
|
6362
|
-
}
|
|
6363
|
-
static newestInfo(events, walletPubkey) {
|
|
6364
|
-
return events.filter((e) => e && e.kind === KIND.info && e.pubkey === walletPubkey && typeof e.content === "string" && e.content.length <= MAX_INFO && Array.isArray(e.tags) && verifyEvent(e)).sort((a, b) => b.created_at - a.created_at)[0];
|
|
6365
|
-
}
|
|
6366
|
-
capabilitiesOf(methods) {
|
|
6367
|
-
const lookup = methods.has("lookup_invoice");
|
|
6368
|
-
return {
|
|
6369
|
-
receive: methods.has("make_invoice"),
|
|
6370
|
-
send: methods.has("pay_invoice") && lookup,
|
|
6371
|
-
balance: methods.has("get_balance"),
|
|
6372
|
-
lookup
|
|
6373
|
-
};
|
|
6374
|
-
}
|
|
6375
|
-
/**
|
|
6376
|
-
* `get_info` says the network and the methods this connection may use (the info event lists the
|
|
6377
|
-
* service's). Without it the wallet is taken to be on Bitcoin: a test wallet is then refused in the
|
|
6378
|
-
* Testnet mode and its invoices in the Mainnet one, while a real-money wallet never passes for a test one.
|
|
6379
|
-
*/
|
|
6380
|
-
async readInfo(offered) {
|
|
6381
|
-
if (!offered.has("get_info")) return;
|
|
6382
|
-
let info;
|
|
6383
|
-
try {
|
|
6384
|
-
info = await this.request("get_info", {});
|
|
6385
|
-
} catch (error) {
|
|
6386
|
-
if (error instanceof NwcError && [
|
|
6387
|
-
"RESTRICTED",
|
|
6388
|
-
"UNAUTHORIZED",
|
|
6389
|
-
"NOT_IMPLEMENTED"
|
|
6390
|
-
].includes(error.code)) return;
|
|
6391
|
-
throw error;
|
|
6392
|
-
}
|
|
6393
|
-
if (info.network !== void 0) {
|
|
6394
|
-
const network = typeof info.network === "string" ? NETWORKS$1[info.network.toLowerCase()] : void 0;
|
|
6395
|
-
if (!network) throw new Error("The wallet is on a network Ghostly does not know");
|
|
6396
|
-
this.network = network;
|
|
6397
|
-
}
|
|
6398
|
-
if (typeof info.alias === "string" && info.alias.trim()) this.alias = info.alias.trim().slice(0, 40);
|
|
6399
|
-
if (Array.isArray(info.methods) && info.methods.every((m) => typeof m === "string")) this.capabilities = this.capabilitiesOf(new Set(info.methods));
|
|
6400
|
-
}
|
|
6401
|
-
listen() {
|
|
6402
|
-
this.relay.subscribe(this.subscription, {
|
|
6403
|
-
kinds: [KIND.response],
|
|
6404
|
-
authors: [this.connection.walletPubkey],
|
|
6405
|
-
"#p": [this.clientPubkey],
|
|
6406
|
-
since: now$1() - 60
|
|
6407
|
-
}, { event: (e) => this.answer(e) });
|
|
6408
|
-
}
|
|
6409
|
-
encrypt(text) {
|
|
6410
|
-
return this.conversationKey ? nip44.v2.encrypt(text, this.conversationKey) : nip04.encrypt(this.connection.secret, this.connection.walletPubkey, text);
|
|
6411
|
-
}
|
|
6412
|
-
decrypt(content) {
|
|
6413
|
-
if (this.conversationKey && !content.includes("?iv=")) return nip44.v2.decrypt(content, this.conversationKey);
|
|
6414
|
-
return nip04.decrypt(this.connection.secret, this.connection.walletPubkey, content);
|
|
6415
|
-
}
|
|
6416
|
-
/**
|
|
6417
|
-
* Sends one request and waits for its answer. `NotDelivered` when it never reached the relay; `NwcError`
|
|
6418
|
-
* for an error the wallet answered; `NoAnswer` (or a relay error) when the outcome is unknown.
|
|
6419
|
-
*/
|
|
6420
|
-
async request(method, params, { ms = this.options.requestMs ?? REQUEST_MS$1, expiresIn } = {}) {
|
|
6421
|
-
if (this.closed) throw new NotDelivered("The wallet connection is closed");
|
|
6422
|
-
const tags = [["p", this.connection.walletPubkey]];
|
|
6423
|
-
if (this.conversationKey) tags.push(["encryption", "nip44_v2"]);
|
|
6424
|
-
if (expiresIn) tags.push(["expiration", String(now$1() + expiresIn)]);
|
|
6425
|
-
const event = finalizeEvent({
|
|
6426
|
-
kind: KIND.request,
|
|
6427
|
-
created_at: now$1(),
|
|
6428
|
-
tags,
|
|
6429
|
-
content: this.encrypt(JSON.stringify({
|
|
6430
|
-
method,
|
|
6431
|
-
params
|
|
6432
|
-
}))
|
|
6433
|
-
}, this.connection.secret);
|
|
6434
|
-
const answer = new Promise((resolve, reject) => {
|
|
6435
|
-
const timer = setTimeout(() => {
|
|
6436
|
-
this.pending.delete(event.id);
|
|
6437
|
-
reject(new NoAnswer(method));
|
|
6438
|
-
}, ms);
|
|
6439
|
-
this.pending.set(event.id, {
|
|
6440
|
-
method,
|
|
6441
|
-
resolve,
|
|
6442
|
-
reject,
|
|
6443
|
-
timer
|
|
6444
|
-
});
|
|
6445
|
-
});
|
|
6446
|
-
const delivered = this.relay.publish(event).then(() => void 0, (error) => {
|
|
6447
|
-
const waiting = this.pending.get(event.id);
|
|
6448
|
-
if (!(error instanceof NotDelivered) || !waiting) return void 0;
|
|
6449
|
-
clearTimeout(waiting.timer);
|
|
6450
|
-
this.pending.delete(event.id);
|
|
6451
|
-
return error;
|
|
6452
|
-
});
|
|
6453
|
-
return Promise.race([answer, delivered.then((error) => error ? Promise.reject(error) : answer)]);
|
|
6454
|
-
}
|
|
6455
|
-
/** Reads an answer: from the wallet, signed, about a request of ours, of the method asked. Anything else is dropped. */
|
|
6456
|
-
answer(raw) {
|
|
6457
|
-
const event = raw;
|
|
6458
|
-
if (!event || typeof event !== "object" || event.kind !== KIND.response || event.pubkey !== this.connection.walletPubkey) return;
|
|
6459
|
-
if (typeof event.content !== "string" || event.content.length > MAX_CONTENT || !Array.isArray(event.tags)) return;
|
|
6460
|
-
const ref = event.tags.find((t) => Array.isArray(t) && t[0] === "e")?.[1];
|
|
6461
|
-
const pending = typeof ref === "string" ? this.pending.get(ref) : void 0;
|
|
6462
|
-
if (!pending || !event.tags.some((t) => Array.isArray(t) && t[0] === "p" && t[1] === this.clientPubkey)) return;
|
|
6463
|
-
if (!verifyEvent(event)) return;
|
|
6464
|
-
let body;
|
|
6465
|
-
try {
|
|
6466
|
-
body = JSON.parse(this.decrypt(event.content));
|
|
6467
|
-
} catch {
|
|
6468
|
-
return;
|
|
6469
|
-
}
|
|
6470
|
-
if (!body || typeof body !== "object" || body.result_type !== pending.method) return;
|
|
6471
|
-
clearTimeout(pending.timer);
|
|
6472
|
-
this.pending.delete(ref);
|
|
6473
|
-
if (body.error) {
|
|
6474
|
-
const code = typeof body.error.code === "string" ? body.error.code.slice(0, 40) : "OTHER";
|
|
6475
|
-
const message = typeof body.error.message === "string" && body.error.message.trim() ? body.error.message.trim().slice(0, 200) : code;
|
|
6476
|
-
pending.reject(new NwcError(code, message));
|
|
6477
|
-
} else if (body.result && typeof body.result === "object" && !Array.isArray(body.result)) pending.resolve(body.result);
|
|
6478
|
-
else pending.reject(new NwcError("OTHER", "The wallet sent an empty answer"));
|
|
6479
|
-
}
|
|
6480
|
-
async lookup(paymentHash) {
|
|
6481
|
-
const tx = await this.request("lookup_invoice", { payment_hash: paymentHash });
|
|
6482
|
-
if (tx.payment_hash !== void 0 && String(tx.payment_hash).toLowerCase() !== paymentHash) throw new Error("The wallet answered about another invoice");
|
|
6483
|
-
return tx;
|
|
6484
|
-
}
|
|
6485
|
-
async info() {
|
|
6486
|
-
let balance;
|
|
6487
|
-
if (this.capabilities.balance) {
|
|
6488
|
-
const result = await this.request("get_balance", {});
|
|
6489
|
-
if (!isMsat(result.balance)) throw new Error("The wallet returned an invalid balance");
|
|
6490
|
-
balance = Math.floor(result.balance / 1e3);
|
|
6491
|
-
}
|
|
6492
|
-
return {
|
|
6493
|
-
network: this.network,
|
|
6494
|
-
alias: `${this.alias ?? "NWC wallet"} via ${new URL(this.relay.url).host}`,
|
|
6495
|
-
balance
|
|
6496
|
-
};
|
|
6497
|
-
}
|
|
6498
|
-
async createInvoice(amount, memo) {
|
|
6499
|
-
if (!Number.isSafeInteger(amount) || amount <= 0 || !Number.isSafeInteger(amount * 1e3)) throw new Error("Invalid amount");
|
|
6500
|
-
const result = await this.request("make_invoice", {
|
|
6501
|
-
amount: amount * 1e3,
|
|
6502
|
-
...memo ? { description: memo.slice(0, 200) } : {},
|
|
6503
|
-
expiry: INVOICE_EXPIRY_S$1
|
|
6504
|
-
});
|
|
6505
|
-
const text = typeof result.invoice === "string" && result.invoice.length <= MAX_INVOICE ? result.invoice : "";
|
|
6506
|
-
const decoded = decodeBolt11(text);
|
|
6507
|
-
if (!decoded?.paymentHash || decoded.amountMsat !== BigInt(amount) * 1000n) throw new Error("The wallet returned an invoice that does not match the request");
|
|
6508
|
-
if (result.payment_hash !== void 0 && String(result.payment_hash).toLowerCase() !== decoded.paymentHash) throw new Error("The wallet returned an invoice that does not match its payment hash");
|
|
6509
|
-
return {
|
|
6510
|
-
invoice: decoded.invoice,
|
|
6511
|
-
paymentHash: decoded.paymentHash,
|
|
6512
|
-
amount,
|
|
6513
|
-
expiresAt: decoded.expiresAt * 1e3
|
|
6514
|
-
};
|
|
6515
|
-
}
|
|
6516
|
-
async invoiceStatus(invoice) {
|
|
6517
|
-
let tx;
|
|
6518
|
-
try {
|
|
6519
|
-
tx = await this.lookup(invoice.paymentHash);
|
|
6520
|
-
} catch (error) {
|
|
6521
|
-
if (error instanceof NwcError && error.code === "NOT_FOUND" && invoice.expiresAt < Date.now()) return { state: "expired" };
|
|
6522
|
-
throw error;
|
|
6523
|
-
}
|
|
6524
|
-
const expired = invoice.expiresAt < Date.now();
|
|
6525
|
-
if (tx.type === "outgoing") return { state: expired ? "expired" : "open" };
|
|
6526
|
-
const state = tx.state ?? (tx.settled_at ? "settled" : "pending");
|
|
6527
|
-
if (state === "settled") return {
|
|
6528
|
-
state: "paid",
|
|
6529
|
-
amount: isMsat(tx.amount) ? Math.floor(tx.amount / 1e3) : invoice.amount
|
|
6530
|
-
};
|
|
6531
|
-
if (state === "expired" || state === "failed") return { state: "expired" };
|
|
6532
|
-
return { state: expired ? "expired" : "open" };
|
|
6533
|
-
}
|
|
6534
|
-
/**
|
|
6535
|
-
* Pays through the wallet. NIP-47 carries no fee limit: the wallet applies its own, and the fee it
|
|
6536
|
-
* reports is passed on as it is.
|
|
6537
|
-
*/
|
|
6538
|
-
async payInvoice(invoice, _maxFee) {
|
|
6539
|
-
const decoded = decodeBolt11(invoice);
|
|
6540
|
-
if (!decoded?.paymentHash || decoded.amountSat === null) throw new NothingSpentError("That invoice cannot be paid through NWC");
|
|
6541
|
-
if (!this.capabilities.send) throw new NothingSpentError("This wallet connection may not pay and look up invoices");
|
|
6542
|
-
let result;
|
|
6543
|
-
try {
|
|
6544
|
-
result = await this.request("pay_invoice", { invoice: decoded.invoice }, {
|
|
6545
|
-
ms: this.options.payMs ?? PAY_MS,
|
|
6546
|
-
expiresIn: PAY_EXPIRY_S
|
|
6547
|
-
});
|
|
6548
|
-
} catch (error) {
|
|
6549
|
-
if (error instanceof NotDelivered) throw new NothingSpentError(`The payment did not reach the wallet: ${error.message}`);
|
|
6550
|
-
if (error instanceof NwcError && REFUSED_BEFORE_PAYING.has(error.code)) throw new NothingSpentError(`The wallet refused the payment: ${error.message}`);
|
|
6551
|
-
if (error instanceof NwcError) return this.confirmFailure(decoded.invoice, decoded.paymentHash, error);
|
|
6552
|
-
throw error;
|
|
6553
|
-
}
|
|
6554
|
-
const preimage = typeof result.preimage === "string" ? result.preimage.toLowerCase() : "";
|
|
6555
|
-
if (!HEX64$3.test(preimage) || hex$6(sha256(bytes$2(preimage))) !== decoded.paymentHash) throw new Error("The wallet said it paid, without a proof that matches the invoice");
|
|
6556
|
-
return {
|
|
6557
|
-
state: "paid",
|
|
6558
|
-
preimage,
|
|
6559
|
-
fee: isMsat(result.fees_paid) ? Math.ceil(result.fees_paid / 1e3) : void 0
|
|
6560
|
-
};
|
|
6561
|
-
}
|
|
6562
|
-
/**
|
|
6563
|
-
* An error after the wallet may have tried (`PAYMENT_FAILED` "may be due to a timeout", `INTERNAL`,
|
|
6564
|
-
* `OTHER`…): its own record decides. Only a payment it records as failed is `NothingSpentError`; one it
|
|
6565
|
-
* records as paid is paid; anything else is pending, or unknown when it cannot be asked.
|
|
6566
|
-
*/
|
|
6567
|
-
async confirmFailure(invoice, paymentHash, failure) {
|
|
6568
|
-
let status;
|
|
6569
|
-
try {
|
|
6570
|
-
status = await this.paymentStatus({
|
|
6571
|
-
invoice,
|
|
6572
|
-
paymentHash
|
|
6573
|
-
});
|
|
6574
|
-
} catch {
|
|
6575
|
-
throw new Error(`The wallet said the payment failed (${failure.message}), and cannot confirm it yet`);
|
|
6576
|
-
}
|
|
6577
|
-
if (status.state === "failed") throw new NothingSpentError(`The payment failed: ${failure.message}`);
|
|
6578
|
-
return status.state === "paid" ? {
|
|
6579
|
-
state: "paid",
|
|
6580
|
-
fee: status.fee,
|
|
6581
|
-
preimage: status.preimage
|
|
6582
|
-
} : { state: "pending" };
|
|
6583
|
-
}
|
|
6584
|
-
async paymentStatus(payment) {
|
|
6585
|
-
let tx;
|
|
6586
|
-
try {
|
|
6587
|
-
tx = await this.lookup(payment.paymentHash);
|
|
6588
|
-
} catch (error) {
|
|
6589
|
-
if (!(error instanceof NwcError && error.code === "NOT_FOUND")) throw error;
|
|
6590
|
-
return { state: (decodeBolt11(payment.invoice)?.expiresAt ?? Infinity) * 1e3 + EXPIRED_MARGIN_MS < Date.now() ? "failed" : "pending" };
|
|
6591
|
-
}
|
|
6592
|
-
if (tx.type === "incoming") return { state: "pending" };
|
|
6593
|
-
const preimage = typeof tx.preimage === "string" && HEX64$3.test(tx.preimage.toLowerCase()) ? tx.preimage.toLowerCase() : void 0;
|
|
6594
|
-
const proven = !!preimage && hex$6(sha256(bytes$2(preimage))) === payment.paymentHash;
|
|
6595
|
-
const state = tx.state ?? (tx.settled_at || proven ? "settled" : "pending");
|
|
6596
|
-
const fee = isMsat(tx.fees_paid) ? Math.ceil(tx.fees_paid / 1e3) : void 0;
|
|
6597
|
-
if (state === "settled") {
|
|
6598
|
-
if (preimage && !proven) throw new Error("The wallet's proof of payment does not match the invoice");
|
|
6599
|
-
return {
|
|
6600
|
-
state: "paid",
|
|
6601
|
-
fee,
|
|
6602
|
-
preimage
|
|
6603
|
-
};
|
|
6604
|
-
}
|
|
6605
|
-
if (state === "failed" || state === "expired") return { state: "failed" };
|
|
6606
|
-
return { state: "pending" };
|
|
6607
|
-
}
|
|
6608
|
-
async close() {
|
|
6609
|
-
if (this.closed) return;
|
|
6610
|
-
this.closed = true;
|
|
6611
|
-
for (const [id, pending] of this.pending) {
|
|
6612
|
-
clearTimeout(pending.timer);
|
|
6613
|
-
this.pending.delete(id);
|
|
6614
|
-
pending.reject(/* @__PURE__ */ new Error("The wallet connection is closed"));
|
|
6615
|
-
}
|
|
6616
|
-
this.relay.close();
|
|
6617
|
-
}
|
|
6618
|
-
};
|
|
6619
|
-
var nwc = {
|
|
6620
|
-
id: "nwc",
|
|
6621
|
-
label: "Nostr Wallet Connect",
|
|
6622
|
-
kind: "lightning",
|
|
6623
|
-
description: "A wallet you connect with its NWC URI (Alby Hub, a node's NWC service, a hosted wallet). That wallet holds the sats and sets its own fee limit: custodial or not depends on the wallet.",
|
|
6624
|
-
networks: [
|
|
6625
|
-
"bitcoin",
|
|
6626
|
-
"testnet",
|
|
6627
|
-
"signet",
|
|
6628
|
-
"mutinynet",
|
|
6629
|
-
"regtest"
|
|
6630
|
-
],
|
|
6631
|
-
platforms: PROVIDER_PLATFORMS,
|
|
6632
|
-
fields: [{
|
|
6633
|
-
name: "uri",
|
|
6634
|
-
label: "Connection URI",
|
|
6635
|
-
kind: "secret",
|
|
6636
|
-
placeholder: "nostr+walletconnect://…",
|
|
6637
|
-
help: "From your wallet's app connections. Allow making, paying and looking up invoices, and reading the balance."
|
|
6638
|
-
}],
|
|
6639
|
-
validate({ secrets }) {
|
|
6640
|
-
parseNwcUri(secrets.uri ?? "");
|
|
6641
|
-
},
|
|
6642
|
-
async create({ secrets }, host) {
|
|
6643
|
-
return NwcLightning.connect(secrets.uri, { signal: host.signal });
|
|
6644
|
-
}
|
|
6645
|
-
};
|
|
6646
5593
|
/** What the recommended macaroon grants: `lncli bakemacaroon info:read invoices:read invoices:write offchain:read offchain:write`. */
|
|
6647
5594
|
var LND_SCOPE = [
|
|
6648
5595
|
"info:read",
|
|
@@ -7757,6 +6704,28 @@ function defaultRegistry() {
|
|
|
7757
6704
|
};
|
|
7758
6705
|
}
|
|
7759
6706
|
//#endregion
|
|
6707
|
+
//#region ../browser/src/proofs/load.ts
|
|
6708
|
+
/**
|
|
6709
|
+
* The identity proof providers, loaded on first need and not with the app: they and their libraries (OpenPGP, the
|
|
6710
|
+
* Bitcoin signer, the DID, AT Protocol and OpenID Connect checks) are a large part of the code, and the first screen
|
|
6711
|
+
* draws none of it. The engine loads them with the profile's proofs; the UI once it draws something about identities.
|
|
6712
|
+
*/
|
|
6713
|
+
var registry;
|
|
6714
|
+
var loading$1;
|
|
6715
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
6716
|
+
function loadIdentityProviders() {
|
|
6717
|
+
return loading$1 ??= import("./registry-OBJBnHVQ.js").then((m) => {
|
|
6718
|
+
registry = m;
|
|
6719
|
+
for (const listener of listeners) listener();
|
|
6720
|
+
return m;
|
|
6721
|
+
}, (error) => {
|
|
6722
|
+
loading$1 = void 0;
|
|
6723
|
+
throw error;
|
|
6724
|
+
});
|
|
6725
|
+
}
|
|
6726
|
+
/** The providers (built in, from plugins, the test ones when asked for), once loaded; none before. */
|
|
6727
|
+
var loadedIdentityProviders = () => registry?.identityProviders() ?? [];
|
|
6728
|
+
//#endregion
|
|
7760
6729
|
//#region ../browser/src/engine/identities.ts
|
|
7761
6730
|
var KEY$2 = "identityProofs";
|
|
7762
6731
|
var REVOCATIONS = "identityRevocations";
|
|
@@ -7797,14 +6766,17 @@ var IdentityProofs = class {
|
|
|
7797
6766
|
/** Chats whose current connection already got the hello: "ready" is reported more than once per connection. */
|
|
7798
6767
|
greeted = /* @__PURE__ */ new Set();
|
|
7799
6768
|
fetch;
|
|
7800
|
-
constructor(host, providers =
|
|
6769
|
+
constructor(host, providers = loadedIdentityProviders) {
|
|
7801
6770
|
this.host = host;
|
|
7802
6771
|
this.providers = providers;
|
|
7803
6772
|
this.fetch = boundedIdentityFetch({ online: host.online });
|
|
7804
6773
|
}
|
|
6774
|
+
/** The profile's proofs, and the providers that check and show them: loaded here, not with the app. */
|
|
7805
6775
|
async load() {
|
|
6776
|
+
const providers = loadIdentityProviders();
|
|
7806
6777
|
const settings = await store(STORES$1.settings, "readonly");
|
|
7807
6778
|
[this.proofs, this.revocations] = await Promise.all([wrap(settings.get(KEY$2)).then((v) => v ?? []), wrap(settings.get(REVOCATIONS)).then((v) => v ?? [])]);
|
|
6779
|
+
await providers;
|
|
7808
6780
|
}
|
|
7809
6781
|
/** Background work: republish revocations, look up the contacts' proofs for revocations. */
|
|
7810
6782
|
start() {
|
|
@@ -23151,22 +22123,30 @@ var Communities = class {
|
|
|
23151
22123
|
/**
|
|
23152
22124
|
* Leaves: the request goes to the hubs, which keep it and commit it (any member can), and the
|
|
23153
22125
|
* group goes from this device at once. An admin hands the role to a member first. Something must
|
|
23154
|
-
* be connected to carry the request.
|
|
22126
|
+
* be connected to carry the request. Answers the member the role went to, when it went to one.
|
|
23155
22127
|
*/
|
|
23156
22128
|
async leave(groupId) {
|
|
23157
22129
|
const live = this.live.get(groupId);
|
|
23158
|
-
if (!live)
|
|
22130
|
+
if (!live) {
|
|
22131
|
+
await this.forget(groupId);
|
|
22132
|
+
return;
|
|
22133
|
+
}
|
|
23159
22134
|
const s = live.session;
|
|
23160
|
-
if (s.status !== "active" || s.others.length === 0)
|
|
22135
|
+
if (s.status !== "active" || s.others.length === 0) {
|
|
22136
|
+
await this.forget(groupId);
|
|
22137
|
+
return;
|
|
22138
|
+
}
|
|
23161
22139
|
const edges = [...this.host.edges(groupId).entries()].filter(([, id]) => this.host.linkReady(id, 2));
|
|
23162
22140
|
if (!edges.length) throw new Error("Nobody in the group is connected right now to take your leave. Try again in a moment.");
|
|
22141
|
+
let next;
|
|
23163
22142
|
if (s.isAdmin) {
|
|
23164
22143
|
const connected = new Set(edges.map(([key]) => key));
|
|
23165
|
-
|
|
22144
|
+
next = s.others.find((key) => connected.has(key)) ?? s.others[0];
|
|
23166
22145
|
await s.transferAdmin(next);
|
|
23167
22146
|
}
|
|
23168
22147
|
await s.leave();
|
|
23169
22148
|
await this.forget(groupId);
|
|
22149
|
+
return next;
|
|
23170
22150
|
}
|
|
23171
22151
|
/**
|
|
23172
22152
|
* Delete from this device: left first, as Leave does. With nothing connected to carry the request, a member's leave
|
|
@@ -24304,7 +23284,7 @@ var Communities = class {
|
|
|
24304
23284
|
const live = this.live.get(groupId);
|
|
24305
23285
|
if (!live || live.session.wasRemoved(peerKey)) return;
|
|
24306
23286
|
if (live.hub && !this.peerIsHub(live, peerKey) && rosterHas(live.session.roster, peerKey)) live.members.set(peerKey, this.now());
|
|
24307
|
-
this.sendTo(linkId, live.session.syncFrame());
|
|
23287
|
+
this.sendTo(linkId, live.session.syncFrame(peerKey));
|
|
24308
23288
|
this.host.emit();
|
|
24309
23289
|
}
|
|
24310
23290
|
edgeNick(groupId, peerKey, nick) {
|
|
@@ -26230,19 +25210,24 @@ var Groups = class {
|
|
|
26230
25210
|
* Leaves, and the group is gone from this device at once: its row, its history, its files and every
|
|
26231
25211
|
* edge but the one to the admin, which stays until the admin's commit removing me comes back (or a
|
|
26232
25212
|
* week passes), so a leave said while the admin was away still reaches it. An admin with other
|
|
26233
|
-
* members hands the role to one who is online first; alone, the group simply goes.
|
|
25213
|
+
* members hands the role to one who is online first; alone, the group simply goes. Answers the member the role went
|
|
25214
|
+
* to, when it went to one.
|
|
26234
25215
|
*/
|
|
26235
25216
|
async leave(groupId) {
|
|
26236
25217
|
if (this.isCommunity(groupId)) return this.communities.leave(groupId);
|
|
26237
25218
|
const session = this.session(groupId);
|
|
26238
25219
|
const group = this.stored.get(groupId);
|
|
26239
|
-
if (session.status !== "active")
|
|
25220
|
+
if (session.status !== "active") {
|
|
25221
|
+
await this.forget(groupId);
|
|
25222
|
+
return;
|
|
25223
|
+
}
|
|
26240
25224
|
if (session.others.length === 0) {
|
|
26241
25225
|
await session.leave();
|
|
26242
|
-
|
|
25226
|
+
await this.forget(groupId);
|
|
25227
|
+
return;
|
|
26243
25228
|
}
|
|
25229
|
+
const next = session.isAdmin ? this.successor(groupId) : void 0;
|
|
26244
25230
|
if (session.isAdmin) {
|
|
26245
|
-
const next = this.successor(groupId);
|
|
26246
25231
|
if (!next) throw new Error("You are the admin and nobody else in the group is online to take over. Try again when a member is, or make someone the admin first.");
|
|
26247
25232
|
await session.transferAdmin(next);
|
|
26248
25233
|
}
|
|
@@ -26296,6 +25281,7 @@ var Groups = class {
|
|
|
26296
25281
|
} catch {}
|
|
26297
25282
|
this.reconcileEdges(groupId);
|
|
26298
25283
|
this.host.emit();
|
|
25284
|
+
return next;
|
|
26299
25285
|
}
|
|
26300
25286
|
/** The admin heard me: the tombstone of a group I left can go. */
|
|
26301
25287
|
async leaveConfirmed(groupId) {
|
|
@@ -26926,7 +25912,7 @@ var Groups = class {
|
|
|
26926
25912
|
if (existing?.left && (existing.left.admin !== frame.admin || existing.contacts?.[existing.left.admin] !== linkId)) return;
|
|
26927
25913
|
if (existing?.invitation && !existing.state && existing.invitation.linkId !== linkId && !existing.invitation.genesis && !existing.invitation.takenBack) return;
|
|
26928
25914
|
if ([...this.stored.values()].filter((x) => x.invitation && !x.invitation.takenBack).length >= 32) return;
|
|
26929
|
-
if (!existing?.invitation && [...this.stored.values()].filter((x) => x.invitation?.linkId === linkId && !x.invitation.takenBack).length >= MAX_INVITATIONS_PER_CHAT) {
|
|
25915
|
+
if (!(existing?.invitation && !existing.invitation.takenBack && existing.invitation.linkId === linkId) && [...this.stored.values()].filter((x) => x.invitation?.linkId === linkId && !x.invitation.takenBack).length >= MAX_INVITATIONS_PER_CHAT) {
|
|
26930
25916
|
try {
|
|
26931
25917
|
this.host.sendOnLink(linkId, {
|
|
26932
25918
|
t: "group-decline",
|
|
@@ -35853,24 +34839,24 @@ var GhostlyNode = class GhostlyNode {
|
|
|
35853
34839
|
* Every wallet twice, one per network (real money, test coins), both open at once. Nothing is parked: a payment
|
|
35854
34840
|
* goes through the wallet of its own network, and a wallet of one network never pays for the other.
|
|
35855
34841
|
*/
|
|
35856
|
-
usdtWallets = perNetwork((network) => new UsdtWallet(network, () => {
|
|
35857
|
-
this.refreshWallet();
|
|
34842
|
+
usdtWallets = perNetwork((network) => new UsdtWallet(network, (viewChanged = true) => {
|
|
34843
|
+
if (viewChanged) this.refreshWallet();
|
|
35858
34844
|
this.desk.reconcileUsdtReceipts().catch(() => {});
|
|
35859
34845
|
}));
|
|
35860
|
-
arkWallets = perNetwork((network) => new ArkWallet(network, () => {
|
|
35861
|
-
this.refreshWallet();
|
|
34846
|
+
arkWallets = perNetwork((network) => new ArkWallet(network, (viewChanged = true) => {
|
|
34847
|
+
if (viewChanged) this.refreshWallet();
|
|
35862
34848
|
this.desk.reconcileArkReceipts().catch(() => {});
|
|
35863
34849
|
}));
|
|
35864
|
-
barkWallets = perNetwork((network) => new BarkWallet(network, () => {
|
|
35865
|
-
this.refreshWallet();
|
|
34850
|
+
barkWallets = perNetwork((network) => new BarkWallet(network, (viewChanged = true) => {
|
|
34851
|
+
if (viewChanged) this.refreshWallet();
|
|
35866
34852
|
this.desk.reconcileBarkReceipts().catch(() => {});
|
|
35867
34853
|
}));
|
|
35868
34854
|
fedimintWallets = perNetwork((network) => new FedimintWallet(network, {
|
|
35869
34855
|
changed: () => void this.refreshWallet(),
|
|
35870
34856
|
received: (paymentId) => void this.desk.onLightningPaid({ paymentId })
|
|
35871
34857
|
}, () => (this.options.fedimintSdk ?? loadFedimintSdk)()));
|
|
35872
|
-
sparkWallets = perNetwork((network) => new SparkWallet(network, () => {
|
|
35873
|
-
this.refreshWallet();
|
|
34858
|
+
sparkWallets = perNetwork((network) => new SparkWallet(network, (viewChanged = true) => {
|
|
34859
|
+
if (viewChanged) this.refreshWallet();
|
|
35874
34860
|
this.desk.reconcileSparkReceipts().catch(() => {});
|
|
35875
34861
|
}));
|
|
35876
34862
|
/** What a call naming no network acts on (a caller from before wallets had their own): Mainnet. */
|
|
@@ -36197,29 +35183,38 @@ var GhostlyNode = class GhostlyNode {
|
|
|
36197
35183
|
if (live.stored.deletedIds?.includes(`peer_${wire.wireId}`)) return null;
|
|
36198
35184
|
live.files.wireIds.add(wire.wireId);
|
|
36199
35185
|
live.files.receivedBytes += wire.size;
|
|
36200
|
-
await
|
|
36201
|
-
|
|
36202
|
-
|
|
36203
|
-
|
|
36204
|
-
|
|
36205
|
-
|
|
36206
|
-
|
|
36207
|
-
|
|
36208
|
-
|
|
36209
|
-
|
|
36210
|
-
|
|
36211
|
-
|
|
36212
|
-
|
|
36213
|
-
|
|
36214
|
-
|
|
36215
|
-
|
|
36216
|
-
|
|
36217
|
-
|
|
36218
|
-
|
|
36219
|
-
|
|
36220
|
-
|
|
36221
|
-
|
|
36222
|
-
|
|
35186
|
+
const storage = await fileBytes();
|
|
35187
|
+
try {
|
|
35188
|
+
await storage.stage(file.id, new Blob([bytes]));
|
|
35189
|
+
await fileStore.put({
|
|
35190
|
+
id: file.id,
|
|
35191
|
+
linkId,
|
|
35192
|
+
bytes: storage.kind,
|
|
35193
|
+
createdAt: Date.now(),
|
|
35194
|
+
direction: "in",
|
|
35195
|
+
wireId: wire.wireId,
|
|
35196
|
+
digest,
|
|
35197
|
+
metadata: {
|
|
35198
|
+
name: wire.name,
|
|
35199
|
+
size: wire.size,
|
|
35200
|
+
mime: wire.mime,
|
|
35201
|
+
timestamp: wire.timestamp,
|
|
35202
|
+
voice: wire.voice,
|
|
35203
|
+
video: wire.video,
|
|
35204
|
+
image: wire.image
|
|
35205
|
+
},
|
|
35206
|
+
transfer: {
|
|
35207
|
+
state: "done",
|
|
35208
|
+
transferred: wire.size,
|
|
35209
|
+
size: wire.size
|
|
35210
|
+
}
|
|
35211
|
+
});
|
|
35212
|
+
} catch (error) {
|
|
35213
|
+
await storage.remove(file.id).catch(() => {});
|
|
35214
|
+
live.files.wireIds.delete(wire.wireId);
|
|
35215
|
+
live.files.receivedBytes -= wire.size;
|
|
35216
|
+
throw error;
|
|
35217
|
+
}
|
|
36223
35218
|
this.transfers.set(file.id, {
|
|
36224
35219
|
state: "done",
|
|
36225
35220
|
transferred: wire.size,
|
|
@@ -36847,6 +35842,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
36847
35842
|
const wallets = this.withHomes(walletInstances(networks));
|
|
36848
35843
|
const intents = (await intentRepository.list()).map((saved) => saved.review);
|
|
36849
35844
|
this.noteOpenReviews(intents);
|
|
35845
|
+
this.paymentCoordinator.notePending(intents);
|
|
36850
35846
|
this.walletView = {
|
|
36851
35847
|
...networks.mainnet,
|
|
36852
35848
|
networks,
|
|
@@ -38569,6 +37565,10 @@ var GhostlyNode = class GhostlyNode {
|
|
|
38569
37565
|
error: `Message exceeds ${LIMITS.maxChatMessageBytes} UTF-8 bytes.`,
|
|
38570
37566
|
refused: true
|
|
38571
37567
|
};
|
|
37568
|
+
if (!pairedMessageFits(timestamp, trimmed, reply && pairedWireReply(reply), forwarded, card)) return {
|
|
37569
|
+
error: PAIRED_FRAME_TOO_LARGE,
|
|
37570
|
+
refused: true
|
|
37571
|
+
};
|
|
38572
37572
|
const stop = this.chatStopped(live);
|
|
38573
37573
|
if (stop) return { error: stop };
|
|
38574
37574
|
if (!link.isDataLinkOpen && link.dhtDelivery?.inviteTaken) return {
|
|
@@ -40835,8 +39835,10 @@ var GhostlyNode = class GhostlyNode {
|
|
|
40835
39835
|
this.links.get(edge)?.link?.sendGroupFrame(frame);
|
|
40836
39836
|
} catch {}
|
|
40837
39837
|
}
|
|
40838
|
-
|
|
40839
|
-
|
|
39838
|
+
/** `admin`: the member a leaving admin handed the role to. */
|
|
39839
|
+
async leaveGroup({ groupId }) {
|
|
39840
|
+
const admin = await this.groups.leave(groupId);
|
|
39841
|
+
return admin ? { admin } : {};
|
|
40840
39842
|
}
|
|
40841
39843
|
removeGroupMember({ groupId, key }) {
|
|
40842
39844
|
return this.groups.remove(groupId, key);
|
|
@@ -43576,6 +42578,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
43576
42578
|
}
|
|
43577
42579
|
});
|
|
43578
42580
|
const link = live.link;
|
|
42581
|
+
if (linkId === this.activeLinkId) link.setChatActive(true);
|
|
43579
42582
|
link.start();
|
|
43580
42583
|
this.emitState();
|
|
43581
42584
|
if (resumed.resume) {
|
|
@@ -43700,7 +42703,7 @@ var GhostlyNode = class GhostlyNode {
|
|
|
43700
42703
|
get nativeFactories() {
|
|
43701
42704
|
const relay = this.relaysHyperdht ? this.hyperdhtRelay : "";
|
|
43702
42705
|
return {
|
|
43703
|
-
...relay ? { "hyperdht/1": async (seedB64) => (await import("./hyperdhtRelay-
|
|
42706
|
+
...relay ? { "hyperdht/1": async (seedB64) => (await import("./hyperdhtRelay-CPYwSLLS.js")).createRelayedHyperEndpoint(seedB64, relay) } : {},
|
|
43704
42707
|
...this.options.nativeTransports,
|
|
43705
42708
|
...this.ownIrohRelays ? { "iroh/1": (seedB64) => this.options.nativeTransports["iroh/1"](seedB64, this.settings.irohRelays?.length ? { relays: [...this.settings.irohRelays] } : {}) } : {},
|
|
43706
42709
|
...this.options.irohWeb ? { "iroh/1": (seedB64) => createIrohWebEndpoint(seedB64, { relays: this.irohRelays }) } : {}
|
|
@@ -44549,4 +43552,4 @@ function signalKind(signal) {
|
|
|
44549
43552
|
//#endregion
|
|
44550
43553
|
export { EngineServer };
|
|
44551
43554
|
|
|
44552
|
-
//# sourceMappingURL=server-
|
|
43555
|
+
//# sourceMappingURL=server-Ss0gJ-nw.js.map
|