@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.
- package/README.md +9 -3
- package/dist/assets/{api-BiaX6l6f.js → api-SYPJgFRU.js} +2 -2
- package/dist/assets/{api-CrxVxtY1.js → api-aup3Amud.js} +141 -40
- package/dist/assets/api-aup3Amud.js.map +1 -0
- package/dist/assets/{apiKit-CajiDxdK.js → apiKit-DYkVEs6r.js} +30 -30
- package/dist/assets/apiKit-DYkVEs6r.js.map +1 -0
- package/dist/assets/{apps-t23gkc28.js → apps-DcZKu29s.js} +1 -1
- package/dist/assets/{apps-CbfiCSIw.js → apps-Rbk2HHXb.js} +3 -4
- package/dist/assets/{apps-CbfiCSIw.js.map → apps-Rbk2HHXb.js.map} +1 -1
- package/dist/assets/{arkade-D4BVJFEQ.js → arkade-CuWlNnCz.js} +4 -4
- package/dist/assets/arkade-CuWlNnCz.js.map +1 -0
- package/dist/assets/{backup-BAMi2lAq.js → backup-CJWLDHuS.js} +8 -8
- package/dist/assets/backup-CJWLDHuS.js.map +1 -0
- package/dist/assets/{backup-Cg52WeQi.js → backup-CSMw2GHv.js} +1 -1
- package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
- package/dist/assets/bitcoind-CZchoh43.js +349 -0
- package/dist/assets/bitcoind-CZchoh43.js.map +1 -0
- package/dist/assets/bitcoindProvider-DGw3xBSa.js +97 -0
- package/dist/assets/bitcoindProvider-DGw3xBSa.js.map +1 -0
- package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -1
- package/dist/assets/buttons-CGRqZ7z0.js +2 -0
- package/dist/assets/{buttons-CX1pKB0j.js → buttons-QEnAGWoC.js} +4 -5
- package/dist/assets/{buttons-CX1pKB0j.js.map → buttons-QEnAGWoC.js.map} +1 -1
- package/dist/assets/{endpoint-Dn7H8qE1.js → endpoint-BOTFMpfS.js} +44 -12
- package/dist/assets/endpoint-BOTFMpfS.js.map +1 -0
- package/dist/assets/{envelope-BvU0fLEt.js → envelope-K27mzoyu.js} +2 -2
- package/dist/assets/{envelope-BvU0fLEt.js.map → envelope-K27mzoyu.js.map} +1 -1
- package/dist/assets/{errors-my6VJR_0.js → errors-DO5z6h_l.js} +29 -1
- package/dist/assets/errors-DO5z6h_l.js.map +1 -0
- package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -1
- package/dist/assets/groupFiles-DHTrTA4I.js +3229 -0
- package/dist/assets/groupFiles-DHTrTA4I.js.map +1 -0
- package/dist/assets/{host-Cmd205Dc.js → host-C7hA-Za7.js} +135 -42
- package/dist/assets/host-C7hA-Za7.js.map +1 -0
- package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js → hyperdhtRelay-BVoWY2ua.js} +2 -3
- package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js.map → hyperdhtRelay-BVoWY2ua.js.map} +1 -1
- package/dist/assets/{irohWeb--YyEA-hX.js → irohWeb-BJPk9bMn.js} +2 -3
- package/dist/assets/{irohWeb--YyEA-hX.js.map → irohWeb-BJPk9bMn.js.map} +1 -1
- package/dist/assets/{main-BzNN6bkx.js → main-DC8cUkDx.js} +142 -35
- package/dist/assets/main-DC8cUkDx.js.map +1 -0
- package/dist/assets/{mainline-D5D5C-jT.js → mainline-CxbxpoU4.js} +2 -2
- package/dist/assets/{mainline-D5D5C-jT.js.map → mainline-CxbxpoU4.js.map} +1 -1
- package/dist/assets/{networkReason-DxZWHw6v.js → networkReason-D42iNuUB.js} +2 -2
- package/dist/assets/{networkReason-DxZWHw6v.js.map → networkReason-D42iNuUB.js.map} +1 -1
- package/dist/assets/nwc-Cm4sYgLv.js +612 -0
- package/dist/assets/nwc-Cm4sYgLv.js.map +1 -0
- package/dist/assets/nwcProvider-0D7vdPDd.js +77 -0
- package/dist/assets/nwcProvider-0D7vdPDd.js.map +1 -0
- package/dist/assets/{profiles-B6F4oa9K.js → profiles-BHc9pEYg.js} +2 -2
- package/dist/assets/{profiles-B6F4oa9K.js.map → profiles-BHc9pEYg.js.map} +1 -1
- package/dist/assets/registry-CLolR0kM.js +2 -0
- package/dist/assets/{groupFiles-BNTSDaoq.js → registry-CVdSZW5l.js} +19 -3911
- package/dist/assets/registry-CVdSZW5l.js.map +1 -0
- package/dist/assets/resolve-BnlEajYz.js +591 -0
- package/dist/assets/resolve-BnlEajYz.js.map +1 -0
- package/dist/assets/{server-CBCEYKMN.js → server-CJNEKxzE.js} +284 -1202
- package/dist/assets/server-CJNEKxzE.js.map +1 -0
- package/dist/assets/{src-DhfGgAUu.js → src-yMUQvdyL.js} +2264 -71
- package/dist/assets/src-yMUQvdyL.js.map +1 -0
- package/dist/assets/{turnRefs-ChEfgbSX.js → turnRefs-B2a8to9q.js} +2 -2
- package/dist/assets/{turnRefs-ChEfgbSX.js.map → turnRefs-B2a8to9q.js.map} +1 -1
- package/dist/assets/types-C4_KafL_.js +122 -0
- package/dist/assets/types-C4_KafL_.js.map +1 -0
- package/dist/assets/{usdt-C1Xao2xH.js → usdt-DesFeDiG.js} +3 -3
- package/dist/assets/{usdt-C1Xao2xH.js.map → usdt-DesFeDiG.js.map} +1 -1
- package/dist/ghostly.mjs +1 -1
- package/package.json +1 -1
- package/dist/assets/api-CrxVxtY1.js.map +0 -1
- package/dist/assets/apiKit-CajiDxdK.js.map +0 -1
- package/dist/assets/arkade-D4BVJFEQ.js.map +0 -1
- package/dist/assets/backup-BAMi2lAq.js.map +0 -1
- package/dist/assets/buttons-CWl2yNJu.js +0 -2
- package/dist/assets/endpoint-Dn7H8qE1.js.map +0 -1
- package/dist/assets/errors-my6VJR_0.js.map +0 -1
- package/dist/assets/groupFiles-BNTSDaoq.js.map +0 -1
- package/dist/assets/groupFiles-DBbjOLuD.js +0 -2088
- package/dist/assets/groupFiles-DBbjOLuD.js.map +0 -1
- package/dist/assets/host-Cmd205Dc.js.map +0 -1
- package/dist/assets/main-BzNN6bkx.js.map +0 -1
- package/dist/assets/server-CBCEYKMN.js.map +0 -1
- package/dist/assets/src-DhfGgAUu.js.map +0 -1
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { n as traceJoin } from "./joinTrace-CjRDFy85.js";
|
|
2
|
-
import { $ as
|
|
3
|
-
import {
|
|
2
|
+
import { $c as 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-
|
|
6
|
-
import { $ as
|
|
7
|
-
import { i as fedimintDatabase, n as createIrohWebEndpoint, o as loadFedimintSdk, r as irohRelayProblem, t as DEFAULT_IROH_RELAYS } from "./irohWeb
|
|
7
|
+
import { a as sealSeed, i as newDeviceKey, o as unsealSeed, r as intentRepository } from "./networkReason-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-
|
|
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-
|
|
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-
|
|
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:
|
|
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$
|
|
1097
|
+
var loading$4;
|
|
1080
1098
|
/** Loads the WebAssembly once, on first use: profiles that never open a Bark wallet never fetch its 7.7 MB. */
|
|
1081
1099
|
function loadBarkSdk() {
|
|
1082
|
-
return loading$
|
|
1100
|
+
return loading$4 ??= (async () => {
|
|
1083
1101
|
const [bark, wasm] = await Promise.all([import("@secondts/bark/web"), import("./bark_ffi_wasm_bg-EY2uc0Bx.js")]);
|
|
1084
1102
|
await bark.default({ module_or_path: wasm.default });
|
|
1085
1103
|
return {
|
|
@@ -1118,7 +1136,7 @@ function loadBarkSdk() {
|
|
|
1118
1136
|
}
|
|
1119
1137
|
};
|
|
1120
1138
|
})().catch((error) => {
|
|
1121
|
-
loading$
|
|
1139
|
+
loading$4 = void 0;
|
|
1122
1140
|
throw error;
|
|
1123
1141
|
});
|
|
1124
1142
|
}
|
|
@@ -1235,7 +1253,7 @@ var BarkAdapter = class BarkAdapter {
|
|
|
1235
1253
|
provider,
|
|
1236
1254
|
asset: "BTC",
|
|
1237
1255
|
unit: "sat",
|
|
1238
|
-
address:
|
|
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$
|
|
2662
|
+
var loading$3;
|
|
2643
2663
|
/** Loads the WebAssembly once, on first use: profiles that never pick Breez never fetch its 12.8 MB. */
|
|
2644
2664
|
function loadBreezSdk() {
|
|
2645
|
-
return loading$
|
|
2665
|
+
return loading$3 ??= (async () => {
|
|
2646
2666
|
const breez = await import("./web-DwgdhfcY.js");
|
|
2647
2667
|
await breez.default();
|
|
2648
2668
|
return { async connect({ network, mnemonic, apiKey, storage }) {
|
|
@@ -2658,11 +2678,11 @@ function loadBreezSdk() {
|
|
|
2658
2678
|
});
|
|
2659
2679
|
} };
|
|
2660
2680
|
})().catch((error) => {
|
|
2661
|
-
loading$
|
|
2681
|
+
loading$3 = void 0;
|
|
2662
2682
|
throw error;
|
|
2663
2683
|
});
|
|
2664
2684
|
}
|
|
2665
|
-
var hex$
|
|
2685
|
+
var hex$8 = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
2666
2686
|
var breezNetwork = (network) => network === "bitcoin" ? "mainnet" : "regtest";
|
|
2667
2687
|
var DISCONNECT_MS = 1e4;
|
|
2668
2688
|
/** One SDK instance per wallet storage: two over one database would each think the leaves are theirs. */
|
|
@@ -2674,7 +2694,7 @@ var open = /* @__PURE__ */ new Map();
|
|
|
2674
2694
|
*/
|
|
2675
2695
|
async function openBreez(params, sdk = loadBreezSdk, storage = breezDatabase) {
|
|
2676
2696
|
const name = await storage(params.network, params.mnemonic);
|
|
2677
|
-
const key = `${name}|${params.apiKey ? hex$
|
|
2697
|
+
const key = `${name}|${params.apiKey ? hex$8(sha256(new TextEncoder().encode(params.apiKey))).slice(0, 8) : ""}`;
|
|
2678
2698
|
let entry = open.get(key);
|
|
2679
2699
|
if (!entry) {
|
|
2680
2700
|
const wallet = sdk().then((module) => module.connect({
|
|
@@ -3008,6 +3028,7 @@ var SparkWallet = class {
|
|
|
3008
3028
|
locked: true,
|
|
3009
3029
|
balance: 0
|
|
3010
3030
|
};
|
|
3031
|
+
/** `changed(false)`: a balance read that found nothing new; the receipts are checked, the wallet view stays. */
|
|
3011
3032
|
constructor(network, changed, sdk = loadBreezSdk) {
|
|
3012
3033
|
this.network = network;
|
|
3013
3034
|
this.changed = changed;
|
|
@@ -3223,6 +3244,7 @@ var SparkWallet = class {
|
|
|
3223
3244
|
const history = await read("history", () => adapter.history(), this.view.history);
|
|
3224
3245
|
if (this.adapter !== adapter) return;
|
|
3225
3246
|
const balancesRead = this.view.read || !failed.includes("balance");
|
|
3247
|
+
const before = this.view;
|
|
3226
3248
|
this.view = {
|
|
3227
3249
|
...this.idle(this.saved),
|
|
3228
3250
|
locked: false,
|
|
@@ -3232,7 +3254,7 @@ var SparkWallet = class {
|
|
|
3232
3254
|
history,
|
|
3233
3255
|
error: failed.length ? `Could not read the ${failed.join(", ")} from Spark. Last values may be stale.` : void 0
|
|
3234
3256
|
};
|
|
3235
|
-
this.changed();
|
|
3257
|
+
this.changed(viewNews(before, this.view));
|
|
3236
3258
|
if (this.adapter) this.timer = setTimeout(() => void this.refresh(), sparkTiming.pollMs);
|
|
3237
3259
|
}
|
|
3238
3260
|
/** Where a contact pays one request: a Spark invoice made for it (its amount, its memo, a quarter of an hour). */
|
|
@@ -3344,6 +3366,17 @@ var PaymentCoordinator = class {
|
|
|
3344
3366
|
adapters;
|
|
3345
3367
|
changed;
|
|
3346
3368
|
active = /* @__PURE__ */ new Map();
|
|
3369
|
+
/**
|
|
3370
|
+
* The reviews still pending (an Approve may send them), by id: what `dropStale` looks at, so that its minute's sweep
|
|
3371
|
+
* does not read every review ever made. `null` until the store was read once. An id leaves when the sweep finds its
|
|
3372
|
+
* review no longer pending, or gone.
|
|
3373
|
+
*/
|
|
3374
|
+
pending = null;
|
|
3375
|
+
/** Reviews read from the store past the coordinator (`refreshWallet`'s list): the pending ones are swept too. */
|
|
3376
|
+
notePending(reviews) {
|
|
3377
|
+
const pending = this.pending ??= /* @__PURE__ */ new Set();
|
|
3378
|
+
for (const review of reviews) if (review.state === "pending") pending.add(review.id);
|
|
3379
|
+
}
|
|
3347
3380
|
constructor(repository, adapters, changed = () => {}) {
|
|
3348
3381
|
this.repository = repository;
|
|
3349
3382
|
this.adapters = adapters;
|
|
@@ -3374,6 +3407,7 @@ var PaymentCoordinator = class {
|
|
|
3374
3407
|
review,
|
|
3375
3408
|
prepared
|
|
3376
3409
|
});
|
|
3410
|
+
this.pending?.add(review.id);
|
|
3377
3411
|
this.changed(review);
|
|
3378
3412
|
return review;
|
|
3379
3413
|
}
|
|
@@ -3444,12 +3478,20 @@ var PaymentCoordinator = class {
|
|
|
3444
3478
|
*/
|
|
3445
3479
|
async dropStale(now = Date.now(), unpayable = () => false) {
|
|
3446
3480
|
const dropped = [];
|
|
3447
|
-
|
|
3448
|
-
|
|
3481
|
+
if (!this.pending) this.notePending((await this.repository.list()).map(({ review }) => review));
|
|
3482
|
+
const pending = this.pending;
|
|
3483
|
+
for (const id of [...pending]) {
|
|
3484
|
+
const review = (await this.repository.get(id))?.review;
|
|
3485
|
+
if (!review || review.state !== "pending") {
|
|
3486
|
+
pending.delete(id);
|
|
3487
|
+
continue;
|
|
3488
|
+
}
|
|
3489
|
+
if (review.expiresAt > now && !unpayable(review)) continue;
|
|
3449
3490
|
try {
|
|
3450
3491
|
const cancelled = await this.cancel(review.id);
|
|
3451
3492
|
if (cancelled.state === "cancelled") dropped.push(cancelled);
|
|
3452
3493
|
} catch {}
|
|
3494
|
+
pending.delete(id);
|
|
3453
3495
|
}
|
|
3454
3496
|
return dropped;
|
|
3455
3497
|
}
|
|
@@ -3781,7 +3823,7 @@ var PAGE = 100;
|
|
|
3781
3823
|
var PAGES = 10;
|
|
3782
3824
|
/** Without any record of a payment, it is called failed only this long after its invoice expired. */
|
|
3783
3825
|
var LOST_AFTER_MS = 36e5;
|
|
3784
|
-
var hex$
|
|
3826
|
+
var hex$7 = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
3785
3827
|
var sats$4 = (value) => {
|
|
3786
3828
|
const n = Number(value);
|
|
3787
3829
|
if (!Number.isSafeInteger(n) || n < 0) throw new Error("Breez returned an invalid amount");
|
|
@@ -3796,7 +3838,7 @@ function idempotencyKey(paymentHash, attempt) {
|
|
|
3796
3838
|
const b = sha256(new TextEncoder().encode(`ghostly-breez-pay:${paymentHash}:${attempt}`)).slice(0, 16);
|
|
3797
3839
|
b[6] = b[6] & 15 | 64;
|
|
3798
3840
|
b[8] = b[8] & 63 | 128;
|
|
3799
|
-
const h = hex$
|
|
3841
|
+
const h = hex$7(b);
|
|
3800
3842
|
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
3801
3843
|
}
|
|
3802
3844
|
/** The payment hash a Breez payment is about, when it is one (Lightning, or a Spark HTLC). */
|
|
@@ -4039,7 +4081,7 @@ var appLoader = async () => {
|
|
|
4039
4081
|
wasm: await fetch(url.default)
|
|
4040
4082
|
};
|
|
4041
4083
|
};
|
|
4042
|
-
var loading$
|
|
4084
|
+
var loading$2;
|
|
4043
4085
|
/**
|
|
4044
4086
|
* Loads the WebAssembly once, on first use: profiles that never set up a BDK wallet never fetch its 2 MB.
|
|
4045
4087
|
* The package is wasm-pack's `bundler` build, whose entry imports the `.wasm` as an ES module (which
|
|
@@ -4047,7 +4089,7 @@ var loading$1;
|
|
|
4047
4089
|
* that entry would.
|
|
4048
4090
|
*/
|
|
4049
4091
|
function loadBdk(loader = appLoader) {
|
|
4050
|
-
return loading$
|
|
4092
|
+
return loading$2 ??= (async () => {
|
|
4051
4093
|
const { glue, wasm } = await loader();
|
|
4052
4094
|
const imports = { [WASM_IMPORTS]: glue };
|
|
4053
4095
|
let instance;
|
|
@@ -4061,7 +4103,7 @@ function loadBdk(loader = appLoader) {
|
|
|
4061
4103
|
instance.exports.__wbindgen_start?.();
|
|
4062
4104
|
return glue;
|
|
4063
4105
|
})().catch((error) => {
|
|
4064
|
-
loading$
|
|
4106
|
+
loading$2 = void 0;
|
|
4065
4107
|
throw error;
|
|
4066
4108
|
});
|
|
4067
4109
|
}
|
|
@@ -4127,7 +4169,7 @@ var idbBdkStore = {
|
|
|
4127
4169
|
});
|
|
4128
4170
|
}
|
|
4129
4171
|
};
|
|
4130
|
-
var hex$
|
|
4172
|
+
var hex$6 = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4131
4173
|
var unhex = (text) => {
|
|
4132
4174
|
if (!/^([0-9a-f]{2})+$/.test(text)) throw new Error("Not hex");
|
|
4133
4175
|
return Uint8Array.from(text.match(/../g), (h) => parseInt(h, 16));
|
|
@@ -4341,7 +4383,7 @@ var BdkOnchain = class BdkOnchain {
|
|
|
4341
4383
|
throw new SourceConfigError(`bad descriptor: the recovery phrase does not make a ${SCRIPT[config.script].label} wallet (${message$3(error)})`, error);
|
|
4342
4384
|
}
|
|
4343
4385
|
const fingerprint = /\[([0-9a-f]{8})\//.exec(publicDescriptor)?.[1] ?? "";
|
|
4344
|
-
const key = `bdkWallet-${config.network}-${hex$
|
|
4386
|
+
const key = `bdkWallet-${config.network}-${hex$6(sha256(new TextEncoder().encode(publicDescriptor))).slice(0, 32)}`;
|
|
4345
4387
|
const walletStore = deps.store ?? idbBdkStore;
|
|
4346
4388
|
let stored = await walletStore.load(key);
|
|
4347
4389
|
if (stored) try {
|
|
@@ -4439,7 +4481,7 @@ var BdkOnchain = class BdkOnchain {
|
|
|
4439
4481
|
amount,
|
|
4440
4482
|
fee,
|
|
4441
4483
|
feeRate,
|
|
4442
|
-
signed: hex$
|
|
4484
|
+
signed: hex$6(tx.to_bytes())
|
|
4443
4485
|
};
|
|
4444
4486
|
}
|
|
4445
4487
|
/** What was signed is what was reviewed: one output of exactly the amount to the address, the rest ours. */
|
|
@@ -4692,435 +4734,6 @@ var bdk = {
|
|
|
4692
4734
|
}
|
|
4693
4735
|
};
|
|
4694
4736
|
//#endregion
|
|
4695
|
-
//#region ../browser/src/engine/paymentAdapters/providers/bitcoind.ts
|
|
4696
|
-
var BitcoindRpcError = class extends Error {
|
|
4697
|
-
kind;
|
|
4698
|
-
code;
|
|
4699
|
-
constructor(kind, message, code) {
|
|
4700
|
-
super(message);
|
|
4701
|
-
this.kind = kind;
|
|
4702
|
-
this.code = code;
|
|
4703
|
-
this.name = "BitcoindRpcError";
|
|
4704
|
-
}
|
|
4705
|
-
/** The node never ran the call: it was not sent, could not connect, or the credentials were refused. */
|
|
4706
|
-
get neverRan() {
|
|
4707
|
-
return this.kind === "refused" || this.kind === "connect" || this.kind === "auth";
|
|
4708
|
-
}
|
|
4709
|
-
};
|
|
4710
|
-
/** The Tauri command, through the Desktop host. */
|
|
4711
|
-
function tauriTransport$1(invoke) {
|
|
4712
|
-
if (!invoke) throw new Error("Bitcoin Core needs the Ghostly desktop app: a browser cannot reach its RPC");
|
|
4713
|
-
return (call) => invoke("bitcoind_rpc", { ...call });
|
|
4714
|
-
}
|
|
4715
|
-
/** Bitcoin Core's JSON-RPC error codes this module acts on (src/rpc/protocol.h). */
|
|
4716
|
-
var RPC = {
|
|
4717
|
-
INVALID_ADDRESS_OR_KEY: -5,
|
|
4718
|
-
VERIFY_ERROR: -25,
|
|
4719
|
-
VERIFY_REJECTED: -26,
|
|
4720
|
-
VERIFY_ALREADY_IN_CHAIN: -27,
|
|
4721
|
-
DESERIALIZATION_ERROR: -22
|
|
4722
|
-
};
|
|
4723
|
-
var CHAINS = {
|
|
4724
|
-
main: "bitcoin",
|
|
4725
|
-
test: "testnet",
|
|
4726
|
-
testnet4: "testnet",
|
|
4727
|
-
signet: "signet",
|
|
4728
|
-
regtest: "regtest"
|
|
4729
|
-
};
|
|
4730
|
-
/** A fee rate for the test networks when the node has too few blocks to estimate one (regtest always). */
|
|
4731
|
-
var TEST_FALLBACK_FEE_RATE = 2;
|
|
4732
|
-
var TXID = /^[0-9a-f]{64}$/;
|
|
4733
|
-
var WALLET_NAME = /^[A-Za-z0-9._-]{0,64}$/;
|
|
4734
|
-
/** Address encodings, for btc-signer to read the outputs back as addresses. */
|
|
4735
|
-
var TESTNET_ENCODING = {
|
|
4736
|
-
bech32: "tb",
|
|
4737
|
-
pubKeyHash: 111,
|
|
4738
|
-
scriptHash: 196,
|
|
4739
|
-
wif: 239
|
|
4740
|
-
};
|
|
4741
|
-
var BTC_NETWORKS = {
|
|
4742
|
-
bitcoin: {
|
|
4743
|
-
bech32: "bc",
|
|
4744
|
-
pubKeyHash: 0,
|
|
4745
|
-
scriptHash: 5,
|
|
4746
|
-
wif: 128
|
|
4747
|
-
},
|
|
4748
|
-
testnet: TESTNET_ENCODING,
|
|
4749
|
-
signet: TESTNET_ENCODING,
|
|
4750
|
-
mutinynet: TESTNET_ENCODING,
|
|
4751
|
-
regtest: {
|
|
4752
|
-
...TESTNET_ENCODING,
|
|
4753
|
-
bech32: "bcrt"
|
|
4754
|
-
}
|
|
4755
|
-
};
|
|
4756
|
-
var TX_OPTS = {
|
|
4757
|
-
allowUnknownOutputs: true,
|
|
4758
|
-
allowUnknownInputs: true,
|
|
4759
|
-
disableScriptCheck: true,
|
|
4760
|
-
allowLegacyWitnessUtxo: true
|
|
4761
|
-
};
|
|
4762
|
-
/** Whole sats as the exact decimal string bitcoind reads as BTC (never a float). */
|
|
4763
|
-
var satsToBtc = (sats) => {
|
|
4764
|
-
if (!Number.isSafeInteger(sats) || sats < 0) throw new Error("Amounts are whole sats");
|
|
4765
|
-
const digits = sats.toString().padStart(9, "0");
|
|
4766
|
-
return `${digits.slice(0, -8)}.${digits.slice(-8)}`;
|
|
4767
|
-
};
|
|
4768
|
-
/** A BTC amount from bitcoind (a JSON number with at most 8 decimals) as whole sats. */
|
|
4769
|
-
var btcToSats = (btc) => {
|
|
4770
|
-
if (typeof btc !== "number" || !Number.isFinite(btc)) throw new Error("The node returned an amount that is not a number");
|
|
4771
|
-
return Math.round(btc * 1e8);
|
|
4772
|
-
};
|
|
4773
|
-
var object = (value, what) => {
|
|
4774
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`The node returned no ${what}`);
|
|
4775
|
-
return value;
|
|
4776
|
-
};
|
|
4777
|
-
var text$2 = (value, what) => {
|
|
4778
|
-
if (typeof value !== "string" || !value) throw new Error(`The node returned no ${what}`);
|
|
4779
|
-
return value;
|
|
4780
|
-
};
|
|
4781
|
-
var BitcoindOnchain = class BitcoindOnchain {
|
|
4782
|
-
config;
|
|
4783
|
-
transport;
|
|
4784
|
-
closed = false;
|
|
4785
|
-
network;
|
|
4786
|
-
constructor(config, transport) {
|
|
4787
|
-
this.config = config;
|
|
4788
|
-
this.transport = transport;
|
|
4789
|
-
}
|
|
4790
|
-
/** Connects and checks the wallet is there and the node is on a network of `mode`. */
|
|
4791
|
-
static async connect(config, mode, transport, signal) {
|
|
4792
|
-
const provider = new BitcoindOnchain(config, transport);
|
|
4793
|
-
signal?.addEventListener("abort", () => void provider.close(), { once: true });
|
|
4794
|
-
const { network } = await provider.info();
|
|
4795
|
-
if (networkMode(network) !== mode) throw new Error(mode === "mainnet" ? `This node is on ${network}, not Bitcoin mainnet` : "This node is on Bitcoin mainnet: set it up as a Mainnet wallet instead");
|
|
4796
|
-
if (object(await provider.call("getwalletinfo"), "wallet information").private_keys_enabled === false) throw new Error("This wallet has no private keys: it cannot sign a payment");
|
|
4797
|
-
return provider;
|
|
4798
|
-
}
|
|
4799
|
-
async call(method, ...params) {
|
|
4800
|
-
if (this.closed) throw new BitcoindRpcError("refused", "This Bitcoin Core source is closed");
|
|
4801
|
-
try {
|
|
4802
|
-
return await this.transport({
|
|
4803
|
-
...this.config,
|
|
4804
|
-
method,
|
|
4805
|
-
params
|
|
4806
|
-
});
|
|
4807
|
-
} catch (error) {
|
|
4808
|
-
if (error instanceof BitcoindRpcError) throw error;
|
|
4809
|
-
const failure = error;
|
|
4810
|
-
if (failure && typeof failure === "object" && typeof failure.kind === "string" && typeof failure.message === "string") throw new BitcoindRpcError(failure.kind, failure.message, failure.code);
|
|
4811
|
-
throw new BitcoindRpcError("transport", error instanceof Error ? error.message : String(error));
|
|
4812
|
-
}
|
|
4813
|
-
}
|
|
4814
|
-
async info() {
|
|
4815
|
-
const chain = object(await this.call("getblockchaininfo"), "chain information").chain;
|
|
4816
|
-
const network = typeof chain === "string" ? CHAINS[chain] : void 0;
|
|
4817
|
-
if (!network) throw new Error(`Ghostly does not know the chain "${String(chain).slice(0, 20)}"`);
|
|
4818
|
-
this.network = network;
|
|
4819
|
-
return {
|
|
4820
|
-
network,
|
|
4821
|
-
alias: this.config.wallet || "Bitcoin Core"
|
|
4822
|
-
};
|
|
4823
|
-
}
|
|
4824
|
-
async receiveAddress() {
|
|
4825
|
-
return text$2(await this.call("getnewaddress", "Ghostly"), "address");
|
|
4826
|
-
}
|
|
4827
|
-
async balance() {
|
|
4828
|
-
const mine = object(object(await this.call("getbalances"), "balances").mine, "balances");
|
|
4829
|
-
return {
|
|
4830
|
-
confirmed: btcToSats(mine.trusted),
|
|
4831
|
-
unconfirmed: btcToSats(mine.untrusted_pending)
|
|
4832
|
-
};
|
|
4833
|
-
}
|
|
4834
|
-
async feeRate(request) {
|
|
4835
|
-
if (request.feeRate !== void 0) {
|
|
4836
|
-
if (!Number.isFinite(request.feeRate) || request.feeRate < 1 || request.feeRate > 1e4) throw new Error("The fee rate must be between 1 and 10,000 sat/vB");
|
|
4837
|
-
return request.feeRate;
|
|
4838
|
-
}
|
|
4839
|
-
const estimate = object(await this.call("estimatesmartfee", 6), "fee estimate");
|
|
4840
|
-
if (typeof estimate.feerate === "number" && estimate.feerate > 0) return Math.max(1, Math.ceil(estimate.feerate * 1e5 * 1e3) / 1e3);
|
|
4841
|
-
if ((this.network ?? (await this.info()).network) === "bitcoin") throw new Error("Your node cannot estimate a fee yet. Nothing was sent.");
|
|
4842
|
-
return TEST_FALLBACK_FEE_RATE;
|
|
4843
|
-
}
|
|
4844
|
-
async prepareSend(request) {
|
|
4845
|
-
if (!Number.isSafeInteger(request.amount) || request.amount <= 0) throw new Error("Amounts are whole sats");
|
|
4846
|
-
const network = this.network ?? (await this.info()).network;
|
|
4847
|
-
const feeRate = await this.feeRate(request);
|
|
4848
|
-
const funded = object(await this.call("walletcreatefundedpsbt", [], [{ [request.address]: satsToBtc(request.amount) }], 0, {
|
|
4849
|
-
fee_rate: feeRate,
|
|
4850
|
-
lockUnspents: true,
|
|
4851
|
-
replaceable: true
|
|
4852
|
-
}), "funded transaction");
|
|
4853
|
-
const inputs = this.outpoints(Transaction.fromPSBT(base64.decode(text$2(funded.psbt, "funded transaction")), TX_OPTS));
|
|
4854
|
-
try {
|
|
4855
|
-
const fee = btcToSats(funded.fee);
|
|
4856
|
-
if (fee > request.feeCap) throw new Error(`The fee (${fee} sats) is above your limit of ${request.feeCap}`);
|
|
4857
|
-
const signed = object(await this.call("walletprocesspsbt", funded.psbt, true, "ALL"), "signed transaction");
|
|
4858
|
-
if (signed.complete !== true) throw new Error("The wallet could not sign this payment (is it locked, or watch-only?)");
|
|
4859
|
-
const final = object(await this.call("finalizepsbt", signed.psbt, false), "finalized transaction");
|
|
4860
|
-
if (final.complete !== true) throw new Error("The wallet could not finalize this payment");
|
|
4861
|
-
return this.check(Transaction.fromPSBT(base64.decode(text$2(final.psbt, "finalized transaction")), TX_OPTS), request, inputs, feeRate, network);
|
|
4862
|
-
} catch (error) {
|
|
4863
|
-
await this.unlock(inputs).catch(() => {});
|
|
4864
|
-
throw error;
|
|
4865
|
-
}
|
|
4866
|
-
}
|
|
4867
|
-
/** The transaction the node signed is exactly the one asked for, before anyone reviews it. */
|
|
4868
|
-
check(tx, request, funded, feeRate, network) {
|
|
4869
|
-
const same = (a, b) => a.length === b.length && a.every((o, i) => o.txid === b[i].txid && o.vout === b[i].vout);
|
|
4870
|
-
if (!same(this.outpoints(tx), funded)) throw new Error("The node signed other coins than it funded the payment with");
|
|
4871
|
-
const script = (() => {
|
|
4872
|
-
try {
|
|
4873
|
-
return hex.encode(OutScript.encode(Address(BTC_NETWORKS[network]).decode(request.address)));
|
|
4874
|
-
} catch {
|
|
4875
|
-
return;
|
|
4876
|
-
}
|
|
4877
|
-
})();
|
|
4878
|
-
let paid = 0;
|
|
4879
|
-
for (let i = 0; i < tx.outputsLength; i++) {
|
|
4880
|
-
const output = tx.getOutput(i);
|
|
4881
|
-
if (script !== void 0 && output.script && hex.encode(output.script) === script) paid += Number(output.amount ?? 0n);
|
|
4882
|
-
}
|
|
4883
|
-
if (paid !== request.amount) throw new Error("The node built a transaction that does not pay the address and amount asked for");
|
|
4884
|
-
const fee = Number(tx.fee);
|
|
4885
|
-
if (!Number.isSafeInteger(fee) || fee < 0 || fee > request.feeCap) throw new Error(`The fee (${fee} sats) is above your limit of ${request.feeCap}`);
|
|
4886
|
-
const txid = tx.id;
|
|
4887
|
-
if (!TXID.test(txid)) throw new Error("The node returned a transaction without a valid id");
|
|
4888
|
-
return {
|
|
4889
|
-
txid,
|
|
4890
|
-
address: request.address,
|
|
4891
|
-
amount: request.amount,
|
|
4892
|
-
fee,
|
|
4893
|
-
feeRate,
|
|
4894
|
-
signed: hex.encode(tx.extract())
|
|
4895
|
-
};
|
|
4896
|
-
}
|
|
4897
|
-
outpoints(tx) {
|
|
4898
|
-
const out = [];
|
|
4899
|
-
for (let i = 0; i < tx.inputsLength; i++) {
|
|
4900
|
-
const input = tx.getInput(i);
|
|
4901
|
-
if (!input.txid || input.index === void 0) throw new Error("The node returned a transaction with an unreadable input");
|
|
4902
|
-
out.push({
|
|
4903
|
-
txid: hex.encode(input.txid),
|
|
4904
|
-
vout: input.index
|
|
4905
|
-
});
|
|
4906
|
-
}
|
|
4907
|
-
return out;
|
|
4908
|
-
}
|
|
4909
|
-
async unlock(outpoints) {
|
|
4910
|
-
if (outpoints.length) await this.call("lockunspent", true, outpoints);
|
|
4911
|
-
}
|
|
4912
|
-
signedTx(prepared) {
|
|
4913
|
-
if (!/^[0-9a-f]+$/.test(prepared.signed)) throw new Error("This prepared payment has no signed transaction");
|
|
4914
|
-
const tx = Transaction.fromRaw(hex.decode(prepared.signed), TX_OPTS);
|
|
4915
|
-
if (tx.id !== prepared.txid) throw new Error("This prepared payment's transaction is not the one reviewed");
|
|
4916
|
-
return tx;
|
|
4917
|
-
}
|
|
4918
|
-
async broadcast(prepared) {
|
|
4919
|
-
const tx = this.signedTx(prepared);
|
|
4920
|
-
let txid;
|
|
4921
|
-
try {
|
|
4922
|
-
txid = await this.call("sendrawtransaction", prepared.signed);
|
|
4923
|
-
} catch (error) {
|
|
4924
|
-
if (!(error instanceof BitcoindRpcError)) throw error;
|
|
4925
|
-
if (error.kind === "rpc" && error.code === RPC.VERIFY_ALREADY_IN_CHAIN) return prepared.txid;
|
|
4926
|
-
const rejected = error.kind === "rpc" && [
|
|
4927
|
-
RPC.VERIFY_ERROR,
|
|
4928
|
-
RPC.VERIFY_REJECTED,
|
|
4929
|
-
RPC.DESERIALIZATION_ERROR
|
|
4930
|
-
].includes(error.code ?? 0);
|
|
4931
|
-
if (!error.neverRan && !rejected) throw error;
|
|
4932
|
-
const seen = rejected ? await this.known(prepared.txid) : await this.known(prepared.txid).catch(() => void 0);
|
|
4933
|
-
if (seen) {
|
|
4934
|
-
if (["mempool", "confirmed"].includes((await this.status(prepared)).state)) return prepared.txid;
|
|
4935
|
-
throw error;
|
|
4936
|
-
}
|
|
4937
|
-
if (seen === false) await this.unlock(this.outpoints(tx)).catch(() => {});
|
|
4938
|
-
throw new NothingSpentError(`Your node did not send it: ${error.message}`);
|
|
4939
|
-
}
|
|
4940
|
-
if (txid !== prepared.txid) throw new Error("The node broadcast a different transaction than the one reviewed");
|
|
4941
|
-
return prepared.txid;
|
|
4942
|
-
}
|
|
4943
|
-
/** Whether the wallet knows this transaction (it does once its node accepted it, even if it was dropped since). */
|
|
4944
|
-
async known(txid) {
|
|
4945
|
-
try {
|
|
4946
|
-
await this.call("gettransaction", txid);
|
|
4947
|
-
return true;
|
|
4948
|
-
} catch (error) {
|
|
4949
|
-
if (error instanceof BitcoindRpcError && error.kind === "rpc" && error.code === RPC.INVALID_ADDRESS_OR_KEY) return false;
|
|
4950
|
-
throw error;
|
|
4951
|
-
}
|
|
4952
|
-
}
|
|
4953
|
-
async status(prepared) {
|
|
4954
|
-
if (!TXID.test(prepared.txid)) throw new Error("Not a transaction id");
|
|
4955
|
-
let tx;
|
|
4956
|
-
try {
|
|
4957
|
-
tx = object(await this.call("gettransaction", prepared.txid), "transaction");
|
|
4958
|
-
} catch (error) {
|
|
4959
|
-
if (error instanceof BitcoindRpcError && error.kind === "rpc" && error.code === RPC.INVALID_ADDRESS_OR_KEY) return {
|
|
4960
|
-
state: "missing",
|
|
4961
|
-
confirmations: 0
|
|
4962
|
-
};
|
|
4963
|
-
throw error;
|
|
4964
|
-
}
|
|
4965
|
-
const confirmations = typeof tx.confirmations === "number" ? tx.confirmations : 0;
|
|
4966
|
-
if (confirmations > 0) return {
|
|
4967
|
-
state: "confirmed",
|
|
4968
|
-
confirmations
|
|
4969
|
-
};
|
|
4970
|
-
if (confirmations < 0) return {
|
|
4971
|
-
state: "conflicted",
|
|
4972
|
-
confirmations: 0
|
|
4973
|
-
};
|
|
4974
|
-
if (Array.isArray(tx.mempoolconflicts) && tx.mempoolconflicts.length > 0) return {
|
|
4975
|
-
state: "conflicted",
|
|
4976
|
-
confirmations: 0
|
|
4977
|
-
};
|
|
4978
|
-
try {
|
|
4979
|
-
await this.call("getmempoolentry", prepared.txid);
|
|
4980
|
-
return {
|
|
4981
|
-
state: "mempool",
|
|
4982
|
-
confirmations: 0
|
|
4983
|
-
};
|
|
4984
|
-
} catch (error) {
|
|
4985
|
-
if (error instanceof BitcoindRpcError && error.kind === "rpc" && error.code === RPC.INVALID_ADDRESS_OR_KEY) return {
|
|
4986
|
-
state: "missing",
|
|
4987
|
-
confirmations: 0
|
|
4988
|
-
};
|
|
4989
|
-
throw error;
|
|
4990
|
-
}
|
|
4991
|
-
}
|
|
4992
|
-
async release(prepared) {
|
|
4993
|
-
await this.unlock(this.outpoints(this.signedTx(prepared)));
|
|
4994
|
-
}
|
|
4995
|
-
async history(limit) {
|
|
4996
|
-
const count = Math.max(1, Math.min(200, Math.floor(limit) * 3));
|
|
4997
|
-
const entries = await this.call("listtransactions", "*", count, 0, true);
|
|
4998
|
-
if (!Array.isArray(entries)) throw new Error("The node returned no transaction list");
|
|
4999
|
-
const byTxid = /* @__PURE__ */ new Map();
|
|
5000
|
-
for (const raw of entries) {
|
|
5001
|
-
if (!raw || typeof raw.txid !== "string" || !TXID.test(raw.txid) || raw.category === "orphan") continue;
|
|
5002
|
-
const tx = byTxid.get(raw.txid) ?? {
|
|
5003
|
-
txid: raw.txid,
|
|
5004
|
-
amount: 0,
|
|
5005
|
-
confirmations: typeof raw.confirmations === "number" ? Math.max(0, raw.confirmations) : 0
|
|
5006
|
-
};
|
|
5007
|
-
tx.amount += btcToSats(raw.amount);
|
|
5008
|
-
if (raw.category === "send" && typeof raw.fee === "number" && tx.fee === void 0) tx.fee = Math.abs(btcToSats(raw.fee));
|
|
5009
|
-
if (typeof raw.time === "number") tx.timestamp = raw.time * 1e3;
|
|
5010
|
-
byTxid.delete(raw.txid);
|
|
5011
|
-
byTxid.set(raw.txid, tx);
|
|
5012
|
-
}
|
|
5013
|
-
return [...byTxid.values()].reverse().slice(0, Math.max(0, limit));
|
|
5014
|
-
}
|
|
5015
|
-
/** The wallet's receives on this address, newest first: only `receive` entries paying exactly it count. */
|
|
5016
|
-
async received(address) {
|
|
5017
|
-
const entries = await this.call("listtransactions", "*", 200, 0, true);
|
|
5018
|
-
if (!Array.isArray(entries)) throw new Error("The node returned no transaction list");
|
|
5019
|
-
const byTxid = /* @__PURE__ */ new Map();
|
|
5020
|
-
for (const raw of entries) {
|
|
5021
|
-
if (!raw || raw.category !== "receive" || raw.address !== address || typeof raw.txid !== "string" || !TXID.test(raw.txid)) continue;
|
|
5022
|
-
const tx = byTxid.get(raw.txid) ?? {
|
|
5023
|
-
txid: raw.txid,
|
|
5024
|
-
amount: 0,
|
|
5025
|
-
confirmations: typeof raw.confirmations === "number" ? Math.max(0, raw.confirmations) : 0
|
|
5026
|
-
};
|
|
5027
|
-
tx.amount += btcToSats(raw.amount);
|
|
5028
|
-
byTxid.delete(raw.txid);
|
|
5029
|
-
byTxid.set(raw.txid, tx);
|
|
5030
|
-
}
|
|
5031
|
-
return [...byTxid.values()].reverse();
|
|
5032
|
-
}
|
|
5033
|
-
async close() {
|
|
5034
|
-
this.closed = true;
|
|
5035
|
-
}
|
|
5036
|
-
};
|
|
5037
|
-
/** `user:password` in the password field (the .cookie file's contents) when no user is given. */
|
|
5038
|
-
function credentials({ config, secrets }) {
|
|
5039
|
-
const password = secrets.password ?? "";
|
|
5040
|
-
const user = config.user?.trim() ?? "";
|
|
5041
|
-
if (!user && password.includes(":")) {
|
|
5042
|
-
const at = password.indexOf(":");
|
|
5043
|
-
return {
|
|
5044
|
-
user: password.slice(0, at),
|
|
5045
|
-
password: password.slice(at + 1)
|
|
5046
|
-
};
|
|
5047
|
-
}
|
|
5048
|
-
return {
|
|
5049
|
-
user,
|
|
5050
|
-
password
|
|
5051
|
-
};
|
|
5052
|
-
}
|
|
5053
|
-
var bitcoindRpc = {
|
|
5054
|
-
id: "bitcoind",
|
|
5055
|
-
label: "Bitcoin Core",
|
|
5056
|
-
kind: "onchain",
|
|
5057
|
-
description: "A wallet on your own Bitcoin Core node, over its RPC. Your node holds the keys and signs. Needs the desktop app.",
|
|
5058
|
-
networks: [
|
|
5059
|
-
"bitcoin",
|
|
5060
|
-
"testnet",
|
|
5061
|
-
"signet",
|
|
5062
|
-
"regtest"
|
|
5063
|
-
],
|
|
5064
|
-
platforms: ["desktop"],
|
|
5065
|
-
experimental: true,
|
|
5066
|
-
fields: [
|
|
5067
|
-
{
|
|
5068
|
-
name: "url",
|
|
5069
|
-
label: "RPC address",
|
|
5070
|
-
kind: "url",
|
|
5071
|
-
placeholder: "http://127.0.0.1:8332",
|
|
5072
|
-
defaults: {
|
|
5073
|
-
mainnet: "http://127.0.0.1:8332",
|
|
5074
|
-
testnet: "http://127.0.0.1:38332"
|
|
5075
|
-
},
|
|
5076
|
-
help: "Your node's rpcbind and rpcport: 8332 mainnet, 38332 signet, 48332 testnet4, 18443 regtest."
|
|
5077
|
-
},
|
|
5078
|
-
{
|
|
5079
|
-
name: "wallet",
|
|
5080
|
-
label: "Wallet name",
|
|
5081
|
-
kind: "text",
|
|
5082
|
-
optional: true,
|
|
5083
|
-
placeholder: "ghostly",
|
|
5084
|
-
help: "A wallet loaded in the node (bitcoin-cli listwallets). Empty: its default wallet."
|
|
5085
|
-
},
|
|
5086
|
-
{
|
|
5087
|
-
name: "user",
|
|
5088
|
-
label: "RPC user",
|
|
5089
|
-
kind: "text",
|
|
5090
|
-
optional: true,
|
|
5091
|
-
help: "Leave empty to paste the .cookie file below."
|
|
5092
|
-
},
|
|
5093
|
-
{
|
|
5094
|
-
name: "password",
|
|
5095
|
-
label: "RPC password or cookie",
|
|
5096
|
-
kind: "secret",
|
|
5097
|
-
help: "rpcpassword, or the contents of the node's .cookie file (it changes when bitcoind restarts)."
|
|
5098
|
-
}
|
|
5099
|
-
],
|
|
5100
|
-
validate(settings) {
|
|
5101
|
-
const url = (() => {
|
|
5102
|
-
try {
|
|
5103
|
-
return new URL(settings.config.url?.trim() ?? "");
|
|
5104
|
-
} catch {
|
|
5105
|
-
return;
|
|
5106
|
-
}
|
|
5107
|
-
})();
|
|
5108
|
-
if (!url || !["http:", "https:"].includes(url.protocol)) throw new Error("Enter the node's RPC address, like http://127.0.0.1:8332");
|
|
5109
|
-
if (url.username || url.password) throw new Error("Put the RPC user and password in their own fields, not in the address");
|
|
5110
|
-
if (url.search || url.hash) throw new Error("The RPC address cannot have a query or fragment");
|
|
5111
|
-
if (!WALLET_NAME.test(settings.config.wallet?.trim() ?? "") || [".", ".."].includes(settings.config.wallet?.trim() ?? "")) throw new Error("A wallet name has only letters, digits, '-', '_' and '.'");
|
|
5112
|
-
const { user, password } = credentials(settings);
|
|
5113
|
-
if (!user || !password) throw new Error("Enter the RPC user and password, or paste the .cookie file");
|
|
5114
|
-
},
|
|
5115
|
-
async create(settings, host) {
|
|
5116
|
-
return BitcoindOnchain.connect({
|
|
5117
|
-
url: settings.config.url.trim(),
|
|
5118
|
-
wallet: settings.config.wallet?.trim() ?? "",
|
|
5119
|
-
...credentials(settings)
|
|
5120
|
-
}, host.mode, tauriTransport$1(host.invoke), host.signal);
|
|
5121
|
-
}
|
|
5122
|
-
};
|
|
5123
|
-
//#endregion
|
|
5124
4737
|
//#region ../browser/src/engine/paymentAdapters/providers/fedimint.ts
|
|
5125
4738
|
var FEDIMINT_SOURCE = "fedimint";
|
|
5126
4739
|
/** How long an invoice made here stays payable. */
|
|
@@ -5692,15 +5305,15 @@ var CLN_RUNE_COMMAND = `lightning-cli createrune restrictions='[[${CLN_METHODS.m
|
|
|
5692
5305
|
var PAY_TIMEOUT_S$1 = 60;
|
|
5693
5306
|
var CALL_TIMEOUT_MS = 3e4;
|
|
5694
5307
|
var PAY_CALL_TIMEOUT_MS = 9e4;
|
|
5695
|
-
var INVOICE_EXPIRY_S$
|
|
5696
|
-
var NETWORKS$
|
|
5308
|
+
var INVOICE_EXPIRY_S$1 = 3600;
|
|
5309
|
+
var NETWORKS$1 = {
|
|
5697
5310
|
bitcoin: "bitcoin",
|
|
5698
5311
|
testnet: "testnet",
|
|
5699
5312
|
testnet4: "testnet",
|
|
5700
5313
|
signet: "signet",
|
|
5701
5314
|
regtest: "regtest"
|
|
5702
5315
|
};
|
|
5703
|
-
var HEX64$
|
|
5316
|
+
var HEX64$3 = /^[0-9a-f]{64}$/;
|
|
5704
5317
|
var NODE_ID = /^0[23][0-9a-f]{64}$/;
|
|
5705
5318
|
var msatToSat = (msat) => {
|
|
5706
5319
|
const value = typeof msat === "number" ? msat : typeof msat === "string" ? Number(msat.replace(/msat$/, "")) : NaN;
|
|
@@ -5778,7 +5391,7 @@ var CoreLightning = class CoreLightning {
|
|
|
5778
5391
|
}
|
|
5779
5392
|
async info() {
|
|
5780
5393
|
const info = await this.rpc.call("getinfo", {}, { timeoutMs: CALL_TIMEOUT_MS });
|
|
5781
|
-
const network = NETWORKS$
|
|
5394
|
+
const network = NETWORKS$1[info?.network];
|
|
5782
5395
|
if (!network) throw new Error(`The node is on a network Ghostly does not know (${String(info?.network).slice(0, 20)})`);
|
|
5783
5396
|
if (this.nodeId && info.id !== this.nodeId) throw new Error("The node answered with another id");
|
|
5784
5397
|
return {
|
|
@@ -5804,7 +5417,7 @@ var CoreLightning = class CoreLightning {
|
|
|
5804
5417
|
amount_msat: amount * 1e3,
|
|
5805
5418
|
label,
|
|
5806
5419
|
description: (memo || "Ghostly").slice(0, 200),
|
|
5807
|
-
expiry: INVOICE_EXPIRY_S$
|
|
5420
|
+
expiry: INVOICE_EXPIRY_S$1
|
|
5808
5421
|
}, { timeoutMs: CALL_TIMEOUT_MS });
|
|
5809
5422
|
const decoded = typeof created?.bolt11 === "string" && created.bolt11.length < 8e3 ? decodeBolt11(created.bolt11) : null;
|
|
5810
5423
|
if (!decoded || decoded.paymentHash !== created.payment_hash || decoded.amountSat !== amount) throw new Error("The node returned an invoice that does not match the request");
|
|
@@ -5904,7 +5517,7 @@ var CoreLightning = class CoreLightning {
|
|
|
5904
5517
|
}
|
|
5905
5518
|
/** What the node's own record of this payment hash says: `none` when it never tried. */
|
|
5906
5519
|
async lookup(paymentHash) {
|
|
5907
|
-
if (!HEX64$
|
|
5520
|
+
if (!HEX64$3.test(paymentHash)) throw new Error("Invalid payment hash");
|
|
5908
5521
|
const { pays } = await this.rpc.call("listpays", { payment_hash: paymentHash }, { timeoutMs: CALL_TIMEOUT_MS });
|
|
5909
5522
|
const mine = (pays ?? []).filter((p) => p.payment_hash === paymentHash);
|
|
5910
5523
|
const complete = mine.find((p) => p.status === "complete");
|
|
@@ -5977,672 +5590,6 @@ var coreLightning = {
|
|
|
5977
5590
|
return CoreLightning.connect(settingsOf(settings), host.signal);
|
|
5978
5591
|
}
|
|
5979
5592
|
};
|
|
5980
|
-
//#endregion
|
|
5981
|
-
//#region ../browser/src/engine/paymentAdapters/providers/nwc.ts
|
|
5982
|
-
/**
|
|
5983
|
-
* Lightning through a wallet reached over Nostr Wallet Connect (NIP-47): Alby Hub, a node's NWC service, a
|
|
5984
|
-
* hosted wallet. The person pastes the wallet's `nostr+walletconnect://` URI; requests are signed with the
|
|
5985
|
-
* key it carries, encrypted to the wallet (NIP-44, or NIP-04 when its info event offers nothing else) and
|
|
5986
|
-
* sent through its relay; the answers come back the same way. That wallet holds the sats, applies its own
|
|
5987
|
-
* fee limit, and is custodial or not depending on what it is.
|
|
5988
|
-
*
|
|
5989
|
-
* Money safety (PROVIDERS.md): a request that never reached the relay, or an error code the wallet answers
|
|
5990
|
-
* before it tries to pay, is `NothingSpentError`. A `pay_invoice` whose answer does not come back in time,
|
|
5991
|
-
* or any other error, is an unknown outcome, and `paymentStatus` asks `lookup_invoice` until it ends.
|
|
5992
|
-
*/
|
|
5993
|
-
var KIND = {
|
|
5994
|
-
info: 13194,
|
|
5995
|
-
request: 23194,
|
|
5996
|
-
response: 23195
|
|
5997
|
-
};
|
|
5998
|
-
/** A relay message, an encrypted answer, an info event, an invoice: nothing bigger is read. */
|
|
5999
|
-
var MAX_MESSAGE = 262144;
|
|
6000
|
-
var MAX_CONTENT = 65536;
|
|
6001
|
-
var MAX_INFO = 4096;
|
|
6002
|
-
var MAX_INVOICE = 7089;
|
|
6003
|
-
var MAX_URI = 2048;
|
|
6004
|
-
var MAX_RELAYS = 5;
|
|
6005
|
-
var CONNECT_MS = 1e4;
|
|
6006
|
-
var ACK_MS = 1e4;
|
|
6007
|
-
var REQUEST_MS$1 = 2e4;
|
|
6008
|
-
/** A payment may take a while to route; an answer after this is an unknown outcome, looked up later. */
|
|
6009
|
-
var PAY_MS = 9e4;
|
|
6010
|
-
/** The wallet must not start a payment it receives after this (NIP-47 `expiration`). */
|
|
6011
|
-
var PAY_EXPIRY_S = 60;
|
|
6012
|
-
/** A payment the wallet never heard of is failed once its invoice can no longer be paid, with room for clocks. */
|
|
6013
|
-
var EXPIRED_MARGIN_MS = 6e5;
|
|
6014
|
-
var INVOICE_EXPIRY_S$1 = 3600;
|
|
6015
|
-
/** Error codes a wallet answers before it tries to pay: nothing can have left it. (`BAD_REQUEST`: Alby Hub, an invoice it cannot read.) */
|
|
6016
|
-
var REFUSED_BEFORE_PAYING = /* @__PURE__ */ new Set([
|
|
6017
|
-
"RATE_LIMITED",
|
|
6018
|
-
"NOT_IMPLEMENTED",
|
|
6019
|
-
"INSUFFICIENT_BALANCE",
|
|
6020
|
-
"QUOTA_EXCEEDED",
|
|
6021
|
-
"RESTRICTED",
|
|
6022
|
-
"UNAUTHORIZED",
|
|
6023
|
-
"UNSUPPORTED_ENCRYPTION",
|
|
6024
|
-
"BAD_REQUEST"
|
|
6025
|
-
]);
|
|
6026
|
-
var NETWORKS$1 = {
|
|
6027
|
-
mainnet: "bitcoin",
|
|
6028
|
-
bitcoin: "bitcoin",
|
|
6029
|
-
testnet: "testnet",
|
|
6030
|
-
testnet3: "testnet",
|
|
6031
|
-
testnet4: "testnet",
|
|
6032
|
-
signet: "signet",
|
|
6033
|
-
mutinynet: "mutinynet",
|
|
6034
|
-
regtest: "regtest"
|
|
6035
|
-
};
|
|
6036
|
-
var HEX64$3 = /^[0-9a-f]{64}$/;
|
|
6037
|
-
var hex$6 = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
6038
|
-
var bytes$2 = (text) => Uint8Array.from(text.match(/../g), (b) => parseInt(b, 16));
|
|
6039
|
-
var now$1 = () => Math.floor(Date.now() / 1e3);
|
|
6040
|
-
var isMsat = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
6041
|
-
/** Only secure relays, except on this machine (a local test relay). */
|
|
6042
|
-
function checkRelay(address) {
|
|
6043
|
-
let relay;
|
|
6044
|
-
try {
|
|
6045
|
-
relay = new URL(address);
|
|
6046
|
-
} catch {
|
|
6047
|
-
throw new Error("The connection URI has a relay that is not a URL");
|
|
6048
|
-
}
|
|
6049
|
-
if (relay.username || relay.password || relay.hash) throw new Error("The connection URI has a relay with credentials in it");
|
|
6050
|
-
const loopback = [
|
|
6051
|
-
"127.0.0.1",
|
|
6052
|
-
"localhost",
|
|
6053
|
-
"[::1]"
|
|
6054
|
-
].includes(relay.hostname);
|
|
6055
|
-
if (!(relay.protocol === "wss:" || relay.protocol === "ws:" && loopback)) throw new Error("Use a wallet whose relay is secure (wss://)");
|
|
6056
|
-
return relay.href;
|
|
6057
|
-
}
|
|
6058
|
-
/** Reads a `nostr+walletconnect://` URI. Its errors never repeat it: it holds a secret. */
|
|
6059
|
-
function parseNwcUri(input) {
|
|
6060
|
-
const text = (input ?? "").trim();
|
|
6061
|
-
if (text.length > MAX_URI) throw new Error("That connection URI is too long");
|
|
6062
|
-
const match = /^(?:nostr\+walletconnect|nostrwalletconnect):(?:\/\/)?([0-9a-fA-F]{64})\/?\?([^#]*)$/.exec(text);
|
|
6063
|
-
if (!match) throw new Error("That is not a Nostr Wallet Connect URI (nostr+walletconnect://…)");
|
|
6064
|
-
const params = new URLSearchParams(match[2]);
|
|
6065
|
-
const secret = (params.get("secret") ?? "").toLowerCase();
|
|
6066
|
-
if (!HEX64$3.test(secret) || /^0+$/.test(secret)) throw new Error("The connection URI has no valid secret");
|
|
6067
|
-
const relays = [...new Set(params.getAll("relay").map(checkRelay))];
|
|
6068
|
-
if (!relays.length) throw new Error("The connection URI names no relay");
|
|
6069
|
-
if (relays.length > MAX_RELAYS) throw new Error("The connection URI names too many relays");
|
|
6070
|
-
return {
|
|
6071
|
-
walletPubkey: match[1].toLowerCase(),
|
|
6072
|
-
relays,
|
|
6073
|
-
secret: bytes$2(secret)
|
|
6074
|
-
};
|
|
6075
|
-
}
|
|
6076
|
-
/** The request never reached the relay (not connected, refused by it): the wallet cannot have seen it. */
|
|
6077
|
-
var NotDelivered = class extends Error {
|
|
6078
|
-
constructor(message) {
|
|
6079
|
-
super(message);
|
|
6080
|
-
this.name = "NotDelivered";
|
|
6081
|
-
}
|
|
6082
|
-
};
|
|
6083
|
-
/** An error the wallet answered, with its NIP-47 code. */
|
|
6084
|
-
var NwcError = class extends Error {
|
|
6085
|
-
code;
|
|
6086
|
-
constructor(code, message) {
|
|
6087
|
-
super(message);
|
|
6088
|
-
this.code = code;
|
|
6089
|
-
this.name = "NwcError";
|
|
6090
|
-
}
|
|
6091
|
-
};
|
|
6092
|
-
/** No answer in time. The request may or may not have been acted on. */
|
|
6093
|
-
var NoAnswer = class extends Error {
|
|
6094
|
-
constructor(method) {
|
|
6095
|
-
super(`The wallet did not answer ${method} in time`);
|
|
6096
|
-
this.name = "NoAnswer";
|
|
6097
|
-
}
|
|
6098
|
-
};
|
|
6099
|
-
/**
|
|
6100
|
-
* A minimal NIP-01 client for one relay: it knows exactly whether an event was written to an open socket
|
|
6101
|
-
* and whether the relay acknowledged it, which is what tells "nothing sent" from "unknown" apart.
|
|
6102
|
-
* It reconnects when asked to send again, and subscriptions follow it.
|
|
6103
|
-
*/
|
|
6104
|
-
var RelayConnection = class {
|
|
6105
|
-
url;
|
|
6106
|
-
WebSocketImpl;
|
|
6107
|
-
socket;
|
|
6108
|
-
opening;
|
|
6109
|
-
subs = /* @__PURE__ */ new Map();
|
|
6110
|
-
acks = /* @__PURE__ */ new Map();
|
|
6111
|
-
closed = false;
|
|
6112
|
-
constructor(url, WebSocketImpl = globalThis.WebSocket) {
|
|
6113
|
-
this.url = url;
|
|
6114
|
-
this.WebSocketImpl = WebSocketImpl;
|
|
6115
|
-
}
|
|
6116
|
-
open() {
|
|
6117
|
-
if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("The wallet connection is closed"));
|
|
6118
|
-
if (this.socket?.readyState === 1) return Promise.resolve(this.socket);
|
|
6119
|
-
if (!this.opening) {
|
|
6120
|
-
const opening = this.connectSocket();
|
|
6121
|
-
this.opening = opening;
|
|
6122
|
-
opening.catch(() => {}).finally(() => {
|
|
6123
|
-
if (this.opening === opening) this.opening = void 0;
|
|
6124
|
-
});
|
|
6125
|
-
}
|
|
6126
|
-
return this.opening;
|
|
6127
|
-
}
|
|
6128
|
-
connectSocket() {
|
|
6129
|
-
return new Promise((resolve, reject) => {
|
|
6130
|
-
let settled = false;
|
|
6131
|
-
const socket = new this.WebSocketImpl(this.url);
|
|
6132
|
-
const done = (error) => {
|
|
6133
|
-
if (settled) return;
|
|
6134
|
-
settled = true;
|
|
6135
|
-
clearTimeout(timer);
|
|
6136
|
-
if (error) {
|
|
6137
|
-
try {
|
|
6138
|
-
socket.close();
|
|
6139
|
-
} catch {}
|
|
6140
|
-
reject(error);
|
|
6141
|
-
return;
|
|
6142
|
-
}
|
|
6143
|
-
if (this.closed) {
|
|
6144
|
-
socket.close();
|
|
6145
|
-
reject(/* @__PURE__ */ new Error("The wallet connection is closed"));
|
|
6146
|
-
return;
|
|
6147
|
-
}
|
|
6148
|
-
this.socket = socket;
|
|
6149
|
-
for (const [id, sub] of this.subs) this.write([
|
|
6150
|
-
"REQ",
|
|
6151
|
-
id,
|
|
6152
|
-
sub.filter
|
|
6153
|
-
]);
|
|
6154
|
-
resolve(socket);
|
|
6155
|
-
};
|
|
6156
|
-
const timer = setTimeout(() => done(/* @__PURE__ */ new Error("The wallet's relay did not answer")), CONNECT_MS);
|
|
6157
|
-
socket.onopen = () => done();
|
|
6158
|
-
socket.onerror = () => done(/* @__PURE__ */ new Error("Could not reach the wallet's relay"));
|
|
6159
|
-
socket.onclose = () => {
|
|
6160
|
-
done(/* @__PURE__ */ new Error("The wallet's relay closed the connection"));
|
|
6161
|
-
if (this.socket !== socket) return;
|
|
6162
|
-
this.socket = void 0;
|
|
6163
|
-
for (const [id, ack] of this.acks) {
|
|
6164
|
-
this.acks.delete(id);
|
|
6165
|
-
ack.fail(/* @__PURE__ */ new Error("The relay connection dropped before it confirmed the request"));
|
|
6166
|
-
}
|
|
6167
|
-
};
|
|
6168
|
-
socket.onmessage = (message) => {
|
|
6169
|
-
if (this.socket === socket) this.receive(message.data);
|
|
6170
|
-
};
|
|
6171
|
-
});
|
|
6172
|
-
}
|
|
6173
|
-
subscribe(id, filter, sub) {
|
|
6174
|
-
this.subs.set(id, {
|
|
6175
|
-
...sub,
|
|
6176
|
-
filter
|
|
6177
|
-
});
|
|
6178
|
-
this.write([
|
|
6179
|
-
"REQ",
|
|
6180
|
-
id,
|
|
6181
|
-
filter
|
|
6182
|
-
]);
|
|
6183
|
-
}
|
|
6184
|
-
unsubscribe(id) {
|
|
6185
|
-
if (this.subs.delete(id)) this.write(["CLOSE", id]);
|
|
6186
|
-
}
|
|
6187
|
-
/**
|
|
6188
|
-
* Events matching `filter` the relay has stored, until it says that is all (or `ms` pass). With
|
|
6189
|
-
* `waitForOne`, a relay that has none yet is listened to until one arrives live, within `ms`.
|
|
6190
|
-
*/
|
|
6191
|
-
async fetch(filter, { ms = CONNECT_MS, waitForOne = false } = {}) {
|
|
6192
|
-
await this.open();
|
|
6193
|
-
const id = `q${hex$6(crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(6)))}`, events = [];
|
|
6194
|
-
try {
|
|
6195
|
-
await new Promise((resolve) => {
|
|
6196
|
-
const timer = setTimeout(resolve, ms);
|
|
6197
|
-
let stored = false;
|
|
6198
|
-
const finish = () => {
|
|
6199
|
-
clearTimeout(timer);
|
|
6200
|
-
resolve();
|
|
6201
|
-
};
|
|
6202
|
-
this.subscribe(id, filter, {
|
|
6203
|
-
event: (e) => {
|
|
6204
|
-
if (events.length < 20) events.push(e);
|
|
6205
|
-
if (stored) finish();
|
|
6206
|
-
},
|
|
6207
|
-
eose: () => {
|
|
6208
|
-
stored = true;
|
|
6209
|
-
if (events.length || !waitForOne) finish();
|
|
6210
|
-
}
|
|
6211
|
-
});
|
|
6212
|
-
});
|
|
6213
|
-
} finally {
|
|
6214
|
-
this.unsubscribe(id);
|
|
6215
|
-
}
|
|
6216
|
-
return events;
|
|
6217
|
-
}
|
|
6218
|
-
/**
|
|
6219
|
-
* Resolves once the relay accepted the event. `NotDelivered` when it certainly did not reach it (no open
|
|
6220
|
-
* socket, refused); any other rejection means it may have.
|
|
6221
|
-
*/
|
|
6222
|
-
async publish(event) {
|
|
6223
|
-
let socket;
|
|
6224
|
-
try {
|
|
6225
|
-
socket = await this.open();
|
|
6226
|
-
} catch (error) {
|
|
6227
|
-
throw new NotDelivered(error instanceof Error ? error.message : "Could not reach the wallet's relay");
|
|
6228
|
-
}
|
|
6229
|
-
return new Promise((resolve, reject) => {
|
|
6230
|
-
const timer = setTimeout(() => {
|
|
6231
|
-
this.acks.delete(event.id);
|
|
6232
|
-
reject(/* @__PURE__ */ new Error("The relay did not confirm the request"));
|
|
6233
|
-
}, ACK_MS);
|
|
6234
|
-
this.acks.set(event.id, {
|
|
6235
|
-
ok: () => {
|
|
6236
|
-
clearTimeout(timer);
|
|
6237
|
-
resolve();
|
|
6238
|
-
},
|
|
6239
|
-
fail: (error) => {
|
|
6240
|
-
clearTimeout(timer);
|
|
6241
|
-
reject(error);
|
|
6242
|
-
}
|
|
6243
|
-
});
|
|
6244
|
-
if (socket.readyState !== 1 || socket !== this.socket) {
|
|
6245
|
-
this.acks.delete(event.id);
|
|
6246
|
-
clearTimeout(timer);
|
|
6247
|
-
reject(new NotDelivered("The relay connection closed"));
|
|
6248
|
-
return;
|
|
6249
|
-
}
|
|
6250
|
-
socket.send(JSON.stringify(["EVENT", event]));
|
|
6251
|
-
});
|
|
6252
|
-
}
|
|
6253
|
-
close() {
|
|
6254
|
-
this.closed = true;
|
|
6255
|
-
for (const [id, ack] of this.acks) {
|
|
6256
|
-
this.acks.delete(id);
|
|
6257
|
-
ack.fail(/* @__PURE__ */ new Error("The wallet connection is closed"));
|
|
6258
|
-
}
|
|
6259
|
-
this.subs.clear();
|
|
6260
|
-
try {
|
|
6261
|
-
this.socket?.close();
|
|
6262
|
-
} catch {}
|
|
6263
|
-
this.socket = void 0;
|
|
6264
|
-
}
|
|
6265
|
-
write(message) {
|
|
6266
|
-
if (this.socket?.readyState === 1) this.socket.send(JSON.stringify(message));
|
|
6267
|
-
}
|
|
6268
|
-
receive(data) {
|
|
6269
|
-
if (typeof data !== "string" || data.length > MAX_MESSAGE) return;
|
|
6270
|
-
let message;
|
|
6271
|
-
try {
|
|
6272
|
-
message = JSON.parse(data);
|
|
6273
|
-
} catch {
|
|
6274
|
-
return;
|
|
6275
|
-
}
|
|
6276
|
-
if (!Array.isArray(message) || typeof message[1] !== "string") return;
|
|
6277
|
-
const [type, id] = message;
|
|
6278
|
-
if (type === "EVENT") this.subs.get(id)?.event(message[2]);
|
|
6279
|
-
else if (type === "EOSE") this.subs.get(id)?.eose?.();
|
|
6280
|
-
else if (type === "OK") {
|
|
6281
|
-
const ack = this.acks.get(id);
|
|
6282
|
-
if (!ack) return;
|
|
6283
|
-
this.acks.delete(id);
|
|
6284
|
-
const reason = typeof message[3] === "string" ? message[3].slice(0, 120) : "";
|
|
6285
|
-
if (message[2] === true || reason.startsWith("duplicate:")) ack.ok();
|
|
6286
|
-
else ack.fail(new NotDelivered(`The relay refused the request${reason ? ` (${reason})` : ""}`));
|
|
6287
|
-
}
|
|
6288
|
-
}
|
|
6289
|
-
};
|
|
6290
|
-
var NwcLightning = class NwcLightning {
|
|
6291
|
-
connection;
|
|
6292
|
-
relay;
|
|
6293
|
-
encryption;
|
|
6294
|
-
options;
|
|
6295
|
-
/** From the info event, then narrowed to what this connection may do (`get_info`). */
|
|
6296
|
-
capabilities;
|
|
6297
|
-
clientPubkey;
|
|
6298
|
-
subscription = `nwc${hex$6(crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(6)))}`;
|
|
6299
|
-
pending = /* @__PURE__ */ new Map();
|
|
6300
|
-
conversationKey;
|
|
6301
|
-
network = "bitcoin";
|
|
6302
|
-
alias;
|
|
6303
|
-
closed = false;
|
|
6304
|
-
constructor(connection, relay, encryption, methods, options) {
|
|
6305
|
-
this.connection = connection;
|
|
6306
|
-
this.relay = relay;
|
|
6307
|
-
this.encryption = encryption;
|
|
6308
|
-
this.options = options;
|
|
6309
|
-
this.clientPubkey = getPublicKey(connection.secret);
|
|
6310
|
-
if (encryption === "nip44_v2") this.conversationKey = nip44.v2.utils.getConversationKey(connection.secret, connection.walletPubkey);
|
|
6311
|
-
this.capabilities = this.capabilitiesOf(methods);
|
|
6312
|
-
}
|
|
6313
|
-
/**
|
|
6314
|
-
* Reaches the wallet through the first relay that answers, reads its info event (the methods and the
|
|
6315
|
-
* encryption it offers), listens for its answers, and asks `get_info` for its network.
|
|
6316
|
-
*/
|
|
6317
|
-
static async connect(uri, options = {}) {
|
|
6318
|
-
const connection = parseNwcUri(uri);
|
|
6319
|
-
options.signal?.throwIfAborted();
|
|
6320
|
-
let relay, lastError;
|
|
6321
|
-
for (const url of connection.relays) {
|
|
6322
|
-
const candidate = new RelayConnection(url, options.WebSocket);
|
|
6323
|
-
try {
|
|
6324
|
-
await candidate.open();
|
|
6325
|
-
relay = candidate;
|
|
6326
|
-
break;
|
|
6327
|
-
} catch (error) {
|
|
6328
|
-
candidate.close();
|
|
6329
|
-
lastError = error;
|
|
6330
|
-
}
|
|
6331
|
-
}
|
|
6332
|
-
if (!relay) throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("Could not reach the wallet's relay");
|
|
6333
|
-
let wallet;
|
|
6334
|
-
try {
|
|
6335
|
-
const info = NwcLightning.newestInfo(await relay.fetch({
|
|
6336
|
-
kinds: [KIND.info],
|
|
6337
|
-
authors: [connection.walletPubkey],
|
|
6338
|
-
limit: 5
|
|
6339
|
-
}, {
|
|
6340
|
-
waitForOne: true,
|
|
6341
|
-
ms: options.infoMs
|
|
6342
|
-
}), connection.walletPubkey);
|
|
6343
|
-
if (!info) throw new Error(`The wallet has not published its NWC info on ${new URL(relay.url).host}: check the URI, or that the wallet is running`);
|
|
6344
|
-
const offered = (info.tags.find((t) => t[0] === "encryption")?.slice(1).join(" ") ?? "").split(/\s+/).filter(Boolean);
|
|
6345
|
-
const encryption = offered.includes("nip44_v2") ? "nip44_v2" : !offered.length || offered.includes("nip04") ? "nip04" : void 0;
|
|
6346
|
-
if (!encryption) throw new Error("The wallet offers no encryption Ghostly speaks (NIP-44 or NIP-04)");
|
|
6347
|
-
const methods = new Set(info.content.split(/\s+/).filter(Boolean));
|
|
6348
|
-
wallet = new NwcLightning(connection, relay, encryption, methods, options);
|
|
6349
|
-
wallet.listen();
|
|
6350
|
-
if (options.signal) {
|
|
6351
|
-
const opened = wallet;
|
|
6352
|
-
if (options.signal.aborted) throw options.signal.reason;
|
|
6353
|
-
options.signal.addEventListener("abort", () => void opened.close(), { once: true });
|
|
6354
|
-
}
|
|
6355
|
-
await wallet.readInfo(methods);
|
|
6356
|
-
return wallet;
|
|
6357
|
-
} catch (error) {
|
|
6358
|
-
if (wallet) await wallet.close();
|
|
6359
|
-
else relay.close();
|
|
6360
|
-
throw error;
|
|
6361
|
-
}
|
|
6362
|
-
}
|
|
6363
|
-
static newestInfo(events, walletPubkey) {
|
|
6364
|
-
return events.filter((e) => e && e.kind === KIND.info && e.pubkey === walletPubkey && typeof e.content === "string" && e.content.length <= MAX_INFO && Array.isArray(e.tags) && verifyEvent(e)).sort((a, b) => b.created_at - a.created_at)[0];
|
|
6365
|
-
}
|
|
6366
|
-
capabilitiesOf(methods) {
|
|
6367
|
-
const lookup = methods.has("lookup_invoice");
|
|
6368
|
-
return {
|
|
6369
|
-
receive: methods.has("make_invoice"),
|
|
6370
|
-
send: methods.has("pay_invoice") && lookup,
|
|
6371
|
-
balance: methods.has("get_balance"),
|
|
6372
|
-
lookup
|
|
6373
|
-
};
|
|
6374
|
-
}
|
|
6375
|
-
/**
|
|
6376
|
-
* `get_info` says the network and the methods this connection may use (the info event lists the
|
|
6377
|
-
* service's). Without it the wallet is taken to be on Bitcoin: a test wallet is then refused in the
|
|
6378
|
-
* Testnet mode and its invoices in the Mainnet one, while a real-money wallet never passes for a test one.
|
|
6379
|
-
*/
|
|
6380
|
-
async readInfo(offered) {
|
|
6381
|
-
if (!offered.has("get_info")) return;
|
|
6382
|
-
let info;
|
|
6383
|
-
try {
|
|
6384
|
-
info = await this.request("get_info", {});
|
|
6385
|
-
} catch (error) {
|
|
6386
|
-
if (error instanceof NwcError && [
|
|
6387
|
-
"RESTRICTED",
|
|
6388
|
-
"UNAUTHORIZED",
|
|
6389
|
-
"NOT_IMPLEMENTED"
|
|
6390
|
-
].includes(error.code)) return;
|
|
6391
|
-
throw error;
|
|
6392
|
-
}
|
|
6393
|
-
if (info.network !== void 0) {
|
|
6394
|
-
const network = typeof info.network === "string" ? NETWORKS$1[info.network.toLowerCase()] : void 0;
|
|
6395
|
-
if (!network) throw new Error("The wallet is on a network Ghostly does not know");
|
|
6396
|
-
this.network = network;
|
|
6397
|
-
}
|
|
6398
|
-
if (typeof info.alias === "string" && info.alias.trim()) this.alias = info.alias.trim().slice(0, 40);
|
|
6399
|
-
if (Array.isArray(info.methods) && info.methods.every((m) => typeof m === "string")) this.capabilities = this.capabilitiesOf(new Set(info.methods));
|
|
6400
|
-
}
|
|
6401
|
-
listen() {
|
|
6402
|
-
this.relay.subscribe(this.subscription, {
|
|
6403
|
-
kinds: [KIND.response],
|
|
6404
|
-
authors: [this.connection.walletPubkey],
|
|
6405
|
-
"#p": [this.clientPubkey],
|
|
6406
|
-
since: now$1() - 60
|
|
6407
|
-
}, { event: (e) => this.answer(e) });
|
|
6408
|
-
}
|
|
6409
|
-
encrypt(text) {
|
|
6410
|
-
return this.conversationKey ? nip44.v2.encrypt(text, this.conversationKey) : nip04.encrypt(this.connection.secret, this.connection.walletPubkey, text);
|
|
6411
|
-
}
|
|
6412
|
-
decrypt(content) {
|
|
6413
|
-
if (this.conversationKey && !content.includes("?iv=")) return nip44.v2.decrypt(content, this.conversationKey);
|
|
6414
|
-
return nip04.decrypt(this.connection.secret, this.connection.walletPubkey, content);
|
|
6415
|
-
}
|
|
6416
|
-
/**
|
|
6417
|
-
* Sends one request and waits for its answer. `NotDelivered` when it never reached the relay; `NwcError`
|
|
6418
|
-
* for an error the wallet answered; `NoAnswer` (or a relay error) when the outcome is unknown.
|
|
6419
|
-
*/
|
|
6420
|
-
async request(method, params, { ms = this.options.requestMs ?? REQUEST_MS$1, expiresIn } = {}) {
|
|
6421
|
-
if (this.closed) throw new NotDelivered("The wallet connection is closed");
|
|
6422
|
-
const tags = [["p", this.connection.walletPubkey]];
|
|
6423
|
-
if (this.conversationKey) tags.push(["encryption", "nip44_v2"]);
|
|
6424
|
-
if (expiresIn) tags.push(["expiration", String(now$1() + expiresIn)]);
|
|
6425
|
-
const event = finalizeEvent({
|
|
6426
|
-
kind: KIND.request,
|
|
6427
|
-
created_at: now$1(),
|
|
6428
|
-
tags,
|
|
6429
|
-
content: this.encrypt(JSON.stringify({
|
|
6430
|
-
method,
|
|
6431
|
-
params
|
|
6432
|
-
}))
|
|
6433
|
-
}, this.connection.secret);
|
|
6434
|
-
const answer = new Promise((resolve, reject) => {
|
|
6435
|
-
const timer = setTimeout(() => {
|
|
6436
|
-
this.pending.delete(event.id);
|
|
6437
|
-
reject(new NoAnswer(method));
|
|
6438
|
-
}, ms);
|
|
6439
|
-
this.pending.set(event.id, {
|
|
6440
|
-
method,
|
|
6441
|
-
resolve,
|
|
6442
|
-
reject,
|
|
6443
|
-
timer
|
|
6444
|
-
});
|
|
6445
|
-
});
|
|
6446
|
-
const delivered = this.relay.publish(event).then(() => void 0, (error) => {
|
|
6447
|
-
const waiting = this.pending.get(event.id);
|
|
6448
|
-
if (!(error instanceof NotDelivered) || !waiting) return void 0;
|
|
6449
|
-
clearTimeout(waiting.timer);
|
|
6450
|
-
this.pending.delete(event.id);
|
|
6451
|
-
return error;
|
|
6452
|
-
});
|
|
6453
|
-
return Promise.race([answer, delivered.then((error) => error ? Promise.reject(error) : answer)]);
|
|
6454
|
-
}
|
|
6455
|
-
/** Reads an answer: from the wallet, signed, about a request of ours, of the method asked. Anything else is dropped. */
|
|
6456
|
-
answer(raw) {
|
|
6457
|
-
const event = raw;
|
|
6458
|
-
if (!event || typeof event !== "object" || event.kind !== KIND.response || event.pubkey !== this.connection.walletPubkey) return;
|
|
6459
|
-
if (typeof event.content !== "string" || event.content.length > MAX_CONTENT || !Array.isArray(event.tags)) return;
|
|
6460
|
-
const ref = event.tags.find((t) => Array.isArray(t) && t[0] === "e")?.[1];
|
|
6461
|
-
const pending = typeof ref === "string" ? this.pending.get(ref) : void 0;
|
|
6462
|
-
if (!pending || !event.tags.some((t) => Array.isArray(t) && t[0] === "p" && t[1] === this.clientPubkey)) return;
|
|
6463
|
-
if (!verifyEvent(event)) return;
|
|
6464
|
-
let body;
|
|
6465
|
-
try {
|
|
6466
|
-
body = JSON.parse(this.decrypt(event.content));
|
|
6467
|
-
} catch {
|
|
6468
|
-
return;
|
|
6469
|
-
}
|
|
6470
|
-
if (!body || typeof body !== "object" || body.result_type !== pending.method) return;
|
|
6471
|
-
clearTimeout(pending.timer);
|
|
6472
|
-
this.pending.delete(ref);
|
|
6473
|
-
if (body.error) {
|
|
6474
|
-
const code = typeof body.error.code === "string" ? body.error.code.slice(0, 40) : "OTHER";
|
|
6475
|
-
const message = typeof body.error.message === "string" && body.error.message.trim() ? body.error.message.trim().slice(0, 200) : code;
|
|
6476
|
-
pending.reject(new NwcError(code, message));
|
|
6477
|
-
} else if (body.result && typeof body.result === "object" && !Array.isArray(body.result)) pending.resolve(body.result);
|
|
6478
|
-
else pending.reject(new NwcError("OTHER", "The wallet sent an empty answer"));
|
|
6479
|
-
}
|
|
6480
|
-
async lookup(paymentHash) {
|
|
6481
|
-
const tx = await this.request("lookup_invoice", { payment_hash: paymentHash });
|
|
6482
|
-
if (tx.payment_hash !== void 0 && String(tx.payment_hash).toLowerCase() !== paymentHash) throw new Error("The wallet answered about another invoice");
|
|
6483
|
-
return tx;
|
|
6484
|
-
}
|
|
6485
|
-
async info() {
|
|
6486
|
-
let balance;
|
|
6487
|
-
if (this.capabilities.balance) {
|
|
6488
|
-
const result = await this.request("get_balance", {});
|
|
6489
|
-
if (!isMsat(result.balance)) throw new Error("The wallet returned an invalid balance");
|
|
6490
|
-
balance = Math.floor(result.balance / 1e3);
|
|
6491
|
-
}
|
|
6492
|
-
return {
|
|
6493
|
-
network: this.network,
|
|
6494
|
-
alias: `${this.alias ?? "NWC wallet"} via ${new URL(this.relay.url).host}`,
|
|
6495
|
-
balance
|
|
6496
|
-
};
|
|
6497
|
-
}
|
|
6498
|
-
async createInvoice(amount, memo) {
|
|
6499
|
-
if (!Number.isSafeInteger(amount) || amount <= 0 || !Number.isSafeInteger(amount * 1e3)) throw new Error("Invalid amount");
|
|
6500
|
-
const result = await this.request("make_invoice", {
|
|
6501
|
-
amount: amount * 1e3,
|
|
6502
|
-
...memo ? { description: memo.slice(0, 200) } : {},
|
|
6503
|
-
expiry: INVOICE_EXPIRY_S$1
|
|
6504
|
-
});
|
|
6505
|
-
const text = typeof result.invoice === "string" && result.invoice.length <= MAX_INVOICE ? result.invoice : "";
|
|
6506
|
-
const decoded = decodeBolt11(text);
|
|
6507
|
-
if (!decoded?.paymentHash || decoded.amountMsat !== BigInt(amount) * 1000n) throw new Error("The wallet returned an invoice that does not match the request");
|
|
6508
|
-
if (result.payment_hash !== void 0 && String(result.payment_hash).toLowerCase() !== decoded.paymentHash) throw new Error("The wallet returned an invoice that does not match its payment hash");
|
|
6509
|
-
return {
|
|
6510
|
-
invoice: decoded.invoice,
|
|
6511
|
-
paymentHash: decoded.paymentHash,
|
|
6512
|
-
amount,
|
|
6513
|
-
expiresAt: decoded.expiresAt * 1e3
|
|
6514
|
-
};
|
|
6515
|
-
}
|
|
6516
|
-
async invoiceStatus(invoice) {
|
|
6517
|
-
let tx;
|
|
6518
|
-
try {
|
|
6519
|
-
tx = await this.lookup(invoice.paymentHash);
|
|
6520
|
-
} catch (error) {
|
|
6521
|
-
if (error instanceof NwcError && error.code === "NOT_FOUND" && invoice.expiresAt < Date.now()) return { state: "expired" };
|
|
6522
|
-
throw error;
|
|
6523
|
-
}
|
|
6524
|
-
const expired = invoice.expiresAt < Date.now();
|
|
6525
|
-
if (tx.type === "outgoing") return { state: expired ? "expired" : "open" };
|
|
6526
|
-
const state = tx.state ?? (tx.settled_at ? "settled" : "pending");
|
|
6527
|
-
if (state === "settled") return {
|
|
6528
|
-
state: "paid",
|
|
6529
|
-
amount: isMsat(tx.amount) ? Math.floor(tx.amount / 1e3) : invoice.amount
|
|
6530
|
-
};
|
|
6531
|
-
if (state === "expired" || state === "failed") return { state: "expired" };
|
|
6532
|
-
return { state: expired ? "expired" : "open" };
|
|
6533
|
-
}
|
|
6534
|
-
/**
|
|
6535
|
-
* Pays through the wallet. NIP-47 carries no fee limit: the wallet applies its own, and the fee it
|
|
6536
|
-
* reports is passed on as it is.
|
|
6537
|
-
*/
|
|
6538
|
-
async payInvoice(invoice, _maxFee) {
|
|
6539
|
-
const decoded = decodeBolt11(invoice);
|
|
6540
|
-
if (!decoded?.paymentHash || decoded.amountSat === null) throw new NothingSpentError("That invoice cannot be paid through NWC");
|
|
6541
|
-
if (!this.capabilities.send) throw new NothingSpentError("This wallet connection may not pay and look up invoices");
|
|
6542
|
-
let result;
|
|
6543
|
-
try {
|
|
6544
|
-
result = await this.request("pay_invoice", { invoice: decoded.invoice }, {
|
|
6545
|
-
ms: this.options.payMs ?? PAY_MS,
|
|
6546
|
-
expiresIn: PAY_EXPIRY_S
|
|
6547
|
-
});
|
|
6548
|
-
} catch (error) {
|
|
6549
|
-
if (error instanceof NotDelivered) throw new NothingSpentError(`The payment did not reach the wallet: ${error.message}`);
|
|
6550
|
-
if (error instanceof NwcError && REFUSED_BEFORE_PAYING.has(error.code)) throw new NothingSpentError(`The wallet refused the payment: ${error.message}`);
|
|
6551
|
-
if (error instanceof NwcError) return this.confirmFailure(decoded.invoice, decoded.paymentHash, error);
|
|
6552
|
-
throw error;
|
|
6553
|
-
}
|
|
6554
|
-
const preimage = typeof result.preimage === "string" ? result.preimage.toLowerCase() : "";
|
|
6555
|
-
if (!HEX64$3.test(preimage) || hex$6(sha256(bytes$2(preimage))) !== decoded.paymentHash) throw new Error("The wallet said it paid, without a proof that matches the invoice");
|
|
6556
|
-
return {
|
|
6557
|
-
state: "paid",
|
|
6558
|
-
preimage,
|
|
6559
|
-
fee: isMsat(result.fees_paid) ? Math.ceil(result.fees_paid / 1e3) : void 0
|
|
6560
|
-
};
|
|
6561
|
-
}
|
|
6562
|
-
/**
|
|
6563
|
-
* An error after the wallet may have tried (`PAYMENT_FAILED` "may be due to a timeout", `INTERNAL`,
|
|
6564
|
-
* `OTHER`…): its own record decides. Only a payment it records as failed is `NothingSpentError`; one it
|
|
6565
|
-
* records as paid is paid; anything else is pending, or unknown when it cannot be asked.
|
|
6566
|
-
*/
|
|
6567
|
-
async confirmFailure(invoice, paymentHash, failure) {
|
|
6568
|
-
let status;
|
|
6569
|
-
try {
|
|
6570
|
-
status = await this.paymentStatus({
|
|
6571
|
-
invoice,
|
|
6572
|
-
paymentHash
|
|
6573
|
-
});
|
|
6574
|
-
} catch {
|
|
6575
|
-
throw new Error(`The wallet said the payment failed (${failure.message}), and cannot confirm it yet`);
|
|
6576
|
-
}
|
|
6577
|
-
if (status.state === "failed") throw new NothingSpentError(`The payment failed: ${failure.message}`);
|
|
6578
|
-
return status.state === "paid" ? {
|
|
6579
|
-
state: "paid",
|
|
6580
|
-
fee: status.fee,
|
|
6581
|
-
preimage: status.preimage
|
|
6582
|
-
} : { state: "pending" };
|
|
6583
|
-
}
|
|
6584
|
-
async paymentStatus(payment) {
|
|
6585
|
-
let tx;
|
|
6586
|
-
try {
|
|
6587
|
-
tx = await this.lookup(payment.paymentHash);
|
|
6588
|
-
} catch (error) {
|
|
6589
|
-
if (!(error instanceof NwcError && error.code === "NOT_FOUND")) throw error;
|
|
6590
|
-
return { state: (decodeBolt11(payment.invoice)?.expiresAt ?? Infinity) * 1e3 + EXPIRED_MARGIN_MS < Date.now() ? "failed" : "pending" };
|
|
6591
|
-
}
|
|
6592
|
-
if (tx.type === "incoming") return { state: "pending" };
|
|
6593
|
-
const preimage = typeof tx.preimage === "string" && HEX64$3.test(tx.preimage.toLowerCase()) ? tx.preimage.toLowerCase() : void 0;
|
|
6594
|
-
const proven = !!preimage && hex$6(sha256(bytes$2(preimage))) === payment.paymentHash;
|
|
6595
|
-
const state = tx.state ?? (tx.settled_at || proven ? "settled" : "pending");
|
|
6596
|
-
const fee = isMsat(tx.fees_paid) ? Math.ceil(tx.fees_paid / 1e3) : void 0;
|
|
6597
|
-
if (state === "settled") {
|
|
6598
|
-
if (preimage && !proven) throw new Error("The wallet's proof of payment does not match the invoice");
|
|
6599
|
-
return {
|
|
6600
|
-
state: "paid",
|
|
6601
|
-
fee,
|
|
6602
|
-
preimage
|
|
6603
|
-
};
|
|
6604
|
-
}
|
|
6605
|
-
if (state === "failed" || state === "expired") return { state: "failed" };
|
|
6606
|
-
return { state: "pending" };
|
|
6607
|
-
}
|
|
6608
|
-
async close() {
|
|
6609
|
-
if (this.closed) return;
|
|
6610
|
-
this.closed = true;
|
|
6611
|
-
for (const [id, pending] of this.pending) {
|
|
6612
|
-
clearTimeout(pending.timer);
|
|
6613
|
-
this.pending.delete(id);
|
|
6614
|
-
pending.reject(/* @__PURE__ */ new Error("The wallet connection is closed"));
|
|
6615
|
-
}
|
|
6616
|
-
this.relay.close();
|
|
6617
|
-
}
|
|
6618
|
-
};
|
|
6619
|
-
var nwc = {
|
|
6620
|
-
id: "nwc",
|
|
6621
|
-
label: "Nostr Wallet Connect",
|
|
6622
|
-
kind: "lightning",
|
|
6623
|
-
description: "A wallet you connect with its NWC URI (Alby Hub, a node's NWC service, a hosted wallet). That wallet holds the sats and sets its own fee limit: custodial or not depends on the wallet.",
|
|
6624
|
-
networks: [
|
|
6625
|
-
"bitcoin",
|
|
6626
|
-
"testnet",
|
|
6627
|
-
"signet",
|
|
6628
|
-
"mutinynet",
|
|
6629
|
-
"regtest"
|
|
6630
|
-
],
|
|
6631
|
-
platforms: PROVIDER_PLATFORMS,
|
|
6632
|
-
fields: [{
|
|
6633
|
-
name: "uri",
|
|
6634
|
-
label: "Connection URI",
|
|
6635
|
-
kind: "secret",
|
|
6636
|
-
placeholder: "nostr+walletconnect://…",
|
|
6637
|
-
help: "From your wallet's app connections. Allow making, paying and looking up invoices, and reading the balance."
|
|
6638
|
-
}],
|
|
6639
|
-
validate({ secrets }) {
|
|
6640
|
-
parseNwcUri(secrets.uri ?? "");
|
|
6641
|
-
},
|
|
6642
|
-
async create({ secrets }, host) {
|
|
6643
|
-
return NwcLightning.connect(secrets.uri, { signal: host.signal });
|
|
6644
|
-
}
|
|
6645
|
-
};
|
|
6646
5593
|
/** What the recommended macaroon grants: `lncli bakemacaroon info:read invoices:read invoices:write offchain:read offchain:write`. */
|
|
6647
5594
|
var LND_SCOPE = [
|
|
6648
5595
|
"info:read",
|
|
@@ -7757,6 +6704,28 @@ function defaultRegistry() {
|
|
|
7757
6704
|
};
|
|
7758
6705
|
}
|
|
7759
6706
|
//#endregion
|
|
6707
|
+
//#region ../browser/src/proofs/load.ts
|
|
6708
|
+
/**
|
|
6709
|
+
* The identity proof providers, loaded on first need and not with the app: they and their libraries (OpenPGP, the
|
|
6710
|
+
* Bitcoin signer, the DID, AT Protocol and OpenID Connect checks) are a large part of the code, and the first screen
|
|
6711
|
+
* draws none of it. The engine loads them with the profile's proofs; the UI once it draws something about identities.
|
|
6712
|
+
*/
|
|
6713
|
+
var registry;
|
|
6714
|
+
var loading$1;
|
|
6715
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
6716
|
+
function loadIdentityProviders() {
|
|
6717
|
+
return loading$1 ??= import("./registry-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 =
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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")
|
|
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
|
-
|
|
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
|
|
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
|
|
33440
|
-
const
|
|
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
|
|
36201
|
-
|
|
36202
|
-
|
|
36203
|
-
|
|
36204
|
-
|
|
36205
|
-
|
|
36206
|
-
|
|
36207
|
-
|
|
36208
|
-
|
|
36209
|
-
|
|
36210
|
-
|
|
36211
|
-
|
|
36212
|
-
|
|
36213
|
-
|
|
36214
|
-
|
|
36215
|
-
|
|
36216
|
-
|
|
36217
|
-
|
|
36218
|
-
|
|
36219
|
-
|
|
36220
|
-
|
|
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
|
-
|
|
40839
|
-
|
|
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-
|
|
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-
|
|
43634
|
+
//# sourceMappingURL=server-CJNEKxzE.js.map
|