mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 19:28:26 +00:00
Support paying of hold lightning invoices
This commit is contained in:
+1
-1
@@ -36,7 +36,7 @@
|
||||
"mobx": "^6.9.0",
|
||||
"mobx-react-lite": "^3.4.3",
|
||||
"mobx-state-tree": "^5.1.8",
|
||||
"nostr-tools": "^1.14.0",
|
||||
"nostr-tools": "^1.17.0",
|
||||
"numbro": "^2.3.6",
|
||||
"patch-package": "^7.0.0",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
|
||||
@@ -281,7 +281,7 @@ index f6bc3b1..0bfe1dc 100644
|
||||
method: 'POST',
|
||||
requestBody: restorePayload
|
||||
diff --git a/node_modules/@cashu/cashu-ts/src/CashuWallet.ts b/node_modules/@cashu/cashu-ts/src/CashuWallet.ts
|
||||
index b7cf354..d3e9f0a 100644
|
||||
index b7cf354..d0a9af2 100644
|
||||
--- a/node_modules/@cashu/cashu-ts/src/CashuWallet.ts
|
||||
+++ b/node_modules/@cashu/cashu-ts/src/CashuWallet.ts
|
||||
@@ -1,7 +1,7 @@
|
||||
@@ -315,6 +315,37 @@ index b7cf354..d3e9f0a 100644
|
||||
|
||||
/**
|
||||
* Class that represents a Cashu wallet.
|
||||
@@ -69,13 +69,16 @@ class CashuWallet {
|
||||
* @param proofs (only the 'secret' field is required)
|
||||
* @returns
|
||||
*/
|
||||
- async checkProofsSpent<T extends { secret: string }>(proofs: Array<T>): Promise<Array<T>> {
|
||||
+ async checkProofsSpent<T extends { secret: string }>(proofs: Array<T>): Promise<{spent: Array<T>, pending: Array<T>}> {
|
||||
const payload = {
|
||||
//send only the secret
|
||||
proofs: proofs.map((p) => ({ secret: p.secret }))
|
||||
};
|
||||
- const { spendable } = await this.mint.check(payload);
|
||||
- return proofs.filter((_, i) => !spendable[i]);
|
||||
+ const { spendable, pending: isPending } = await this.mint.check(payload);
|
||||
+
|
||||
+ const spent = proofs.filter((_, i) => !spendable[i]);
|
||||
+ const pending = isPending && Array.isArray(isPending) ? proofs.filter((_, i) => isPending[i]) : [];
|
||||
+ return {spent, pending}
|
||||
}
|
||||
/**
|
||||
* Starts a minting process by requesting an invoice from the mint
|
||||
@@ -512,8 +515,8 @@ class CashuWallet {
|
||||
const secrets: Array<Uint8Array> = [];
|
||||
const rs: Array<bigint> = [];
|
||||
for (let i = 0; i < amounts.length; i++) {
|
||||
- let deterministicR = undefined;
|
||||
- let secret = undefined;
|
||||
+ let deterministicR: bigint | undefined = undefined;
|
||||
+ let secret: Uint8Array | undefined = undefined;
|
||||
if (this._seed && count != undefined) {
|
||||
secret = deriveSecret(this._seed, keysetId??this.keysetId, count + i);
|
||||
deterministicR = bytesToNumber(deriveBlindingFactor(this._seed, keysetId??this.keysetId, count + i));
|
||||
diff --git a/node_modules/@cashu/cashu-ts/src/DHKE.ts b/node_modules/@cashu/cashu-ts/src/DHKE.ts
|
||||
index ca37559..02843b5 100644
|
||||
--- a/node_modules/@cashu/cashu-ts/src/DHKE.ts
|
||||
@@ -352,6 +383,18 @@ index b1f2c00..138c251 100644
|
||||
|
||||
export {
|
||||
CashuMint,
|
||||
diff --git a/node_modules/@cashu/cashu-ts/src/model/types/index.ts b/node_modules/@cashu/cashu-ts/src/model/types/index.ts
|
||||
index 4855c33..4546acc 100644
|
||||
--- a/node_modules/@cashu/cashu-ts/src/model/types/index.ts
|
||||
+++ b/node_modules/@cashu/cashu-ts/src/model/types/index.ts
|
||||
@@ -215,6 +215,7 @@ export type CheckSpendableResponse = {
|
||||
* Ordered list for checked proofs. True if the secret has not been redeemed at the mint before
|
||||
*/
|
||||
spendable: Array<boolean>;
|
||||
+ pending?: Array<boolean>;
|
||||
} & ApiError;
|
||||
/**
|
||||
* blinded message for sending to the mint
|
||||
diff --git a/node_modules/@cashu/cashu-ts/src/secrets.ts b/node_modules/@cashu/cashu-ts/src/secrets.ts
|
||||
index ce70d91..4ebe676 100644
|
||||
--- a/node_modules/@cashu/cashu-ts/src/secrets.ts
|
||||
|
||||
@@ -66,6 +66,10 @@ export const MintsStoreModel = types
|
||||
}
|
||||
},
|
||||
blockMint(mintToBeBlocked: Mint) {
|
||||
if(self.blockedMintUrls.some(url => url === mintToBeBlocked.mintUrl)) {
|
||||
return
|
||||
}
|
||||
|
||||
self.blockedMintUrls.push(mintToBeBlocked.mintUrl)
|
||||
log.debug('[blockMint]', 'Mint blocked in MintsStore')
|
||||
},
|
||||
|
||||
@@ -17,6 +17,7 @@ export const ProofsStoreModel = types
|
||||
.model('Proofs', {
|
||||
proofs: types.array(ProofModel),
|
||||
pendingProofs: types.array(ProofModel),
|
||||
pendingByMintSecrets: types.array(types.string),
|
||||
})
|
||||
.actions(withSetPropAction)
|
||||
.views(self => ({
|
||||
@@ -55,23 +56,34 @@ export const ProofsStoreModel = types
|
||||
|
||||
return proofInstance
|
||||
},
|
||||
|
||||
alreadyExists(proof: Proof, isPending: boolean = false) {
|
||||
const proofs = isPending ? self.pendingProofs : self.proofs
|
||||
return proofs.some(p => p.secret === proof.secret) ? true : false
|
||||
},
|
||||
}))
|
||||
.actions(self => ({
|
||||
addProofs(newProofs: Proof[], isPending: boolean = false) {
|
||||
addProofs(newProofs: Proof[], isPending: boolean = false): number {
|
||||
try {
|
||||
const proofs = isPending ? self.pendingProofs : self.proofs
|
||||
let addedAmount: number = 0
|
||||
|
||||
for (const proof of newProofs) {
|
||||
if(self.alreadyExists(proof)) {
|
||||
log.warn('[addProofs]', `${isPending ? ' pending' : ''} proof already exists in the ProofsStore`, proof.secret)
|
||||
continue
|
||||
}
|
||||
|
||||
if (isStateTreeNode(proof)) {
|
||||
proofs.push(proof)
|
||||
} else {
|
||||
const proofInstance = ProofModel.create(proof)
|
||||
proofs.push(proofInstance)
|
||||
}
|
||||
|
||||
addedAmount += proof.amount
|
||||
}
|
||||
|
||||
log.debug('[addProofs]', `Added new ${newProofs.length}${isPending ? ' pending' : ''} proofs to ProofsStore`,)
|
||||
log.debug('[addProofs]', `Added new ${newProofs.length}${isPending ? ' pending' : ''} proofs to the ProofsStore`,)
|
||||
|
||||
const rootStore = getRootStore(self)
|
||||
const {userSettingsStore} = rootStore
|
||||
@@ -79,6 +91,8 @@ export const ProofsStoreModel = types
|
||||
if (userSettingsStore.isLocalBackupOn === true) {
|
||||
Database.addOrUpdateProofs(newProofs, isPending) // isSpent = false
|
||||
}
|
||||
|
||||
return addedAmount
|
||||
} catch (e: any) {
|
||||
throw new AppError(Err.STORAGE_ERROR, e.message)
|
||||
}
|
||||
@@ -119,6 +133,19 @@ export const ProofsStoreModel = types
|
||||
throw new AppError(Err.STORAGE_ERROR, e.message.toString())
|
||||
}
|
||||
},
|
||||
addToPendingByMint(proof: Proof): boolean {
|
||||
if(self.pendingByMintSecrets.some(s => s === proof.secret)) {
|
||||
return false
|
||||
}
|
||||
|
||||
self.pendingByMintSecrets.push(proof.secret)
|
||||
log.trace('[addToPendingByMint]', 'Proof marked as pending by mint, secret', proof.secret)
|
||||
return true
|
||||
},
|
||||
removeFromPendingByMint(proof: Proof) {
|
||||
self.pendingByMintSecrets.remove(proof.secret)
|
||||
log.trace('[removeFromPendingByMint]', 'Proof removed from pending by mint, secret', proof.secret)
|
||||
},
|
||||
}))
|
||||
.views(self => ({
|
||||
get proofsCount() {
|
||||
|
||||
@@ -141,6 +141,13 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
onPress={gotoLocalRecovery}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
style={$card}
|
||||
HeadingComponent={
|
||||
<>
|
||||
<ListItem
|
||||
tx="backupScreen.removeSpentCoins"
|
||||
subTx="backupScreen.removeSpentCoinsDescription"
|
||||
@@ -197,7 +204,7 @@ const $contentContainer: TextStyle = {
|
||||
}
|
||||
|
||||
const $card: ViewStyle = {
|
||||
marginBottom: 0,
|
||||
marginBottom: spacing.small,
|
||||
}
|
||||
|
||||
const $item: ViewStyle = {
|
||||
|
||||
@@ -10,6 +10,10 @@ import {
|
||||
useColorScheme,
|
||||
} from 'react-native'
|
||||
import {formatDistance, toDate} from 'date-fns'
|
||||
import {
|
||||
type TokenEntry as CashuTokenEntry,
|
||||
type Proof as CashuProof,
|
||||
} from '@cashu/cashu-ts'
|
||||
import {useThemeColor, spacing, colors, typography} from '../theme'
|
||||
import {
|
||||
Button,
|
||||
@@ -29,10 +33,11 @@ import {Database} from '../services'
|
||||
import AppError from '../utils/AppError'
|
||||
import {BackupProof, Proof} from '../models/Proof'
|
||||
import { useStores } from '../models'
|
||||
import { getMintForToken, getProofsAmount } from '../services/cashu/cashuUtils'
|
||||
import { CashuUtils } from '../services/cashu/cashuUtils'
|
||||
import JSONTree from 'react-native-json-tree'
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import { getEncodedToken } from '@cashu/cashu-ts'
|
||||
import { TokenEntry } from '../models/TokenEntry'
|
||||
|
||||
interface LocalRecoveryScreenProps
|
||||
extends SettingsStackScreenProps<'LocalRecovery'> {}
|
||||
@@ -127,30 +132,46 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
|
||||
const copyEncodedTokens = function (proofs: BackupProof[]) {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
const encodedTokens: string[] = []
|
||||
// TODO group by mints
|
||||
|
||||
for (const mint of mintsStore.allMints) {
|
||||
|
||||
let proofsByMint: CashuProof[] = []
|
||||
|
||||
for (const proof of proofs) {
|
||||
const mint = getMintForToken(proof, mintsStore.allMints)
|
||||
|
||||
const proofMint = CashuUtils.getMintFromProof(proof, mintsStore.allMints)
|
||||
const { tId, isPending, isSpent, updatedAt, ...cleanedProof } = proof
|
||||
|
||||
if(mint) {
|
||||
const encoded = getEncodedToken({
|
||||
if (!proofMint) { continue }
|
||||
|
||||
if(mint.mintUrl === proofMint.mintUrl) {
|
||||
proofsByMint.push(cleanedProof)
|
||||
}
|
||||
}
|
||||
|
||||
if (proofsByMint.length > 0) {
|
||||
const tokenByMint = {
|
||||
token: [
|
||||
{
|
||||
mint: mint.mintUrl,
|
||||
proofs: [
|
||||
cleanedProof
|
||||
]
|
||||
proofs: proofsByMint
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
encodedTokens.push(encoded)
|
||||
log.trace(tokenByMint)
|
||||
|
||||
const encodedByMint = getEncodedToken(tokenByMint)
|
||||
encodedTokens.push(encodedByMint)
|
||||
}
|
||||
}
|
||||
|
||||
Clipboard.setString(JSON.stringify(encodedTokens))
|
||||
setIsLoading(false)
|
||||
|
||||
} catch (e: any) {
|
||||
setInfo(`Could not copy: ${e.message}`)
|
||||
}
|
||||
@@ -234,7 +255,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
ContentComponent={
|
||||
<>
|
||||
<Text
|
||||
style={{color: dateColor, fontSize: 14}}
|
||||
style={{textAlign: 'center', marginVertical: spacing.small}}
|
||||
text="Backed up proofs"
|
||||
/>
|
||||
<JSONTree
|
||||
|
||||
@@ -11,6 +11,7 @@ import { NostrClient } from "../../services"
|
||||
import { colors, spacing, typography, useThemeColor } from "../../theme"
|
||||
import { getImageSource } from '../../utils/utils'
|
||||
import { ContactListItem } from "../Contacts/ContactListItem"
|
||||
import { CurrencyCode, CurrencySign } from "../Wallet/CurrencySign"
|
||||
|
||||
|
||||
export interface PaymentRequestListProps {
|
||||
@@ -38,10 +39,9 @@ export const PaymentRequestListItem = observer(function (props: PaymentRequestLi
|
||||
preset='heading'
|
||||
text={pr.amount.toLocaleString()}
|
||||
/>
|
||||
<Text
|
||||
text='SATS'
|
||||
size='xxs'
|
||||
style={{color: hintColor, fontFamily: typography.primary?.light, marginBottom: spacing.small}}
|
||||
<CurrencySign
|
||||
currencyCode={CurrencyCode.SATS}
|
||||
containerStyle={{marginBottom: spacing.small}}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import {MintListItem} from './Mints/MintListItem'
|
||||
import useIsInternetReachable from '../utils/useIsInternetReachable'
|
||||
import { resolveTxt } from 'dns'
|
||||
import { verticalScale } from '@gocodingnow/rn-size-matters'
|
||||
import { CurrencyCode, CurrencySign } from './Wallet/CurrencySign'
|
||||
|
||||
export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
function ReceiveScreen({route, navigation}) {
|
||||
@@ -207,10 +208,8 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
tx="receiveScreen.toReceive"
|
||||
style={{color: 'white'}}
|
||||
/>
|
||||
<Text
|
||||
text='SATS'
|
||||
size='xxs'
|
||||
style={{color: satsColor, fontFamily: typography.primary?.light}}
|
||||
<CurrencySign
|
||||
currencyCode={CurrencyCode.SATS}
|
||||
/>
|
||||
<Text
|
||||
style={$amountToReceive}
|
||||
@@ -225,10 +224,9 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
tx="receiveScreen.received"
|
||||
style={{color: 'white'}}
|
||||
/>
|
||||
<Text
|
||||
text='SATS'
|
||||
size='xxs'
|
||||
style={{color: satsColor, fontFamily: typography.primary?.light}}
|
||||
<CurrencySign
|
||||
currencyCode={CurrencyCode.SATS}
|
||||
containerStyle={{marginTop: -5}}
|
||||
/>
|
||||
<Text
|
||||
style={$amountToReceive}
|
||||
|
||||
@@ -59,6 +59,7 @@ import { getImageSource, infoMessage } from '../utils/utils'
|
||||
import { NotificationService } from '../services/notificationService'
|
||||
import { SendOption } from './SendOptionsScreen'
|
||||
import { verticalScale } from '@gocodingnow/rn-size-matters'
|
||||
import { CurrencyCode, CurrencySign } from './Wallet/CurrencySign'
|
||||
|
||||
|
||||
if (Platform.OS === 'android' &&
|
||||
@@ -548,10 +549,8 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
style={{color: 'white'}}
|
||||
/>
|
||||
<View style={$amountContainer}>
|
||||
<Text
|
||||
text='SATS'
|
||||
size='xxs'
|
||||
style={{color: satsColor, fontFamily: typography.primary?.light}}
|
||||
<CurrencySign
|
||||
currencyCode={CurrencyCode.SATS}
|
||||
/>
|
||||
<TextInput
|
||||
ref={amountInputRef}
|
||||
|
||||
@@ -52,6 +52,7 @@ import { roundDown } from '../utils/number'
|
||||
import { LnurlClient, LnurlWithdrawResult } from '../services/lnurlService'
|
||||
import { update } from 'lodash'
|
||||
import { verticalScale } from '@gocodingnow/rn-size-matters'
|
||||
import { CurrencyCode, CurrencySign } from './Wallet/CurrencySign'
|
||||
|
||||
if (
|
||||
Platform.OS === 'android' &&
|
||||
@@ -551,10 +552,8 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
style={{color: 'white'}}
|
||||
/>
|
||||
<View style={$amountContainer}>
|
||||
<Text
|
||||
text='SATS'
|
||||
size='xxs'
|
||||
style={{color: satsColor, fontFamily: typography.primary?.light}}
|
||||
<CurrencySign
|
||||
currencyCode={CurrencyCode.SATS}
|
||||
/>
|
||||
<TextInput
|
||||
ref={amountInputRef}
|
||||
|
||||
@@ -783,10 +783,12 @@ const SendInfoBlock = function (props: {
|
||||
<Card
|
||||
style={$dataCard}
|
||||
ContentComponent={
|
||||
<>
|
||||
<TranItem
|
||||
label="tranDetailScreen.sentFrom"
|
||||
value={transaction.mint as string}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{isDataParsable && (
|
||||
@@ -1038,10 +1040,18 @@ const TransferInfoBlock = function (props: {
|
||||
label="tranDetailScreen.lightningFee"
|
||||
value={`${transaction.fee}`}
|
||||
/>
|
||||
{transaction.memo && (
|
||||
<TranItem
|
||||
label="tranDetailScreen.memoFromInvoice"
|
||||
value={transaction.memo as string}
|
||||
/>
|
||||
)}
|
||||
{transaction.sentTo && (
|
||||
<TranItem
|
||||
label="tranDetailScreen.sentTo"
|
||||
value={transaction.sentTo as string}
|
||||
/>
|
||||
)}
|
||||
<TranItem
|
||||
label="tranDetailScreen.type"
|
||||
value={transaction.type as string}
|
||||
@@ -1201,6 +1211,7 @@ const $actionCard: ViewStyle = {
|
||||
const $dataCard: ViewStyle = {
|
||||
padding: spacing.medium,
|
||||
marginBottom: spacing.extraSmall,
|
||||
paddingTop: spacing.extraSmall,
|
||||
}
|
||||
|
||||
const $item: ViewStyle = {
|
||||
|
||||
@@ -28,13 +28,13 @@ export const TransactionListItem = observer(function (props: {tx: Transaction, i
|
||||
|
||||
switch(tx.type) {
|
||||
case TransactionType.RECEIVE || TransactionType.RECEIVE_OFFLINE:
|
||||
return 'You received'
|
||||
return tx.sentFrom ? `Received from ${tx.sentFrom}` : 'You received'
|
||||
case TransactionType.SEND:
|
||||
return 'You paid'
|
||||
return tx.sentTo ? `Sent to ${tx.sentTo}` : 'You sent'
|
||||
case TransactionType.TOPUP:
|
||||
return 'You funded your wallet'
|
||||
return tx.sentFrom ? `Received from ${tx.sentFrom}` : 'You received'
|
||||
case TransactionType.TRANSFER:
|
||||
return 'You transfered'
|
||||
return tx.sentTo ? `Paid to ${tx.sentTo}` : 'You paid'
|
||||
default:
|
||||
return 'Uknown transaction'
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import { LNURLPayParams } from 'js-lnurl'
|
||||
import { roundDown, roundUp } from '../utils/number'
|
||||
import { LnurlClient } from '../services/lnurlService'
|
||||
import { moderateScale, scale, verticalScale } from '@gocodingnow/rn-size-matters'
|
||||
import { CurrencyCode, CurrencySign } from './Wallet/CurrencySign'
|
||||
|
||||
if (
|
||||
Platform.OS === 'android' &&
|
||||
@@ -61,7 +62,7 @@ export const TransferScreen: FC<WalletStackScreenProps<'Transfer'>> = observer(
|
||||
})
|
||||
|
||||
const amountInputRef = useRef<TextInput>(null)
|
||||
const {proofsStore, mintsStore, paymentRequestsStore} = useStores()
|
||||
const {proofsStore, mintsStore, paymentRequestsStore, transactionsStore} = useStores()
|
||||
|
||||
const [encodedInvoice, setEncodedInvoice] = useState<string>('')
|
||||
const [invoice, setInvoice] = useState<DecodedLightningInvoice | undefined>()
|
||||
@@ -401,10 +402,17 @@ const transfer = async function () {
|
||||
const {status} = transaction as Transaction
|
||||
setTransactionStatus(status)
|
||||
|
||||
if (error) {
|
||||
if(transaction && lnurlPayParams && lnurlPayParams.address) {
|
||||
await transactionsStore.updateSentTo( // set ln address to send to to the tx, could be elsewhere //
|
||||
transaction.id as number,
|
||||
lnurlPayParams.address as string
|
||||
)
|
||||
}
|
||||
|
||||
if (error) { // This handles timed out pending payments
|
||||
setResultModalInfo({
|
||||
status,
|
||||
message: error.message,
|
||||
message: message || error.message,
|
||||
})
|
||||
} else {
|
||||
if(!isInvoiceDonation) { // Donation polling triggers own ResultModal on paid invoice
|
||||
@@ -469,10 +477,8 @@ const satsColor = colors.palette.primary200
|
||||
style={{color: 'white'}}
|
||||
/>
|
||||
<View style={$amountContainer}>
|
||||
<Text
|
||||
text='SATS'
|
||||
size='xxs'
|
||||
style={{color: satsColor, fontFamily: typography.primary?.light}}
|
||||
<CurrencySign
|
||||
currencyCode={CurrencyCode.SATS}
|
||||
/>
|
||||
<TextInput
|
||||
ref={amountInputRef}
|
||||
@@ -597,9 +603,9 @@ const satsColor = colors.palette.primary200
|
||||
if(isInvoiceDonation) {
|
||||
navigation.navigate('ContactsNavigator', {screen: 'Contacts', params: {}})
|
||||
} else {
|
||||
navigation.navigate('Wallet', {})}
|
||||
}
|
||||
navigation.navigate('Wallet', {})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
@@ -639,6 +645,26 @@ const satsColor = colors.palette.primary200
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
{resultModalInfo &&
|
||||
transactionStatus === TransactionStatus.PENDING && (
|
||||
<>
|
||||
<ResultModalInfo
|
||||
icon="faTriangleExclamation"
|
||||
iconColor={colors.palette.iconYellow300}
|
||||
title="Transfer pending"
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
onPress={() => {
|
||||
navigation.navigate('Wallet', {})
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from "react"
|
||||
import { TextStyle, View, ViewStyle } from "react-native"
|
||||
import { Button, Icon, IconTypes, ListItem, Screen, Text } from "../../components"
|
||||
import { colors, spacing, typography, useThemeColor } from "../../theme"
|
||||
|
||||
export enum CurrencyCode {
|
||||
SATS = 'SATS',
|
||||
}
|
||||
|
||||
export const CurrencySign = function(props: {
|
||||
currencyCode: CurrencyCode,
|
||||
containerStyle?: ViewStyle,
|
||||
textStyle?: TextStyle
|
||||
}
|
||||
) {
|
||||
|
||||
const textColor = useThemeColor('header')
|
||||
const bgColor = colors.palette.primary200
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[{
|
||||
paddingHorizontal: spacing.tiny,
|
||||
borderRadius: spacing.tiny,
|
||||
backgroundColor: bgColor,
|
||||
}, props.containerStyle || {}]}
|
||||
>
|
||||
<Text
|
||||
text='₿ sats'
|
||||
style={[{
|
||||
color: textColor,
|
||||
fontSize: 10,
|
||||
fontFamily: typography.primary?.light,
|
||||
padding: 0,
|
||||
lineHeight: 16,
|
||||
}, props.textStyle || {}]}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ import { poller } from '../utils/poller'
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import { IncomingDataType, IncomingParser } from '../services/incomingParser'
|
||||
import useIsInternetReachable from '../utils/useIsInternetReachable'
|
||||
import { CurrencyCode, CurrencySign } from './Wallet/CurrencySign'
|
||||
|
||||
|
||||
|
||||
@@ -414,9 +415,8 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
<TotalBalanceBlock
|
||||
totalBalance={balances.totalBalance}
|
||||
pendingBalance={balances.totalPendingBalance}
|
||||
// gotoTranHistory={gotoTranHistory}
|
||||
/>
|
||||
<View style={$contentContainer}>
|
||||
<View style={[$contentContainer, groupedMints.length > 1 && ({marginTop: -spacing.extraLarge * 2.5})]}>
|
||||
{mintsStore.mintCount === 0 ? (
|
||||
<PromoBlock addMint={addMint} />
|
||||
) : (
|
||||
@@ -428,7 +428,7 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
inActiveDotColor={colors.palette.primary300}
|
||||
activeDotColor={colors.palette.primary100}
|
||||
activeDotScale={1.2}
|
||||
containerStyle={{bottom: undefined, position: undefined, marginTop: -spacing.small, paddingBottom: spacing.medium}}
|
||||
containerStyle={{bottom: undefined, position: undefined, marginTop: -spacing.tiny, paddingBottom: spacing.small}}
|
||||
//@ts-ignore
|
||||
scrollX={scrollX}
|
||||
dotSize={30}
|
||||
@@ -559,10 +559,9 @@ const TotalBalanceBlock = observer(function (props: {
|
||||
|
||||
return (
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
<Text
|
||||
text='SATS'
|
||||
size='xxs'
|
||||
style={{color: pendingBalanceColor, fontFamily: typography.primary?.light}}
|
||||
<CurrencySign
|
||||
currencyCode={CurrencyCode.SATS}
|
||||
containerStyle={{marginTop: -5}}
|
||||
/>
|
||||
<Text
|
||||
testID='total-balance'
|
||||
@@ -640,8 +639,8 @@ const MintsByHostnameListItem = observer(function (props: {
|
||||
RightComponent={
|
||||
<View style={$balanceContainer}>
|
||||
<Text style={[$balance, {color: balanceColor}]}>
|
||||
{props.mintBalances.find(b => b.mint === mint.mintUrl)
|
||||
?.balance || 0}
|
||||
{(props.mintBalances.find(b => b.mint === mint.mintUrl)
|
||||
?.balance || 0).toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
@@ -667,7 +666,10 @@ const $headerContainer: TextStyle = {
|
||||
alignItems: 'center',
|
||||
paddingBottom: spacing.medium,
|
||||
paddingTop: 0,
|
||||
marginTop: 0,
|
||||
height: spacing.screenHeight * 0.18,
|
||||
// borderWidth: 1,
|
||||
// borderColor: 'red',
|
||||
}
|
||||
|
||||
const $buttonContainer: ViewStyle = {
|
||||
@@ -679,7 +681,9 @@ const $buttonContainer: ViewStyle = {
|
||||
const $contentContainer: TextStyle = {
|
||||
marginTop: -spacing.extraLarge * 2,
|
||||
flex: 0.9,
|
||||
paddingTop: spacing.extraSmall,
|
||||
paddingTop: spacing.extraSmall - 3,
|
||||
// borderWidth: 1,
|
||||
// borderColor: 'green',
|
||||
}
|
||||
|
||||
const $card: ViewStyle = {
|
||||
|
||||
@@ -167,7 +167,7 @@ const sendFromMint = async function (
|
||||
}
|
||||
}
|
||||
|
||||
const getSpentProofsFromMint = async function (
|
||||
const getSpentOrPendingProofsFromMint = async function (
|
||||
mintUrl: string,
|
||||
proofs: Proof[],
|
||||
) {
|
||||
@@ -175,13 +175,20 @@ const getSpentProofsFromMint = async function (
|
||||
const cashuWallet = getWallet(mintUrl)
|
||||
|
||||
const spentPendingProofs = await cashuWallet.checkProofsSpent(proofs)
|
||||
return spentPendingProofs
|
||||
|
||||
log.trace('[getSpentOrPendingProofsFromMint]', spentPendingProofs)
|
||||
|
||||
return spentPendingProofs as {
|
||||
spent: CashuProof[]
|
||||
pending: CashuProof[]
|
||||
}
|
||||
|
||||
} catch (e: any) {
|
||||
throw new AppError(
|
||||
Err.MINT_ERROR,
|
||||
'The mint could not check if the proofs were spent.',
|
||||
'The mint could not check if the proofs were spent or are pending.',
|
||||
{
|
||||
caller: 'getSpentProofsFromMint',
|
||||
caller: 'getSpentOrPendingProofsFromMint',
|
||||
mintUrl,
|
||||
message: e.message
|
||||
}
|
||||
@@ -241,9 +248,15 @@ const payLightningInvoice = async function (
|
||||
preimage,
|
||||
newKeys
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.log(e)
|
||||
throw new AppError(Err.MINT_ERROR, e.message)
|
||||
} catch (e: any) {throw new AppError(
|
||||
Err.MINT_ERROR,
|
||||
'Lightning payment failed.',
|
||||
{
|
||||
caller: 'payLightningInvoice',
|
||||
mintUrl,
|
||||
message: e.message
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,7 +304,7 @@ const requestProofs = async function (
|
||||
}
|
||||
} catch (e: any) {
|
||||
if(e.message.includes('Invoice not paid') === false) {
|
||||
log.info(`[MINT_ERROR] ${e.message}`,[],'cashuMintClient.requestProofs')
|
||||
log.warn('[requestProofs]', `${e.message}`)
|
||||
}
|
||||
|
||||
return {proofs: []}
|
||||
@@ -302,7 +315,7 @@ export const MintClient = {
|
||||
getMintKeys,
|
||||
receiveFromMint,
|
||||
sendFromMint,
|
||||
getSpentProofsFromMint,
|
||||
getSpentOrPendingProofsFromMint,
|
||||
getLightningFee,
|
||||
payLightningInvoice,
|
||||
requestLightningInvoice,
|
||||
|
||||
@@ -117,17 +117,6 @@ const getLnurlAddressParams = async (lnurlAddress: string) => {
|
||||
return {
|
||||
lnurlParams
|
||||
} as LnurlParamsResult
|
||||
|
||||
/*const encodedInvoice = await getInvoice(lnurlParams)
|
||||
|
||||
if(encodedInvoice) {
|
||||
return {
|
||||
lnurlParams,
|
||||
encodedInvoice
|
||||
} as LnurlParamsResult
|
||||
}
|
||||
|
||||
throw new AppError(Err.CONNECTION_ERROR, 'Could not get lightning invoice from the LNURL provider', {domain: lnurlParams.domain, caller: 'getLnurlAddressParams'})*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -185,8 +185,8 @@ const publish = async function (
|
||||
log.trace('Event to be published', event, 'publish')
|
||||
|
||||
const pool = getRelayPool()
|
||||
await pool.publish(relays, event)
|
||||
await delay(1000)
|
||||
let pubs = pool.publish(relays, event)
|
||||
await Promise.all(pubs)
|
||||
|
||||
const published: NostrEvent = await pool.get(relays, {
|
||||
ids: [event.id]
|
||||
|
||||
@@ -646,7 +646,7 @@ const updateSentToAsync = async function (id: number, sentTo: string) {
|
||||
const db = getInstance()
|
||||
await db.executeAsync(query, params)
|
||||
|
||||
log.debug('[updateSentToAsync]', 'Transaction sentFrom updated', {id, sentTo})
|
||||
log.debug('[updateSentToAsync]', 'Transaction sentTo updated', {id, sentTo})
|
||||
|
||||
const updatedTx = getTransactionById(id as number)
|
||||
|
||||
|
||||
+250
-57
@@ -474,21 +474,22 @@ async function _checkSpentByMint(mintUrl: string, isPending: boolean = false) {
|
||||
return
|
||||
}
|
||||
|
||||
const spentProofs = await MintClient.getSpentProofsFromMint(
|
||||
const {spent: spentProofs, pending: pendingProofs} = await MintClient.getSpentOrPendingProofsFromMint(
|
||||
mintUrl,
|
||||
proofsFromMint,
|
||||
)
|
||||
|
||||
const spentCount = spentProofs.length
|
||||
const spentAmount = CashuUtils.getProofsAmount(spentProofs)
|
||||
|
||||
log.trace('[_checkSpentByMint]', `spentProofs amount: ${spentAmount}`)
|
||||
|
||||
if (spentProofs.length < 1) {
|
||||
log.trace('[_checkSpentByMint]', `No spent ${isPending ? 'pending' : ''} proofs returned from the mint`, mintUrl)
|
||||
return
|
||||
for (const proof of pendingProofs) {
|
||||
proofsStore.addToPendingByMint(proof as Proof)
|
||||
}
|
||||
|
||||
const spentCount = spentProofs.length
|
||||
const spentAmount = CashuUtils.getProofsAmount(spentProofs as Proof[])
|
||||
const pendingAmount = CashuUtils.getProofsAmount(pendingProofs as Proof[])
|
||||
|
||||
log.trace('[_checkSpentByMint]', `spentProofs and pendingProofs amounts`, {spentAmount, pendingAmount, isPending})
|
||||
|
||||
if (spentProofs.length > 0) {
|
||||
// we need to identify txIds to update their statuses, there might be more then one tx to complete
|
||||
let relatedTransactionIds: number[] = []
|
||||
|
||||
@@ -502,6 +503,19 @@ async function _checkSpentByMint(mintUrl: string, isPending: boolean = false) {
|
||||
}
|
||||
}
|
||||
|
||||
// Clean pendingByMint secrets if in-flight payment completed and they came back as spent
|
||||
if(proofsStore.pendingByMintSecrets.length > 0) {
|
||||
|
||||
log.trace('[_checkSpentByMint]', 'Starting sweep of spent pendingByMint proofs')
|
||||
|
||||
for (const proof of spentProofs) {
|
||||
const secret = proofsStore.pendingByMintSecrets.find((s => s === proof.secret))
|
||||
if(secret) {
|
||||
proofsStore.removeFromPendingByMint(proof as Proof)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove spent proofs model instances from pending
|
||||
proofsStore.removeProofs(spentProofs as Proof[], isPending)
|
||||
|
||||
@@ -524,8 +538,76 @@ async function _checkSpentByMint(mintUrl: string, isPending: boolean = false) {
|
||||
EventEmitter.emit('sendCompleted', relatedTransactionIds)
|
||||
stopPolling('checkSpentByMintPoller')
|
||||
}
|
||||
} else {
|
||||
log.trace('[_checkSpentByMint]', `No spent ${isPending ? 'pending' : ''} proofs returned from the mint`, mintUrl)
|
||||
}
|
||||
|
||||
// Check if remaining pendingByMint secrets are still pending with the mint.
|
||||
// If not, move related wallet's pending proofs back to spendable as the payment failed and they were not spent.
|
||||
const remainingSecrets = getSnapshot(proofsStore.pendingByMintSecrets)
|
||||
|
||||
log.trace('[_checkSpentByMint]', 'pendingByMintSecrets', remainingSecrets)
|
||||
|
||||
if(remainingSecrets.length > 0 && isPending) {
|
||||
|
||||
log.trace('[_checkSpentByMint]', 'Starting sweep of pendingByMintSecrets')
|
||||
|
||||
const movedProofs: Proof[] = []
|
||||
|
||||
for (const secret of remainingSecrets) {
|
||||
const proofToMove = proofsStore.getBySecret(secret, true) // find the proof in wallet's pending
|
||||
|
||||
// only move if it is from current mint
|
||||
if(proofToMove && proofToMove.mintUrl === mintUrl) {
|
||||
const stillPendingByMint = pendingProofs.find((p => p.secret === secret))
|
||||
|
||||
if(!stillPendingByMint) {
|
||||
// move it if it is not pending by mint anymore
|
||||
proofsStore.removeFromPendingByMint(proofToMove as Proof)
|
||||
movedProofs.push(proofToMove)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(movedProofs.length > 0) {
|
||||
|
||||
log.trace('[_checkSpentByMint]', 'Moving proofs from pending to spendable', movedProofs.length)
|
||||
|
||||
// Update related transactions as reverted
|
||||
let relatedTransactionIds: number[] = []
|
||||
|
||||
for (const movedProof of movedProofs) {
|
||||
const tId = movedProof.tId
|
||||
|
||||
if (tId && !relatedTransactionIds.includes(tId)) {
|
||||
relatedTransactionIds.push(tId)
|
||||
}
|
||||
}
|
||||
|
||||
// remove it from pending proofs in the wallet
|
||||
proofsStore.removeProofs(movedProofs, true, true)
|
||||
// add proofs back to the spendable wallet
|
||||
proofsStore.addProofs(movedProofs)
|
||||
|
||||
if(relatedTransactionIds.length > 0) {
|
||||
const transactionDataUpdate = {
|
||||
status: TransactionStatus.REVERTED,
|
||||
createdAt: new Date(),
|
||||
}
|
||||
|
||||
await transactionsStore.updateStatuses(
|
||||
relatedTransactionIds,
|
||||
TransactionStatus.REVERTED,
|
||||
JSON.stringify(transactionDataUpdate),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
log.trace('[_checkSpentByMint]', `No moved proofs from pending`, mintUrl)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// TODO what to do with tx error status after removing spent proofs
|
||||
return {
|
||||
mintUrl,
|
||||
spentCount,
|
||||
@@ -660,19 +742,14 @@ const receive = async function (
|
||||
)
|
||||
}
|
||||
|
||||
// attach transaction id and mintUrl to the proofs before storing them
|
||||
const newProofs: Proof[] = []
|
||||
|
||||
for (const entry of updatedToken.token) {
|
||||
for (const proof of entry.proofs) {
|
||||
proof.tId = transactionId
|
||||
proof.mintUrl = entry.mint
|
||||
|
||||
newProofs.push(proof)
|
||||
}
|
||||
}
|
||||
// create ProofModel instances and store them into the proofsStore
|
||||
proofsStore.addProofs(newProofs)
|
||||
_addCashuProofs(
|
||||
entry.proofs,
|
||||
entry.mint,
|
||||
transactionId as number
|
||||
)
|
||||
}
|
||||
|
||||
// This should be amountToReceive - amountWithErrors but let's set it from updated token
|
||||
const receivedAmount = CashuUtils.getTokenAmounts(updatedToken as Token).totalAmount
|
||||
@@ -939,19 +1016,14 @@ const receiveOfflineComplete = async function (
|
||||
)
|
||||
}
|
||||
|
||||
// attach transaction id and mintUrl to the proofs before storing them
|
||||
const newProofs: Proof[] = []
|
||||
|
||||
for (const entry of updatedToken.token) {
|
||||
for (const proof of entry.proofs) {
|
||||
proof.tId = transaction.id
|
||||
proof.mintUrl = entry.mint
|
||||
|
||||
newProofs.push(proof)
|
||||
}
|
||||
}
|
||||
// create ProofModel instances and store them into the proofsStore
|
||||
proofsStore.addProofs(newProofs)
|
||||
_addCashuProofs(
|
||||
entry.proofs,
|
||||
entry.mint,
|
||||
transaction.id as number
|
||||
)
|
||||
}
|
||||
|
||||
// This should be amountToReceive - amountWithErrors but let's set it from updated token
|
||||
const receivedAmount = CashuUtils.getTokenAmounts(updatedToken as Token).totalAmount
|
||||
@@ -1391,13 +1463,32 @@ const transfer = async function (
|
||||
|
||||
// We've sent the proofsToPay to the mint, so we remove those pending proofs from model storage.
|
||||
// Hopefully mint gets important shit done synchronously.
|
||||
// We might not need to await for this and set it up as async poller with 1 poll?
|
||||
await _checkSpentByMint(mintUrl, true)
|
||||
|
||||
// I have no idea yet if this can happen, return sent Proofs to the store an track tx as Reverted
|
||||
if (!isPaid) {
|
||||
_addCashuProofs(proofsToPay, mintUrl, transactionId, true)
|
||||
const { amountPendingByMint } = await _moveProofsFromPending(proofsToPay, mintUrl, transactionId)
|
||||
|
||||
if(amountPendingByMint > 0) {
|
||||
transactionData.push({
|
||||
status: TransactionStatus.PENDING,
|
||||
amountPendingByMint,
|
||||
})
|
||||
|
||||
const pendingTransaction = await transactionsStore.updateStatus(
|
||||
transactionId,
|
||||
TransactionStatus.PENDING,
|
||||
JSON.stringify(transactionData),
|
||||
)
|
||||
|
||||
// There is not clear way to get refund for saved fees later so we treat estimated fees as final
|
||||
await transactionsStore.updateFee(transactionId, estimatedFee)
|
||||
|
||||
return {
|
||||
transaction: pendingTransaction,
|
||||
message: 'Lightning payment did not complete in time. Your ecash will remain pending until the payment completes or fails.',
|
||||
} as TransactionResult
|
||||
} else {
|
||||
transactionData.push({
|
||||
status: TransactionStatus.REVERTED,
|
||||
createdAt: new Date(),
|
||||
@@ -1414,12 +1505,17 @@ const transfer = async function (
|
||||
message: 'Payment of lightning invoice failed. Coins were returned to your wallet.',
|
||||
} as TransactionResult
|
||||
}
|
||||
}
|
||||
|
||||
// If real fees were less then estimated, cash the returned savings.
|
||||
let finalFee = estimatedFee
|
||||
|
||||
if (feeSavedProofs.length) {
|
||||
const feeSaved = _addCashuProofs(feeSavedProofs, mintUrl, transactionId)
|
||||
const {addedAmount: feeSaved} = _addCashuProofs(
|
||||
feeSavedProofs,
|
||||
mintUrl,
|
||||
transactionId
|
||||
)
|
||||
|
||||
finalFee = estimatedFee - feeSaved
|
||||
}
|
||||
@@ -1445,7 +1541,7 @@ const transfer = async function (
|
||||
|
||||
return {
|
||||
transaction: completedTransaction,
|
||||
message: `Lightning invoice has been successfully paid and settled with your minibits coins. Final lightning network fee has been ${finalFee} sats.`,
|
||||
message: `Lightning invoice has been successfully paid and settled with your minibits coins. Final network fee has been ${finalFee} sats.`,
|
||||
finalFee,
|
||||
} as TransactionResult
|
||||
} catch (e: any) {
|
||||
@@ -1453,6 +1549,42 @@ const transfer = async function (
|
||||
let errorTransaction: TransactionRecord | undefined = undefined
|
||||
|
||||
if (transactionId > 0) {
|
||||
// Return tokens intended for payment to the wallet if payment failed with an error
|
||||
if (proofsToPay.length > 0) {
|
||||
log.warn('[transfer]', 'Returning proofsToPay to the wallet likely after failed lightning payment.', proofsToPay.length)
|
||||
|
||||
const {
|
||||
amountToMove,
|
||||
amountPendingByMint
|
||||
} = await _moveProofsFromPending(proofsToPay, mintUrl, transactionId)
|
||||
|
||||
// keep tx as pending if proofs were not added because of a mint that keeps them as pending for timed out in-flight payment
|
||||
if(amountPendingByMint > 0) {
|
||||
transactionData.push({
|
||||
status: TransactionStatus.PENDING,
|
||||
error: _formatError(e),
|
||||
amountToMove,
|
||||
amountPendingByMint,
|
||||
})
|
||||
|
||||
const pendingTransaction = await transactionsStore.updateStatus(
|
||||
transactionId,
|
||||
TransactionStatus.PENDING,
|
||||
JSON.stringify(transactionData),
|
||||
)
|
||||
|
||||
// There is not clear way to get refund for saved fees later so we treat estimated fees as final
|
||||
await transactionsStore.updateFee(transactionId, estimatedFee)
|
||||
|
||||
return {
|
||||
transaction: pendingTransaction,
|
||||
message: 'Lightning payment did not complete in time. Your ecash will remain pending until the payment completes or fails.',
|
||||
error: _formatError(e),
|
||||
} as TransactionResult
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
transactionData.push({
|
||||
status: TransactionStatus.ERROR,
|
||||
error: _formatError(e),
|
||||
@@ -1463,12 +1595,6 @@ const transfer = async function (
|
||||
TransactionStatus.ERROR,
|
||||
JSON.stringify(transactionData),
|
||||
)
|
||||
|
||||
// Return tokens intended for payment to the wallet if payment failed with an error
|
||||
if (proofsToPay.length > 0) {
|
||||
log.info('[transfer]', 'Returning proofsToPay to the wallet likely after failed lightning payment', proofsToPay.length)
|
||||
const amount = _addCashuProofs(proofsToPay, mintUrl, transactionId, true)
|
||||
}
|
||||
}
|
||||
|
||||
log.error(e.name, e.message, {}, 'transfer')
|
||||
@@ -1486,26 +1612,93 @@ const transfer = async function (
|
||||
const _addCashuProofs = function (
|
||||
proofsToAdd: CashuProof[],
|
||||
mintUrl: string,
|
||||
transactionId: number,
|
||||
isRecoveredFromPending: boolean = false
|
||||
): number {
|
||||
transactionId: number
|
||||
): {
|
||||
amountToAdd: number,
|
||||
addedAmount: number
|
||||
} {
|
||||
// Add internal references
|
||||
for (const proof of proofsToAdd as Proof[]) {
|
||||
proof.tId = transactionId
|
||||
proof.mintUrl = mintUrl
|
||||
}
|
||||
// Creates proper model instances and adds them to storage
|
||||
proofsStore.addProofs(proofsToAdd as Proof[])
|
||||
const amount = CashuUtils.getProofsAmount(proofsToAdd as Proof[])
|
||||
|
||||
log.trace('[_addCashuProofs]', 'Added proofs with amount', { amount, isRecoveredFromPending })
|
||||
const amountToAdd = CashuUtils.getProofsAmount(proofsToAdd as Proof[])
|
||||
// Creates proper model instances and adds them to the wallet
|
||||
const addedAmount = proofsStore.addProofs(proofsToAdd as Proof[])
|
||||
|
||||
if(isRecoveredFromPending) {
|
||||
// Remove them from pending if they are returned to the wallet due to failed lightning payment
|
||||
// Do not mark them as spent as they are recovered back to wallet
|
||||
proofsStore.removeProofs(proofsToAdd as Proof[], true, isRecoveredFromPending)
|
||||
log.trace('[_addCashuProofs]', 'Added proofs to the wallet with amount', { amountToAdd, addedAmount })
|
||||
|
||||
return {
|
||||
amountToAdd,
|
||||
addedAmount
|
||||
}
|
||||
}
|
||||
|
||||
return amount
|
||||
|
||||
const _moveProofsFromPending = async function (
|
||||
proofsToMove: CashuProof[],
|
||||
mintUrl: string,
|
||||
transactionId: number,
|
||||
): Promise<{
|
||||
amountToMove: number,
|
||||
amountPendingByMint: number,
|
||||
movedAmount: number
|
||||
}> {
|
||||
// Add internal references
|
||||
for (const proof of proofsToMove as Proof[]) {
|
||||
proof.tId = transactionId
|
||||
proof.mintUrl = mintUrl
|
||||
}
|
||||
|
||||
const amountToMove = CashuUtils.getProofsAmount(proofsToMove as Proof[])
|
||||
|
||||
// Here we move proofs from pending back to spendable wallet in case of lightning payment failure
|
||||
|
||||
// Check with the mint if the proofs are not marked as pending. This happens when lightning payment fails
|
||||
// due to the timeout but mint's node keeps the payment as in-flight (e.g. receiving node holds the invoice)
|
||||
// In this case we need to keep such proofs as pending and not move them back to wallet as in other payment failures.
|
||||
const {pending: pendingByMint} = await MintClient.getSpentOrPendingProofsFromMint(
|
||||
mintUrl,
|
||||
proofsToMove as Proof[],
|
||||
)
|
||||
|
||||
let amountPendingByMint: number = 0
|
||||
let movedAmount: number = 0
|
||||
const movedProofs: Proof[] = []
|
||||
|
||||
for (const proof of proofsToMove) {
|
||||
// if proof to return to the wallet is tracked by mint as pending we do not move it from wallet's pending
|
||||
if(pendingByMint.some(p => p.secret === proof.secret)){
|
||||
// add it to the list of secrets so we can later handle them based on eventual lightning payment result
|
||||
if(proofsStore.addToPendingByMint(proof as Proof)) {
|
||||
amountPendingByMint += proof.amount
|
||||
}
|
||||
} else {
|
||||
movedAmount += proof.amount
|
||||
movedProofs.push(proof as Proof)
|
||||
}
|
||||
}
|
||||
|
||||
if(movedProofs.length > 0) {
|
||||
// remove it from pending proofs in the wallet
|
||||
proofsStore.removeProofs(movedProofs, true, true)
|
||||
// add proofs back to the spendable wallet
|
||||
proofsStore.addProofs(movedProofs)
|
||||
|
||||
log.trace('[_moveProofsFromPending]', 'Moved proofs back from pending to spendable', {amountToMove, amountPendingByMint, movedAmount})
|
||||
}
|
||||
|
||||
if(amountPendingByMint > 0 && amountPendingByMint !== amountToMove) {
|
||||
// This should not happen, monitor
|
||||
log.warn('[_moveProofsFromPending]', 'Not all proofs to be moved were pending by the mint')
|
||||
}
|
||||
|
||||
return {
|
||||
amountToMove,
|
||||
amountPendingByMint,
|
||||
movedAmount
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1690,11 +1883,11 @@ const checkPendingTopups = async function () {
|
||||
|
||||
if(newKeys) {_updateMintKeys(pr.mint as string, newKeys)}
|
||||
|
||||
// accept whatever we've got
|
||||
const receivedAmount = _addCashuProofs(
|
||||
// accept to the wallet whatever we've got
|
||||
const {addedAmount: receivedAmount} = _addCashuProofs(
|
||||
proofs,
|
||||
pr.mint as string,
|
||||
pr.transactionId as number,
|
||||
pr.transactionId as number
|
||||
)
|
||||
|
||||
if (receivedAmount !== pr.amount) {
|
||||
|
||||
@@ -1606,6 +1606,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz#ea6d23ade78a325f7a52750aab1526b02b628c29"
|
||||
integrity sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==
|
||||
|
||||
"@noble/ciphers@0.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.2.0.tgz#a12cda60f3cf1ab5d7c77068c3711d2366649ed7"
|
||||
integrity sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw==
|
||||
|
||||
"@noble/curves@1.1.0", "@noble/curves@~1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d"
|
||||
@@ -6257,11 +6262,12 @@ normalize-path@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
nostr-tools@^1.14.0:
|
||||
version "1.14.0"
|
||||
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.14.0.tgz#5cb21cd9f7b1a471b9010e3feed6c74d126ab106"
|
||||
integrity sha512-hwq2i1z5/DneXRE5Zu/TzQuKzVLcB+gOdfT9CeoiScvNw/2dWRGJvyTXIdF92d7NQ7nMcEwqVJPDytLpEpiiKw==
|
||||
nostr-tools@^1.17.0:
|
||||
version "1.17.0"
|
||||
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.17.0.tgz#b6f62e32fedfd9e68ec0a7ce57f74c44fc768e8c"
|
||||
integrity sha512-LZmR8GEWKZeElbFV5Xte75dOeE9EFUW/QLI1Ncn3JKn0kFddDKEfBbFN8Mu4TMs+L4HR/WTPha2l+PPuRnJcMw==
|
||||
dependencies:
|
||||
"@noble/ciphers" "0.2.0"
|
||||
"@noble/curves" "1.1.0"
|
||||
"@noble/hashes" "1.3.1"
|
||||
"@scure/base" "1.1.1"
|
||||
|
||||
Reference in New Issue
Block a user