mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-08-11 17:07:44 +00:00
Option to optimize backup size for big wallets by ecash swap, receive very large ecash tokens in batches
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "minibits_wallet",
|
||||
"version": "0.1.9-beta.22",
|
||||
"version": "0.1.9-beta.23",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android:clean": "cd android && ./gradlew clean",
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
},
|
||||
"scan": "Scan"
|
||||
},
|
||||
"copyAsEncodedTokens": "Copy as encoded tokens",
|
||||
"copyAsEncodedTokens": "Copy as sendable tokens",
|
||||
"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.",
|
||||
"copyProofs": "Copy proofs",
|
||||
|
||||
@@ -5,9 +5,10 @@ import {
|
||||
ViewStyle,
|
||||
View,
|
||||
Switch,
|
||||
Alert,
|
||||
} from 'react-native'
|
||||
import {btoa, fromByteArray} from 'react-native-quick-base64'
|
||||
import {useThemeColor, spacing, typography} from '../theme'
|
||||
import {useThemeColor, spacing, typography, colors} from '../theme'
|
||||
import {
|
||||
Button,
|
||||
Icon,
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
ErrorModal,
|
||||
InfoModal,
|
||||
Loading,
|
||||
BottomModal,
|
||||
} from '../components'
|
||||
import {SettingsStackScreenProps} from '../navigation'
|
||||
import {useHeader} from '../utils/useHeader'
|
||||
@@ -25,6 +27,7 @@ import {log} from '../services/logService'
|
||||
import AppError from '../utils/AppError'
|
||||
import { Proof } from '../models/Proof'
|
||||
import { useStores } from '../models'
|
||||
import EventEmitter from '../utils/eventEmitter'
|
||||
import { CashuUtils, ProofV3, TokenV3 } from '../services/cashu/cashuUtils'
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import { translate } from '../i18n'
|
||||
@@ -32,10 +35,14 @@ import { ProofsStoreSnapshot } from '../models/ProofsStore'
|
||||
import { getSnapshot } from 'mobx-state-tree'
|
||||
import { ContactsStoreSnapshot } from '../models/ContactsStore'
|
||||
import { MintsStoreSnapshot } from '../models/MintsStore'
|
||||
import { Database } from '../services'
|
||||
import { Database, TransactionTaskResult, WalletTask, WalletTaskResult } from '../services'
|
||||
import { Transaction, TransactionStatus } from '../models/Transaction'
|
||||
import { ResultModalInfo } from './Wallet/ResultModalInfo'
|
||||
import { verticalScale } from '@gocodingnow/rn-size-matters'
|
||||
|
||||
interface ExportBackupScreenProps extends SettingsStackScreenProps<'ExportBackup'> {}
|
||||
|
||||
const OPTIMIZE_FROM_PROOFS_COUNT = 100
|
||||
|
||||
export const ExportBackupScreen: FC<ExportBackupScreenProps> =
|
||||
function ExportBackup(_props) {
|
||||
@@ -56,9 +63,18 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
|
||||
const [error, setError] = useState<AppError | undefined>()
|
||||
const [orphanedProofs, setOrphanedProofs] = useState<Proof[]>([])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isSendAllSentToQueue, setIsSendAllSentToQueue] = useState<boolean>(false)
|
||||
const [isReceiveBatchSentToQueue, setIsReceiveBatchSentToQueue] = useState<boolean>(false)
|
||||
const [totalSentProofsCount, setTotalSentProofsCount] = useState<number>(0)
|
||||
const [totalReceiveErrorCount, setTotalReceiveErrorCount] = useState<number>(0)
|
||||
const [totalReceiveCompleteCount, setTotalReceiveCompleteCount] = useState<number>(0)
|
||||
const [isEcashInBackup, setIsEcashInBackup] = useState(true)
|
||||
const [isMintsInBackup, setIsMintsInBackup] = useState(true)
|
||||
const [isContactsInBackup, setIsContactsInBackup] = useState(true)
|
||||
const [isResultModalVisible, setIsResultModalVisible] = useState(false)
|
||||
const [resultModalInfo, setResultModalInfo] = useState<
|
||||
{status: TransactionStatus; title?: string, message: string} | undefined
|
||||
>()
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@@ -85,7 +101,101 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
|
||||
return () => {}
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const handleSendAllResult = async (result: TransactionTaskResult) => {
|
||||
log.trace('[handleSendAllResults] event handler triggered')
|
||||
|
||||
if (!isSendAllSentToQueue) { return false }
|
||||
|
||||
// runs per each mint and unit
|
||||
if (result.transaction && result.transaction.status === TransactionStatus.PENDING) {
|
||||
|
||||
// now we batch receive the pending encoded token
|
||||
// this forces the proofs swap with the mint for standard denomination amounts
|
||||
const encodedTokenToReceive: string = result.encodedTokenToSend
|
||||
const tokenToReceive = CashuUtils.decodeToken(encodedTokenToReceive)
|
||||
const {totalAmount: tokenAmount} = CashuUtils.getTokenAmounts(tokenToReceive)
|
||||
const proofsCount = tokenToReceive.token[0].proofs.length
|
||||
|
||||
setTotalSentProofsCount(prev => prev + proofsCount)
|
||||
setIsReceiveBatchSentToQueue(true)
|
||||
|
||||
WalletTask.receiveBatch(
|
||||
tokenToReceive,
|
||||
tokenAmount,
|
||||
tokenToReceive.memo as string,
|
||||
encodedTokenToReceive
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if(isSendAllSentToQueue) {
|
||||
EventEmitter.on('ev_sendTask_result', handleSendAllResult)
|
||||
}
|
||||
|
||||
return () => {
|
||||
EventEmitter.off('ev_sendTask_result', handleSendAllResult)
|
||||
}
|
||||
}, [isSendAllSentToQueue])
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
// runs for every receive in a batch
|
||||
const handleReceiveTaskResult = async (result: TransactionTaskResult) => {
|
||||
log.trace('handleReceiveTaskResult event handler triggered')
|
||||
|
||||
const {error} = result
|
||||
|
||||
if (error) {
|
||||
setTotalReceiveErrorCount(prev => prev + 1)
|
||||
} else {
|
||||
setTotalReceiveCompleteCount(prev => prev + 1)
|
||||
}
|
||||
}
|
||||
|
||||
if(isReceiveBatchSentToQueue) {
|
||||
EventEmitter.on('ev_receiveTask_result', handleReceiveTaskResult)
|
||||
}
|
||||
|
||||
}, [isReceiveBatchSentToQueue])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
// runs for every receive in a batch
|
||||
const showProofOptimizationResult = async () => {
|
||||
log.trace('handleReceiveTaskResult event handler triggered')
|
||||
|
||||
setIsLoading(false)
|
||||
|
||||
const currentProofsCount = proofsStore.proofsCount
|
||||
|
||||
let message = `Original proofs count: ${totalSentProofsCount}, Optimized proofs count: ${currentProofsCount}`
|
||||
|
||||
if (totalReceiveErrorCount > 0) {
|
||||
message += `, errors: ${totalReceiveErrorCount}`
|
||||
}
|
||||
|
||||
setResultModalInfo({
|
||||
status: totalReceiveErrorCount > 0 ? TransactionStatus.ERROR : TransactionStatus.COMPLETED,
|
||||
message,
|
||||
})
|
||||
|
||||
setIsResultModalVisible(true)
|
||||
}
|
||||
|
||||
if(totalReceiveCompleteCount > 0 || totalReceiveCompleteCount > 0) {
|
||||
showProofOptimizationResult()
|
||||
}
|
||||
|
||||
}, [totalReceiveErrorCount, totalReceiveCompleteCount])
|
||||
|
||||
|
||||
const toggleResultModal = () =>
|
||||
setIsResultModalVisible(previousState => !previousState)
|
||||
|
||||
|
||||
const toggleBackupEcashSwitch = () =>
|
||||
setIsEcashInBackup(previousState => !previousState)
|
||||
|
||||
@@ -98,6 +208,31 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
|
||||
setIsContactsInBackup(previousState => !previousState)
|
||||
|
||||
|
||||
const optimizeProofAmountsStart = function () {
|
||||
Alert.alert(
|
||||
'Optimize ecash proofs',
|
||||
'Do you want to swap your wallet ecash for proofs with optimal denominations? The size of your backup will decrease.',
|
||||
[
|
||||
{
|
||||
text: translate('common.cancel'),
|
||||
style: 'cancel',
|
||||
onPress: () => { /* Action canceled */ },
|
||||
},
|
||||
{
|
||||
text: translate('common.confirm'),
|
||||
onPress: async () => {
|
||||
// Moves all wallet proofs to pending in transactions split by mints and by units and in offline mode
|
||||
setIsLoading(true)
|
||||
setIsSendAllSentToQueue(true)
|
||||
WalletTask.sendAll()
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
const copyBackup = function () {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
@@ -123,14 +258,15 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
|
||||
}
|
||||
|
||||
if(isEcashInBackup) {
|
||||
// This is emptied in snapshot postprocess!
|
||||
const proofsSnapshot = getSnapshot(proofsStore.proofs)
|
||||
// proofsStore is emptied in snapshot postprocess!
|
||||
const proofsSnapshot = getSnapshot(proofsStore.proofs)
|
||||
|
||||
// Do not include orphaned proofs as they can not be imported without mintUrl
|
||||
const cleaned = proofsSnapshot.filter(p => p.mintUrl && p.mintUrl.length > 0)
|
||||
|
||||
exportedProofsStore = {
|
||||
proofs: cleaned,
|
||||
pendingProofs: getSnapshot(proofsStore.pendingProofs),
|
||||
pendingProofs: getSnapshot(proofsStore.pendingProofs) || [],
|
||||
pendingByMintSecrets: getSnapshot(proofsStore.pendingByMintSecrets)
|
||||
}
|
||||
}
|
||||
@@ -259,6 +395,7 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
|
||||
|
||||
|
||||
const copyOrphanedProofs = function() {
|
||||
log.trace({orphanedProofs})
|
||||
if(orphanedProofs.length > 0) {
|
||||
Clipboard.setString(JSON.stringify(orphanedProofs))
|
||||
}
|
||||
@@ -447,13 +584,21 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
|
||||
subText={`Number of proofs: ${proofsStore.proofsCount + proofsStore.pendingProofsCount}`}
|
||||
RightComponent={
|
||||
<View style={$rightContainer}>
|
||||
{proofsStore.proofsCount > OPTIMIZE_FROM_PROOFS_COUNT && (
|
||||
<Button
|
||||
preset='secondary'
|
||||
onPress={optimizeProofAmountsStart}
|
||||
textStyle={{lineHeight: verticalScale(16), fontSize: verticalScale(14)}}
|
||||
style={{minHeight: verticalScale(40), paddingVertical: verticalScale(spacing.tiny)}}
|
||||
text={'Optimize'}
|
||||
/>
|
||||
)}
|
||||
<Switch
|
||||
onValueChange={toggleBackupEcashSwitch}
|
||||
value={isEcashInBackup}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
topSeparator
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{mintsStore.mintCount > 0 && (
|
||||
@@ -468,7 +613,7 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
topSeparator
|
||||
topSeparator={proofsStore.proofsCount + proofsStore.pendingProofsCount > 0 ? true : false}
|
||||
/>
|
||||
)}
|
||||
{contactsStore.count > 0 && (
|
||||
@@ -483,7 +628,7 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
topSeparator
|
||||
topSeparator={mintsStore.mintCount > 0 ? true : false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -492,16 +637,19 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
|
||||
/>
|
||||
<View style={$bottomContainer}>
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: spacing.small,
|
||||
paddingRight: spacing.medium
|
||||
paddingRight: spacing.medium,
|
||||
marginLeft: -spacing.medium
|
||||
|
||||
}}
|
||||
>
|
||||
<Icon icon='faInfoCircle' />
|
||||
<Icon icon='faInfoCircle' containerStyle={{marginRight: spacing.extraSmall}}/>
|
||||
<Text
|
||||
style={{color: hint}}
|
||||
size='xs'
|
||||
size='xs'
|
||||
preset='formHelper'
|
||||
text='You will still need your seed phrase when using this backup to recover your wallet.'
|
||||
/>
|
||||
</View>
|
||||
@@ -528,15 +676,61 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
|
||||
tx="copyAsEncodedTokens"
|
||||
textStyle={{fontSize: 14}}
|
||||
/>
|
||||
<Button
|
||||
preset="tertiary"
|
||||
onPress={copyOrphanedProofs}
|
||||
text="Copy orphaned proofs"
|
||||
textStyle={{fontSize: 14, marginLeft: spacing.small}}
|
||||
/>
|
||||
{orphanedProofs.length > 0 && (
|
||||
<Button
|
||||
preset="tertiary"
|
||||
onPress={copyOrphanedProofs}
|
||||
text="Copy orphaned proofs"
|
||||
textStyle={{fontSize: 14, marginLeft: spacing.small}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<BottomModal
|
||||
isVisible={isResultModalVisible ? true : false}
|
||||
ContentComponent={
|
||||
<>
|
||||
{resultModalInfo?.status === TransactionStatus.COMPLETED && (
|
||||
<>
|
||||
<ResultModalInfo
|
||||
icon={'faCheckCircle'}
|
||||
iconColor={colors.palette.success200}
|
||||
title={resultModalInfo.title || translate('common.success')}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx='common.close'
|
||||
onPress={toggleResultModal}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
{(resultModalInfo?.status === TransactionStatus.ERROR ||
|
||||
resultModalInfo?.status === TransactionStatus.BLOCKED) && (
|
||||
<>
|
||||
<ResultModalInfo
|
||||
icon="faTriangleExclamation"
|
||||
iconColor={colors.palette.focus300}
|
||||
title={resultModalInfo?.title as string || translate('transactionCommon.receiveFailed')}
|
||||
message={resultModalInfo?.message as string}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
onPress={toggleResultModal}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
onBackButtonPress={toggleResultModal}
|
||||
onBackdropPress={toggleResultModal}
|
||||
/>
|
||||
</Screen>
|
||||
)
|
||||
}
|
||||
@@ -561,7 +755,7 @@ const $contentContainer: TextStyle = {
|
||||
|
||||
const $card: ViewStyle = {
|
||||
marginBottom: spacing.small,
|
||||
paddingTop: 0,
|
||||
//paddingTop: 0,
|
||||
}
|
||||
|
||||
const $buttonContainer: ViewStyle = {
|
||||
@@ -588,7 +782,8 @@ const $rightContainer: ViewStyle = {
|
||||
padding: spacing.extraSmall,
|
||||
// alignSelf: 'center',
|
||||
marginLeft: spacing.tiny,
|
||||
marginRight: -10
|
||||
marginRight: -10,
|
||||
flexDirection: 'row'
|
||||
}
|
||||
|
||||
const $bottomContainer: ViewStyle = {
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import {Mint} from '../models/Mint'
|
||||
import {Transaction, TransactionStatus} from '../models/Transaction'
|
||||
import {useStores} from '../models'
|
||||
import {TransactionTaskResult, WalletTask} from '../services'
|
||||
import {MAX_SWAP_INPUT_SIZE, TransactionTaskResult, WalletTask} from '../services'
|
||||
import {log} from '../services/logService'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import EventEmitter from '../utils/eventEmitter'
|
||||
@@ -45,6 +45,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
const [encodedToken, setEncodedToken] = useState<string | undefined>()
|
||||
const [amountToReceive, setAmountToReceive] = useState<string>('0')
|
||||
const [unit, setUnit] = useState<MintUnit>('sat')
|
||||
const [totalReceived, setTotalReceived] = useState<number>(0)
|
||||
const [receivedAmount, setReceivedAmount] = useState<string>('0')
|
||||
const [transactionStatus, setTransactionStatus] = useState<
|
||||
TransactionStatus | undefined
|
||||
@@ -103,14 +104,17 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
}
|
||||
|
||||
if (receivedAmount && receivedAmount > 0) {
|
||||
// accumulate received amount in case of multiple receives in batch
|
||||
setTotalReceived(prev => prev + receivedAmount)
|
||||
|
||||
const currency = getCurrency(unit)
|
||||
setReceivedAmount(`${numbro(receivedAmount / currency.precision).format({thousandSeparated: true, mantissa: currency.mantissa})}`)
|
||||
setReceivedAmount(`${numbro(totalReceived / currency.precision).format({thousandSeparated: true, mantissa: currency.mantissa})}`)
|
||||
}
|
||||
|
||||
setIsResultModalVisible(true)
|
||||
}
|
||||
|
||||
// Subscribe to the 'sendCompleted' event
|
||||
|
||||
if(isReceiveTaskSentToQueue) {
|
||||
EventEmitter.on('ev_receiveTask_result', handleReceiveTaskResult)
|
||||
EventEmitter.on('ev_receiveOfflinePrepareTask_result', handleReceiveTaskResult)
|
||||
@@ -123,6 +127,20 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
}
|
||||
}, [isReceiveTaskSentToQueue])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const updateReceivedAmount = async () => {
|
||||
log.trace('[updateTotalReceived] start')
|
||||
|
||||
const currency = getCurrency(unit)
|
||||
setReceivedAmount(`${numbro(totalReceived / currency.precision).format({thousandSeparated: true, mantissa: currency.mantissa})}`)
|
||||
}
|
||||
|
||||
if(totalReceived > 0) {
|
||||
updateReceivedAmount()
|
||||
}
|
||||
}, [totalReceived])
|
||||
|
||||
const resetState = function () {
|
||||
setToken(undefined)
|
||||
setEncodedToken(undefined)
|
||||
@@ -174,17 +192,31 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
|
||||
|
||||
const receiveToken = async function () {
|
||||
setIsLoading(true)
|
||||
setIsReceiveTaskSentToQueue(true)
|
||||
|
||||
setIsLoading(true)
|
||||
setIsReceiveTaskSentToQueue(true)
|
||||
|
||||
const amountToReceiveInt = round(toNumber(amountToReceive) * getCurrency(unit).precision, 0)
|
||||
const proofsCount = token!.token.flatMap(entry => entry.proofs).length
|
||||
|
||||
WalletTask.receive(
|
||||
if(proofsCount > MAX_SWAP_INPUT_SIZE) {
|
||||
|
||||
WalletTask.receiveBatch(
|
||||
token as TokenV3,
|
||||
amountToReceiveInt,
|
||||
memo,
|
||||
encodedToken as string,
|
||||
)
|
||||
)
|
||||
|
||||
} else {
|
||||
|
||||
WalletTask.receive(
|
||||
token as TokenV3,
|
||||
amountToReceiveInt,
|
||||
memo,
|
||||
encodedToken as string,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -69,7 +69,9 @@ export const RecoveryOptionsScreen: FC<AppStackScreenProps<'RecoveryOptions'>> =
|
||||
}
|
||||
}
|
||||
|
||||
EventEmitter.on('ev__syncStateWithMintTask_result', removeSpentByMintTaskResult)
|
||||
if(isSyncStateSentToQueue) {
|
||||
EventEmitter.on('ev__syncStateWithMintTask_result', removeSpentByMintTaskResult)
|
||||
}
|
||||
|
||||
return () => {
|
||||
EventEmitter.off('ev__syncStateWithMintTask_result', removeSpentByMintTaskResult)
|
||||
|
||||
@@ -382,6 +382,11 @@ const findMinExcess = function (requestedAmount: number, proofs: Proof[]): Proof
|
||||
return selectedProofs;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function attempts to find exact match combination of proofs for a transaction amount.
|
||||
* If not found, minimal number of proofs exceeding the amount is selected
|
||||
* It is intended to minimize number of swaps and possible fees.
|
||||
*/
|
||||
const getProofsToSend = function (requestedAmount: number, proofs: Proof[]): Proof[] {
|
||||
const proofsAmount = getProofsAmount(proofs)
|
||||
if(requestedAmount > proofsAmount) {
|
||||
@@ -400,6 +405,7 @@ const getProofsToSend = function (requestedAmount: number, proofs: Proof[]): Pro
|
||||
return findMinExcess(requestedAmount, proofs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* removes a set of tokens from another set of tokens, and returns the remaining.
|
||||
* @param proofs
|
||||
|
||||
@@ -885,7 +885,7 @@ const updateInputToken = function (id: number, inputToken: string) {
|
||||
const db = getInstance()
|
||||
db.execute(query, params)
|
||||
|
||||
log.debug('[updateInputToken]', 'Transaction outputToken updated', {id, inputToken})
|
||||
log.debug('[updateInputToken]', 'Transaction inputToken updated in database', {id})
|
||||
|
||||
const updatedTx = getTransactionById(id as number)
|
||||
|
||||
|
||||
@@ -119,9 +119,8 @@ export const receiveTask = async function (
|
||||
// Increase the proofs counter before the mint call so that in case the response
|
||||
// is not received our recovery index counts for sigs the mint has already issued
|
||||
const amountPreferences = getDefaultAmountPreference(amountToReceive)
|
||||
const countOfInFlightProofs = CashuUtils.getAmountPreferencesCount(amountPreferences)
|
||||
const tokenEntries: TokenEntryV3[] = token.token
|
||||
const proofsToReceive = tokenEntries[0].proofs as ProofV3[]
|
||||
const countOfInFlightProofs = CashuUtils.getAmountPreferencesCount(amountPreferences)
|
||||
const proofsToReceive = token.token.flatMap(entry => entry.proofs)
|
||||
const mintFeeReserve = mintInstance.getMintFeeReserve(proofsToReceive)
|
||||
|
||||
log.trace('[receiveTask]', 'amountPreferences', {amountPreferences, transactionId: transaction.id})
|
||||
|
||||
@@ -31,6 +31,8 @@ import { MintUnit, formatCurrency, getCurrency } from './wallet/currency'
|
||||
import { MinibitsClient } from './minibitsService'
|
||||
|
||||
|
||||
export const MAX_SWAP_INPUT_SIZE = 50
|
||||
|
||||
type WalletTaskService = {
|
||||
syncPendingStateWithMints: () => Promise<void>
|
||||
syncSpendableStateWithMints: () => Promise<void>
|
||||
@@ -71,6 +73,12 @@ type WalletTaskService = {
|
||||
memo: string,
|
||||
encodedToken: string,
|
||||
) => Promise<void>
|
||||
receiveBatch: (
|
||||
token: TokenV3,
|
||||
amountToReceive: number,
|
||||
memo: string,
|
||||
encodedToken: string,
|
||||
) => Promise<void>
|
||||
receiveOfflinePrepare: (
|
||||
token: TokenV3,
|
||||
amountToReceive: number,
|
||||
@@ -87,6 +95,7 @@ type WalletTaskService = {
|
||||
memo: string,
|
||||
selectedProofs: Proof[]
|
||||
) => Promise<void>
|
||||
sendAll: () => Promise<void>
|
||||
topup: (
|
||||
mintBalanceToTopup: MintBalance,
|
||||
amountToTopup: number,
|
||||
@@ -202,6 +211,65 @@ const receive = async function (
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
* Receive big tokens in batches to keep mint load reasonable.
|
||||
* Used when optimizing wallet proof amounts but might become the default.
|
||||
*/
|
||||
const receiveBatch = async function (
|
||||
token: TokenV3,
|
||||
amountToReceive: number,
|
||||
memo: string,
|
||||
encodedToken: string,
|
||||
) {
|
||||
const maxBatchSize = MAX_SWAP_INPUT_SIZE
|
||||
const mintUrl = token.token[0].mint
|
||||
const proofsToReceive = token.token.flatMap(entry => entry.proofs)
|
||||
const unit = token.unit
|
||||
|
||||
if (proofsToReceive.length > maxBatchSize) {
|
||||
|
||||
let index =0
|
||||
for (let i = 0; i < proofsToReceive.length; i += maxBatchSize) {
|
||||
|
||||
index++
|
||||
const batch = proofsToReceive.slice(i, i + maxBatchSize)
|
||||
const batchAmount = CashuUtils.getProofsAmount(batch)
|
||||
|
||||
const batchToken: TokenV3 = {
|
||||
token: [
|
||||
{
|
||||
mint: mintUrl,
|
||||
proofs: batch
|
||||
}
|
||||
],
|
||||
memo: `${memo} #${index}`,
|
||||
unit
|
||||
}
|
||||
|
||||
const batchEncodedToken = CashuUtils.encodeToken(batchToken)
|
||||
|
||||
// Queued WalletTask
|
||||
receive(
|
||||
batchToken,
|
||||
batchAmount,
|
||||
`${memo} #${index}`,
|
||||
batchEncodedToken,
|
||||
)
|
||||
}
|
||||
|
||||
} else {
|
||||
// If the length is less than or equal to 100, do normal receive
|
||||
receive(
|
||||
token,
|
||||
amountToReceive,
|
||||
memo,
|
||||
encodedToken,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const receiveOfflinePrepare = async function (
|
||||
token: TokenV3,
|
||||
@@ -259,6 +327,39 @@ const send = async function (
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* sendAll moves all proofs to pending to prepare to swap them for standard amount preference
|
||||
* This decreases the total number of proofs held by the wallet. Used to optimize exported backup size.
|
||||
*/
|
||||
const sendAll = async function (): Promise<void> {
|
||||
log.trace('[sendAll] start')
|
||||
if (mintsStore.mintCount === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// Move all proofs by mint units to pending as in offline mode (do not ask for swap)
|
||||
for (const mint of mintsStore.allMints) {
|
||||
|
||||
for (const unit of mint.units) {
|
||||
const proofsToOptimize = proofsStore.getByMint(mint.mintUrl, { isPending: false, unit })
|
||||
const proofsAmount = CashuUtils.getProofsAmount(proofsToOptimize)
|
||||
const mintBalance = mint.balances
|
||||
|
||||
// Queued WalletTask.send
|
||||
send(
|
||||
mintBalance!,
|
||||
proofsAmount,
|
||||
unit,
|
||||
`Optimize proof amounts`,
|
||||
proofsToOptimize // forces offline mode
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const topup = async function (
|
||||
mintBalanceToTopup: MintBalance,
|
||||
amountToTopup: number,
|
||||
@@ -1642,7 +1743,6 @@ const _extractZapSenderData = function (str: string) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const WalletTask: WalletTaskService = {
|
||||
syncPendingStateWithMints,
|
||||
syncSpendableStateWithMints,
|
||||
@@ -1654,9 +1754,11 @@ export const WalletTask: WalletTaskService = {
|
||||
handleClaim,
|
||||
receiveEventsFromRelays,
|
||||
receive,
|
||||
receiveBatch,
|
||||
receiveOfflinePrepare,
|
||||
receiveOfflineComplete,
|
||||
send,
|
||||
sendAll,
|
||||
transfer,
|
||||
topup,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user