diff --git a/package.json b/package.json
index 6f5ba811..11643a35 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "minibits_wallet",
- "version": "0.1.11-beta.1",
+ "version": "0.1.11-beta.2",
"private": true,
"scripts": {
"android:clean": "cd android && ./gradlew clean",
diff --git a/src/App.tsx b/src/App.tsx
index 4d426b9e..f41479fc 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -58,7 +58,7 @@ interface AppProps {
}
function App(props: AppProps) {
- const {userSettingsStore, relaysStore, walletProfileStore} = useStores()
+ const {userSettingsStore, relaysStore} = useStores()
const {rehydrated} = useInitialRootStore(async() => {
log.trace('[useInitialRootStore]', 'Root store rehydrated')
diff --git a/src/models/NwcStore.ts b/src/models/NwcStore.ts
index d7cd67a8..98c9ec84 100644
--- a/src/models/NwcStore.ts
+++ b/src/models/NwcStore.ts
@@ -10,17 +10,16 @@ import {
import { NWCWalletResponse, NWCWalletInfo, NWCWalletRequest } from 'nostr-tools/kinds'
import {withSetPropAction} from './helpers/withSetPropAction'
import {log} from '../services/logService'
-import EventEmitter from '../utils/eventEmitter'
import { getRootStore } from './helpers/getRootStore'
import {
+ HANDLE_NWC_REQUEST_TASK,
KeyChain,
KeyPair,
NostrClient,
NostrEvent,
NostrUnsignedEvent,
SyncQueue,
- TransactionTaskResult,
- WalletTask
+ WalletTaskResult
} from '../services'
import AppError, { Err } from '../utils/AppError'
import { LightningUtils } from '../services/lightning/lightningUtils'
@@ -206,9 +205,7 @@ export const NwcConnectionModel = types.model('NwcConnection', {
yield NostrClient.publish(
responseEvent,
self.connectionRelays
- )
-
- yield NotificationService.stopForegroundService()
+ )
}),
}))
.actions(self => ({
@@ -559,6 +556,8 @@ export const NwcConnectionModel = types.model('NwcConnection', {
// needs to be set before sendResponse but after switch / pay_invoice
yield self.sendResponse(nwcResponse, requestEvent)
+ return nwcResponse
+
}),
}))
@@ -731,32 +730,49 @@ export const NwcStoreModel = types
)
if(!targetConnection) {
- log.error('[handleNwcRequestFromNotification] Missing connection matching event pubkey.', {pubkey: event.pubkey})
+ const message = `Your wallet has received a NWC command, but could not find related NWC connection to handle it.`
+ log.error('[handleNwcRequestFromNotification]', message, {pubkey: event.pubkey})
yield NotificationService.stopForegroundService()
yield NotificationService.createLocalNotification(
`Nostr Wallet Connect error`,
- `Your wallet has received a NWC command, but could not find related NWC connection to handle it.`,
+ message,
nwcPngUrl
)
- return
+ return {
+ taskFunction: HANDLE_NWC_REQUEST_TASK,
+ message,
+ error: new AppError(Err.WALLET_ERROR, message)
+ } as WalletTaskResult
}
- if(!event) {
- log.error('[handleNwcRequestFromNotification] Missing connection matching event pubkey.')
+ if(!event) {
+ const message = `Your wallet has received a NWC command, but could not retrieve the required data.`
+ log.error('[handleNwcRequestFromNotification]', message)
yield NotificationService.stopForegroundService()
yield NotificationService.createLocalNotification(
`Nostr Wallet Connect error`,
- `Your wallet has received a NWC command, but could not retrieve the required data.`,
+ message,
nwcPngUrl
)
- return
+ return {
+ taskFunction: HANDLE_NWC_REQUEST_TASK,
+ message,
+ error: new AppError(Err.WALLET_ERROR, message)
+ } as WalletTaskResult
}
- yield targetConnection.handleNwcRequestTask(event, decryptedNwcRequest)
+ const nwcResponse: NwcResponse | NwcError =
+ yield targetConnection.handleNwcRequestTask(event, decryptedNwcRequest)
+
+ return {
+ taskFunction: HANDLE_NWC_REQUEST_TASK,
+ message: (nwcResponse as NwcResponse).result || undefined ,
+ error: (nwcResponse as NwcError).error ? new AppError(Err.WALLET_ERROR, (nwcResponse as NwcError).error.message) : undefined
+ } as WalletTaskResult
})
}))
.views(self => ({
diff --git a/src/models/WalletProfileStore.ts b/src/models/WalletProfileStore.ts
index be97d88d..1108a905 100644
--- a/src/models/WalletProfileStore.ts
+++ b/src/models/WalletProfileStore.ts
@@ -42,8 +42,7 @@ export const WalletProfileStoreModel = types
lud16: types.maybe(types.maybeNull(types.string)),
device: types.maybe(types.maybeNull(types.string)),
seedHash: types.maybe(types.maybeNull(types.string)),
- isOwnProfile: types.optional(types.boolean, false),
- // isBatchClaimOn: types.maybe(types.boolean), // legacy, not used
+ isOwnProfile: types.optional(types.boolean, false),
})
.actions(self => ({
publishToRelays: flow(function* publishToRelays() {
@@ -148,11 +147,15 @@ export const WalletProfileStoreModel = types
let profileRecord: WalletProfileRecord
self.seedHash = seedHash
- log.trace('[create]', {seedHash, publicKey})
+ log.trace('[create]', {seedHash, publicKey, walletId})
try {
// creates new profile. If all params equal existing one, it is returned
- profileRecord = yield MinibitsClient.createWalletProfile(publicKey, walletId, seedHash)
+ profileRecord = yield MinibitsClient.createWalletProfile(publicKey, walletId, seedHash)
+ self.hydrate(profileRecord)
+
+ log.info('[create]', 'Wallet profile saved in WalletProfileStore', {self})
+ return self
} catch (e: any) {
// Unlikely we might hit the same walletId so we retry with another one
if(e.name === Err.ALREADY_EXISTS_ERROR) {
@@ -167,14 +170,8 @@ export const WalletProfileStoreModel = types
self.hydrate(profileRecord)
return
}
-
throw e
- }
-
- self.hydrate(profileRecord)
-
- log.info('[create]', 'Wallet profile saved in WalletProfileStore', {self})
- return self
+ }
}),
updateName: flow(function* updateName(name: string) {
diff --git a/src/screens/WalletScreen.tsx b/src/screens/WalletScreen.tsx
index 9daca0c7..9ca6537e 100644
--- a/src/screens/WalletScreen.tsx
+++ b/src/screens/WalletScreen.tsx
@@ -42,7 +42,7 @@ import {useStores} from '../models'
import {WalletStackScreenProps} from '../navigation'
import {Mint, UnitBalance} from '../models/Mint'
import {MintsByUnit} from '../models/MintsStore'
-import {HANDLE_CLAIM_TASK, HANDLE_RECEIVED_EVENT_TASK, log, NostrClient, WalletTaskResult} from '../services'
+import {HANDLE_CLAIM_TASK, HANDLE_RECEIVED_EVENT_TASK, KeyChain, log, NostrClient, WalletTaskResult} from '../services'
import {Env} from '../utils/envtypes'
import {Transaction} from '../models/Transaction'
import {TransactionListItem} from './Transactions/TransactionListItem'
@@ -170,6 +170,9 @@ export const WalletScreen: FC = observer(
await addMint()
}
+ // const nostrKeyPair = await KeyChain.loadNostrKeyPair()
+ // log.trace('[getInitialData] KEYS CHECK', {keychainPubkey: nostrKeyPair?.publicKey, profilePubkey: walletProfileStore.pubkey})
+
// Only once on startup - Create websocket subscriptions to receive tokens or payment requests by NOSTR DMs
WalletTask.receiveEventsFromRelaysQueue()
diff --git a/src/services/cashu/cashuUtils.ts b/src/services/cashu/cashuUtils.ts
index 08b11d26..cd806595 100644
--- a/src/services/cashu/cashuUtils.ts
+++ b/src/services/cashu/cashuUtils.ts
@@ -20,6 +20,7 @@ const CASHU_URI_PREFIXES = [
'cashu://',
'cashu:'
]
+
const CASHU_TOKEN_PREFIXES = [
'cashuA',
'cashuB'
diff --git a/src/services/lightning/lightningUtils.ts b/src/services/lightning/lightningUtils.ts
index f18f69cd..7bd2b523 100644
--- a/src/services/lightning/lightningUtils.ts
+++ b/src/services/lightning/lightningUtils.ts
@@ -5,6 +5,10 @@ import { log } from '../logService'
import { roundUp, toNumber } from "../../utils/number"
// TODO refactor all this into own module
+const LIGHTNING_URI_PREFIXES = [
+ 'lightning://',
+ 'lightning:',
+]
export type DecodedLightningInvoice = {
paymentRequest: string
@@ -40,17 +44,11 @@ const extractEncodedLightningInvoice = function (maybeInvoice: string) {
let invoice: DecodedLightningInvoice
let encodedInvoice: string = ''
- if (maybeInvoice && maybeInvoice.startsWith('lightning:')) {
+ if (maybeInvoice && maybeInvoice.toLowerCase().startsWith('lightning:')) {
- // URI token formats
- const uriPrefixes = [
- 'lightning://',
- 'lightning:',
- ]
-
- for (const prefix of uriPrefixes) {
- if (maybeInvoice && maybeInvoice.startsWith(prefix)) {
- encodedInvoice = maybeInvoice.slice(prefix.length)
+ for (const prefix of LIGHTNING_URI_PREFIXES) {
+ if (maybeInvoice && maybeInvoice.toLowerCase().startsWith(prefix)) {
+ encodedInvoice = maybeInvoice.toLowerCase().slice(prefix.length)
break // necessary
}
}
@@ -59,8 +57,8 @@ const extractEncodedLightningInvoice = function (maybeInvoice: string) {
return encodedInvoice
}
- if (maybeInvoice && maybeInvoice.startsWith('bitcoin:')) {
- const url = new URL(maybeInvoice)
+ if (maybeInvoice && maybeInvoice.toLowerCase().startsWith('bitcoin:' || 'http')) {
+ const url = new URL(maybeInvoice.toLowerCase())
// Use URLSearchParams to get the value of the "lightning" parameter
encodedInvoice = url.searchParams.get("lightning") as string
invoice = decodeInvoice(encodedInvoice) // throws
diff --git a/src/services/lnurl/lnurlUtils.ts b/src/services/lnurl/lnurlUtils.ts
index 66ed0d51..e19c4518 100644
--- a/src/services/lnurl/lnurlUtils.ts
+++ b/src/services/lnurl/lnurlUtils.ts
@@ -4,6 +4,14 @@ import AppError, {Err} from '../../utils/AppError'
import { log } from '../logService'
import { LightningUtils, isLightningInvoice } from '../lightning/lightningUtils'
+const LNURL_URI_PREFIXES = [
+ 'lightning://',
+ 'lightning:',
+ 'lnurlw://',
+ 'lnurlw:',
+ 'lnurlp://',
+ 'lnurlp:',
+ ]
const findEncodedLnurl = function (content: string) {
const words = content.split(/\s+|\n+/)
@@ -17,14 +25,7 @@ const findEncodedLnurlAddress = function (content: string) {
return maybeAddress || null
}
-const lnurlUriPrefixes = [
- 'lightning://',
- 'lightning:',
- 'lnurlw://',
- 'lnurlw:',
- 'lnurlp://',
- 'lnurlp:',
-]
+
function isLnurlAddress(address: string) {
// Regular expression for a basic email validation
@@ -42,8 +43,8 @@ const extractEncodedLnurl = function (maybeLnurl: string) {
return maybeLnurl
}
- if (maybeLnurl && maybeLnurl.startsWith('http')) { // e.g. lnbits withdraw extension links
- const parsed = new URL(maybeLnurl)
+ if (maybeLnurl && maybeLnurl.toLowerCase().startsWith('http')) { // e.g. lnbits withdraw extension links
+ const parsed = new URL(maybeLnurl.toLowerCase())
encodedLnurl = parsed.searchParams.get('lightning')
if(encodedLnurl) {
@@ -53,9 +54,9 @@ const extractEncodedLnurl = function (maybeLnurl: string) {
}
}
- for (const prefix of lnurlUriPrefixes) {
- if (maybeLnurl && maybeLnurl.startsWith(prefix)) {
- encodedLnurl = maybeLnurl.slice(prefix.length)
+ for (const prefix of LNURL_URI_PREFIXES) {
+ if (maybeLnurl && maybeLnurl.toLowerCase().startsWith(prefix)) {
+ encodedLnurl = maybeLnurl.toLowerCase().slice(prefix.length)
break // necessary
}
}
@@ -73,7 +74,7 @@ const extractEncodedLnurl = function (maybeLnurl: string) {
function extractLnurlAddress(maybeAddress: string) {
let address: string | null = null
- for (const prefix of lnurlUriPrefixes) {
+ for (const prefix of LNURL_URI_PREFIXES) {
if (maybeAddress && maybeAddress.startsWith(prefix)) {
address = maybeAddress.slice(prefix.length)
break; // necessary
diff --git a/src/services/nostrService.ts b/src/services/nostrService.ts
index 1f179d9c..731b97a4 100644
--- a/src/services/nostrService.ts
+++ b/src/services/nostrService.ts
@@ -114,7 +114,9 @@ const getOrCreateKeyPair = async function (): Promise {
keyPair = KeyChain.generateNostrKeyPair() as KeyPair
await KeyChain.saveNostrKeyPair(keyPair)
- log.trace('[getOrCreateKeyPair]', 'Created and saved new NOSTR keypair')
+ log.info('[getOrCreateKeyPair]', 'Created and saved new NOSTR keypair', {pubkey: keyPair.publicKey})
+ } else {
+ log.trace('[getOrCreateKeyPair]', 'Returning existing NOSTR keypair', {pubkey: keyPair.publicKey})
}
return keyPair
diff --git a/src/services/walletService.ts b/src/services/walletService.ts
index de8f8906..f27fc620 100644
--- a/src/services/walletService.ts
+++ b/src/services/walletService.ts
@@ -1711,7 +1711,14 @@ const handleClaimQueue = async function (): Promise {
const profile = await MinibitsClient.getWalletProfileBySeedHash(seedHash || recoveredSeedHash!)
if(profile) {
- walletProfileStore.hydrate(profile)
+ // make sure we did not lose and thus rotated nostr keys as well
+ const keyPair = await NostrClient.getOrCreateKeyPair()
+
+ if(keyPair.publicKey === profile.pubkey) {
+ walletProfileStore.hydrate(profile)
+ } else {
+ throw new AppError(Err.KEYCHAIN_ERROR, 'Wallet public key does not match server profile, please reinstall wallet.', {walletPubkey: keyPair.publicKey, profilePubkey: profile.pubkey})
+ }
}
}