mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 19:28:26 +00:00
Lightning: Support for payment comments, Tor mint support (#83)
Co-authored-by: minibits-cash <mail@minibits.cash>
This commit is contained in:
co-authored by
minibits-cash
parent
ae192570ce
commit
307e3b93c8
+3
-1
@@ -67,4 +67,6 @@ yarn-error.log
|
|||||||
.metro-health-check*
|
.metro-health-check*
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
|
minibits.inlang/cache
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
cache
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
"modules": [
|
"modules": [
|
||||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js",
|
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js",
|
||||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@latest/dist/index.js",
|
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@latest/dist/index.js",
|
||||||
"https://cdn.jsdelivr.net/gh/KraXen72/inlang-t-function-jsx-hybrid-matcher/dist/index.js",
|
"https://cdn.jsdelivr.net/gh/KraXen72/inlang-t-function-jsx-hybrid-matcher/dist/index.js",
|
||||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-json@latest/dist/index.js"
|
"https://cdn.jsdelivr.net/npm/@inlang/plugin-json@latest/dist/index.js"
|
||||||
],
|
],
|
||||||
"plugin.inlang.json": {
|
"plugin.inlang.json": {
|
||||||
@@ -22,6 +22,11 @@
|
|||||||
"preferredTfuncName": "translate",
|
"preferredTfuncName": "translate",
|
||||||
"recognizedTfuncNames": [
|
"recognizedTfuncNames": [
|
||||||
"translate"
|
"translate"
|
||||||
|
],
|
||||||
|
"recognizedJSXAttributes": [
|
||||||
|
"tx",
|
||||||
|
"subTx",
|
||||||
|
"contentTx"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ export const AvatarHeader = observer(function (props: AvatarHeaderProps) {
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{props.heading && <Text style={{ fontSize: 26, lineHeight: 40 }} text={props.heading} adjustsFontSizeToFit={true} numberOfLines={1} />}
|
{props.heading && <Text style={{ fontSize: 26, lineHeight: 40 }} text={props.heading} adjustsFontSizeToFit={true} numberOfLines={1} />}
|
||||||
{props.text && <Text preset='bold' text={props.text} style={{ color: 'white', marginBottom: spacing.small }} />}
|
{props.text && <Text preset='bold' text={props.text} style={{ color: 'white', marginBottom: spacing.small }} numberOfLines={2} />}
|
||||||
{props.children}
|
{props.children}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export function BottomModal(props: ModalProps) {
|
|||||||
isVisible = true,
|
isVisible = true,
|
||||||
onBackdropPress,
|
onBackdropPress,
|
||||||
onBackButtonPress,
|
onBackButtonPress,
|
||||||
backdropOpacity = 0.45,
|
backdropOpacity = 0.25,
|
||||||
content,
|
content,
|
||||||
contentTx,
|
contentTx,
|
||||||
contentTxOptions,
|
contentTxOptions,
|
||||||
@@ -216,19 +216,17 @@ export function BottomModal(props: ModalProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const $outerContainerBase: ViewStyle = {
|
const $outerContainerBase: ViewStyle = {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
margin: 0,
|
margin: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const $innerContainerBase: ViewStyle = {
|
const $innerContainerBase: ViewStyle = {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderTopLeftRadius: spacing.small,
|
borderTopLeftRadius: spacing.small,
|
||||||
borderTopRightRadius: spacing.small,
|
borderTopRightRadius: spacing.small,
|
||||||
padding: spacing.small
|
padding: spacing.small
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
+21
-16
@@ -1,20 +1,25 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { StyleSheet, View, ActivityIndicator, ViewProps, TextStyle } from 'react-native'
|
import { StyleSheet, View, ActivityIndicator, ViewProps, TextStyle, ColorValue, ViewStyle } from 'react-native'
|
||||||
import { useThemeColor } from '../theme'
|
import { useThemeColor } from '../theme'
|
||||||
import { Text } from './Text'
|
import { Text } from './Text'
|
||||||
|
import { spacing } from '../theme'
|
||||||
|
|
||||||
export function Loading(props: ViewProps & {statusMessage?: string, textStyle?: TextStyle}) {
|
export function Loading(props: ViewProps & { statusMessage?: string, textStyle?: TextStyle, shiftedUp?: boolean }) {
|
||||||
return (
|
return (
|
||||||
<View style={[{
|
<View style={[StyleSheet.absoluteFillObject, $loading(useThemeColor('background'), props?.shiftedUp ?? false), props.style]}>
|
||||||
flex: 1,
|
<ActivityIndicator color="#ccc" animating size="large" />
|
||||||
alignItems: 'center',
|
{props.statusMessage && (<Text style={[{opacity: 1}, props.textStyle]} text={props.statusMessage}/>)}
|
||||||
backgroundColor: useThemeColor('background'),
|
</View>
|
||||||
opacity: 0.5,
|
)
|
||||||
justifyContent: 'center',
|
}
|
||||||
zIndex: 9999,
|
|
||||||
}, StyleSheet.absoluteFillObject, props.style]}>
|
const $loading = (bg: ColorValue, shiftedUp = false) => ({
|
||||||
<ActivityIndicator color="#ccc" animating size="large" />
|
flex: 1,
|
||||||
{props.statusMessage && (<Text style={[{opacity: 1}, props.textStyle]} text={props.statusMessage}/>)}
|
alignItems: 'center',
|
||||||
</View>
|
justifyContent: 'center',
|
||||||
)
|
backgroundColor: bg,
|
||||||
}
|
opacity: 0.5,
|
||||||
|
zIndex: 9999,
|
||||||
|
// for cards that go up in the header, the loading should cover them
|
||||||
|
marginTop: shiftedUp ? -(spacing.extraLarge * 2 + spacing.small) : 0,
|
||||||
|
} satisfies ViewStyle)
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import React, {forwardRef} from 'react'
|
||||||
|
import {View, ViewStyle, TextInput, TextStyle} from 'react-native'
|
||||||
|
import {spacing} from '../theme'
|
||||||
|
import {Button} from './Button'
|
||||||
|
import {Card} from './Card'
|
||||||
|
import {translate} from '../i18n'
|
||||||
|
|
||||||
|
interface MemoInputProps {
|
||||||
|
memo: string
|
||||||
|
setMemo: (memo: string) => void
|
||||||
|
disabled?: boolean
|
||||||
|
onMemoDone: () => void
|
||||||
|
onMemoEndEditing?: () => void
|
||||||
|
maxLength?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MemoInputCard = forwardRef<TextInput, MemoInputProps>((props, memoInputRef) => {
|
||||||
|
const {
|
||||||
|
memo,
|
||||||
|
setMemo,
|
||||||
|
disabled = true,
|
||||||
|
onMemoDone,
|
||||||
|
onMemoEndEditing = () => {},
|
||||||
|
maxLength = 200,
|
||||||
|
} = props
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
style={$memoCard}
|
||||||
|
ContentComponent={
|
||||||
|
<View style={$memoContainer}>
|
||||||
|
<TextInput
|
||||||
|
ref={memoInputRef}
|
||||||
|
onChangeText={memo => setMemo(memo)}
|
||||||
|
onEndEditing={onMemoEndEditing}
|
||||||
|
value={`${memo}`}
|
||||||
|
style={$memoInput}
|
||||||
|
maxLength={maxLength}
|
||||||
|
keyboardType="default"
|
||||||
|
selectTextOnFocus={true}
|
||||||
|
placeholder={translate('sendScreen.memo')}
|
||||||
|
editable={!disabled}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
preset="secondary"
|
||||||
|
style={$memoButton}
|
||||||
|
text="Done"
|
||||||
|
onPress={onMemoDone}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const $memoContainer: ViewStyle = {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}
|
||||||
|
|
||||||
|
const $memoCard: ViewStyle = {
|
||||||
|
marginBottom: spacing.small,
|
||||||
|
}
|
||||||
|
|
||||||
|
const $memoButton: ViewStyle = {
|
||||||
|
maxHeight: 50,
|
||||||
|
}
|
||||||
|
|
||||||
|
const $memoInput: TextStyle = {
|
||||||
|
flex: 1,
|
||||||
|
borderRadius: spacing.small,
|
||||||
|
fontSize: 16,
|
||||||
|
textAlignVertical: 'center',
|
||||||
|
marginRight: spacing.small,
|
||||||
|
}
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
"copyAsEncodedTokens": "Copy as encoded tokens",
|
"copyAsEncodedTokens": "Copy as encoded tokens",
|
||||||
"copyContactPublicKey": "Copy contact's public key",
|
"copyContactPublicKey": "Copy contact's public key",
|
||||||
"copyMnemonicBackupWorkaround": "To apply backup to your existing funds, send your entire balance to yourself. Otherwise, only funds from this point onward can be restored.",
|
"copyMnemonicBackupWorkaround": "To apply backup to your existing funds, send your entire balance to yourself. Otherwise, only funds from this point onward can be restored.",
|
||||||
"copyProofs": "text=\"Copy proofs\"",
|
"copyProofs": "Copy proofs",
|
||||||
"decodedMissingCurrencyUnit": "Currency unit is missing in the received token. Wallet will assume token amount is in bitcoin $%{unit}. Do not continue if you are not sure this is correct.",
|
"decodedMissingCurrencyUnit": "Currency unit is missing in the received token. Wallet will assume token amount is in bitcoin $%{unit}. Do not continue if you are not sure this is correct.",
|
||||||
"deleteContact": "Delete contact",
|
"deleteContact": "Delete contact",
|
||||||
"deleteContactDesc": "Remove this contact from your wallet.",
|
"deleteContactDesc": "Remove this contact from your wallet.",
|
||||||
@@ -150,7 +150,8 @@
|
|||||||
"lightningAddress": "Lightning address",
|
"lightningAddress": "Lightning address",
|
||||||
"lightningInvoiceSharedWaiting": "Lightning invoice has been shared, waiting to be paid by receiver.",
|
"lightningInvoiceSharedWaiting": "Lightning invoice has been shared, waiting to be paid by receiver.",
|
||||||
"lightningPayScreen": {
|
"lightningPayScreen": {
|
||||||
"onPasteEmptyClipboard": "First copy Lightning invoice, address or pay code to pay to. Then paste."
|
"onPasteEmptyClipboard": "First copy Lightning invoice, address or pay code to pay to. Then paste.",
|
||||||
|
"payHeading": "Pay"
|
||||||
},
|
},
|
||||||
"loadingPublicInfo": "Loading public info",
|
"loadingPublicInfo": "Loading public info",
|
||||||
"localBackupDisabled": "Local backup is off",
|
"localBackupDisabled": "Local backup is off",
|
||||||
@@ -398,7 +399,8 @@
|
|||||||
"sendToContactDescription": "Send your ecash to one of your contacts stored in your contact list.",
|
"sendToContactDescription": "Send your ecash to one of your contacts stored in your contact list.",
|
||||||
"showOrShareToken": "Share ecash",
|
"showOrShareToken": "Share ecash",
|
||||||
"showOrShareTokenDescription": "Present or share ecash token for an amount you want to send.",
|
"showOrShareTokenDescription": "Present or share ecash token for an amount you want to send.",
|
||||||
"title": "Send"
|
"title": "Send",
|
||||||
|
"memo": "Memo for recipient"
|
||||||
},
|
},
|
||||||
"saveContactPrivate": "Save as private contact",
|
"saveContactPrivate": "Save as private contact",
|
||||||
"saveContactPrivateDesc": "Save %{nip05} to your Private contacts so you can pay faster",
|
"saveContactPrivateDesc": "Save %{nip05} to your Private contacts so you can pay faster",
|
||||||
@@ -522,7 +524,8 @@
|
|||||||
"insufficientFunds": "There is not enough balance in %{currency} to pay the invoice amount and expected fees: %{amount} %{currency}",
|
"insufficientFunds": "There is not enough balance in %{currency} to pay the invoice amount and expected fees: %{amount} %{currency}",
|
||||||
"LUD18unsupported": "Minibits does not yet support entering of payer identity data (LUD18).",
|
"LUD18unsupported": "Minibits does not yet support entering of payer identity data (LUD18).",
|
||||||
"noMintWithBalance": "There is no mint with %{unit} balance from which payment can be made.",
|
"noMintWithBalance": "There is no mint with %{unit} balance from which payment can be made.",
|
||||||
"selectMintFrom": "Select mint balance to transfer from"
|
"selectMintFrom": "Select mint balance to transfer from",
|
||||||
|
"requestInvoice": "Continue"
|
||||||
},
|
},
|
||||||
"updateScreen": {
|
"updateScreen": {
|
||||||
"gotoPlayStore": "Visit Play Store",
|
"gotoPlayStore": "Visit Play Store",
|
||||||
|
|||||||
@@ -43,7 +43,8 @@
|
|||||||
"walletLabel": "Peňaženka"
|
"walletLabel": "Peňaženka"
|
||||||
},
|
},
|
||||||
"transferScreen": {
|
"transferScreen": {
|
||||||
"noMintWithBalance": "Neexistuje žiaden mint so zostatkom %{unit} z ktorého je možné uskutočniť platbu."
|
"noMintWithBalance": "Neexistuje žiaden mint so zostatkom %{unit} z ktorého je možné uskutočniť platbu.",
|
||||||
|
"requestInvoice": "Pokračovať"
|
||||||
},
|
},
|
||||||
"walletScreen": {
|
"walletScreen": {
|
||||||
"sendEcash": "Poslať Ecash",
|
"sendEcash": "Poslať Ecash",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {Switch, TextStyle, View, ViewStyle} from 'react-native'
|
|||||||
import {colors, spacing, useThemeColor} from '../theme'
|
import {colors, spacing, useThemeColor} from '../theme'
|
||||||
import {SettingsStackScreenProps} from '../navigation'
|
import {SettingsStackScreenProps} from '../navigation'
|
||||||
import {
|
import {
|
||||||
Icon,
|
|
||||||
ListItem,
|
ListItem,
|
||||||
Screen,
|
Screen,
|
||||||
Text,
|
Text,
|
||||||
|
|||||||
@@ -466,7 +466,7 @@ export const OwnName = observer(function (props: {navigation: any, pubkey: strin
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isLoading && <Loading />}
|
{isLoading && <Loading />}
|
||||||
</View>
|
</View>
|
||||||
<BottomModal
|
<BottomModal
|
||||||
isVisible={isResultModalVisible ? true : false}
|
isVisible={isResultModalVisible ? true : false}
|
||||||
|
|||||||
@@ -163,9 +163,8 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
|||||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||||
<Text
|
<Text
|
||||||
preset="heading"
|
preset="heading"
|
||||||
text={'Pay'}
|
tx="lightningPayScreen.payHeading"
|
||||||
style={{color: 'white'}}
|
style={{color: 'white'}}
|
||||||
// style={$tranAmount}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={$contentContainer}>
|
<View style={$contentContainer}>
|
||||||
@@ -300,11 +299,11 @@ const $buttonContainer: ViewStyle = {
|
|||||||
|
|
||||||
|
|
||||||
const $addressInput: TextStyle = {
|
const $addressInput: TextStyle = {
|
||||||
textAlignVertical: 'top' ,
|
textAlignVertical: 'top' ,
|
||||||
borderRadius: spacing.extraSmall,
|
borderRadius: spacing.extraSmall,
|
||||||
padding: spacing.extraSmall,
|
padding: spacing.extraSmall,
|
||||||
alignSelf: 'stretch',
|
alignSelf: 'stretch',
|
||||||
height: 120,
|
height: 120,
|
||||||
}
|
}
|
||||||
|
|
||||||
const $bottomContainer: ViewStyle = {
|
const $bottomContainer: ViewStyle = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { FC, useEffect, useMemo, useState } from 'react'
|
import React, { FC, useEffect, useMemo, useState } from 'react'
|
||||||
import { GetInfoResponse, SwapMethod } from '@cashu/cashu-ts'
|
import { GetInfoResponse as _GetInfoResponse, SwapMethod } from '@cashu/cashu-ts'
|
||||||
import { isObj } from '@cashu/cashu-ts/src/utils'
|
import { isObj } from '@cashu/cashu-ts/src/utils'
|
||||||
import Clipboard from '@react-native-clipboard/clipboard'
|
import Clipboard from '@react-native-clipboard/clipboard'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
@@ -34,6 +34,16 @@ import { useHeader } from '../utils/useHeader'
|
|||||||
import { CurrencySign } from './Wallet/CurrencySign'
|
import { CurrencySign } from './Wallet/CurrencySign'
|
||||||
import { SvgXml } from 'react-native-svg'
|
import { SvgXml } from 'react-native-svg'
|
||||||
|
|
||||||
|
// cashu-ts currently does not type NUT15 (multipath payments) correctly
|
||||||
|
// relevant issue: https://github.com/cashubtc/cashu-ts/issues/142
|
||||||
|
// nut-15 spec: https://github.com/cashubtc/nuts/blob/main/15.md
|
||||||
|
type NUT15Entry = { method: string, unit: string, mpp: boolean }
|
||||||
|
type GetInfoResponse = _GetInfoResponse & {
|
||||||
|
nuts: {
|
||||||
|
15: Array<NUT15Entry>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
|
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
|
||||||
UIManager.setLayoutAnimationEnabledExperimental(true)
|
UIManager.setLayoutAnimationEnabledExperimental(true)
|
||||||
@@ -177,6 +187,13 @@ function NutsCard(props: {info: GetInfoResponse}) {
|
|||||||
|
|
||||||
// detailed nuts are separated from simple ones if we want to show more info abt them in the future
|
// detailed nuts are separated from simple ones if we want to show more info abt them in the future
|
||||||
for (const [nut, info] of Object.entries(props.info.nuts)) {
|
for (const [nut, info] of Object.entries(props.info.nuts)) {
|
||||||
|
if (nut === '15' && (info as NUT15Entry[]).length > 0) {
|
||||||
|
// see https://github.com/cashubtc/nuts/blob/main/15.md - multipath payments
|
||||||
|
// in the future, it might be nice to show for which currencies are multipath payments supported
|
||||||
|
// for example by extending NutItem
|
||||||
|
nutsSimple.push(['15', (info as NUT15Entry[])[0]?.mpp ?? false])
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if ('disabled' in info && info.disabled === false) { // detailed
|
if ('disabled' in info && info.disabled === false) { // detailed
|
||||||
supportedNutsDetailed.push([nut, info])
|
supportedNutsDetailed.push([nut, info])
|
||||||
} else if ('supported' in info) { // simple
|
} else if ('supported' in info) { // simple
|
||||||
@@ -357,12 +374,12 @@ export const MintInfoScreen: FC<SettingsStackScreenProps<'MintInfo'>> = observer
|
|||||||
const mint = mintsStore.findByUrl(route.params.mintUrl)
|
const mint = mintsStore.findByUrl(route.params.mintUrl)
|
||||||
|
|
||||||
if (mint) {
|
if (mint) {
|
||||||
const info: GetInfoResponse = await MintClient.getMintInfo(mint.mintUrl)
|
const info: _GetInfoResponse = await MintClient.getMintInfo(mint.mintUrl)
|
||||||
mint.setStatus(MintStatus.ONLINE)
|
mint.setStatus(MintStatus.ONLINE)
|
||||||
if(info.name && info.name !== mint.shortname) {
|
if(info.name && info.name !== mint.shortname) {
|
||||||
await mint.setShortname()
|
await mint.setShortname()
|
||||||
}
|
}
|
||||||
setMintInfo(info)
|
setMintInfo(info as GetInfoResponse)
|
||||||
setMint(mint)
|
setMint(mint)
|
||||||
} else {
|
} else {
|
||||||
throw new AppError(Err.VALIDATION_ERROR, 'Could not find mint', { mintUrl: route.params.mintUrl })
|
throw new AppError(Err.VALIDATION_ERROR, 'Could not find mint', { mintUrl: route.params.mintUrl })
|
||||||
|
|||||||
@@ -88,13 +88,6 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
|||||||
const addMint = async function () {
|
const addMint = async function () {
|
||||||
setIsAddMintVisible(false)
|
setIsAddMintVisible(false)
|
||||||
|
|
||||||
if(mintUrl.includes('.onion')) {
|
|
||||||
if(!userSettingsStore.isTorDaemonOn) {
|
|
||||||
setInfo('Tor support has been discontinued in version v0.1.7-beta. Minibits needs help to get Tor support back. Visit our Github for more info.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mintsStore.alreadyExists(mintUrl)) {
|
if (mintsStore.alreadyExists(mintUrl)) {
|
||||||
const msg = translate('mintsScreen.mintExists')
|
const msg = translate('mintsScreen.mintExists')
|
||||||
log.trace(msg)
|
log.trace(msg)
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ export const IncomingRequests = observer(function (props: {
|
|||||||
style={$card}
|
style={$card}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isLoading && <Loading />}
|
|
||||||
</View>
|
</View>
|
||||||
|
{isLoading && <Loading />}
|
||||||
{error && <ErrorModal error={error} />}
|
{error && <ErrorModal error={error} />}
|
||||||
{info && <InfoModal message={info} />}
|
{info && <InfoModal message={info} />}
|
||||||
</Screen>
|
</Screen>
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ const $unselected: ViewStyle = {
|
|||||||
|
|
||||||
const $contentContainer: TextStyle = {
|
const $contentContainer: TextStyle = {
|
||||||
padding: spacing.small,
|
padding: spacing.small,
|
||||||
|
minHeight: spacing.huge,
|
||||||
}
|
}
|
||||||
|
|
||||||
const $picturesContainer: TextStyle = {
|
const $picturesContainer: TextStyle = {
|
||||||
|
|||||||
+11
-37
@@ -64,6 +64,7 @@ import { round, toNumber } from '../utils/number'
|
|||||||
import { QRCodeBlock } from './Wallet/QRCode'
|
import { QRCodeBlock } from './Wallet/QRCode'
|
||||||
import numbro from 'numbro'
|
import numbro from 'numbro'
|
||||||
import { TranItem } from './TranDetailScreen'
|
import { TranItem } from './TranDetailScreen'
|
||||||
|
import { MemoInputCard } from '../components/MemoInputCard'
|
||||||
|
|
||||||
|
|
||||||
if (Platform.OS === 'android' &&
|
if (Platform.OS === 'android' &&
|
||||||
@@ -610,43 +611,16 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={$contentContainer}>
|
<View style={$contentContainer}>
|
||||||
{!encodedTokenToSend && (
|
{!encodedTokenToSend && (
|
||||||
<Card
|
<MemoInputCard
|
||||||
style={$memoCard}
|
memo={memo}
|
||||||
ContentComponent={
|
ref={memoInputRef}
|
||||||
<View style={$memoContainer}>
|
setMemo={setMemo}
|
||||||
<TextInput
|
disabled={transactionStatus === TransactionStatus.PENDING}
|
||||||
ref={memoInputRef}
|
onMemoDone={onMemoDone}
|
||||||
onChangeText={memo => setMemo(memo)}
|
onMemoEndEditing={onMemoEndEditing}
|
||||||
onEndEditing={onMemoEndEditing}
|
/>
|
||||||
value={`${memo}`}
|
)}
|
||||||
style={$memoInput}
|
|
||||||
maxLength={200}
|
|
||||||
keyboardType="default"
|
|
||||||
selectTextOnFocus={true}
|
|
||||||
placeholder="Memo for recipient"
|
|
||||||
editable={
|
|
||||||
transactionStatus === TransactionStatus.PENDING
|
|
||||||
? false
|
|
||||||
: true
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
preset="secondary"
|
|
||||||
style={$memoButton}
|
|
||||||
text="Done"
|
|
||||||
onPress={onMemoDone}
|
|
||||||
disabled={
|
|
||||||
transactionStatus === TransactionStatus.PENDING
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isMintSelectorVisible && (
|
{isMintSelectorVisible && (
|
||||||
<MintBalanceSelector
|
<MintBalanceSelector
|
||||||
mintBalances={availableMintBalances}
|
mintBalances={availableMintBalances}
|
||||||
|
|||||||
@@ -349,8 +349,8 @@ export const TranHistoryScreen: FC<TransactionsStackScreenProps<'TranHistory'>>
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
style={{maxHeight: spacing.screenHeight * 0.66}}
|
style={{maxHeight: spacing.screenHeight * 0.66}}
|
||||||
/>
|
/>
|
||||||
{isLoading && <Loading />}
|
{isLoading && <Loading shiftedUp={true} />}
|
||||||
</View>
|
</View>
|
||||||
<BottomModal
|
<BottomModal
|
||||||
isVisible={isDeleteModalVisible ? true : false}
|
isVisible={isDeleteModalVisible ? true : false}
|
||||||
|
|||||||
@@ -39,29 +39,29 @@ export const TransactionListItem = observer(function (
|
|||||||
case TransactionType.RECEIVE || TransactionType.RECEIVE_OFFLINE:
|
case TransactionType.RECEIVE || TransactionType.RECEIVE_OFFLINE:
|
||||||
if (tx.sentFrom) {
|
if (tx.sentFrom) {
|
||||||
if (!tx.memo || tx.memo.includes('Sent from Minibits')) {
|
if (!tx.memo || tx.memo.includes('Sent from Minibits')) {
|
||||||
return translate('transactionCommon.from', {sender: tx.sentFrom})
|
return translate('transactionCommon.from', {sender: tx.sentFrom}).slice(0, 30)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return tx.memo ? tx.memo : translate('transactionCommon.youReceived')
|
return tx.memo ? tx.memo : translate('transactionCommon.youReceived')
|
||||||
}
|
}
|
||||||
case TransactionType.SEND:
|
case TransactionType.SEND:
|
||||||
return tx.memo
|
return (tx.memo
|
||||||
? tx.memo
|
? tx.memo
|
||||||
: tx.sentTo
|
: tx.sentTo
|
||||||
? translate('transactionCommon.sentTo', {receiver: tx.sentTo})
|
? translate('transactionCommon.sentTo', {receiver: tx.sentTo})
|
||||||
: translate('transactionCommon.youSent')
|
: translate('transactionCommon.youSent')).slice(0, 30)
|
||||||
case TransactionType.TOPUP:
|
case TransactionType.TOPUP:
|
||||||
return tx.memo
|
return (tx.memo
|
||||||
? tx.memo
|
? tx.memo
|
||||||
: tx.sentFrom
|
: tx.sentFrom
|
||||||
? translate('transactionCommon.receivedFrom', {sender: tx.sentFrom})
|
? translate('transactionCommon.receivedFrom', {sender: tx.sentFrom})
|
||||||
: translate('transactionCommon.youReceived')
|
: translate('transactionCommon.youReceived')).slice(0, 30)
|
||||||
case TransactionType.TRANSFER:
|
case TransactionType.TRANSFER:
|
||||||
return tx.memo
|
return (tx.memo
|
||||||
? tx.memo
|
? tx.memo
|
||||||
: tx.sentTo
|
: tx.sentTo
|
||||||
? translate('transactionCommon.paidTo', {receiver: tx.sentTo})
|
? translate('transactionCommon.paidTo', {receiver: tx.sentTo})
|
||||||
: translate('transactionCommon.youPaid')
|
: translate('transactionCommon.youPaid')).slice(0, 30)
|
||||||
default:
|
default:
|
||||||
return translate('transactionCommon.unknown')
|
return translate('transactionCommon.unknown')
|
||||||
}
|
}
|
||||||
|
|||||||
+344
-258
@@ -9,6 +9,7 @@ import {
|
|||||||
ViewStyle,
|
ViewStyle,
|
||||||
FlatList,
|
FlatList,
|
||||||
TextInput,
|
TextInput,
|
||||||
|
Keyboard,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {spacing, useThemeColor, colors, typography} from '../theme'
|
import {spacing, useThemeColor, colors, typography} from '../theme'
|
||||||
import {WalletStackScreenProps} from '../navigation'
|
import {WalletStackScreenProps} from '../navigation'
|
||||||
@@ -51,6 +52,7 @@ import numbro from 'numbro'
|
|||||||
import { TranItem } from './TranDetailScreen'
|
import { TranItem } from './TranDetailScreen'
|
||||||
import useIsInternetReachable from '../utils/useIsInternetReachable'
|
import useIsInternetReachable from '../utils/useIsInternetReachable'
|
||||||
import { translate } from '../i18n'
|
import { translate } from '../i18n'
|
||||||
|
import { MemoInputCard } from '../components/MemoInputCard'
|
||||||
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -64,6 +66,8 @@ export const TransferScreen: FC<WalletStackScreenProps<'Transfer'>> = observer(
|
|||||||
function TransferScreen({route, navigation}) {
|
function TransferScreen({route, navigation}) {
|
||||||
|
|
||||||
const amountInputRef = useRef<TextInput>(null)
|
const amountInputRef = useRef<TextInput>(null)
|
||||||
|
const lnurlCommentInputRef = useRef<TextInput>(null)
|
||||||
|
|
||||||
const {proofsStore, mintsStore, paymentRequestsStore, transactionsStore} = useStores()
|
const {proofsStore, mintsStore, paymentRequestsStore, transactionsStore} = useStores()
|
||||||
|
|
||||||
const isInternetReachable = useIsInternetReachable()
|
const isInternetReachable = useIsInternetReachable()
|
||||||
@@ -80,6 +84,8 @@ export const TransferScreen: FC<WalletStackScreenProps<'Transfer'>> = observer(
|
|||||||
const [finalFee, setFinalFee] = useState<number>(0)
|
const [finalFee, setFinalFee] = useState<number>(0)
|
||||||
const [memo, setMemo] = useState('')
|
const [memo, setMemo] = useState('')
|
||||||
const [lnurlDescription, setLnurlDescription] = useState('')
|
const [lnurlDescription, setLnurlDescription] = useState('')
|
||||||
|
const [lnurlPayCommentAllowed, setLnurlPayCommentAllowed] = useState(0)
|
||||||
|
const [lnurlPayComment, setLnurlPayComment] = useState('')
|
||||||
const [availableMintBalances, setAvailableMintBalances] = useState<MintBalance[]>([])
|
const [availableMintBalances, setAvailableMintBalances] = useState<MintBalance[]>([])
|
||||||
const [mintBalanceToTransferFrom, setMintBalanceToTransferFrom] = useState<MintBalance | undefined>()
|
const [mintBalanceToTransferFrom, setMintBalanceToTransferFrom] = useState<MintBalance | undefined>()
|
||||||
const [transactionStatus, setTransactionStatus] = useState<TransactionStatus | undefined>()
|
const [transactionStatus, setTransactionStatus] = useState<TransactionStatus | undefined>()
|
||||||
@@ -94,23 +100,23 @@ export const TransferScreen: FC<WalletStackScreenProps<'Transfer'>> = observer(
|
|||||||
const [resultModalInfo, setResultModalInfo] = useState<{status: TransactionStatus; title?: string, message: string} | undefined>()
|
const [resultModalInfo, setResultModalInfo] = useState<{status: TransactionStatus; title?: string, message: string} | undefined>()
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const focus = () => {
|
const focus = () => {
|
||||||
if(route.params?.paymentOption === SendOption.LNURL_PAY) {
|
if (route.params?.paymentOption === SendOption.LNURL_PAY) {
|
||||||
amountInputRef && amountInputRef.current
|
amountInputRef && amountInputRef.current
|
||||||
? amountInputRef.current.focus()
|
? amountInputRef.current.focus()
|
||||||
: false
|
: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const timer = setTimeout(() => focus(), 100)
|
const timer = setTimeout(() => focus(), 100)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: fix indentation in this component
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const setUnitAndMint = () => {
|
const setUnitAndMint = () => {
|
||||||
@@ -203,9 +209,12 @@ useFocusEffect(
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ('commentAllowed' in lnurlParams && lnurlParams.commentAllowed > 0) {
|
||||||
|
setLnurlPayCommentAllowed(lnurlParams.commentAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
if(desc) {
|
if(desc) {
|
||||||
setLnurlDescription(desc)
|
setLnurlDescription(desc)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(address) {
|
if(address) {
|
||||||
@@ -272,7 +281,13 @@ useEffect(() => {
|
|||||||
try {
|
try {
|
||||||
log.trace('[getEstimatedFee]', {mintBalanceToTransferFrom})
|
log.trace('[getEstimatedFee]', {mintBalanceToTransferFrom})
|
||||||
if (!mintBalanceToTransferFrom || !mintBalanceToTransferFrom.balances[unit] || !encodedInvoice) {
|
if (!mintBalanceToTransferFrom || !mintBalanceToTransferFrom.balances[unit] || !encodedInvoice) {
|
||||||
log.trace('[getEstimatedFee]', 'Not ready... exiting')
|
log.trace(
|
||||||
|
'[getEstimatedFee]',
|
||||||
|
'Not ready... exiting',
|
||||||
|
mintBalanceToTransferFrom,
|
||||||
|
mintBalanceToTransferFrom?.balances[unit],
|
||||||
|
encodedInvoice,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,6 +427,8 @@ const resetState = function () {
|
|||||||
setIsTransferTaskSentToQueue(false)
|
setIsTransferTaskSentToQueue(false)
|
||||||
setIsResultModalVisible(false)
|
setIsResultModalVisible(false)
|
||||||
setResultModalInfo(undefined)
|
setResultModalInfo(undefined)
|
||||||
|
setLnurlPayCommentAllowed(0)
|
||||||
|
setLnurlPayComment('')
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleResultModal = () => setIsResultModalVisible(previousState => !previousState)
|
const toggleResultModal = () => setIsResultModalVisible(previousState => !previousState)
|
||||||
@@ -421,10 +438,9 @@ const onMintBalanceSelect = function (balance: MintBalance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Amount is editable only in case of LNURL Pay, while invoice is not yet retrieved
|
// Amount is editable only in case of LNURL Pay, while invoice is not yet retrieved
|
||||||
const onAmountEndEditing = async function () {
|
const onRequestLnurlInvoice = async function () { // onAmountEndEditing
|
||||||
try {
|
try {
|
||||||
const precision = getCurrency(unit).precision
|
const precision = getCurrency(unit).precision
|
||||||
const mantissa = getCurrency(unit).mantissa
|
|
||||||
const amount = round(toNumber(amountToTransfer) * precision, 0)
|
const amount = round(toNumber(amountToTransfer) * precision, 0)
|
||||||
|
|
||||||
if (!amount || amount === 0) {
|
if (!amount || amount === 0) {
|
||||||
@@ -444,7 +460,7 @@ const onAmountEndEditing = async function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lnurlPayParams.maxSendable && amount > lnurlPayParams.maxSendable / 1000 ) {
|
if (lnurlPayParams.maxSendable && amount > lnurlPayParams.maxSendable / 1000) {
|
||||||
infoMessage(translate("payCommon.maximumPay", {
|
infoMessage(translate("payCommon.maximumPay", {
|
||||||
amount: roundDown(lnurlPayParams.maxSendable / 1000, 0),
|
amount: roundDown(lnurlPayParams.maxSendable / 1000, 0),
|
||||||
currency: CurrencyCode.SATS
|
currency: CurrencyCode.SATS
|
||||||
@@ -453,23 +469,32 @@ const onAmountEndEditing = async function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lnurlPayParams.payerData) {
|
if (lnurlPayParams.payerData) {
|
||||||
infoMessage(translate("transferScreen.LUD18unsupported"))
|
infoMessage(translate("transferScreen.LUD18unsupported"))
|
||||||
}
|
}
|
||||||
|
|
||||||
setAmountToTransfer(`${numbro(amountToTransfer).format({thousandSeparated: true, mantissa: getCurrency(unit).mantissa})}`)
|
setAmountToTransfer(`${numbro(amountToTransfer).format({thousandSeparated: true, mantissa: getCurrency(unit).mantissa})}`)
|
||||||
|
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const encoded = await LnurlClient.getInvoice(lnurlPayParams, amount * 1000)
|
const encoded = await LnurlClient.getInvoice(lnurlPayParams, amount * 1000, lnurlPayCommentAllowed > 0 ? lnurlPayComment : void 0)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
|
||||||
if (encoded) return onEncodedInvoice(encoded);
|
if (encoded) return onEncodedInvoice(encoded)
|
||||||
|
|
||||||
throw new AppError(Err.NOTFOUND_ERROR, `Could not get lightning invoice from ${lnurlPayParams.domain}`)
|
throw new AppError(Err.NOTFOUND_ERROR, `Could not get lightning invoice from ${lnurlPayParams.domain}`)
|
||||||
} catch (e: any) { handleError(e) }
|
} catch (e: any) { handleError(e) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ensureCommentNotTooLong = async function () {
|
||||||
|
Keyboard.dismiss()
|
||||||
|
if (!lnurlPayCommentAllowed || lnurlPayComment.trim().length === 0) return;
|
||||||
|
if (lnurlPayComment.trim().length > lnurlPayCommentAllowed) {
|
||||||
|
setLnurlPayComment(lnurlPayComment.slice(0, lnurlPayCommentAllowed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const onEncodedInvoice = async function (encoded: string, paymentRequestDesc: string = '') {
|
const onEncodedInvoice = async function (encoded: string, paymentRequestDesc: string = '') {
|
||||||
|
log.trace("onEncodedInvoice")
|
||||||
try {
|
try {
|
||||||
navigation.setParams({encodedInvoice: undefined})
|
navigation.setParams({encodedInvoice: undefined})
|
||||||
navigation.setParams({paymentRequest: undefined})
|
navigation.setParams({paymentRequest: undefined})
|
||||||
@@ -480,9 +505,6 @@ const onEncodedInvoice = async function (encoded: string, paymentRequestDesc: st
|
|||||||
const {amount, expiry, description, timestamp} = LightningUtils.getInvoiceData(invoice)
|
const {amount, expiry, description, timestamp} = LightningUtils.getInvoiceData(invoice)
|
||||||
const expiresAt = addSeconds(new Date(timestamp as number * 1000), expiry as number)
|
const expiresAt = addSeconds(new Date(timestamp as number * 1000), expiry as number)
|
||||||
|
|
||||||
// log.trace('Decoded invoice', invoice, 'onEncodedInvoice')
|
|
||||||
log.trace('[onEncodedInvoice] Invoice data', {amount, expiresAt, description})
|
|
||||||
|
|
||||||
if (!amount || amount === 0) {
|
if (!amount || amount === 0) {
|
||||||
infoMessage(translate('payCommon.amountZeroOrNegative'))
|
infoMessage(translate('payCommon.amountZeroOrNegative'))
|
||||||
return;
|
return;
|
||||||
@@ -494,10 +516,16 @@ const onEncodedInvoice = async function (encoded: string, paymentRequestDesc: st
|
|||||||
setInvoice(invoice)
|
setInvoice(invoice)
|
||||||
setInvoiceExpiry(expiresAt)
|
setInvoiceExpiry(expiresAt)
|
||||||
|
|
||||||
|
if(description) {
|
||||||
|
setMemo(description)
|
||||||
|
}
|
||||||
|
|
||||||
if (paymentRequestDesc) {
|
if (paymentRequestDesc) {
|
||||||
setMemo(paymentRequestDesc)
|
setMemo(paymentRequestDesc)
|
||||||
} else if(description) {
|
}
|
||||||
setMemo(description)
|
|
||||||
|
if (lnurlPayComment) {
|
||||||
|
setMemo(lnurlPayComment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to retrieve the quote first to know how much is needed to settle invoice in selected currency unit
|
// We need to retrieve the quote first to know how much is needed to settle invoice in selected currency unit
|
||||||
@@ -522,36 +550,36 @@ const onEncodedInvoice = async function (encoded: string, paymentRequestDesc: st
|
|||||||
}
|
}
|
||||||
|
|
||||||
const transfer = async function () {
|
const transfer = async function () {
|
||||||
try {
|
try {
|
||||||
if(!meltQuote) {
|
if(!meltQuote) {
|
||||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing quote to initiate transfer transaction')
|
throw new AppError(Err.VALIDATION_ERROR, 'Missing quote to initiate transfer transaction')
|
||||||
}
|
|
||||||
|
|
||||||
if (!mintBalanceToTransferFrom) {
|
|
||||||
setInfo(translate("transferScreen.selectMintFrom"))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(true)
|
|
||||||
setIsTransferTaskSentToQueue(true)
|
|
||||||
|
|
||||||
|
|
||||||
log.trace('[transfer]', {isInvoiceDonation})
|
|
||||||
|
|
||||||
const amountToTransferInt = round(toNumber(amountToTransfer) * getCurrency(unit).precision, 0)
|
|
||||||
|
|
||||||
WalletTask.transfer(
|
|
||||||
mintBalanceToTransferFrom,
|
|
||||||
amountToTransferInt,
|
|
||||||
unit,
|
|
||||||
meltQuote,
|
|
||||||
memo,
|
|
||||||
invoiceExpiry as Date,
|
|
||||||
encodedInvoice,
|
|
||||||
)
|
|
||||||
} catch (e: any) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mintBalanceToTransferFrom) {
|
||||||
|
setInfo(translate("transferScreen.selectMintFrom"))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true)
|
||||||
|
setIsTransferTaskSentToQueue(true)
|
||||||
|
|
||||||
|
|
||||||
|
log.trace('[transfer]', {isInvoiceDonation})
|
||||||
|
|
||||||
|
const amountToTransferInt = round(toNumber(amountToTransfer) * getCurrency(unit).precision, 0)
|
||||||
|
|
||||||
|
WalletTask.transfer(
|
||||||
|
mintBalanceToTransferFrom,
|
||||||
|
amountToTransferInt,
|
||||||
|
unit,
|
||||||
|
meltQuote,
|
||||||
|
memo,
|
||||||
|
invoiceExpiry as Date,
|
||||||
|
encodedInvoice,
|
||||||
|
)
|
||||||
|
} catch (e: any) {
|
||||||
|
handleError(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -567,218 +595,268 @@ const handleError = function(e: AppError): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const headerBg = useThemeColor('header')
|
const headerBg = useThemeColor('header')
|
||||||
const feeColor = colors.palette.primary200
|
|
||||||
const iconColor = useThemeColor('textDim')
|
const iconColor = useThemeColor('textDim')
|
||||||
const satsColor = colors.palette.primary200
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen preset="fixed" contentContainerStyle={$screen}>
|
<Screen preset="fixed" contentContainerStyle={$screen}>
|
||||||
<MintHeader
|
<MintHeader
|
||||||
mint={mintBalanceToTransferFrom ? mintsStore.findByUrl(mintBalanceToTransferFrom?.mintUrl) : undefined}
|
mint={
|
||||||
|
mintBalanceToTransferFrom
|
||||||
|
? mintsStore.findByUrl(mintBalanceToTransferFrom?.mintUrl)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
unit={unit}
|
||||||
|
navigation={navigation}
|
||||||
|
/>
|
||||||
|
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||||
|
<View style={$amountContainer}>
|
||||||
|
<TextInput
|
||||||
|
ref={amountInputRef}
|
||||||
|
onChangeText={amount => setAmountToTransfer(amount)}
|
||||||
|
// onEndEditing={onAmountEndEditing}
|
||||||
|
value={amountToTransfer}
|
||||||
|
style={$amountInput}
|
||||||
|
maxLength={9}
|
||||||
|
keyboardType="numeric"
|
||||||
|
selectTextOnFocus={true}
|
||||||
|
editable={encodedInvoice ? false : true}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{encodedInvoice && (meltQuote?.fee_reserve || finalFee) ? (
|
||||||
|
<FeeBadge
|
||||||
|
currencyCode={getCurrency(unit).code}
|
||||||
|
estimatedFee={meltQuote?.fee_reserve || 0}
|
||||||
|
finalFee={finalFee}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text
|
||||||
|
size="sm"
|
||||||
|
tx="payCommon.amountToPayLabel"
|
||||||
|
style={{color: 'white', textAlign: 'center'}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={$contentContainer}>
|
||||||
|
{transactionStatus !== TransactionStatus.COMPLETED && (
|
||||||
|
<Card
|
||||||
|
style={[$card, {minHeight: 50}]}
|
||||||
|
ContentComponent={
|
||||||
|
<>
|
||||||
|
<ListItem
|
||||||
|
text={
|
||||||
|
lnurlPayParams?.address ||
|
||||||
|
memo ||
|
||||||
|
lnurlPayParams?.domain ||
|
||||||
|
translate('common.noDescPlaceholder')
|
||||||
|
}
|
||||||
|
subText={lnurlDescription}
|
||||||
|
LeftComponent={
|
||||||
|
<Icon
|
||||||
|
containerStyle={$iconContainer}
|
||||||
|
icon="faInfoCircle"
|
||||||
|
size={spacing.medium}
|
||||||
|
color={iconColor}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
style={$item}
|
||||||
|
/>
|
||||||
|
{lnurlPayComment && encodedInvoice && (
|
||||||
|
<ListItem
|
||||||
|
text={lnurlPayComment}
|
||||||
|
topSeparator={true}
|
||||||
|
LeftComponent={
|
||||||
|
<Icon
|
||||||
|
containerStyle={$iconContainer}
|
||||||
|
icon="faPencil"
|
||||||
|
size={spacing.medium}
|
||||||
|
color={iconColor}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
style={$item}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!encodedInvoice && transactionStatus !== TransactionStatus.COMPLETED && lnurlPayCommentAllowed > 0 && (
|
||||||
|
<MemoInputCard
|
||||||
|
memo={lnurlPayComment}
|
||||||
|
setMemo={setLnurlPayComment}
|
||||||
|
ref={lnurlCommentInputRef}
|
||||||
|
onMemoDone={ensureCommentNotTooLong}
|
||||||
|
disabled={encodedInvoice ? true : false}
|
||||||
|
maxLength={lnurlPayCommentAllowed}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!encodedInvoice && transactionStatus !== TransactionStatus.COMPLETED && (
|
||||||
|
<View style={$bottomContainer}>
|
||||||
|
<View style={$buttonContainer}>
|
||||||
|
<Button
|
||||||
|
tx="transferScreen.requestInvoice"
|
||||||
|
onPress={onRequestLnurlInvoice}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{availableMintBalances.length > 0 &&
|
||||||
|
transactionStatus !== TransactionStatus.COMPLETED && (
|
||||||
|
<MintBalanceSelector
|
||||||
|
mintBalances={availableMintBalances}
|
||||||
|
selectedMintBalance={mintBalanceToTransferFrom}
|
||||||
unit={unit}
|
unit={unit}
|
||||||
navigation={navigation}
|
title={translate('payCommon.payFrom')}
|
||||||
|
confirmTitle={translate('payCommon.payNow')}
|
||||||
|
onMintBalanceSelect={onMintBalanceSelect}
|
||||||
|
onCancel={onClose}
|
||||||
|
onMintBalanceConfirm={transfer}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{transaction && transactionStatus === TransactionStatus.COMPLETED && (
|
||||||
|
<Card
|
||||||
|
style={{padding: spacing.medium}}
|
||||||
|
ContentComponent={
|
||||||
|
<>
|
||||||
|
<TranItem
|
||||||
|
label="tranDetailScreen.trasferredTo"
|
||||||
|
isFirst={true}
|
||||||
|
value={
|
||||||
|
mintsStore.findByUrl(transaction.mint)
|
||||||
|
?.shortname as string
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{transaction?.memo && (
|
||||||
|
<TranItem
|
||||||
|
label="tranDetailScreen.memoFromInvoice"
|
||||||
|
value={transaction.memo as string}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<TranItem
|
||||||
|
label="transactionCommon.feePaid"
|
||||||
|
value={transaction.fee || 0}
|
||||||
|
unit={unit}
|
||||||
|
isCurrency={true}
|
||||||
|
/>
|
||||||
|
<TranItem
|
||||||
|
label="tranDetailScreen.status"
|
||||||
|
value={transaction.status as string}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
)}
|
||||||
<View style={$amountContainer}>
|
{transactionStatus === TransactionStatus.COMPLETED && (
|
||||||
<TextInput
|
<View style={$bottomContainer}>
|
||||||
ref={amountInputRef}
|
<View style={$buttonContainer}>
|
||||||
onChangeText={amount => setAmountToTransfer(amount)}
|
<Button
|
||||||
onEndEditing={onAmountEndEditing}
|
preset="secondary"
|
||||||
value={amountToTransfer}
|
tx={'common.close'}
|
||||||
style={$amountInput}
|
onPress={onClose}
|
||||||
maxLength={9}
|
/>
|
||||||
keyboardType="numeric"
|
</View>
|
||||||
selectTextOnFocus={true}
|
|
||||||
editable={
|
|
||||||
encodedInvoice ? false : true
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{encodedInvoice && (meltQuote?.fee_reserve || finalFee) ? (
|
|
||||||
<FeeBadge
|
|
||||||
currencyCode={getCurrency(unit).code}
|
|
||||||
estimatedFee={meltQuote?.fee_reserve || 0}
|
|
||||||
finalFee={finalFee}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Text
|
|
||||||
size='sm'
|
|
||||||
tx="payCommon.amountToPayLabel"
|
|
||||||
style={{color: 'white', textAlign: 'center'}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={$contentContainer}>
|
)}
|
||||||
{transactionStatus !== TransactionStatus.COMPLETED && (
|
{isLoading && <Loading />}
|
||||||
<Card
|
{error && <ErrorModal error={error} />}
|
||||||
style={[$card, {minHeight: 50}]}
|
{info && <InfoModal message={info} />}
|
||||||
ContentComponent={
|
</View>
|
||||||
<ListItem
|
<BottomModal
|
||||||
text={lnurlPayParams?.address || memo || lnurlPayParams?.domain || translate("common.noDescPlaceholder")}
|
isVisible={isResultModalVisible}
|
||||||
subText={lnurlDescription}
|
ContentComponent={
|
||||||
LeftComponent={
|
<>
|
||||||
<Icon
|
{resultModalInfo &&
|
||||||
containerStyle={$iconContainer}
|
transactionStatus === TransactionStatus.COMPLETED && (
|
||||||
icon="faInfoCircle"
|
<>
|
||||||
size={spacing.medium}
|
<ResultModalInfo
|
||||||
color={iconColor}
|
icon="faCheckCircle"
|
||||||
/>
|
iconColor={colors.palette.success200}
|
||||||
}
|
title={translate('payCommon.completed')}
|
||||||
style={$item}
|
message={resultModalInfo?.message}
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
<View style={$buttonContainer}>
|
||||||
{availableMintBalances.length > 0 &&
|
<Button
|
||||||
transactionStatus !== TransactionStatus.COMPLETED && (
|
preset="secondary"
|
||||||
<MintBalanceSelector
|
tx={'common.close'}
|
||||||
mintBalances={availableMintBalances}
|
onPress={() => {
|
||||||
selectedMintBalance={mintBalanceToTransferFrom}
|
if (isInvoiceDonation) {
|
||||||
unit={unit}
|
navigation.navigate('ContactsNavigator', {
|
||||||
title={translate("payCommon.payFrom")}
|
screen: 'Contacts',
|
||||||
confirmTitle={translate("payCommon.payNow")}
|
params: {},
|
||||||
onMintBalanceSelect={onMintBalanceSelect}
|
})
|
||||||
onCancel={onClose}
|
} else {
|
||||||
onMintBalanceConfirm={transfer}
|
navigation.navigate('Wallet', {})
|
||||||
/>
|
}
|
||||||
)}
|
}}
|
||||||
{transaction && transactionStatus === TransactionStatus.COMPLETED && (
|
/>
|
||||||
<Card
|
|
||||||
style={{padding: spacing.medium}}
|
|
||||||
ContentComponent={
|
|
||||||
<>
|
|
||||||
<TranItem
|
|
||||||
label="tranDetailScreen.trasferredTo"
|
|
||||||
isFirst={true}
|
|
||||||
value={mintsStore.findByUrl(transaction.mint)?.shortname as string}
|
|
||||||
/>
|
|
||||||
{transaction?.memo && (
|
|
||||||
<TranItem
|
|
||||||
label="tranDetailScreen.memoFromInvoice"
|
|
||||||
value={transaction.memo as string}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<TranItem
|
|
||||||
label="transactionCommon.feePaid"
|
|
||||||
value={transaction.fee || 0}
|
|
||||||
unit={unit}
|
|
||||||
isCurrency={true}
|
|
||||||
/>
|
|
||||||
<TranItem
|
|
||||||
label="tranDetailScreen.status"
|
|
||||||
value={transaction.status as string}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{transactionStatus === TransactionStatus.COMPLETED && (
|
|
||||||
<View style={$bottomContainer}>
|
|
||||||
<View style={$buttonContainer}>
|
|
||||||
<Button
|
|
||||||
preset="secondary"
|
|
||||||
tx={'common.close'}
|
|
||||||
onPress={onClose}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{isLoading && <Loading />}
|
{resultModalInfo &&
|
||||||
{error && <ErrorModal error={error} />}
|
transactionStatus === TransactionStatus.REVERTED && (
|
||||||
{info && <InfoModal message={info} />}
|
<>
|
||||||
</View>
|
<ResultModalInfo
|
||||||
<BottomModal
|
icon="faRotate"
|
||||||
isVisible={isResultModalVisible}
|
iconColor={colors.palette.accent300}
|
||||||
ContentComponent={
|
title={translate('transactionCommon.reverted')}
|
||||||
<>
|
message={resultModalInfo?.message}
|
||||||
{resultModalInfo &&
|
/>
|
||||||
transactionStatus === TransactionStatus.COMPLETED && (
|
<View style={$buttonContainer}>
|
||||||
<>
|
<Button
|
||||||
<ResultModalInfo
|
preset="secondary"
|
||||||
icon="faCheckCircle"
|
tx={'common.close'}
|
||||||
iconColor={colors.palette.success200}
|
onPress={toggleResultModal}
|
||||||
title={translate('payCommon.completed')}
|
/>
|
||||||
message={resultModalInfo?.message}
|
</View>
|
||||||
/>
|
</>
|
||||||
<View style={$buttonContainer}>
|
)}
|
||||||
<Button
|
{resultModalInfo &&
|
||||||
preset="secondary"
|
transactionStatus === TransactionStatus.ERROR && (
|
||||||
tx={'common.close'}
|
<>
|
||||||
onPress={() => {
|
<ResultModalInfo
|
||||||
if(isInvoiceDonation) {
|
icon="faTriangleExclamation"
|
||||||
navigation.navigate('ContactsNavigator', {screen: 'Contacts', params: {}})
|
iconColor={colors.palette.angry500}
|
||||||
} else {
|
title={
|
||||||
navigation.navigate('Wallet', {})
|
resultModalInfo?.title || translate('payCommon.failed')
|
||||||
}
|
}
|
||||||
}}
|
message={resultModalInfo?.message}
|
||||||
/>
|
/>
|
||||||
</View>
|
<View style={$buttonContainer}>
|
||||||
</>
|
<Button
|
||||||
)}
|
preset="secondary"
|
||||||
{resultModalInfo &&
|
tx={'common.close'}
|
||||||
transactionStatus === TransactionStatus.REVERTED && (
|
onPress={toggleResultModal}
|
||||||
<>
|
/>
|
||||||
<ResultModalInfo
|
</View>
|
||||||
icon="faRotate"
|
</>
|
||||||
iconColor={colors.palette.accent300}
|
)}
|
||||||
title={translate('transactionCommon.reverted')}
|
{resultModalInfo &&
|
||||||
message={resultModalInfo?.message}
|
transactionStatus === TransactionStatus.PENDING && (
|
||||||
/>
|
<>
|
||||||
<View style={$buttonContainer}>
|
<ResultModalInfo
|
||||||
<Button
|
icon="faTriangleExclamation"
|
||||||
preset="secondary"
|
iconColor={colors.palette.iconYellow300}
|
||||||
tx={'common.close'}
|
title={translate('payCommon.isPending')}
|
||||||
onPress={toggleResultModal}
|
message={resultModalInfo?.message}
|
||||||
/>
|
/>
|
||||||
</View>
|
<View style={$buttonContainer}>
|
||||||
</>
|
<Button
|
||||||
)}
|
preset="secondary"
|
||||||
{resultModalInfo &&
|
tx={'common.close'}
|
||||||
transactionStatus === TransactionStatus.ERROR && (
|
onPress={() => {
|
||||||
<>
|
navigation.navigate('Wallet', {})
|
||||||
<ResultModalInfo
|
}}
|
||||||
icon="faTriangleExclamation"
|
/>
|
||||||
iconColor={colors.palette.angry500}
|
</View>
|
||||||
title={resultModalInfo?.title || translate('payCommon.failed')}
|
</>
|
||||||
message={resultModalInfo?.message}
|
)}
|
||||||
/>
|
</>
|
||||||
<View style={$buttonContainer}>
|
}
|
||||||
<Button
|
onBackButtonPress={toggleResultModal}
|
||||||
preset="secondary"
|
onBackdropPress={toggleResultModal}
|
||||||
tx={'common.close'}
|
/>
|
||||||
onPress={toggleResultModal}
|
</Screen>
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{resultModalInfo &&
|
|
||||||
transactionStatus === TransactionStatus.PENDING && (
|
|
||||||
<>
|
|
||||||
<ResultModalInfo
|
|
||||||
icon="faTriangleExclamation"
|
|
||||||
iconColor={colors.palette.iconYellow300}
|
|
||||||
title={translate('payCommon.isPending')}
|
|
||||||
message={resultModalInfo?.message}
|
|
||||||
/>
|
|
||||||
<View style={$buttonContainer}>
|
|
||||||
<Button
|
|
||||||
preset="secondary"
|
|
||||||
tx={'common.close'}
|
|
||||||
onPress={() => {
|
|
||||||
navigation.navigate('Wallet', {})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
|
|
||||||
}
|
|
||||||
onBackButtonPress={toggleResultModal}
|
|
||||||
onBackdropPress={toggleResultModal}
|
|
||||||
/>
|
|
||||||
</Screen>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -808,6 +886,14 @@ const $headerContainer: TextStyle = {
|
|||||||
color: 'white',
|
color: 'white',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const $commentInput: TextStyle = {
|
||||||
|
textAlignVertical: 'top' ,
|
||||||
|
borderRadius: spacing.extraSmall,
|
||||||
|
padding: spacing.extraSmall,
|
||||||
|
alignSelf: 'stretch',
|
||||||
|
height: 120,
|
||||||
|
}
|
||||||
|
|
||||||
const $contentContainer: TextStyle = {
|
const $contentContainer: TextStyle = {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
padding: spacing.extraSmall,
|
padding: spacing.extraSmall,
|
||||||
|
|||||||
@@ -298,12 +298,12 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
|||||||
|
|
||||||
log.trace('newMintUrl', newMintUrl)
|
log.trace('newMintUrl', newMintUrl)
|
||||||
|
|
||||||
if(newMintUrl.includes('.onion')) {
|
// if(newMintUrl.includes('.onion')) {
|
||||||
if(!userSettingsStore.isTorDaemonOn) {
|
// if(!userSettingsStore.isTorDaemonOn) {
|
||||||
setInfo('Please enable Tor daemon in Privacy settings before connecting to the mint using .onion address.')
|
// setInfo('Please enable Tor daemon in Privacy settings before connecting to the mint using .onion address.')
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (mintsStore.alreadyExists(newMintUrl)) {
|
if (mintsStore.alreadyExists(newMintUrl)) {
|
||||||
const msg = translate('walletScreen.mintExists')
|
const msg = translate('walletScreen.mintExists')
|
||||||
|
|||||||
@@ -32,6 +32,14 @@ let _seed: Uint8Array | undefined = undefined
|
|||||||
|
|
||||||
const { mintsStore } = rootStoreInstance
|
const { mintsStore } = rootStoreInstance
|
||||||
|
|
||||||
|
function isOnionMint(mintUrl: string) {
|
||||||
|
return new URL(mintUrl).hostname.endsWith('.onion')
|
||||||
|
}
|
||||||
|
const TorVPNSetupInstructions = `
|
||||||
|
Is your Tor VPN running?
|
||||||
|
Mints on Tor require a Tor VPN like Orbot. You can get it on Google Play or Github (guardianproject/orbot).
|
||||||
|
Tor can sometimes be slow, so requests might time out. If Orbot is running, try again in a bit.`
|
||||||
|
|
||||||
const getOrCreateMnemonic = async function (): Promise<string> {
|
const getOrCreateMnemonic = async function (): Promise<string> {
|
||||||
let mnemonic: string | undefined = undefined
|
let mnemonic: string | undefined = undefined
|
||||||
|
|
||||||
@@ -169,7 +177,9 @@ const getWallet = async function (
|
|||||||
log.trace('[getWallet]', 'Returning new cashuWallet instance')
|
log.trace('[getWallet]', 'Returning new cashuWallet instance')
|
||||||
return newWallet
|
return newWallet
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
throw new AppError(Err.NETWORK_ERROR, 'Could not get keys from the mint', {message: e.message, caller: 'getWallet'})
|
let message = 'Could not connect to the selected mint.'
|
||||||
|
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||||
|
throw new AppError(Err.NETWORK_ERROR, message, {message: e.message, caller: 'getWallet'})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,11 +198,9 @@ const getMintKeysets = async function (mintUrl: string) {
|
|||||||
const {keysets} = await cashuMint.getKeySets() // all
|
const {keysets} = await cashuMint.getKeySets() // all
|
||||||
return keysets
|
return keysets
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
throw new AppError(
|
let message = 'Could not connect to the selected mint.'
|
||||||
Err.CONNECTION_ERROR,
|
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||||
`Could not connect to the selected mint.`,
|
throw new AppError(Err.CONNECTION_ERROR, message, { message: e.message, mintUrl })
|
||||||
{message: e.message, mintUrl},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,16 +295,17 @@ const sendFromMint = async function (
|
|||||||
proofsToSend: send as Proof[],
|
proofsToSend: send as Proof[],
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
let message = 'The mint could not return signatures necessary for this transaction'
|
||||||
|
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||||
throw new AppError(
|
throw new AppError(
|
||||||
Err.MINT_ERROR,
|
Err.MINT_ERROR,
|
||||||
`The mint could not return signatures necessary for this transaction`,
|
message,
|
||||||
{
|
{
|
||||||
message: e.message,
|
message: e.message,
|
||||||
mintUrl,
|
mintUrl,
|
||||||
caller: 'MintClient.sendFromMint',
|
caller: 'MintClient.sendFromMint',
|
||||||
proofsToSendFrom,
|
proofsToSendFrom,
|
||||||
|
}
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -322,13 +331,15 @@ const getSpentOrPendingProofsFromMint = async function (
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
let message = 'Could not get response from the mint.'
|
||||||
|
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||||
throw new AppError(
|
throw new AppError(
|
||||||
Err.MINT_ERROR,
|
Err.MINT_ERROR,
|
||||||
'Could not get response from the mint.',
|
message,
|
||||||
{
|
{
|
||||||
message: e.message,
|
message: e.message,
|
||||||
caller: 'CashuMintClient.getSpentOrPendingProofsFromMint',
|
caller: 'CashuMintClient.getSpentOrPendingProofsFromMint',
|
||||||
mintUrl
|
mintUrl
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -353,13 +364,15 @@ const getLightningMeltQuote = async function (
|
|||||||
return lightningQuote
|
return lightningQuote
|
||||||
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
let message = 'The mint could not return the lightning quote.'
|
||||||
|
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||||
throw new AppError(
|
throw new AppError(
|
||||||
Err.MINT_ERROR,
|
Err.MINT_ERROR,
|
||||||
'The mint could not return the lightning quote.',
|
message,
|
||||||
{
|
{
|
||||||
message: e.message,
|
message: e.message,
|
||||||
caller: 'getLightningMeltQuote',
|
caller: 'getLightningMeltQuote',
|
||||||
request: {mintUrl, unit, encodedInvoice},
|
request: {mintUrl, unit, encodedInvoice},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -395,9 +408,11 @@ const payLightningMelt = async function (
|
|||||||
preimage,
|
preimage,
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
let message = 'Lightning payment failed.'
|
||||||
|
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||||
throw new AppError(
|
throw new AppError(
|
||||||
Err.MINT_ERROR,
|
Err.MINT_ERROR,
|
||||||
'Lightning payment failed.',
|
message,
|
||||||
{
|
{
|
||||||
message: isObj(e.message) ? JSON.stringify(e.message) : e.message,
|
message: isObj(e.message) ? JSON.stringify(e.message) : e.message,
|
||||||
caller: 'payLightningMelt',
|
caller: 'payLightningMelt',
|
||||||
@@ -431,14 +446,16 @@ const getBolt11MintQuote = async function (
|
|||||||
mintQuote,
|
mintQuote,
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
let message = 'The mint could not return an invoice.'
|
||||||
|
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||||
throw new AppError(
|
throw new AppError(
|
||||||
Err.MINT_ERROR,
|
Err.MINT_ERROR,
|
||||||
'The mint could not return an invoice.',
|
message,
|
||||||
{
|
{
|
||||||
message: e.message,
|
message: e.message,
|
||||||
caller: 'getBolt11MintQuote',
|
caller: 'getBolt11MintQuote',
|
||||||
mintUrl,
|
mintUrl,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,9 +483,11 @@ const getBolt11MintQuoteIsPaid = async function (
|
|||||||
isPaid
|
isPaid
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
let message = 'The mint could not return the state of a mint quote.'
|
||||||
|
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||||
throw new AppError(
|
throw new AppError(
|
||||||
Err.MINT_ERROR,
|
Err.MINT_ERROR,
|
||||||
'The mint could not return the state of a mint quote.',
|
message,
|
||||||
{
|
{
|
||||||
message: e.message,
|
message: e.message,
|
||||||
caller: 'getBolt11MintQuoteIsPaid',
|
caller: 'getBolt11MintQuoteIsPaid',
|
||||||
@@ -514,10 +533,11 @@ const mintProofs = async function (
|
|||||||
proofs: [],
|
proofs: [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let message = 'The mint returned error on request to mint new ecash.'
|
||||||
|
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||||
throw new AppError(
|
throw new AppError(
|
||||||
Err.MINT_ERROR,
|
Err.MINT_ERROR,
|
||||||
'The mint returned error on request to mint new ecash.',
|
message,
|
||||||
{
|
{
|
||||||
message: e.message,
|
message: e.message,
|
||||||
caller: 'mintProofs',
|
caller: 'mintProofs',
|
||||||
@@ -577,16 +597,17 @@ const getMintInfo = async function (
|
|||||||
log.trace('[getMintInfo]', {info})
|
log.trace('[getMintInfo]', {info})
|
||||||
return info
|
return info
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
throw new AppError(
|
let message = 'The mint could not return mint information.';
|
||||||
Err.MINT_ERROR,
|
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||||
'The mint could not return mint information.',
|
throw new AppError(
|
||||||
{
|
Err.MINT_ERROR,
|
||||||
message: e.message,
|
message,
|
||||||
caller: 'getMintInfo',
|
{
|
||||||
mintUrl,
|
message: e.message,
|
||||||
|
caller: 'getMintInfo',
|
||||||
}
|
mintUrl
|
||||||
)
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -123,27 +123,27 @@ const getLnurlAddressParams = async (lnurlAddress: string) => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const getInvoice = async(lnurlParams: LNURLPayParams, amountMsat?: number) => {
|
const getInvoice = async (lnurlParams: LNURLPayParams, amountMsat?: number, comment?: string) => {
|
||||||
|
if (!amountMsat || amountMsat === 0) { amountMsat = lnurlParams.minSendable }
|
||||||
|
|
||||||
if(!amountMsat || amountMsat === 0) {
|
const url = new URL(lnurlParams.callback)
|
||||||
amountMsat = lnurlParams.minSendable
|
url.searchParams.set('amount', amountMsat.toString())
|
||||||
}
|
if (comment) url.searchParams.set('comment', comment)
|
||||||
|
const strUrl = url.toString()
|
||||||
const url = lnurlParams.callback.includes('?') ? `${lnurlParams.callback}&amount=${amountMsat}` : `${lnurlParams.callback}?amount=${amountMsat}`
|
|
||||||
const method = 'GET'
|
const method = 'GET'
|
||||||
const headers = MinibitsClient.getPublicHeaders()
|
const headers = MinibitsClient.getPublicHeaders()
|
||||||
|
|
||||||
const invoiceResult: any = await MinibitsClient.fetchApi(url, {
|
const invoiceResult: any = await MinibitsClient.fetchApi(strUrl, {
|
||||||
method,
|
method,
|
||||||
headers,
|
headers,
|
||||||
})
|
})
|
||||||
|
|
||||||
if(invoiceResult.status && invoiceResult.status === 'ERROR') {
|
if(invoiceResult.status && invoiceResult.status === 'ERROR') {
|
||||||
throw new AppError(Err.CONNECTION_ERROR, invoiceResult.reason, {domain: lnurlParams.domain, caller: 'getLnurlParams'})
|
throw new AppError(Err.CONNECTION_ERROR, invoiceResult.reason, {domain: lnurlParams.domain, caller: 'getLnurlParams'})
|
||||||
}
|
}
|
||||||
|
|
||||||
if(invoiceResult.pr) {
|
if(invoiceResult.pr) {
|
||||||
return invoiceResult.pr as string
|
return invoiceResult.pr as string
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new AppError(Err.CONNECTION_ERROR, 'Could not get lightning invoice from the LNURL provider', {domain: lnurlParams.domain, caller: 'getLnurlParams'})
|
throw new AppError(Err.CONNECTION_ERROR, 'Could not get lightning invoice from the LNURL provider', {domain: lnurlParams.domain, caller: 'getLnurlParams'})
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ export const transferTask = async function (
|
|||||||
// get locked counter values
|
// get locked counter values
|
||||||
const lockedProofsCounter = await mintInstance.getProofsCounterByUnit?.(unit)
|
const lockedProofsCounter = await mintInstance.getProofsCounterByUnit?.(unit)
|
||||||
|
|
||||||
const {isPaid, feeSavedProofs} = await MintClient.payLightningMelt(
|
const {isPaid, preimage, feeSavedProofs} = await MintClient.payLightningMelt(
|
||||||
mintUrl,
|
mintUrl,
|
||||||
unit,
|
unit,
|
||||||
meltQuote,
|
meltQuote,
|
||||||
@@ -206,6 +206,7 @@ export const transferTask = async function (
|
|||||||
transactionData.push({
|
transactionData.push({
|
||||||
status: TransactionStatus.COMPLETED,
|
status: TransactionStatus.COMPLETED,
|
||||||
finalFee,
|
finalFee,
|
||||||
|
preimage, // TODO add to tx details
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user