mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-06 03:38:24 +00:00
Fixes: support for LNURL codes in uppercase, return correct type from NWC task
This commit is contained in:
+1
-1
@@ -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",
|
||||
|
||||
+1
-1
@@ -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')
|
||||
|
||||
+30
-14
@@ -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(
|
||||
`<b>Nostr Wallet Connect</b> 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(
|
||||
`<b>Nostr Wallet Connect</b> 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 => ({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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<WalletScreenProps> = 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()
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ const CASHU_URI_PREFIXES = [
|
||||
'cashu://',
|
||||
'cashu:'
|
||||
]
|
||||
|
||||
const CASHU_TOKEN_PREFIXES = [
|
||||
'cashuA',
|
||||
'cashuB'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -114,7 +114,9 @@ const getOrCreateKeyPair = async function (): Promise<KeyPair> {
|
||||
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
|
||||
|
||||
@@ -1711,7 +1711,14 @@ const handleClaimQueue = async function (): Promise<void> {
|
||||
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})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user