Refactor nostrService

This commit is contained in:
minibits-cash
2026-01-23 11:07:16 +01:00
parent db14e6de0d
commit 9c3cc83601
10 changed files with 100 additions and 75 deletions
+31 -17
View File
@@ -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) {
+3 -1
View File
@@ -76,12 +76,14 @@ export const WalletProfileStoreModel = types
}
const relaysToPublish: string[] = relaysStore.allUrls
const keys: NostrKeyPair = (yield rootStore.walletStore.getCachedWalletKeys() as Promise<any>).NOSTR
log.debug('[publishToRelays]', 'Publish profile to relays', {profileEvent, relaysToPublish})
const publishedEvent: Event | undefined = yield NostrClient.publish(
profileEvent,
relaysToPublish
relaysToPublish,
keys
)
return publishedEvent
+7 -4
View File
@@ -117,7 +117,7 @@ const SYNC_STATE_WITH_MINT_TIMEOUT = 10 * 1000
export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
const navigation = useNavigation<any>()
const { mintsStore, walletStore, proofsStore, transactionsStore } = useStores()
const { mintsStore, walletStore, proofsStore, transactionsStore, walletProfileStore } = useStores()
const unitRef = useRef<MintUnit>('sat')
const isOnline = useRef<boolean>(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) {
+2 -1
View File
@@ -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})
+6 -3
View File
@@ -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)
+6 -3
View File
@@ -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)
+4 -1
View File
@@ -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)
}
})
+28 -36
View File
@@ -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<string> {
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<string> {
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<NostrEvent | void> {
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,
+6 -4
View File
@@ -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)
}
+7 -5
View File
@@ -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<void> {
}
}
const handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Promise<WalletTaskResult> {
const handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Promise<WalletTaskResult> {
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
}