@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.
Files changed (80) hide show
  1. package/README.md +9 -3
  2. package/dist/assets/{api-CrxVxtY1.js → api-Dq2QXQDk.js} +141 -40
  3. package/dist/assets/api-Dq2QXQDk.js.map +1 -0
  4. package/dist/assets/{api-BiaX6l6f.js → api-TiB5QSG9.js} +2 -2
  5. package/dist/assets/{apiKit-CajiDxdK.js → apiKit-CA2x5GJF.js} +7 -3
  6. package/dist/assets/apiKit-CA2x5GJF.js.map +1 -0
  7. package/dist/assets/{apps-CbfiCSIw.js → apps-BxV9i_Y4.js} +3 -4
  8. package/dist/assets/{apps-CbfiCSIw.js.map → apps-BxV9i_Y4.js.map} +1 -1
  9. package/dist/assets/{apps-t23gkc28.js → apps-C-T8jeji.js} +1 -1
  10. package/dist/assets/{arkade-D4BVJFEQ.js → arkade-C_qDmHnm.js} +2 -2
  11. package/dist/assets/{arkade-D4BVJFEQ.js.map → arkade-C_qDmHnm.js.map} +1 -1
  12. package/dist/assets/{backup-Cg52WeQi.js → backup-Bugnn3Ue.js} +1 -1
  13. package/dist/assets/{backup-BAMi2lAq.js → backup-CnMN2oUK.js} +8 -8
  14. package/dist/assets/backup-CnMN2oUK.js.map +1 -0
  15. package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
  16. package/dist/assets/bitcoind-CZchoh43.js +349 -0
  17. package/dist/assets/bitcoind-CZchoh43.js.map +1 -0
  18. package/dist/assets/bitcoindProvider-DGw3xBSa.js +97 -0
  19. package/dist/assets/bitcoindProvider-DGw3xBSa.js.map +1 -0
  20. package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -1
  21. package/dist/assets/buttons-Bj7Vf7Pn.js +2 -0
  22. package/dist/assets/{buttons-CX1pKB0j.js → buttons-CvNBzh2S.js} +4 -5
  23. package/dist/assets/{buttons-CX1pKB0j.js.map → buttons-CvNBzh2S.js.map} +1 -1
  24. package/dist/assets/{endpoint-Dn7H8qE1.js → endpoint-BOTFMpfS.js} +44 -12
  25. package/dist/assets/endpoint-BOTFMpfS.js.map +1 -0
  26. package/dist/assets/{envelope-BvU0fLEt.js → envelope-WjingYSc.js} +2 -2
  27. package/dist/assets/{envelope-BvU0fLEt.js.map → envelope-WjingYSc.js.map} +1 -1
  28. package/dist/assets/{errors-my6VJR_0.js → errors-DO5z6h_l.js} +29 -1
  29. package/dist/assets/errors-DO5z6h_l.js.map +1 -0
  30. package/dist/assets/groupFiles-IEUW3DQz.js +3202 -0
  31. package/dist/assets/groupFiles-IEUW3DQz.js.map +1 -0
  32. package/dist/assets/{host-Cmd205Dc.js → host-CWt-jtqz.js} +41 -16
  33. package/dist/assets/host-CWt-jtqz.js.map +1 -0
  34. package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js → hyperdhtRelay-CPYwSLLS.js} +2 -3
  35. package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js.map → hyperdhtRelay-CPYwSLLS.js.map} +1 -1
  36. package/dist/assets/{irohWeb--YyEA-hX.js → irohWeb-4oChIOIp.js} +2 -3
  37. package/dist/assets/{irohWeb--YyEA-hX.js.map → irohWeb-4oChIOIp.js.map} +1 -1
  38. package/dist/assets/{main-BzNN6bkx.js → main-DbqcdShN.js} +142 -35
  39. package/dist/assets/main-DbqcdShN.js.map +1 -0
  40. package/dist/assets/{mainline-D5D5C-jT.js → mainline-ZkiOnevs.js} +2 -2
  41. package/dist/assets/{mainline-D5D5C-jT.js.map → mainline-ZkiOnevs.js.map} +1 -1
  42. package/dist/assets/{networkReason-DxZWHw6v.js → networkReason-CetKkTPo.js} +2 -2
  43. package/dist/assets/{networkReason-DxZWHw6v.js.map → networkReason-CetKkTPo.js.map} +1 -1
  44. package/dist/assets/nwc-CgkqyvsE.js +612 -0
  45. package/dist/assets/nwc-CgkqyvsE.js.map +1 -0
  46. package/dist/assets/nwcProvider-4eVz_8HP.js +77 -0
  47. package/dist/assets/nwcProvider-4eVz_8HP.js.map +1 -0
  48. package/dist/assets/{profiles-B6F4oa9K.js → profiles-BHc9pEYg.js} +2 -2
  49. package/dist/assets/{profiles-B6F4oa9K.js.map → profiles-BHc9pEYg.js.map} +1 -1
  50. package/dist/assets/registry-DlAKO0Fy.js +3793 -0
  51. package/dist/assets/registry-DlAKO0Fy.js.map +1 -0
  52. package/dist/assets/registry-OBJBnHVQ.js +2 -0
  53. package/dist/assets/resolve-CouHhCpJ.js +591 -0
  54. package/dist/assets/resolve-CouHhCpJ.js.map +1 -0
  55. package/dist/assets/{server-CBCEYKMN.js → server-Ss0gJ-nw.js} +184 -1181
  56. package/dist/assets/server-Ss0gJ-nw.js.map +1 -0
  57. package/dist/assets/{src-DhfGgAUu.js → src-DDzSERyc.js} +2249 -72
  58. package/dist/assets/src-DDzSERyc.js.map +1 -0
  59. package/dist/assets/{turnRefs-ChEfgbSX.js → turnRefs-B87dtrHM.js} +2 -2
  60. package/dist/assets/{turnRefs-ChEfgbSX.js.map → turnRefs-B87dtrHM.js.map} +1 -1
  61. package/dist/assets/types-C4_KafL_.js +122 -0
  62. package/dist/assets/types-C4_KafL_.js.map +1 -0
  63. package/dist/assets/{usdt-C1Xao2xH.js → usdt-Fj8CQO2z.js} +3 -3
  64. package/dist/assets/{usdt-C1Xao2xH.js.map → usdt-Fj8CQO2z.js.map} +1 -1
  65. package/dist/ghostly.mjs +1 -1
  66. package/package.json +1 -1
  67. package/dist/assets/api-CrxVxtY1.js.map +0 -1
  68. package/dist/assets/apiKit-CajiDxdK.js.map +0 -1
  69. package/dist/assets/backup-BAMi2lAq.js.map +0 -1
  70. package/dist/assets/buttons-CWl2yNJu.js +0 -2
  71. package/dist/assets/endpoint-Dn7H8qE1.js.map +0 -1
  72. package/dist/assets/errors-my6VJR_0.js.map +0 -1
  73. package/dist/assets/groupFiles-BNTSDaoq.js +0 -7685
  74. package/dist/assets/groupFiles-BNTSDaoq.js.map +0 -1
  75. package/dist/assets/groupFiles-DBbjOLuD.js +0 -2088
  76. package/dist/assets/groupFiles-DBbjOLuD.js.map +0 -1
  77. package/dist/assets/host-Cmd205Dc.js.map +0 -1
  78. package/dist/assets/main-BzNN6bkx.js.map +0 -1
  79. package/dist/assets/server-CBCEYKMN.js.map +0 -1
  80. 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 appRef, D as parseVideoMeta, Ft as toBase64Url, G as claimedTime, It as toZ32, J as shownTime, K as heardTime, Lt as utf8Decode, N as STATUS_CARD_LIMITS, Nt as randomBytes, O as BUTTON_ID, Ot as bytesEqual, Q as appFingerprint, Rt as utf8Encode, T as parseVoiceMeta, U as statusCardText, V as sameButtonText, W as arrivalKey, Z as APP_STATEMENT_LIMITS, _ as safeBlobType, _t as LIMITS, at as readAppRevocation, bt as encodeControl, c as readGroupFileMeta, g as formatFileSize, ht as readCanonicalJson, it as isAppRef, jt as fromBase64Url, nt as isAppHash, o as groupFileFallback, p as parseImageMeta, pt as canonicalJson, q as receivedTimestamp, rt as isAppKey, t as GROUP_FILE_LIMITS, v as sanitizeFileName, w as fileMessageText, y as sanitizeMime, yt as decodeControl, z as checkStatusCard } from "./groupFiles-DBbjOLuD.js";
3
- import { $a as HANDOFF_HELLO, $i as EDIT_RATE_WINDOW_MS, $o as readEnrollGrant, $r as STATUS_CARD_CAPABILITY, $t as didDhtFromPublicKey, A as CommunitySession, Aa as RELAY_POLL_INTERVALS, Ao as readHandoffHello, Ar as typingActivity, As as nextTurnPosition, B as knockRecords, Bi as PeerProofs, Bn as isFederationId, Bo as signTombstone, Br as deviceWakeRequest, Bs as RelayTransport, C as sha256Paced, Ca as rosterHas, Ci as CapsExchange, Co as handoffStreamKey, Cr as FILE_LIMITS, Cs as TURN_RAISE_READ_TIMEOUT_MS, D as openGroupTyping, Do as readHandoffCancel, Ds as TURN_LAST_SEQUENCE, E as groupTypingEpoch, Ea as DirectPathWatch, Ei as dialDescriptors, Eo as readHandoffAck, Es as turnPutSummary, Et as normalizeAtprotoHandle, Fa as webLocalFetch, Fi as identityStatement, Fn as decodeBolt11, Fr as pinIsNewer, Fs as turnKeys, G as groupWakeFrame, Ga as emptyLinkRecords, Gn as LIVENESS_PING_MS, Gr as checkPushEndpoint, Gs as budgetRetryMs, H as readKnocks, Hi as validExternalKey, Hn as parseFedimintRequestPayload, Hr as parseWakeFrame, Hs as normalizeRelayUrl, I as decodeGroupEntryLink, Ii as newIdentityBinding, In as ENDPOINT, Ir as pinNumberHolds, Is as turnName, J as GROUP_LIMITS, Ja as serviceIdFromName, Ji as DHT_MESSAGE_TTL, Jn as engineText, Jo as enrollDigits, Jr as chatAppId, Js as PacketTooLargeError, K as groupWakes, Ka as HTTP_SERVICE_PROTO, Kn as pairedMessageFrame, Ko as enrollCancelFrame, Kr as vapidKeysMatch, Ks as isDiscoveryBudgetError, L as encodeGroupEntryLink, La as RTC_CONFIG, Li as receivedIdentityStatus, Ln as cashuRequestPayload, Lo as readSetUpdate, Lr as readPin, Ls as turnStatedVerifier, M as communityHasFrame, Ma as GhostlyHttpError, Mo as readHandoffTurnFrame, Mr as PIN_LIMITS, Ms as signTurnPacket, N as decodeCommunityLink, Na as formatLocalTarget, Ni as emptyIdentityLedger, No as readHandoffVerified, Nr as mayPin, Ns as signTurnRelease, O as sealGroupTyping, Oa as DHT_POLL_INTERVALS, Oo as readHandoffDone, Or as TypingSender, Os as TURN_MAX, P as encodeCommunityLink, Pa as parseLocalTarget, Pi as identityRevocationValue, Po as sealHandoffPiece, Pr as nextPinNumber, Ps as turnInstance, Q as frameBytes, Qa as HANDOFF_HAVE, Qi as EDIT_BUFFER_MS, Qo as enrollProofFrame, Qr as EDIT_CAPABILITY, Qt as didDhtDocument, R as entryParams, Ri as revokesIdentity, Rn as fedimintRequestPayload, Ro as setAckFrame, Rr as WAKE_CALL_INTERVAL_MS, Rs as turnVerifierMark, S as readAppBundleAsync, Sa as commitHash, Sn as isBitcoinAddress, So as handoffRetryAfter, Sr as ChatFiles, T as GroupTypingBoard, Ta as edgeParams, Ti as capsDescriptors, To as newHandoffId, Tr as transferStalled, Ts as classifyTurnRead, Tt as isAtprotoDid, Ua as parseCallSignal, Ui as TRANSPORTS, Un as GhostLink, Ur as relayRequest, V as mergeKnocks, Vi as emptyProofLedger, Vn as parseCashuRequestPayload, Vo as verifySetUpdate, Vr as newWakeToken, Vs as currentRelays, W as GroupWakeLimiter, Wi as automaticTransport, Wn as INVITE_TAKEN, Wr as wakeRequest, Ws as traceLink, X as GROUP_READ_NOTE, Xa as HANDOFF_CANCEL, Xi as DHT_FILE_CAPABILITY, Xo as enrollGrantFrame, Xr as APPS_CAPABILITY, Xs as createRelayPayload, Ya as HANDOFF_ACK, Yi as peekDhtMailbox, Yn as CarriedTransport, Yo as enrollDoneFrame, Yr as isAppVersion, Z as GroupSession, Za as HANDOFF_DONE, Zi as dhtFileId, Zo as enrollHelloFrame, Zr as BUTTONS_CAPABILITY, _a as readReply, _n as walletNetworkOf, _o as handoffHelloFrame, _r as rankHubs, _s as deviceLinkPairing, a as planAppUpdate, aa as COMMUNITY_REACTION_FRAME, ai as HoldRefusedError, an as EVM_TEST_CHAINS, ao as HANDOFF_VERIFIED, ar as pickMeshHubs, as as OwnInviteError, at as groupName, b as pacer, bn as sparkAddressKind, bo as handoffPakeFrame, br as readLobby, bs as seedSigner, ca as ReactionWindow, ci as readManifest, cn as SEPOLIA_TEST_USDT, co as handoffAttemptFailed, cr as beaconRecords, cs as encodeInviteCode, d as APP_BUNDLE_LIMITS, da as reactionEmoji, dn as USDT_PUBLIC_RPC, do as handoffCancelFrame, dr as lobbyKeys, ds as DEVICES_CAPABILITY, ea as RateWindow, ec as createIdentity, ei as UPGRADE_CAPABILITY, en as encodeDidDhtDeactivated, eo as HANDOFF_OFFER, er as groupSignalFrame, es as readEnrollHello, f as APP_BUNDLE_MAGIC, fa as reactionIsNewer, fn as WALLET_NETWORKS, fo as handoffContext, fr as lobbyRecords, fs as ENROLL_CAPABILITY, g as appViewOf, ga as pairedReplyAuthor, gn as validatePaymentTarget, go as handoffEphemeral, gr as pickHubs, gs as deviceKeyZ32, h as appDigest, ha as wireReaction, hn as formatPaymentAmount, ho as handoffDigest, hr as newerHead, hs as deviceEchoNonce, i as checkAppBeforeRun, ia as readForwarded, ic as verify, ii as HoldKeys, in as ETHEREUM_USDT, io as HANDOFF_TIMINGS, ir as meshHubs, is as readDeviceInvite, it as mentionsMember, j as GROUP_READ_NOTE_COMMUNITY, ja as presenceSeenAt, ji as IdentityExchange, jo as readHandoffPake, js as readTurnPacket, k as COMMUNITY_LIMITS, ki as IDENTITY_REVOCATION_LABEL, ko as readHandoffHave, ks as firstDeviceSetSecret, l as readAppStoreSteps, la as nextReactionNumber, lo as handoffAttemptSucceeded, lr as doorHubs, ma as validReactionNumber, mn as assertWholeSats, mo as handoffDataFrame, mr as mergeLobby, ms as deviceEchoFrame, n as appStoreDecision, na as parseLinkPreview, nc as identityFromSeedB64, ni as HOLD_DATED_BACK_MS, no as HANDOFF_PIECE_BYTES, nr as MESH_HUBS, ns as createDeviceInvite, nt as carryMentions, o as APP_STORE_LIMITS, oa as GROUP_REACTION_FRAME, oc as sanitizeAvatar, oi as isHoldUrl, on as ONCHAIN_PROVIDER, oo as filePartName, or as COMMUNITY_TOPOLOGY, os as createChatInvite, p as APP_ICON_PATH, pa as readReaction, pn as assertTokenUnits, po as handoffContextProof, pr as mergeBeacon, ps as HANDOFF_CAPABILITY, q as parseGroupWakeFrame, qa as LEGACY_SERVICES, qn as engineError, qo as enrollCancelReason, qs as withRequestOptions, r as appUpdateDecision, ra as forwardedAgain, rc as publicKeyFromZ32, ri as HOLD_LIMITS, rn as signDidDhtPacket, ro as HANDOFF_REQUEST, rr as mayBeHub, rs as deviceInviteJoinerParams, rt as parseCommunityEdit, sa as REACTION_LIMITS, si as newMailbox, sn as PaymentPreflightError, so as handoffAttemptAllowed, sr as beaconKeys, ss as decodeInviteCode, t as appRevocationFor, ta as dhtEditId, tc as identityFromSeed, ti as HOLD_CAPABILITY, tn as encodeDidDhtPacket, to as HANDOFF_PAKE, tr as readGroupSignal, ts as verifyEnrollProof, tt as GROUP_EDIT_TEXT_BYTES, ua as queueReaction, un as TEST_USDT_FAUCET_AMOUNT, uo as handoffBusyFrame, ur as freshHubs, us as inviteOwnership, va as replySnippet, vn as SPARK_NETWORKS, vo as handoffManifestFrames, vr as readBeacon, vs as devicePingFrame, wa as verifyCommitSignature, wi as DHT_TEXT_CAPABILITY, wn as isPubkyKey, wo as handoffVersions, wr as transferEnded, ws as TURN_SETTLE_MS, xa as MEMBER_KEY, xn as sparkInvoiceDetails, xo as handoffReleaseFrame, xr as shouldBeHub, xs as webCryptoSigner, y as isAppUrl, ya as GROUP_MEMBER_CAP, yn as SPARK_PROVIDER, yo as handoffOfferFrame, yr as readBeaconHead, ys as newDeviceSetSecret, z as knockIdentity, zi as lastSharedWithMe, zn as findEndpoint, zo as setUpdateFrame, zr as WakeLimiter, zs as DEFAULT_RELAYS } from "./src-DhfGgAUu.js";
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-DxZWHw6v.js";
6
- import { $ as SourceConfigError, A as identityProviders, B as verifyIdentity, C as FileAppender, D as streamStored, E as storedSize, F as onAdaptersChanged, G as normalizePhrase, H as LightningCards, I as registeredLightningProviders, J as sourceKey, K as lightningPaying, L as registeredOnchainProviders, M as resolveAtprotoDid, N as setOwnDidSource, P as chosenResolver, Q as PROVIDER_PLATFORMS, R as reserveAdapterIds, S as WALLET_TYPES, T as removeStored, U as cardsKey, V as CASHU_CARD, W as isRecoveryPhrase, X as cashuMint, Y as CASHU_MINT_SOURCE, Z as NothingSpentError, _ as crossNetwork, _t as setAwayWallets, a as storedGroupFiles, at as breezDatabaseInUse, b as walletInstances, c as receivedPairedReply, ct as iceServerProblem, d as paymentAlias, dt as ModeGate, et as SourceUnreachableError, f as paymentWireId, ft as WrongNetworkError, g as createTiming, gt as requireTurn, h as createFailure, ht as refuseAway, i as groupOfFile, it as breezDatabase, j as assertPublicServer, l as replyRef, lt as iceServerSetting, m as assertConfirmedReal, mt as awayFrom, n as groupFileId, nt as networkMode, o as groupReply, ot as breezDatabasesFor, p as WALLET_NAMES, pt as networkLabel, q as ProviderSources, r as groupFileText, rt as redact, s as pairedWireReply, st as dropBreezDatabase, t as GroupFileDesk, tt as isNothingSpentError, u as replyTo, ut as ModeChanged, v as paymentNetwork, vt as setSingleWriterGate, w as readStored$1, y as paymentNetworksOf, z as boundedIdentityFetch } from "./groupFiles-BNTSDaoq.js";
7
- import { i as fedimintDatabase, n as createIrohWebEndpoint, o as loadFedimintSdk, r as irohRelayProblem, t as DEFAULT_IROH_RELAYS } from "./irohWeb--YyEA-hX.js";
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-BvU0fLEt.js";
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-C1Xao2xH.js");
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-D4BVJFEQ.js");
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$3;
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$3 ??= (async () => {
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$3 = void 0;
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$2;
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$2 ??= (async () => {
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$2 = void 0;
2681
+ loading$3 = void 0;
2662
2682
  throw error;
2663
2683
  });
2664
2684
  }
2665
- var hex$9 = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
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$9(sha256(new TextEncoder().encode(params.apiKey))).slice(0, 8) : ""}`;
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
- for (const { review } of await this.repository.list()) {
3448
- if (review.state !== "pending" || review.expiresAt > now && !unpayable(review)) continue;
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$8 = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
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$8(b);
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$1;
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$1 ??= (async () => {
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$1 = void 0;
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$7 = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
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$7(sha256(new TextEncoder().encode(publicDescriptor))).slice(0, 32)}`;
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$7(tx.to_bytes())
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$2 = 3600;
5696
- var NETWORKS$2 = {
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$4 = /^[0-9a-f]{64}$/;
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$2[info?.network];
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$2
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$4.test(paymentHash)) throw new Error("Invalid payment hash");
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 = identityProviders) {
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) return this.forget(groupId);
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) return this.forget(groupId);
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
- const next = s.others.find((key) => connected.has(key)) ?? s.others[0];
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") return this.forget(groupId);
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
- return this.forget(groupId);
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 fileStore.put({
36201
- id: file.id,
36202
- linkId,
36203
- blob: new Blob([bytes], { type: safeBlobType(file.mime) }),
36204
- createdAt: Date.now(),
36205
- direction: "in",
36206
- wireId: wire.wireId,
36207
- digest,
36208
- metadata: {
36209
- name: wire.name,
36210
- size: wire.size,
36211
- mime: wire.mime,
36212
- timestamp: wire.timestamp,
36213
- voice: wire.voice,
36214
- video: wire.video,
36215
- image: wire.image
36216
- },
36217
- transfer: {
36218
- state: "done",
36219
- transferred: wire.size,
36220
- size: wire.size
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
- leaveGroup({ groupId }) {
40839
- return this.groups.leave(groupId);
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-CEmBpT0Q.js")).createRelayedHyperEndpoint(seedB64, relay) } : {},
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-CBCEYKMN.js.map
43555
+ //# sourceMappingURL=server-Ss0gJ-nw.js.map