@ghostlytools/cli 1.3.0 → 1.3.2

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 (81) hide show
  1. package/README.md +9 -3
  2. package/dist/assets/{api-BiaX6l6f.js → api-SYPJgFRU.js} +2 -2
  3. package/dist/assets/{api-CrxVxtY1.js → api-aup3Amud.js} +141 -40
  4. package/dist/assets/api-aup3Amud.js.map +1 -0
  5. package/dist/assets/{apiKit-CajiDxdK.js → apiKit-DYkVEs6r.js} +30 -30
  6. package/dist/assets/apiKit-DYkVEs6r.js.map +1 -0
  7. package/dist/assets/{apps-t23gkc28.js → apps-DcZKu29s.js} +1 -1
  8. package/dist/assets/{apps-CbfiCSIw.js → apps-Rbk2HHXb.js} +3 -4
  9. package/dist/assets/{apps-CbfiCSIw.js.map → apps-Rbk2HHXb.js.map} +1 -1
  10. package/dist/assets/{arkade-D4BVJFEQ.js → arkade-CuWlNnCz.js} +4 -4
  11. package/dist/assets/arkade-CuWlNnCz.js.map +1 -0
  12. package/dist/assets/{backup-BAMi2lAq.js → backup-CJWLDHuS.js} +8 -8
  13. package/dist/assets/backup-CJWLDHuS.js.map +1 -0
  14. package/dist/assets/{backup-Cg52WeQi.js → backup-CSMw2GHv.js} +1 -1
  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-CGRqZ7z0.js +2 -0
  22. package/dist/assets/{buttons-CX1pKB0j.js → buttons-QEnAGWoC.js} +4 -5
  23. package/dist/assets/{buttons-CX1pKB0j.js.map → buttons-QEnAGWoC.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-K27mzoyu.js} +2 -2
  27. package/dist/assets/{envelope-BvU0fLEt.js.map → envelope-K27mzoyu.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/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -1
  31. package/dist/assets/groupFiles-DHTrTA4I.js +3229 -0
  32. package/dist/assets/groupFiles-DHTrTA4I.js.map +1 -0
  33. package/dist/assets/{host-Cmd205Dc.js → host-C7hA-Za7.js} +135 -42
  34. package/dist/assets/host-C7hA-Za7.js.map +1 -0
  35. package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js → hyperdhtRelay-BVoWY2ua.js} +2 -3
  36. package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js.map → hyperdhtRelay-BVoWY2ua.js.map} +1 -1
  37. package/dist/assets/{irohWeb--YyEA-hX.js → irohWeb-BJPk9bMn.js} +2 -3
  38. package/dist/assets/{irohWeb--YyEA-hX.js.map → irohWeb-BJPk9bMn.js.map} +1 -1
  39. package/dist/assets/{main-BzNN6bkx.js → main-DC8cUkDx.js} +142 -35
  40. package/dist/assets/main-DC8cUkDx.js.map +1 -0
  41. package/dist/assets/{mainline-D5D5C-jT.js → mainline-CxbxpoU4.js} +2 -2
  42. package/dist/assets/{mainline-D5D5C-jT.js.map → mainline-CxbxpoU4.js.map} +1 -1
  43. package/dist/assets/{networkReason-DxZWHw6v.js → networkReason-D42iNuUB.js} +2 -2
  44. package/dist/assets/{networkReason-DxZWHw6v.js.map → networkReason-D42iNuUB.js.map} +1 -1
  45. package/dist/assets/nwc-Cm4sYgLv.js +612 -0
  46. package/dist/assets/nwc-Cm4sYgLv.js.map +1 -0
  47. package/dist/assets/nwcProvider-0D7vdPDd.js +77 -0
  48. package/dist/assets/nwcProvider-0D7vdPDd.js.map +1 -0
  49. package/dist/assets/{profiles-B6F4oa9K.js → profiles-BHc9pEYg.js} +2 -2
  50. package/dist/assets/{profiles-B6F4oa9K.js.map → profiles-BHc9pEYg.js.map} +1 -1
  51. package/dist/assets/registry-CLolR0kM.js +2 -0
  52. package/dist/assets/{groupFiles-BNTSDaoq.js → registry-CVdSZW5l.js} +19 -3911
  53. package/dist/assets/registry-CVdSZW5l.js.map +1 -0
  54. package/dist/assets/resolve-BnlEajYz.js +591 -0
  55. package/dist/assets/resolve-BnlEajYz.js.map +1 -0
  56. package/dist/assets/{server-CBCEYKMN.js → server-CJNEKxzE.js} +284 -1202
  57. package/dist/assets/server-CJNEKxzE.js.map +1 -0
  58. package/dist/assets/{src-DhfGgAUu.js → src-yMUQvdyL.js} +2264 -71
  59. package/dist/assets/src-yMUQvdyL.js.map +1 -0
  60. package/dist/assets/{turnRefs-ChEfgbSX.js → turnRefs-B2a8to9q.js} +2 -2
  61. package/dist/assets/{turnRefs-ChEfgbSX.js.map → turnRefs-B2a8to9q.js.map} +1 -1
  62. package/dist/assets/types-C4_KafL_.js +122 -0
  63. package/dist/assets/types-C4_KafL_.js.map +1 -0
  64. package/dist/assets/{usdt-C1Xao2xH.js → usdt-DesFeDiG.js} +3 -3
  65. package/dist/assets/{usdt-C1Xao2xH.js.map → usdt-DesFeDiG.js.map} +1 -1
  66. package/dist/ghostly.mjs +1 -1
  67. package/package.json +1 -1
  68. package/dist/assets/api-CrxVxtY1.js.map +0 -1
  69. package/dist/assets/apiKit-CajiDxdK.js.map +0 -1
  70. package/dist/assets/arkade-D4BVJFEQ.js.map +0 -1
  71. package/dist/assets/backup-BAMi2lAq.js.map +0 -1
  72. package/dist/assets/buttons-CWl2yNJu.js +0 -2
  73. package/dist/assets/endpoint-Dn7H8qE1.js.map +0 -1
  74. package/dist/assets/errors-my6VJR_0.js.map +0 -1
  75. package/dist/assets/groupFiles-BNTSDaoq.js.map +0 -1
  76. package/dist/assets/groupFiles-DBbjOLuD.js +0 -2088
  77. package/dist/assets/groupFiles-DBbjOLuD.js.map +0 -1
  78. package/dist/assets/host-Cmd205Dc.js.map +0 -1
  79. package/dist/assets/main-BzNN6bkx.js.map +0 -1
  80. package/dist/assets/server-CBCEYKMN.js.map +0 -1
  81. 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 normalizeRelayUrl, $i as lastSharedWithMe, $o as parseCallSignal, $r as WakeLimiter, $t as didDhtFromPublicKey, A as CommunitySession, Aa as readForwarded, Ac as newDeviceSetSecret, Ao as rosterHas, Ar as pickHubs, As as handoffPakeFrame, B as knockRecords, Ba as validReactionNumber, Bc as firstDeviceSetSecret, Bi as dialDescriptors, Bn as findEndpoint, Bo as formatLocalTarget, Bs as readHandoffHello, C as sha256Paced, Cc as ENROLL_CAPABILITY, Cl as utf8Decode, Cn as isBitcoinAddress, Co as readCanonicalJson, Cr as doorHubs, Cs as handoffContextProof, D as openGroupTyping, Da as dhtEditId, Dc as deviceKeyZ32, Do as commitHash, Dr as mergeBeacon, Ds as handoffHelloFrame, E as groupTypingEpoch, Ea as RateWindow, Ec as deviceEchoNonce, Eo as MEMBER_KEY, Er as lobbyRecords, Es as handoffEphemeral, Et as normalizeAtprotoHandle, Fa as nextReactionNumber, Fc as TURN_SETTLE_MS, Fo as DHT_POLL_INTERVALS, Fr as shouldBeHub, Fs as newHandoffId, G as groupWakeFrame, Ga as BUTTON_ID, Gc as turnInstance, Gi as IdentityExchange, Gn as INVITE_TAKEN, Go as encodeControl, H as readKnocks, Ha as pairedReplyAuthor, Hc as readTurnPacket, Hn as parseCashuRequestPayload, Ho as webLocalFetch, Hr as TypingSender, Hs as readHandoffTurnFrame, I as decodeGroupEntryLink, Ia as queueReaction, Ic as classifyTurnRead, In as decodeBolt11, Ir as ChatFiles, Is as readHandoffAck, J as GROUP_LIMITS, Jc as turnStatedVerifier, Ji as identityRevocationValue, Jn as pairedMessageFits, Jr as nextPinNumber, Js as setAckFrame, K as groupWakes, Kc as turnKeys, Kn as LIVENESS_PING_MS, Kr as PIN_LIMITS, L as encodeGroupEntryLink, La as reactionEmoji, Lc as turnPutSummary, Li as CapsExchange, Ln as ENDPOINT, Lo as RELAY_POLL_INTERVALS, Lr as FILE_LIMITS, Ls as readHandoffCancel, M as communityHasFrame, Ma as GROUP_REACTION_FRAME, Mc as webCryptoSigner, Mo as edgeParams, Mr as readBeacon, Ms as handoffRetryAfter, N as decodeCommunityLink, Na as REACTION_LIMITS, No as DirectPathWatch, Nr as readBeaconHead, Ns as handoffStreamKey, O as sealGroupTyping, Oa as parseLinkPreview, Oc as deviceLinkPairing, Oo as parseGroupCommit, Or as mergeLobby, Os as handoffManifestFrames, P as encodeCommunityLink, Pa as ReactionWindow, Pc as TURN_RAISE_READ_TIMEOUT_MS, Pr as readLobby, Ps as handoffVersions, Q as frameBytes, Qc as currentRelays, Qi as revokesIdentity, Qn as GROUP_FILE_LIMITS, Qr as WAKE_CALL_INTERVAL_MS, Qt as didDhtDocument, R as entryParams, Ra as reactionIsNewer, Rc as TURN_LAST_SEQUENCE, Ri as DHT_TEXT_CAPABILITY, Rn as cashuRequestPayload, Ro as presenceSeenAt, Rr as transferEnded, Rs as readHandoffDone, S as readAppBundleAsync, Sa as parseImageMeta, Sc as DEVICES_CAPABILITY, Si as readManifest, Sl as toZ32, Sn as sparkInvoiceDetails, Sr as beaconRecords, Ss as handoffContext, T as GroupTypingBoard, Ta as EDIT_RATE_WINDOW_MS, Tc as deviceEchoFrame, Tn as isPubkyKey, Tr as lobbyKeys, Ts as handoffDigest, Tt as isAtprotoDid, Ua as readReply, Uc as signTurnPacket, Ui as IDENTITY_REVOCATION_LABEL, Un as parseFedimintRequestPayload, Uo as LIMITS, Us as readHandoffVerified, V as mergeKnocks, Va as wireReaction, Vc as nextTurnPosition, Vn as isFederationId, Vo as parseLocalTarget, Vs as readHandoffPake, W as GroupWakeLimiter, Wa as replySnippet, Wc as signTurnRelease, Wn as GhostLink, Wo as decodeControl, Wr as typingActivity, Ws as sealHandoffPiece, X as GROUP_READ_NOTE, Xa as STATUS_CARD_LIMITS, Xc as DEFAULT_RELAYS, Xi as newIdentityBinding, Xn as engineError, Xr as pinNumberHolds, Xs as signTombstone, Yc as turnVerifierMark, Yi as identityStatement, Yn as pairedMessageFrame, Yr as pinIsNewer, Ys as setUpdateFrame, Z as GroupSession, Zc as RelayTransport, Zi as receivedIdentityStatus, Zn as engineText, Zr as readPin, Zs as verifySetUpdate, _c as decodeInviteCode, _i as HOLD_LIMITS, _l as sanitizeAvatar, _n as validatePaymentTarget, _o as readAppRevocation, _r as mayBeHub, _s as handoffAttemptAllowed, a as planAppUpdate, ac as enrollDoneFrame, ai as checkPushEndpoint, al as PacketTooLargeError, an as EVM_TEST_CHAINS, ao as claimedTime, ar as readGroupFileMeta, as as HANDOFF_ACK, at as groupName, b as pacer, bc as inviteOwnership, bi as isHoldUrl, bl as randomBytes, bn as SPARK_PROVIDER, br as COMMUNITY_TOPOLOGY, bs as handoffBusyFrame, ca as DHT_MESSAGE_TTL, cc as enrollProofFrame, ci as chatAppId, cn as SEPOLIA_TEST_USDT, co as shownTime, cs as HANDOFF_HAVE, d as APP_BUNDLE_LIMITS, da as dhtFileId, dc as verifyEnrollProof, di as BUTTONS_CAPABILITY, dl as createIdentity, dn as USDT_PUBLIC_RPC, do as appFingerprint, ds as HANDOFF_PAKE, ea as PeerProofs, ei as deviceWakeRequest, en as encodeDidDhtDeactivated, f as APP_BUNDLE_MAGIC, fa as formatFileSize, fc as createDeviceInvite, fi as EDIT_CAPABILITY, fl as identityFromSeed, fn as WALLET_NETWORKS, fo as appRef, fs as HANDOFF_PIECE_BYTES, g as appViewOf, gc as createChatInvite, gi as HOLD_DATED_BACK_MS, gn as formatPaymentAmount, go as isAppRef, gr as MESH_HUBS, gs as filePartName, h as appDigest, hc as OwnInviteError, hi as HOLD_CAPABILITY, hl as verify, hn as assertWholeSats, ho as isAppKey, hr as readGroupSignal, hs as HANDOFF_VERIFIED, i as checkAppBeforeRun, ia as automaticTransport, ic as enrollDigits, ii as wakeRequest, il as withRequestOptions, in as ETHEREUM_USDT, io as arrivalKey, is as serviceIdFromName, it as mentionsMember, j as GROUP_READ_NOTE_COMMUNITY, ja as COMMUNITY_REACTION_FRAME, jc as seedSigner, jo as verifyCommitSignature, jr as rankHubs, js as handoffReleaseFrame, k as COMMUNITY_LIMITS, ka as forwardedAgain, kc as devicePingFrame, ko as rosterAdmin, kr as newerHead, ks as handoffOfferFrame, l as readAppStoreSteps, la as peekDhtMailbox, lc as readEnrollGrant, li as isAppVersion, lr as CarriedTransport, ls as HANDOFF_HELLO, ma as sanitizeMime, mc as readDeviceInvite, mi as UPGRADE_CAPABILITY, ml as publicKeyFromZ32, mn as assertTokenUnits, mo as isAppHash, mr as groupSignalFrame, ms as HANDOFF_TIMINGS, n as appStoreDecision, na as validExternalKey, nc as enrollCancelFrame, ni as parseWakeFrame, nl as budgetRetryMs, no as sameButtonText, ns as HTTP_SERVICE_PROTO, nt as carryMentions, o as APP_STORE_LIMITS, oc as enrollGrantFrame, oi as vapidKeysMatch, on as ONCHAIN_PROVIDER, oo as heardTime, os as HANDOFF_CANCEL, p as APP_ICON_PATH, pa as sanitizeFileName, pc as deviceInviteJoinerParams, pi as STATUS_CARD_CAPABILITY, pl as identityFromSeedB64, pn as arkAddressPrefix, ps as HANDOFF_REQUEST, q as parseGroupWakeFrame, qc as turnName, qi as emptyIdentityLedger, qn as PAIRED_FRAME_TOO_LARGE, qo as RTC_CONFIG, qr as mayPin, qs as readSetUpdate, r as appUpdateDecision, ra as TRANSPORTS, rc as enrollCancelReason, ri as relayRequest, rl as isDiscoveryBudgetError, rn as signDidDhtPacket, ro as statusCardText, rr as groupFileFallback, rs as LEGACY_SERVICES, rt as parseCommunityEdit, sc as enrollHelloFrame, sl as createRelayPayload, sn as PaymentPreflightError, so as receivedTimestamp, ss as HANDOFF_DONE, t as appRevocationFor, ta as emptyProofLedger, ti as newWakeToken, tl as traceLink, tn as encodeDidDhtPacket, to as checkStatusCard, ts as emptyLinkRecords, tt as GROUP_EDIT_TEXT_BYTES, ua as DHT_FILE_CAPABILITY, uc as readEnrollHello, ui as APPS_CAPABILITY, un as TEST_USDT_FAUCET_AMOUNT, uo as APP_STATEMENT_LIMITS, us as HANDOFF_OFFER, va as fileMessageText, vc as encodeInviteCode, vi as HoldKeys, vl as bytesEqual, vn as walletNetworkOf, vr as meshHubs, vs as handoffAttemptFailed, wa as EDIT_BUFFER_MS, wc as HANDOFF_CAPABILITY, wl as utf8Encode, wo as GROUP_MEMBER_CAP, wr as freshHubs, ws as handoffDataFrame, xa as parseVideoMeta, xi as newMailbox, xl as toBase64Url, xn as sparkAddressKind, xo as canonicalJson, xr as beaconKeys, xs as handoffCancelFrame, y as isAppUrl, ya as parseVoiceMeta, yi as HoldRefusedError, yl as fromBase64Url, yn as SPARK_NETWORKS, yr as pickMeshHubs, ys as handoffAttemptSucceeded, z as knockIdentity, za as readReaction, zc as TURN_MAX, zi as capsDescriptors, zn as fedimintRequestPayload, zo as GhostlyHttpError, zr as transferStalled, zs as readHandoffHave } from "./src-yMUQvdyL.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-0D7vdPDd.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-D42iNuUB.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-DHTrTA4I.js";
9
+ import { i as fedimintDatabase, n as createIrohWebEndpoint, o as loadFedimintSdk, r as irohRelayProblem, t as DEFAULT_IROH_RELAYS } from "./irohWeb-BJPk9bMn.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-BnlEajYz.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-K27mzoyu.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-DesFeDiG.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-CuWlNnCz.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. */
@@ -648,7 +664,7 @@ var checkProviders = (network, ...urls) => {
648
664
  provider,
649
665
  asset: "BTC",
650
666
  unit: "sat",
651
- address: "configuration",
667
+ address: `${arkAddressPrefix(network)}configuration`,
652
668
  expiresAt: Date.now() + 6e4
653
669
  });
654
670
  };
@@ -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
  }
@@ -1235,7 +1253,7 @@ var BarkAdapter = class BarkAdapter {
1235
1253
  provider,
1236
1254
  asset: "BTC",
1237
1255
  unit: "sat",
1238
- address: "configuration",
1256
+ address: `${barkAddressPrefix(config.network)}configuration`,
1239
1257
  expiresAt: Date.now() + 6e4
1240
1258
  });
1241
1259
  }
@@ -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-CLolR0kM.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() {
@@ -10923,7 +9895,8 @@ async function copyForForward(sourceId, target) {
10923
9895
  const source = await fileStore.get(sourceId);
10924
9896
  if (!source?.metadata) throw new Error("The file is not on this device any more");
10925
9897
  const size = storedSize(source);
10926
- if (source.direction === "in" && source.wire3 && source.wire3.state !== "done" || await bytesHere(source) !== size) throw new Error("The file has not arrived yet");
9898
+ const checked = source.wire3 ? source.wire3.state === "done" : !source.group || source.transfer?.state === "done";
9899
+ if (source.direction === "in" && !checked || await bytesHere(source) !== size) throw new Error("The file has not arrived yet");
10927
9900
  const { file, linkId, wireId, timestamp } = target;
10928
9901
  const metadata = {
10929
9902
  name: file.name,
@@ -11400,6 +10373,10 @@ var Apps = class {
11400
10373
  /** What each `ref\0scope` holds, read once from its counter row and kept in step by the writes here. */
11401
10374
  usage = /* @__PURE__ */ new Map();
11402
10375
  queues = /* @__PURE__ */ new Map();
10376
+ /** The keys read of each `ref\0scope` running, and the one every call made while it runs shares after it. */
10377
+ keyReads = /* @__PURE__ */ new Map();
10378
+ /** Uninstalls begun per reference, for this engine's life: a storage write that waited its turn across one is refused. */
10379
+ uninstalls = /* @__PURE__ */ new Map();
11403
10380
  timer = null;
11404
10381
  /** Drops what `staged` holds past `STAGED.ms`, set while it holds anything: a closed install screen sends no call. */
11405
10382
  sweep = null;
@@ -12687,6 +11664,8 @@ var Apps = class {
12687
11664
  STORES$1.appStorage,
12688
11665
  ...builtIn ? [STORES$1.settings] : []
12689
11666
  ], "readwrite");
11667
+ this.uninstalls.set(ref, (this.uninstalls.get(ref) ?? 0) + 1);
11668
+ this.held.delete(ref);
12690
11669
  tx.objectStore(STORES$1.apps).delete(ref);
12691
11670
  tx.objectStore(STORES$1.appStorage).delete(rowsOf(ref));
12692
11671
  if (builtIn) markBuiltIns(tx.objectStore(STORES$1.settings), (marks) => {
@@ -12765,6 +11744,7 @@ var Apps = class {
12765
11744
  * it, so the app can make room.
12766
11745
  */
12767
11746
  async storageSet({ ref, scope, key, value, digest }) {
11747
+ const uninstalls = this.uninstalls.get(ref);
12768
11748
  const app = await this.runningOrFail(ref, digest);
12769
11749
  const where = this.scopeOf(scope), name = this.keyOf(key);
12770
11750
  let text;
@@ -12795,6 +11775,10 @@ var Apps = class {
12795
11775
  this.usage.delete(`${app.ref}\0${where}`);
12796
11776
  fail("bad-scope", "Not a chat of this profile");
12797
11777
  }
11778
+ if (this.uninstalls.get(app.ref) !== uninstalls) {
11779
+ this.usage.delete(`${app.ref}\0${where}`);
11780
+ fail("not-installed", "This app is not installed");
11781
+ }
12798
11782
  writing.put({
12799
11783
  ref: app.ref,
12800
11784
  scope: where,
@@ -12834,7 +11818,28 @@ var Apps = class {
12834
11818
  }
12835
11819
  async storageKeys({ ref, scope, digest }) {
12836
11820
  const app = await this.runningOrFail(ref, digest);
12837
- return (await wrap((await store(STORES$1.appStorage, "readonly")).getAllKeys(keysOf(app.ref, this.scopeOf(scope))))).map((k) => k[2]);
11821
+ const where = this.scopeOf(scope);
11822
+ return this.keysOnce(`${app.ref}\0${where}`, async () => {
11823
+ return (await wrap((await store(STORES$1.appStorage, "readonly")).getAllKeys(keysOf(app.ref, where)))).map((k) => k[2]);
11824
+ });
11825
+ }
11826
+ /**
11827
+ * One read of a scope's keys at a time. Every call made while one runs shares the single read that starts after it, so
11828
+ * it still sees each write done before it was made: 50 calls a second on a full scope held the profile's database,
11829
+ * a chat message's write waited 17 s behind them.
11830
+ */
11831
+ keysOnce(id, read) {
11832
+ const waiting = this.keyReads.get(id);
11833
+ if (waiting) return waiting.next ??= waiting.running.then(() => this.keysStart(id, read), () => this.keysStart(id, read));
11834
+ return this.keysStart(id, read);
11835
+ }
11836
+ keysStart(id, read) {
11837
+ const slot = { running: read() };
11838
+ this.keyReads.set(id, slot);
11839
+ slot.running.catch(() => {}).then(() => {
11840
+ if (this.keyReads.get(id) === slot && !slot.next) this.keyReads.delete(id);
11841
+ });
11842
+ return slot.running;
12838
11843
  }
12839
11844
  /** An app's storage in every scope, as export files (WISP 1200 § Where installed apps live), offered before an uninstall. */
12840
11845
  async exportData({ ref }) {
@@ -23151,22 +22156,30 @@ var Communities = class {
23151
22156
  /**
23152
22157
  * Leaves: the request goes to the hubs, which keep it and commit it (any member can), and the
23153
22158
  * 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.
22159
+ * be connected to carry the request. Answers the member the role went to, when it went to one.
23155
22160
  */
23156
22161
  async leave(groupId) {
23157
22162
  const live = this.live.get(groupId);
23158
- if (!live) return this.forget(groupId);
22163
+ if (!live) {
22164
+ await this.forget(groupId);
22165
+ return;
22166
+ }
23159
22167
  const s = live.session;
23160
- if (s.status !== "active" || s.others.length === 0) return this.forget(groupId);
22168
+ if (s.status !== "active" || s.others.length === 0) {
22169
+ await this.forget(groupId);
22170
+ return;
22171
+ }
23161
22172
  const edges = [...this.host.edges(groupId).entries()].filter(([, id]) => this.host.linkReady(id, 2));
23162
22173
  if (!edges.length) throw new Error("Nobody in the group is connected right now to take your leave. Try again in a moment.");
22174
+ let next;
23163
22175
  if (s.isAdmin) {
23164
22176
  const connected = new Set(edges.map(([key]) => key));
23165
- const next = s.others.find((key) => connected.has(key)) ?? s.others[0];
22177
+ next = s.others.find((key) => connected.has(key)) ?? s.others[0];
23166
22178
  await s.transferAdmin(next);
23167
22179
  }
23168
22180
  await s.leave();
23169
22181
  await this.forget(groupId);
22182
+ return next;
23170
22183
  }
23171
22184
  /**
23172
22185
  * Delete from this device: left first, as Leave does. With nothing connected to carry the request, a member's leave
@@ -24304,7 +23317,7 @@ var Communities = class {
24304
23317
  const live = this.live.get(groupId);
24305
23318
  if (!live || live.session.wasRemoved(peerKey)) return;
24306
23319
  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());
23320
+ this.sendTo(linkId, live.session.syncFrame(peerKey));
24308
23321
  this.host.emit();
24309
23322
  }
24310
23323
  edgeNick(groupId, peerKey, nick) {
@@ -25503,6 +24516,11 @@ var ACCEPT_AGAINS = 5;
25503
24516
  var REWELCOME_MS = ACCEPT_AGAIN_MS / 2;
25504
24517
  /** Invitee side: the invitations one contact chat may have waiting here at a time. More are answered as declined. */
25505
24518
  var MAX_INVITATIONS_PER_CHAT = 4;
24519
+ /** Invitee side, removed and invited again: the chat that let me into the group I kept, if it is its admin's. */
24520
+ var keptAdminChat = (kept) => {
24521
+ const admin = kept?.state && rosterAdmin(kept.state.chain[kept.state.chain.length - 1].m);
24522
+ return admin ? kept.contacts?.[admin] : void 0;
24523
+ };
25506
24524
  /** What an invitation says of its group, kept as a claim within what a group can be: an epoch from 0, a roster of 1 to the most a group holds. */
25507
24525
  function invitationClaims(frame) {
25508
24526
  const e = Number.isSafeInteger(frame.e) ? frame.e : 0, n = Number.isSafeInteger(frame.n) ? frame.n : 1;
@@ -26046,7 +25064,7 @@ var Groups = class {
26046
25064
  const previous = group.previous;
26047
25065
  if (!previous) return this.forget(group.id, false);
26048
25066
  if (owed) previous.declined = owed;
26049
- else delete previous.declined;
25067
+ else if (previous.declined?.linkId === group.invitation?.linkId) delete previous.declined;
26050
25068
  this.acceptSaid.delete(group.id);
26051
25069
  this.stored.set(group.id, previous);
26052
25070
  if (previous.state) this.attach(previous.state);
@@ -26230,19 +25248,24 @@ var Groups = class {
26230
25248
  * Leaves, and the group is gone from this device at once: its row, its history, its files and every
26231
25249
  * edge but the one to the admin, which stays until the admin's commit removing me comes back (or a
26232
25250
  * 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.
25251
+ * members hands the role to one who is online first; alone, the group simply goes. Answers the member the role went
25252
+ * to, when it went to one.
26234
25253
  */
26235
25254
  async leave(groupId) {
26236
25255
  if (this.isCommunity(groupId)) return this.communities.leave(groupId);
26237
25256
  const session = this.session(groupId);
26238
25257
  const group = this.stored.get(groupId);
26239
- if (session.status !== "active") return this.forget(groupId);
25258
+ if (session.status !== "active") {
25259
+ await this.forget(groupId);
25260
+ return;
25261
+ }
26240
25262
  if (session.others.length === 0) {
26241
25263
  await session.leave();
26242
- return this.forget(groupId);
25264
+ await this.forget(groupId);
25265
+ return;
26243
25266
  }
25267
+ const next = session.isAdmin ? this.successor(groupId) : void 0;
26244
25268
  if (session.isAdmin) {
26245
- const next = this.successor(groupId);
26246
25269
  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
25270
  await session.transferAdmin(next);
26248
25271
  }
@@ -26296,6 +25319,7 @@ var Groups = class {
26296
25319
  } catch {}
26297
25320
  this.reconcileEdges(groupId);
26298
25321
  this.host.emit();
25322
+ return next;
26299
25323
  }
26300
25324
  /** The admin heard me: the tombstone of a group I left can go. */
26301
25325
  async leaveConfirmed(groupId) {
@@ -26916,7 +25940,7 @@ var Groups = class {
26916
25940
  return;
26917
25941
  }
26918
25942
  if (existing?.state && existing.state.status === "active") {
26919
- if (this.sessions.get(g)?.admin === frame.admin) this.host.sendOnLink(linkId, {
25943
+ if (this.sessions.get(g)?.admin === frame.admin && existing.contacts?.[frame.admin] === linkId) this.host.sendOnLink(linkId, {
26920
25944
  t: "group-decline",
26921
25945
  g
26922
25946
  });
@@ -26924,9 +25948,9 @@ var Groups = class {
26924
25948
  }
26925
25949
  if (existing?.invitation?.seedB64 && (existing.invitation.linkId !== linkId || existing.invitation.admin !== frame.admin)) return;
26926
25950
  if (existing?.left && (existing.left.admin !== frame.admin || existing.contacts?.[existing.left.admin] !== linkId)) return;
26927
- if (existing?.invitation && !existing.state && existing.invitation.linkId !== linkId && !existing.invitation.genesis && !existing.invitation.takenBack) return;
25951
+ if (existing?.invitation && !existing.state && existing.invitation.linkId !== linkId && !(existing.invitation.genesis && linkId === keptAdminChat(existing.previous)) && !existing.invitation.takenBack) return;
26928
25952
  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) {
25953
+ 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
25954
  try {
26931
25955
  this.host.sendOnLink(linkId, {
26932
25956
  t: "group-decline",
@@ -27058,10 +26082,12 @@ var Groups = class {
27058
26082
  const group = this.stored.get(g);
27059
26083
  if (!group?.invitation?.seedB64 || group.invitation.linkId !== linkId || !Array.isArray(frame.commits)) return;
27060
26084
  if (group.invitation.pieces.reduce((n, p) => n + p.commits.length, 0) + frame.commits.length > 1024) return;
26085
+ const commits = frame.commits.map(parseGroupCommit);
26086
+ if (commits.some((c) => !c || c.g !== g)) return;
27061
26087
  group.invitation.pieces.push({
27062
26088
  t: "group-chain",
27063
26089
  g,
27064
- commits: frame.commits
26090
+ commits
27065
26091
  });
27066
26092
  return;
27067
26093
  }
@@ -33424,6 +32450,11 @@ var PaymentDesk = class {
33424
32450
  return;
33425
32451
  }
33426
32452
  }
32453
+ if (target && request.network && request.network !== walletNetworkOf(target.network)) {
32454
+ if (!federations) return;
32455
+ federations = void 0;
32456
+ target = void 0;
32457
+ }
33427
32458
  const network = request.network ?? paymentNetwork({
33428
32459
  target,
33429
32460
  mints: parseCashuRequestPayload(findEndpoint(request.endpoints, ENDPOINT.cashu) ?? ""),
@@ -33436,8 +32467,10 @@ var PaymentDesk = class {
33436
32467
  }
33437
32468
  const allowed = (method) => (held ? !!link?.paymentEnabled(method) : !!link?.allowsPayment(method)) && this.accepts(linkId, method, network);
33438
32469
  const bolt11 = allowed("lightning") ? findEndpoint(request.endpoints, ENDPOINT.bolt11) : void 0;
33439
- const chain = bolt11 ? decodeBolt11(bolt11)?.network : void 0;
33440
- const invoice = bolt11 && !(network === "mainnet" && chain && chain !== "bitcoin") ? bolt11 : void 0;
32470
+ const decoded = bolt11 ? decodeBolt11(bolt11) : null;
32471
+ const chain = decoded?.network;
32472
+ const sameTarget = !(target?.method === "bitcoin" || target?.method === "arkade" || target?.method === "bark") || chain === "bitcoin" === (walletNetworkOf(target.network) === "mainnet");
32473
+ const invoice = bolt11 && decoded?.amountSat === amount && sameTarget && !(network === "mainnet" && chain && chain !== "bitcoin") ? bolt11 : void 0;
33441
32474
  const mints = allowed("cashu") ? parseCashuRequestPayload(findEndpoint(request.endpoints, ENDPOINT.cashu) ?? "").filter((mint) => mintNetwork(mint) === network) : [];
33442
32475
  if (!target && !invoice && !mints.length && !federations) return;
33443
32476
  await this.save({
@@ -33532,6 +32565,14 @@ var PaymentDesk = class {
33532
32565
  return;
33533
32566
  }
33534
32567
  const inspected = this.wallet.inspect?.(payment.endpoint[1]);
32568
+ if (payment.endpoint[0] === ENDPOINT.cashu && inspected !== void 0 && inspected?.kind !== "token") {
32569
+ link.sendPaymentResult({
32570
+ id: payment.id,
32571
+ ok: false,
32572
+ error: "That is not a valid ecash token"
32573
+ });
32574
+ return;
32575
+ }
33535
32576
  if (inspected?.kind === "token" && !this.accepts(linkId, "cashu", mintNetwork(inspected.mint))) {
33536
32577
  link.sendPaymentResult({
33537
32578
  id: payment.id,
@@ -34520,7 +33561,7 @@ var PaymentDesk = class {
34520
33561
  /**
34521
33562
  * Notes from the contact. Redeemed only when they are of a federation we joined and this chat allows Fedimint;
34522
33563
  * otherwise refused unredeemed, and the contact takes them back. Written down (with the notes) before redeeming:
34523
- * an interrupted redeem is finished from the journal, never lost.
33564
+ * an interrupted redeem is finished from the journal, never lost. The chat says it once the federation answered.
34524
33565
  */
34525
33566
  async receiveFedimint(linkId, payment) {
34526
33567
  const link = this.host.getLink(linkId);
@@ -34593,6 +33634,16 @@ var PaymentDesk = class {
34593
33634
  await refuse(`${networkLabel(walletNetworkOf(network))} Fedimint is off in this chat`);
34594
33635
  return;
34595
33636
  }
33637
+ const digest = digestHex(notes.trim());
33638
+ const before = [...this.payments.values()].find((p) => p.notesDigest === digest && p.direction === "in" && p.kind === "payment");
33639
+ if (before) {
33640
+ link.sendPaymentResult({
33641
+ id: payment.id,
33642
+ ok: false,
33643
+ error: before.state === "failed" ? before.error ?? "These notes were already redeemed" : "These notes were already received"
33644
+ });
33645
+ return;
33646
+ }
34596
33647
  await this.save({
34597
33648
  id: payment.id,
34598
33649
  linkId,
@@ -34605,16 +33656,8 @@ var PaymentDesk = class {
34605
33656
  createdAt: payment.timestamp,
34606
33657
  requestId: payment.requestId,
34607
33658
  federation: found.federation,
34608
- token: notes
34609
- });
34610
- await this.host.storeMessage({
34611
- linkId,
34612
- id: `peer_${payment.timestamp}`,
34613
- text: `⚡ ${amount.toLocaleString()} ${network === "bitcoin" ? "sats" : "test sats"} on Fedimint`,
34614
- sender: "peer",
34615
- timestamp: payment.timestamp,
34616
- via: "datalink",
34617
- paymentId: payment.id
33659
+ token: notes,
33660
+ notesDigest: digest
34618
33661
  });
34619
33662
  await this.finishRedeem(this.payments.get(payment.id));
34620
33663
  }
@@ -34662,8 +33705,30 @@ var PaymentDesk = class {
34662
33705
  ok: false,
34663
33706
  error: "These notes were already redeemed"
34664
33707
  });
33708
+ try {
33709
+ await this.host.storeMessage({
33710
+ linkId: payment.linkId,
33711
+ id: `peer_${payment.createdAt}_refused`,
33712
+ text: "Could not receive Fedimint ecash: These notes were already redeemed",
33713
+ sender: "peer",
33714
+ timestamp: payment.createdAt,
33715
+ via: "datalink"
33716
+ });
33717
+ } catch {}
34665
33718
  return;
34666
33719
  }
33720
+ if (state) {
33721
+ const test = fedimint.federation(payment.federation)?.network !== "bitcoin";
33722
+ await this.host.storeMessage({
33723
+ linkId: payment.linkId,
33724
+ id: `peer_${payment.createdAt}`,
33725
+ text: `⚡ ${payment.amount.toLocaleString()} ${test ? "test sats" : "sats"} on Fedimint`,
33726
+ sender: "peer",
33727
+ timestamp: payment.createdAt,
33728
+ via: "datalink",
33729
+ paymentId: id
33730
+ });
33731
+ }
34667
33732
  if (state !== "done") return;
34668
33733
  await this.save({
34669
33734
  ...this.current(payment),
@@ -35853,24 +34918,24 @@ var GhostlyNode = class GhostlyNode {
35853
34918
  * Every wallet twice, one per network (real money, test coins), both open at once. Nothing is parked: a payment
35854
34919
  * goes through the wallet of its own network, and a wallet of one network never pays for the other.
35855
34920
  */
35856
- usdtWallets = perNetwork((network) => new UsdtWallet(network, () => {
35857
- this.refreshWallet();
34921
+ usdtWallets = perNetwork((network) => new UsdtWallet(network, (viewChanged = true) => {
34922
+ if (viewChanged) this.refreshWallet();
35858
34923
  this.desk.reconcileUsdtReceipts().catch(() => {});
35859
34924
  }));
35860
- arkWallets = perNetwork((network) => new ArkWallet(network, () => {
35861
- this.refreshWallet();
34925
+ arkWallets = perNetwork((network) => new ArkWallet(network, (viewChanged = true) => {
34926
+ if (viewChanged) this.refreshWallet();
35862
34927
  this.desk.reconcileArkReceipts().catch(() => {});
35863
34928
  }));
35864
- barkWallets = perNetwork((network) => new BarkWallet(network, () => {
35865
- this.refreshWallet();
34929
+ barkWallets = perNetwork((network) => new BarkWallet(network, (viewChanged = true) => {
34930
+ if (viewChanged) this.refreshWallet();
35866
34931
  this.desk.reconcileBarkReceipts().catch(() => {});
35867
34932
  }));
35868
34933
  fedimintWallets = perNetwork((network) => new FedimintWallet(network, {
35869
34934
  changed: () => void this.refreshWallet(),
35870
34935
  received: (paymentId) => void this.desk.onLightningPaid({ paymentId })
35871
34936
  }, () => (this.options.fedimintSdk ?? loadFedimintSdk)()));
35872
- sparkWallets = perNetwork((network) => new SparkWallet(network, () => {
35873
- this.refreshWallet();
34937
+ sparkWallets = perNetwork((network) => new SparkWallet(network, (viewChanged = true) => {
34938
+ if (viewChanged) this.refreshWallet();
35874
34939
  this.desk.reconcileSparkReceipts().catch(() => {});
35875
34940
  }));
35876
34941
  /** What a call naming no network acts on (a caller from before wallets had their own): Mainnet. */
@@ -36197,29 +35262,38 @@ var GhostlyNode = class GhostlyNode {
36197
35262
  if (live.stored.deletedIds?.includes(`peer_${wire.wireId}`)) return null;
36198
35263
  live.files.wireIds.add(wire.wireId);
36199
35264
  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
- });
35265
+ const storage = await fileBytes();
35266
+ try {
35267
+ await storage.stage(file.id, new Blob([bytes]));
35268
+ await fileStore.put({
35269
+ id: file.id,
35270
+ linkId,
35271
+ bytes: storage.kind,
35272
+ createdAt: Date.now(),
35273
+ direction: "in",
35274
+ wireId: wire.wireId,
35275
+ digest,
35276
+ metadata: {
35277
+ name: wire.name,
35278
+ size: wire.size,
35279
+ mime: wire.mime,
35280
+ timestamp: wire.timestamp,
35281
+ voice: wire.voice,
35282
+ video: wire.video,
35283
+ image: wire.image
35284
+ },
35285
+ transfer: {
35286
+ state: "done",
35287
+ transferred: wire.size,
35288
+ size: wire.size
35289
+ }
35290
+ });
35291
+ } catch (error) {
35292
+ await storage.remove(file.id).catch(() => {});
35293
+ live.files.wireIds.delete(wire.wireId);
35294
+ live.files.receivedBytes -= wire.size;
35295
+ throw error;
35296
+ }
36223
35297
  this.transfers.set(file.id, {
36224
35298
  state: "done",
36225
35299
  transferred: wire.size,
@@ -36847,6 +35921,7 @@ var GhostlyNode = class GhostlyNode {
36847
35921
  const wallets = this.withHomes(walletInstances(networks));
36848
35922
  const intents = (await intentRepository.list()).map((saved) => saved.review);
36849
35923
  this.noteOpenReviews(intents);
35924
+ this.paymentCoordinator.notePending(intents);
36850
35925
  this.walletView = {
36851
35926
  ...networks.mainnet,
36852
35927
  networks,
@@ -38569,6 +37644,10 @@ var GhostlyNode = class GhostlyNode {
38569
37644
  error: `Message exceeds ${LIMITS.maxChatMessageBytes} UTF-8 bytes.`,
38570
37645
  refused: true
38571
37646
  };
37647
+ if (!pairedMessageFits(timestamp, trimmed, reply && pairedWireReply(reply), forwarded, card)) return {
37648
+ error: PAIRED_FRAME_TOO_LARGE,
37649
+ refused: true
37650
+ };
38572
37651
  const stop = this.chatStopped(live);
38573
37652
  if (stop) return { error: stop };
38574
37653
  if (!link.isDataLinkOpen && link.dhtDelivery?.inviteTaken) return {
@@ -40835,8 +39914,10 @@ var GhostlyNode = class GhostlyNode {
40835
39914
  this.links.get(edge)?.link?.sendGroupFrame(frame);
40836
39915
  } catch {}
40837
39916
  }
40838
- leaveGroup({ groupId }) {
40839
- return this.groups.leave(groupId);
39917
+ /** `admin`: the member a leaving admin handed the role to. */
39918
+ async leaveGroup({ groupId }) {
39919
+ const admin = await this.groups.leave(groupId);
39920
+ return admin ? { admin } : {};
40840
39921
  }
40841
39922
  removeGroupMember({ groupId, key }) {
40842
39923
  return this.groups.remove(groupId, key);
@@ -43576,6 +42657,7 @@ var GhostlyNode = class GhostlyNode {
43576
42657
  }
43577
42658
  });
43578
42659
  const link = live.link;
42660
+ if (linkId === this.activeLinkId) link.setChatActive(true);
43579
42661
  link.start();
43580
42662
  this.emitState();
43581
42663
  if (resumed.resume) {
@@ -43700,7 +42782,7 @@ var GhostlyNode = class GhostlyNode {
43700
42782
  get nativeFactories() {
43701
42783
  const relay = this.relaysHyperdht ? this.hyperdhtRelay : "";
43702
42784
  return {
43703
- ...relay ? { "hyperdht/1": async (seedB64) => (await import("./hyperdhtRelay-CEmBpT0Q.js")).createRelayedHyperEndpoint(seedB64, relay) } : {},
42785
+ ...relay ? { "hyperdht/1": async (seedB64) => (await import("./hyperdhtRelay-BVoWY2ua.js")).createRelayedHyperEndpoint(seedB64, relay) } : {},
43704
42786
  ...this.options.nativeTransports,
43705
42787
  ...this.ownIrohRelays ? { "iroh/1": (seedB64) => this.options.nativeTransports["iroh/1"](seedB64, this.settings.irohRelays?.length ? { relays: [...this.settings.irohRelays] } : {}) } : {},
43706
42788
  ...this.options.irohWeb ? { "iroh/1": (seedB64) => createIrohWebEndpoint(seedB64, { relays: this.irohRelays }) } : {}
@@ -44549,4 +43631,4 @@ function signalKind(signal) {
44549
43631
  //#endregion
44550
43632
  export { EngineServer };
44551
43633
 
44552
- //# sourceMappingURL=server-CBCEYKMN.js.map
43634
+ //# sourceMappingURL=server-CJNEKxzE.js.map