mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 19:28:26 +00:00
Fix payment request pay with POST transport, Optimze ecash menu item.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "minibits_wallet",
|
||||
"version": "0.4.2-beta.2",
|
||||
"version": "0.4.2-beta.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android:clean": "cd android && ./gradlew clean",
|
||||
|
||||
@@ -534,6 +534,7 @@
|
||||
"settingsScreen_preferredUnit": "Preferred unit",
|
||||
"settingsScreen_privacy": "Privacy",
|
||||
"settingsScreen_recovery": "Recovery",
|
||||
"settingsScreen_optimizeEcash": "Optimize ecash",
|
||||
"settingsScreen_relaysCount": {
|
||||
"one": "%{count} relay",
|
||||
"other": "%{count} relays"
|
||||
|
||||
@@ -533,6 +533,7 @@
|
||||
"settingsScreen_preferredUnit": "Unidad preferida",
|
||||
"settingsScreen_privacy": "Privacidad",
|
||||
"settingsScreen_recovery": "Recuperación",
|
||||
"settingsScreen_optimizeEcash": "Optimizar ecash",
|
||||
"settingsScreen_relaysCount": {
|
||||
"one": "Relé %{count}",
|
||||
"other": "%{count} relés"
|
||||
|
||||
@@ -534,6 +534,7 @@
|
||||
"settingsScreen_preferredUnit": "Unidade preferida",
|
||||
"settingsScreen_privacy": "Privacidade",
|
||||
"settingsScreen_recovery": "Recuperação",
|
||||
"settingsScreen_optimizeEcash": "Otimizar ecash",
|
||||
"settingsScreen_relaysCount": {
|
||||
"one": "%{count} relay",
|
||||
"other": "%{count} relays"
|
||||
|
||||
@@ -534,6 +534,7 @@
|
||||
"settingsScreen_preferredUnit": "Preferovaná mena",
|
||||
"settingsScreen_privacy": "Súkromie",
|
||||
"settingsScreen_recovery": "Obnova",
|
||||
"settingsScreen_optimizeEcash": "Optimalizovať ecash",
|
||||
"settingsScreen_relaysCount": {
|
||||
"one": "%{count} relay",
|
||||
"other": "%{count} relays"
|
||||
|
||||
@@ -61,7 +61,8 @@ export const MintsStoreModel = types
|
||||
.actions(withSetPropAction)
|
||||
.actions(self => ({
|
||||
mintExists: (mintUrl: string | URL) => {
|
||||
const mint = self.mints.find(m => m.mintUrl === mintUrl)
|
||||
const normalized = String(mintUrl).replace(/\/$/, '')
|
||||
const mint = self.mints.find(m => m.mintUrl.replace(/\/$/, '') === normalized)
|
||||
if(mint) {return true} else {return false}
|
||||
},
|
||||
addOrUpdateCounterBackup(mintToRemove: Mint) {
|
||||
@@ -109,7 +110,10 @@ export const MintsStoreModel = types
|
||||
if(!mintUrl) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint URL is required.')
|
||||
}
|
||||
|
||||
|
||||
// Cashu spec: mint URL must be stripped of trailing slashes
|
||||
mintUrl = mintUrl.replace(/\/$/, '')
|
||||
|
||||
if(!mintUrl.includes('.onion') && !mintUrl.startsWith('https')) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint URL needs to start with https.')
|
||||
}
|
||||
@@ -119,7 +123,7 @@ export const MintsStoreModel = types
|
||||
}
|
||||
|
||||
log.trace('[addMint] start')
|
||||
|
||||
|
||||
const newMint = new CashuMint(mintUrl)
|
||||
// get fresh keysets
|
||||
const keySetResult: GetKeysetsResponse = yield newMint.getKeySets()
|
||||
|
||||
@@ -411,7 +411,7 @@ import {
|
||||
}))
|
||||
|
||||
.views(self => ({
|
||||
getMintBalance: (mintUrl: string) => self.balances.mintBalances.find(b => b.mintUrl === mintUrl),
|
||||
getMintBalance: (mintUrl: string) => self.balances.mintBalances.find(b => b.mintUrl.replace(/\/$/, '') === mintUrl.replace(/\/$/, '')),
|
||||
getMintBalancesWithEnoughBalance: (amount: number, unit: MintUnit) =>
|
||||
self.balances.mintBalances
|
||||
.filter(b => (b.balances[unit] || 0) >= amount)
|
||||
|
||||
@@ -29,6 +29,7 @@ import { InFlightRequest, Mint } from './Mint'
|
||||
import { getRootStore } from './helpers/getRootStore'
|
||||
import { Transaction } from './Transaction'
|
||||
|
||||
//
|
||||
|
||||
/*
|
||||
Not persisted, in-memory only model of the cashu-ts wallet instances and wallet keys persisted in the device secure store.
|
||||
|
||||
@@ -35,6 +35,8 @@ import { TransactionStatus } from '../models/Transaction'
|
||||
import { maxTransactionsInHistory } from '../models/TransactionsStore'
|
||||
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
||||
|
||||
//
|
||||
|
||||
type Props = StaticScreenProps<undefined>
|
||||
|
||||
export const DeveloperScreen = observer(function DeveloperScreen({ route }: Props) {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import React, {useState, useEffect, useLayoutEffect} from 'react'
|
||||
import {
|
||||
TextStyle,
|
||||
ViewStyle,
|
||||
View,
|
||||
Platform,
|
||||
ScrollView,
|
||||
} from 'react-native'
|
||||
import Animated, {
|
||||
useSharedValue,
|
||||
useAnimatedScrollHandler,
|
||||
} from 'react-native-reanimated'
|
||||
import notifee from '@notifee/react-native'
|
||||
import {useThemeColor, spacing, colors} from '../theme'
|
||||
import {
|
||||
@@ -18,6 +21,7 @@ import {
|
||||
Loading,
|
||||
BottomModal,
|
||||
Header,
|
||||
AnimatedHeader,
|
||||
} from '../components'
|
||||
import AppError from '../utils/AppError'
|
||||
import {useStores} from '../models'
|
||||
@@ -35,6 +39,23 @@ type Props = StaticScreenProps<undefined>
|
||||
export const OptimizeEcashScreen = function OptimizeEcash(_props: Props) {
|
||||
const navigation = useNavigation()
|
||||
const {proofsStore} = useStores()
|
||||
const scrollY = useSharedValue(0)
|
||||
const HEADER_SCROLL_DISTANCE = spacing.screenHeight * 0.15
|
||||
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerShown: true,
|
||||
header: () => (
|
||||
<Header
|
||||
leftIcon="faArrowLeft"
|
||||
onLeftPress={() => navigation.goBack()}
|
||||
title="Optimize ecash"
|
||||
scrollY={scrollY}
|
||||
scrollDistance={HEADER_SCROLL_DISTANCE}
|
||||
/>
|
||||
),
|
||||
})
|
||||
}, [])
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<AppError | undefined>()
|
||||
@@ -131,17 +152,29 @@ export const OptimizeEcashScreen = function OptimizeEcash(_props: Props) {
|
||||
setError(e)
|
||||
}
|
||||
|
||||
const headerBg = useThemeColor('header')
|
||||
const headerTitle = useThemeColor('headerTitle')
|
||||
const hint = useThemeColor('textDim')
|
||||
|
||||
const scrollHandler = useAnimatedScrollHandler({
|
||||
onScroll: (event) => {
|
||||
scrollY.value = event.contentOffset.y
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<Screen contentContainerStyle={$screen} preset="fixed">
|
||||
<Header leftIcon="faArrowLeft" onLeftPress={() => navigation.goBack()} />
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
<Text preset="heading" text="Optimize ecash" style={{color: headerTitle}} />
|
||||
</View>
|
||||
<ScrollView style={$contentContainer}>
|
||||
<Animated.ScrollView
|
||||
style={$contentContainer}
|
||||
onScroll={scrollHandler}
|
||||
scrollEventThrottle={16}
|
||||
>
|
||||
<AnimatedHeader
|
||||
title="Optimize ecash"
|
||||
scrollY={scrollY}
|
||||
/>
|
||||
<Card
|
||||
content={`Only denominations with more than ${OPTIMIZE_DENOMINATION_THRESHOLD} proofs can be optimized - by swapping them with the mint for lower number of ecash notes with higher amounts.`}
|
||||
style={[$card, {marginTop: -spacing.extraLarge * 1.5}]}
|
||||
/>
|
||||
<Card
|
||||
ContentComponent={
|
||||
<>
|
||||
@@ -177,15 +210,7 @@ export const OptimizeEcashScreen = function OptimizeEcash(_props: Props) {
|
||||
}
|
||||
style={$card}
|
||||
/>
|
||||
<View style={$hintContainer}>
|
||||
<Text
|
||||
style={{color: hint}}
|
||||
size="xs"
|
||||
preset="formHelper"
|
||||
text={`Denominations with more than ${OPTIMIZE_DENOMINATION_THRESHOLD} proofs can be optimized individually.`}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</Animated.ScrollView>
|
||||
{isLoading && <Loading />}
|
||||
{error && <ErrorModal error={error} />}
|
||||
<BottomModal
|
||||
@@ -247,23 +272,16 @@ export const OptimizeEcashScreen = function OptimizeEcash(_props: Props) {
|
||||
}
|
||||
|
||||
const $screen: ViewStyle = {
|
||||
flex: 1,
|
||||
}
|
||||
|
||||
const $headerContainer: TextStyle = {
|
||||
alignItems: 'center',
|
||||
paddingBottom: spacing.medium,
|
||||
height: spacing.screenHeight * 0.15,
|
||||
// flex: 1,
|
||||
}
|
||||
|
||||
const $contentContainer: TextStyle = {
|
||||
flex: 1,
|
||||
marginTop: -spacing.extraLarge * 2,
|
||||
padding: spacing.extraSmall,
|
||||
//flex: 1,
|
||||
}
|
||||
|
||||
const $card: ViewStyle = {
|
||||
marginBottom: spacing.small,
|
||||
marginHorizontal: spacing.small,
|
||||
}
|
||||
|
||||
const $buttonContainer: ViewStyle = {
|
||||
|
||||
@@ -238,6 +238,24 @@ export const SendScreen = observer(function SendScreen({ route }: Props) {
|
||||
|
||||
const handlePaymentRequest = async () => {
|
||||
try {
|
||||
|
||||
/*const cashuPaymentRequest = new CashuPaymentRequest(
|
||||
[
|
||||
{
|
||||
type: PaymentRequestTransportType.POST,
|
||||
target: 'https://shuestand.mountainlake.io/api/v1/withdrawals/wd_62b13c9d-1082-4b2d-809e-1e6723f74c52/nut18'
|
||||
}
|
||||
],
|
||||
'test1234',
|
||||
10,
|
||||
'sat',
|
||||
['https://m7.mountainlake.io'],
|
||||
'memo'
|
||||
)
|
||||
|
||||
const encoded = cashuPaymentRequest.toEncodedRequest()
|
||||
log.trace('[handlePaymentRequest] TEST payment request', encoded)*/
|
||||
|
||||
setPaymentOption(SendOption.PAY_CASHU_PAYMENT_REQUEST)
|
||||
setContactToSendFrom(getContactFrom())
|
||||
|
||||
@@ -357,25 +375,32 @@ export const SendScreen = observer(function SendScreen({ route }: Props) {
|
||||
}
|
||||
|
||||
if (availableBalances.length === 0) {
|
||||
throw new AppError(Err.NOTFOUND_ERROR, 'Wallet does not have any of the mints accepted by Cashu payment request.', {mints: pr.mints})
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'None of the mints accepted by this payment request are in your wallet.', {mints: pr.mints})
|
||||
}
|
||||
|
||||
|
||||
const withEnoughBalance = availableBalances.filter(balance => {
|
||||
const unitBalance = balance.balances[unitRef.current]
|
||||
|
||||
if(!pr.amount) {
|
||||
return balance
|
||||
}
|
||||
|
||||
|
||||
if(pr.amount && pr.amount > 0 && unitBalance && unitBalance >= pr.amount) {
|
||||
return balance
|
||||
}
|
||||
|
||||
return null
|
||||
return null
|
||||
})
|
||||
|
||||
|
||||
if (pr.amount && withEnoughBalance.length === 0) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, `Not enough balance to pay this payment request. Required: ${pr.amount} ${unitRef.current}.`)
|
||||
|
||||
}
|
||||
|
||||
setAvailableMintBalances(withEnoughBalance)
|
||||
setMintBalanceToSendFrom(withEnoughBalance[0])
|
||||
setMintBalanceToSendFrom(withEnoughBalance[0])
|
||||
|
||||
log.trace('[handlePaymentRequest] available mint balances for this payment request', {availableBalances, withEnoughBalance})
|
||||
|
||||
} else {
|
||||
let withEnoughBalance: MintBalance[] = []
|
||||
@@ -443,7 +468,6 @@ export const SendScreen = observer(function SendScreen({ route }: Props) {
|
||||
setIsOnline(isInternetReachable)
|
||||
|
||||
if(isInternetReachable) {
|
||||
setAvailableMintBalances([])
|
||||
setInfo('') // clear any offline-specific messages
|
||||
} else {
|
||||
const availableBalances = proofsStore.getMintBalancesWithEnoughBalance(1, unitRef.current)
|
||||
|
||||
@@ -216,6 +216,11 @@ export const SettingsScreen = observer(function SettingsScreen({ route }: Props)
|
||||
navigation.navigate('RecoveryOptions')
|
||||
}
|
||||
|
||||
const gotoOptimizeEcash = function() {
|
||||
// @ts-ignore
|
||||
navigation.navigate('OptimizeEcash')
|
||||
}
|
||||
|
||||
const gotoUpdate = function() {
|
||||
// @ts-ignore
|
||||
navigation.navigate('Update', {
|
||||
@@ -303,7 +308,7 @@ export const SettingsScreen = observer(function SettingsScreen({ route }: Props)
|
||||
}
|
||||
|
||||
|
||||
const $itemRight = {color: useThemeColor('textDim'), fontFamily: typography.light} as TextStyle
|
||||
const $itemRight = {color: useThemeColor('textDim'), fontFamily: typography.primary?.light} as TextStyle
|
||||
const colorScheme = useColorScheme()
|
||||
const defaultThemeColor = colorScheme === 'dark' ? Themes[ThemeCode.DARK]?.color : Themes[ThemeCode.LIGHT]?.color
|
||||
|
||||
@@ -468,9 +473,18 @@ export const SettingsScreen = observer(function SettingsScreen({ route }: Props)
|
||||
leftIcon='faHeartPulse'
|
||||
leftIconColor={colors.palette.angry300}
|
||||
leftIconInverse={true}
|
||||
style={$item}
|
||||
style={$item}
|
||||
bottomSeparator={true}
|
||||
onPress={gotoRecoveryOptions}
|
||||
/>
|
||||
/>
|
||||
<ListItem
|
||||
tx='settingsScreen_optimizeEcash'
|
||||
leftIcon='faWandMagicSparkles'
|
||||
leftIconColor={colors.palette.gold200}
|
||||
leftIconInverse={true}
|
||||
style={$item}
|
||||
onPress={gotoOptimizeEcash}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -830,7 +830,7 @@ export const WalletScreen = observer(function WalletScreen({ route }: Props) {
|
||||
LeftComponent={
|
||||
<View style={{marginRight: spacing.medium}}>
|
||||
<FastImage
|
||||
source={{uri: 'https://www.minibits.cash/img/minibits_icon-192.png'}}
|
||||
source={{uri: 'https://minibits.cash/icon-192.png'}}
|
||||
style={{width: 40, height: 40}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Platform } from "react-native"
|
||||
import AppError, { Err } from "../utils/AppError"
|
||||
|
||||
const { userSettingsStore } = rootStoreInstance
|
||||
// refresh
|
||||
//
|
||||
|
||||
if (!__DEV__) {
|
||||
Sentry.init({
|
||||
|
||||
@@ -2766,7 +2766,8 @@ const handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Pro
|
||||
// We do it defensively only after cash is received
|
||||
// and asynchronously so we speed up queue
|
||||
_sendReceiveNotification(
|
||||
receivedAmount,
|
||||
transaction.amount,
|
||||
transaction.fee,
|
||||
transaction.unit,
|
||||
isZap,
|
||||
sentFrom,
|
||||
@@ -2986,7 +2987,8 @@ const handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Pro
|
||||
// We do it defensively only after cash is received
|
||||
// and asynchronously so we speed up queue
|
||||
_sendReceiveNotification(
|
||||
receivedAmount,
|
||||
transaction.amount,
|
||||
transaction.fee,
|
||||
transaction.unit,
|
||||
false,
|
||||
sentFrom,
|
||||
@@ -3021,6 +3023,7 @@ const handleReceivedEventTask = async function (encryptedEvent: NostrEvent): Pro
|
||||
|
||||
const _sendReceiveNotification = async function (
|
||||
receivedAmount: number,
|
||||
feePaid: number,
|
||||
unit: MintUnit,
|
||||
isZap: boolean,
|
||||
sentFrom: string,
|
||||
@@ -3036,8 +3039,8 @@ const _sendReceiveNotification = async function (
|
||||
? `<b>⚡${formatCurrency(amount, currency)} ${currency}</b> received!`
|
||||
: `⚡${formatCurrency(amount, currency)} ${currency} received!`
|
||||
const body = Platform.OS === 'android'
|
||||
? `${zap ? 'Zap' : 'Ecash'} from <b>${sender || 'unknown payer'}</b> is now in your wallet.`
|
||||
: `${zap ? 'Zap' : 'Ecash'} from ${sender || 'unknown payer'} is now in your wallet.`
|
||||
? `${zap ? 'Zap' : 'Ecash'} from <b>${sender || 'unknown payer'}</b> is now in your wallet.${feePaid > 0 ? ` Fee paid: ${formatCurrency(feePaid, currency)} ${currency}.` : ''}`
|
||||
: `${zap ? 'Zap' : 'Ecash'} from ${sender || 'unknown payer'} is now in your wallet.${feePaid > 0 ? ` Fee paid: ${formatCurrency(feePaid, currency)} ${currency}.` : ''}`
|
||||
return { title, body };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user