diff --git a/src/models/NwcStore.ts b/src/models/NwcStore.ts index 9892cb45..30426dbd 100644 --- a/src/models/NwcStore.ts +++ b/src/models/NwcStore.ts @@ -26,7 +26,7 @@ import AppError, { Err } from '../utils/AppError' import { LightningUtils } from '../services/lightning/lightningUtils' import { addSeconds } from 'date-fns/addSeconds' import { Transaction, TransactionStatus, TransactionType } from './Transaction' -import { MeltQuoteResponse } from '@cashu/cashu-ts' +import { MeltQuoteBolt11Response } from '@cashu/cashu-ts' import { WalletStore } from './WalletStore' import { ProofsStore } from './ProofsStore' import { isSameDay } from 'date-fns/isSameDay' @@ -165,9 +165,13 @@ export const NwcConnectionModel = types.model('NwcConnection', { // eventInFlight.pubkey should = connectionPubkey log.trace('Encrypt response', {connectionPubkey: self.connectionPubkey, requestEventPubkey: requestEvent.pubkey}) + const walletStore = self.getWalletStore() + const keys: NostrKeyPair = (yield walletStore.getCachedWalletKeys()).NOSTR + const encryptedContent = yield NostrClient.encryptNip04( - requestEvent.pubkey, - JSON.stringify(nwcResponse) + requestEvent.pubkey, + JSON.stringify(nwcResponse), + keys ) const responseEvent: NostrUnsignedEvent = { @@ -216,7 +220,8 @@ export const NwcConnectionModel = types.model('NwcConnection', { yield NostrClient.publish( responseEvent, self.connectionRelays, - false + keys, + false ) }), payInvoice: flow(function* payInvoice(nwcRequest: NwcRequest, encodedInvoice: string, requestEvent: NostrEvent) { @@ -268,7 +273,7 @@ export const NwcConnectionModel = types.model('NwcConnection', { } as NwcError } - const meltQuote: MeltQuoteResponse = yield walletStore.createLightningMeltQuote( + const meltQuote: MeltQuoteBolt11Response = yield walletStore.createLightningMeltQuote( mintBalance.mintUrl, 'sat', encodedInvoice, @@ -365,7 +370,7 @@ export const NwcConnectionModel = types.model('NwcConnection', { ) // TODO barebones implementation, no paging commands support - const transactions = lightningTransactions.map(t => { + const transactions = lightningTransactions.map((t: Transaction) => { return { type: t.type === TransactionType.TOPUP ? 'incoming' : 'outgoing', invoice: t.paymentRequest, @@ -555,13 +560,16 @@ export const NwcConnectionModel = types.model('NwcConnection', { }) })) .actions(self => ({ - handleNwcRequestTask: flow(function* handleNwcRequestTask(requestEvent: NostrEvent, decryptedNwcRequest?: NwcRequest) { + handleNwcRequestTask: flow(function* handleNwcRequestTask(requestEvent: NostrEvent, decryptedNwcRequest?: NwcRequest) { let nwcRequest: NwcRequest if(!decryptedNwcRequest) { - + const walletStore = self.getWalletStore() + const keys: NostrKeyPair = (yield walletStore.getCachedWalletKeys()).NOSTR + const decryptedContent = yield NostrClient.decryptNip04( - requestEvent.pubkey, - requestEvent.content + requestEvent.pubkey, + requestEvent.content, + keys ) nwcRequest = JSON.parse(decryptedContent) @@ -708,16 +716,22 @@ export const NwcStoreModel = types const infoEvent: NostrUnsignedEvent = { kind: NWCWalletInfo, pubkey: newConnection.walletPubkey, - tags: [], + tags: [], content: self.supportedMethods.join(' '), - created_at: Math.floor(Date.now() / 1000) + created_at: Math.floor(Date.now() / 1000) } - NostrClient.publish( - infoEvent, - newConnection.connectionRelays, - false - ) + // Fire and forget - publish without waiting + ;(async () => { + const rootStore = getRootStore(self) + const keys: NostrKeyPair = (await rootStore.walletStore.getCachedWalletKeys()).NOSTR + NostrClient.publish( + infoEvent, + newConnection.connectionRelays, + keys, + false + ) + })() } }), removeConnection(connectionToRemove: NwcConnection) { diff --git a/src/models/WalletProfileStore.ts b/src/models/WalletProfileStore.ts index 68ff841f..fee66534 100644 --- a/src/models/WalletProfileStore.ts +++ b/src/models/WalletProfileStore.ts @@ -76,12 +76,14 @@ export const WalletProfileStoreModel = types } const relaysToPublish: string[] = relaysStore.allUrls + const keys: NostrKeyPair = (yield rootStore.walletStore.getCachedWalletKeys() as Promise).NOSTR log.debug('[publishToRelays]', 'Publish profile to relays', {profileEvent, relaysToPublish}) const publishedEvent: Event | undefined = yield NostrClient.publish( profileEvent, - relaysToPublish + relaysToPublish, + keys ) return publishedEvent diff --git a/src/screens/NfcPayScreen.tsx b/src/screens/NfcPayScreen.tsx index 9db401ae..95a07688 100644 --- a/src/screens/NfcPayScreen.tsx +++ b/src/screens/NfcPayScreen.tsx @@ -117,7 +117,7 @@ const SYNC_STATE_WITH_MINT_TIMEOUT = 10 * 1000 export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) { const navigation = useNavigation() - const { mintsStore, walletStore, proofsStore, transactionsStore } = useStores() + const { mintsStore, walletStore, proofsStore, transactionsStore, walletProfileStore } = useStores() const unitRef = useRef('sat') const isOnline = useRef(true) @@ -601,10 +601,13 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) { proofs: decodedTokenToSend.proofs, }) - const sentEvent = await NostrClient.encryptAndSendDirectMessageNip17( - pubkey, + const keys = await walletStore.getCachedWalletKeys() + const sentEvent = await NostrClient.encryptAndSendDirectMessageNip17( + pubkey, messageContent, - relays + relays, + keys.NOSTR, + walletProfileStore.nip05 ) if(!sentEvent) { diff --git a/src/screens/ReceiveScreen.tsx b/src/screens/ReceiveScreen.tsx index 88475bd9..71dc601b 100644 --- a/src/screens/ReceiveScreen.tsx +++ b/src/screens/ReceiveScreen.tsx @@ -142,7 +142,8 @@ export const ReceiveScreen = observer(function ReceiveScreen({ route }: Props) { if(isLocked) { const lockedToPK = CashuUtils.getP2PKPubkeySecret(decoded.proofs[0].secret) - isLockedToWallet = lockedToPK === '02' + (await NostrClient.getNostrKeys()).publicKey + const keys = await walletStore.getCachedWalletKeys() + isLockedToWallet = lockedToPK === '02' + keys.NOSTR.publicKey } log.trace('decoded token', {decoded, isLocked, isLockedToWallet}) diff --git a/src/screens/SendScreen.tsx b/src/screens/SendScreen.tsx index 9ab2d99a..f68b9a18 100644 --- a/src/screens/SendScreen.tsx +++ b/src/screens/SendScreen.tsx @@ -932,10 +932,13 @@ export const SendScreen = observer(function SendScreen({ route }: Props) { }) } - const sentEvent = await NostrClient.encryptAndSendDirectMessageNip17( - contactToSendTo.pubkey, + const keys = await walletStore.getCachedWalletKeys() + const sentEvent = await NostrClient.encryptAndSendDirectMessageNip17( + contactToSendTo.pubkey, messageContent!, - relaysToShareTo + relaysToShareTo, + keys.NOSTR, + walletProfileStore.nip05 ) setIsNostrDMSending(false) diff --git a/src/screens/TopupScreen.tsx b/src/screens/TopupScreen.tsx index c6ef4a36..06c5f468 100644 --- a/src/screens/TopupScreen.tsx +++ b/src/screens/TopupScreen.tsx @@ -501,10 +501,13 @@ export const TopupScreen = observer(function TopupScreen({ route }: Props) { content = content + `Memo: ${memo}` } - const sentEvent = await NostrClient.encryptAndSendDirectMessageNip17( - receiverPubkey as string, + const keys = await walletStore.getCachedWalletKeys() + const sentEvent = await NostrClient.encryptAndSendDirectMessageNip17( + receiverPubkey as string, content as string, - relaysToShareTo + relaysToShareTo, + keys.NOSTR, + walletProfileStore.nip05 ) setIsNostrDMSending(false) diff --git a/src/screens/WalletScreen.tsx b/src/screens/WalletScreen.tsx index ed446395..fdaf7773 100644 --- a/src/screens/WalletScreen.tsx +++ b/src/screens/WalletScreen.tsx @@ -370,7 +370,10 @@ export const WalletScreen = observer(function WalletScreen({ route }: Props) { // Real foreground – run your normal logic (e.g., unlock check, etc.) log.trace('[handleAppStateChange] WalletScreen active again') performChecks() - NostrClient.reconnectToRelays().catch(e => false) + NostrClient.reconnectToRelays({ + hasDeviceId: !!walletProfileStore.device, + onReconnect: () => nwcStore.listenForNwcEvents() + }).catch(e => false) } }) diff --git a/src/services/nostrService.ts b/src/services/nostrService.ts index b7de9804..89ddc45f 100644 --- a/src/services/nostrService.ts +++ b/src/services/nostrService.ts @@ -19,7 +19,6 @@ import {NostrKeyPair} from './keyChain' import {log} from './logService' import AppError, { Err } from '../utils/AppError' import { MinibitsClient } from './minibitsService' -import { rootStoreInstance } from '../models' import { WalletTask } from './walletService' import { Relay } from 'nostr-tools/relay' @@ -60,8 +59,6 @@ const _searchRelays: string[] = ['wss://relay.nostr.band', 'wss://relay.noswhere let _pool: any = undefined -const {walletProfileStore, nwcStore, walletStore} = rootStoreInstance - const getRelayPool = function () { if(!_pool) { _pool = new SimplePool() @@ -88,12 +85,12 @@ const getAllRelays = function () { return [..._minibitsRelays, ..._defaultPublicRelays].map(url => getNormalizedRelayUrl(url)) } -const getNostrKeys = async function () { - const keys = await walletStore.getCachedWalletKeys() - return keys.NOSTR +export type ReconnectToRelaysOptions = { + hasDeviceId: boolean + onReconnect?: () => void } -const reconnectToRelays = async function () { +const reconnectToRelays = async function (options: ReconnectToRelaysOptions) { const pool = getRelayPool() const connections = pool.listConnectionStatus() @@ -101,22 +98,22 @@ const reconnectToRelays = async function () { let isRefreshSubNeeded: boolean = false - for (const conn of Array.from(connections)) { + for (const conn of Array.from(connections)) { if(conn[1] === false) { pool.ensureRelay(conn[0]) isRefreshSubNeeded = true } - } + } // recreate subscriptions if all relays down if(isRefreshSubNeeded) { log.trace('[reconnectToRelays] Refreshing Nostr subscriptions') WalletTask.receiveEventsFromRelaysQueue().catch(e => false) - if(!walletProfileStore.device) { - nwcStore.listenForNwcEvents() + if(!options.hasDeviceId && options.onReconnect) { + options.onReconnect() } - } + } } @@ -191,13 +188,13 @@ const encodeNprofile = function (pubkey: string, relays: string[] = []): string }*/ -const encryptNip04 = async function ( - receiverPubkey: string, - content: string +const encryptNip04 = async function ( + receiverPubkey: string, + content: string, + keys: NostrKeyPair ): Promise { try { - const keys: NostrKeyPair = await getNostrKeys() - const encryptedContent = await encrypt(keys.privateKey, receiverPubkey, content) + const encryptedContent = await encrypt(keys.privateKey, receiverPubkey, content) return encryptedContent @@ -208,11 +205,10 @@ const encryptNip04 = async function ( const decryptNip04 = async function( - senderPubKey: string, - encryptedContent: string + senderPubKey: string, + encryptedContent: string, + keys: NostrKeyPair ): Promise { - - const keys: NostrKeyPair = await getNostrKeys() const decryptedContent = await decrypt(keys.privateKey, senderPubKey, encryptedContent) log.trace('[decryptNip04]', {decryptedContent}) @@ -224,14 +220,14 @@ const decryptNip04 = async function( const encryptAndSendDirectMessageNip17 = async function ( recipientPublicKey: string, message: string, - relays: string[] + relays: string[], + keys: NostrKeyPair, + senderNip05: string ) { - - const keys: NostrKeyPair = await getNostrKeys() const directMessageEvent: NostrEventTemplate = { created_at: Math.ceil(Date.now() / 1000), kind: PrivateDirectMessage, - tags: [['p', recipientPublicKey], ['from', walletProfileStore.nip05]], + tags: [['p', recipientPublicKey], ['from', senderNip05]], content: message, } @@ -264,11 +260,10 @@ const encryptAndSendDirectMessageNip17 = async function ( const decryptDirectMessageNip17 = async function ( - wrappedEvent: NostrEvent -) { - const keys: NostrKeyPair = await getNostrKeys() - - // log.trace('[decryptDirectMessageNip59]', {keys}) + wrappedEvent: NostrEvent, + keys: NostrKeyPair +) { + // log.trace('[decryptDirectMessageNip59]', {keys}) const decryptedEvent = unwrapEvent( wrappedEvent, @@ -284,12 +279,10 @@ const decryptDirectMessageNip17 = async function ( const publish = async function ( event: NostrUnsignedEvent, relays: string[], - retrievePublishedEvent: boolean = true + keys: NostrKeyPair, + retrievePublishedEvent: boolean = true ): Promise { try{ - - const keys: NostrKeyPair = await getNostrKeys() - const privateKeyBytes = hexToBytes(keys.privateKey) const finalEvent = finalizeEvent(event, privateKeyBytes) @@ -620,12 +613,11 @@ const findZapRequest = function (message: string): string | undefined { export const NostrClient = { // TODO split helper functions to separate module - getRelayPool, + getRelayPool, getDefaultRelays, getMinibitsRelays, getSearchRelays, getAllRelays, - getNostrKeys, reconnectToRelays, getNpubkey, getHexkey, diff --git a/src/services/notificationService.ts b/src/services/notificationService.ts index 6f403606..2bd4f381 100644 --- a/src/services/notificationService.ts +++ b/src/services/notificationService.ts @@ -262,20 +262,22 @@ const _nwcRequestHandler = async function(remoteData: NotifyNwcRequestData) { } const _getRemoteData = async function(remoteMessage: FirebaseMessagingTypes.RemoteMessage) { - + const {encrypted} = remoteMessage.data! if (!encrypted) { - throw new AppError(Err.VALIDATION_ERROR, 'Unknown remote message data received', {data: remoteMessage.data}) + throw new AppError(Err.VALIDATION_ERROR, 'Unknown remote message data received', {data: remoteMessage.data}) } + const {walletStore} = rootStoreInstance + const keys = (await walletStore.getCachedWalletKeys()).NOSTR const serverPubkey = MINIBIT_SERVER_NOSTR_PUBKEY - const decrypted = await NostrClient.decryptNip04(serverPubkey, encrypted as string) + const decrypted = await NostrClient.decryptNip04(serverPubkey, encrypted as string, keys) if (!decrypted) { throw new AppError(Err.VALIDATION_ERROR, 'Unknown remote message data received', {data: remoteMessage.data}) } - + return JSON.parse(decrypted) } diff --git a/src/services/walletService.ts b/src/services/walletService.ts index 4a6a1657..342d732e 100644 --- a/src/services/walletService.ts +++ b/src/services/walletService.ts @@ -2254,7 +2254,8 @@ const handleClaimTask = async function (params: { } const encryptedToken = claimedToken.token - const encodedToken = await NostrClient.decryptNip04(MINIBIT_SERVER_NOSTR_PUBKEY, encryptedToken) + const keys = (await walletStore.getCachedWalletKeys()).NOSTR + const encodedToken = await NostrClient.decryptNip04(MINIBIT_SERVER_NOSTR_PUBKEY, encryptedToken, keys) log.debug('[handleClaimTask] decrypted token', {encodedToken}) @@ -2427,18 +2428,19 @@ const receiveEventsFromRelaysQueue = async function (): Promise { } } -const handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Promise { +const handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Promise { try { let directMessageEvent: NostrEvent | UnsignedEvent | undefined = undefined let decryptedMessage: string | undefined = undefined - + const keys = (await walletStore.getCachedWalletKeys()).NOSTR + if (encryptedEvent.kind === EncryptedDirectMessage) { directMessageEvent = encryptedEvent - decryptedMessage = await NostrClient.decryptNip04(encryptedEvent.pubkey, encryptedEvent.content) + decryptedMessage = await NostrClient.decryptNip04(encryptedEvent.pubkey, encryptedEvent.content, keys) } if (encryptedEvent.kind === GiftWrap) { - directMessageEvent = await NostrClient.decryptDirectMessageNip17(encryptedEvent) + directMessageEvent = await NostrClient.decryptDirectMessageNip17(encryptedEvent, keys) decryptedMessage = directMessageEvent.content }