From e3ec278e284472b0598e88f2b7b9d6a4a4d04e23 Mon Sep 17 00:00:00 2001 From: minibits-cash Date: Mon, 13 Apr 2026 12:51:18 +0200 Subject: [PATCH] Finalize keysets V2, bug fixes --- .../xcschemes/minibits_wallet.xcscheme | 2 +- src/i18n_messages/en.json | 2 +- src/i18n_messages/es.json | 2 +- src/i18n_messages/pt.json | 2 +- src/i18n_messages/sk.json | 2 +- src/models/ContactsStore.ts | 7 +- src/models/MintsStore.ts | 2 + src/screens/Contacts/ContactListItem.tsx | 9 +- src/screens/NfcPayScreen.tsx | 22 +-- src/screens/ReceiveScreen.tsx | 160 ++++++++++------- src/screens/SendScreen.tsx | 163 +++++++++--------- src/screens/TranDetailScreen.tsx | 32 ++-- src/services/cashu/cashuUtils.ts | 7 +- src/services/wallet/receiveTask.ts | 35 ++-- src/services/walletService.ts | 49 +++--- 15 files changed, 262 insertions(+), 234 deletions(-) diff --git a/ios/minibits_wallet.xcodeproj/xcshareddata/xcschemes/minibits_wallet.xcscheme b/ios/minibits_wallet.xcodeproj/xcshareddata/xcschemes/minibits_wallet.xcscheme index 9a1b755f..72677cb8 100644 --- a/ios/minibits_wallet.xcodeproj/xcshareddata/xcschemes/minibits_wallet.xcscheme +++ b/ios/minibits_wallet.xcodeproj/xcshareddata/xcschemes/minibits_wallet.xcscheme @@ -31,7 +31,7 @@ shouldAutocreateTestPlan = "YES"> () + const [tokenInfo, setTokenInfo] = useState() const [encodedToken, setEncodedToken] = useState() const [amountToReceive, setAmountToReceive] = useState('0') const [unit, setUnit] = useState('sat') + const [mintUrl, setMintUrl] = useState(undefined) + const [mint, setMint] = useState(undefined) const [totalReceived, setTotalReceived] = useState(0) const [receivedAmount, setReceivedAmount] = useState('0') const [transactionStatus, setTransactionStatus] = useState< @@ -73,6 +74,7 @@ export const ReceiveScreen = observer(function ReceiveScreen({ route }: Props) { const [memo, setMemo] = useState('') const [info, setInfo] = useState('') const [error, setError] = useState() + const [isNewMint, setIsNewMint] = useState(false) const [isP2PKLocked, setIsP2PKLocked] = useState(false) const [isP2PKLockedToWallet, setIsP2PKLockedToWallet] = useState(false) const [isLoading, setIsLoading] = useState(false) @@ -111,7 +113,7 @@ export const ReceiveScreen = observer(function ReceiveScreen({ route }: Props) { }, [totalReceived]) const resetState = function () { - setToken(undefined) + setTokenInfo(undefined) setEncodedToken(undefined) setAmountToReceive('0') setReceivedAmount('0') @@ -137,62 +139,84 @@ export const ReceiveScreen = observer(function ReceiveScreen({ route }: Props) { // keysetsV2 support const tokenInfo = getTokenMetadata(encoded) - const mintKeysetIds = mintsStore.findByUrl(tokenInfo.mint)?.keysetIds - if(!mintKeysetIds || mintKeysetIds.length === 0) { - throw new AppError(Err.NOTFOUND_ERROR, 'Missing keysetIds in the wallet state', { - mintUrl: tokenInfo.mint - }) + const {amount, unit, memo, mint: mintUrl} = tokenInfo + + if(!unit) { + throw new AppError(Err.VALIDATION_ERROR, translate("decodedMissingCurrencyUnit", { unit: CurrencyCode.SAT })) + } + + if(!mintUrl) { + throw new AppError(Err.VALIDATION_ERROR, 'Decoded token is missing mint url') } - const decoded = getDecodedToken(encoded, mintKeysetIds) - - const tokenAmount = CashuUtils.getProofsAmount(decoded.proofs) - const isLocked = CashuUtils.isTokenP2PKLocked(decoded) + const isLocked = CashuUtils.isTokenP2PKLocked(tokenInfo) let isLockedToWallet = false if(isLocked) { - const lockedToPK = CashuUtils.getP2PKPubkeySecret(decoded.proofs[0].secret) + const lockedToPK = CashuUtils.getP2PKPubkeySecret(tokenInfo.incompleteProofs[0].secret) const keys = await walletStore.getCachedWalletKeys() isLockedToWallet = lockedToPK === '02' + keys.NOSTR.publicKey } - log.trace('decoded token', {decoded, isLocked, isLockedToWallet}) - log.trace('tokenAmount', {tokenAmount}) + log.trace('decoded tokenMetadata', {tokenInfo, isLocked, isLockedToWallet}) + log.trace('tokenAmount', {amount, unit}) - if(!decoded.unit) { - setInfo(translate("decodedMissingCurrencyUnit", { unit: CurrencyCode.SAT })) - decoded.unit = 'sat' - } + const currency = getCurrency(unit as MintUnit) - const currency = getCurrency(decoded.unit as MintUnit) - - setToken(decoded) + setMintUrl(mintUrl) + setTokenInfo(tokenInfo) setIsP2PKLocked(isLocked) setIsP2PKLockedToWallet(isLockedToWallet) - setAmountToReceive(numbro(tokenAmount / currency.precision).format({thousandSeparated: true, mantissa: currency.mantissa})) - setUnit(decoded.unit as MintUnit) - - if (decoded.memo && decoded.memo.length > 0) { - setMemo(decoded.memo as string) + setAmountToReceive(numbro(amount / currency.precision).format({thousandSeparated: true, mantissa: currency.mantissa})) + setUnit(unit as MintUnit) + if (memo && memo.length > 0) { + setMemo(memo as string) } + + const mintExists = mintsStore.mintExists(tokenInfo.mint) + + if(!mintExists) { + setIsNewMint(true) + } else { + setIsNewMint(false) + const mintInstance = mintsStore.findByUrl(tokenInfo.mint) + setMint(mintInstance) + } + } catch (e: any) { handleError(e) } } - const receiveToken = async function () { setIsLoading(true) setIsReceiveTaskSentToQueue(true) - const amountToReceiveInt = round(toNumber(amountToReceive) * getCurrency(unit).precision, 0) - const proofsCount = token!.proofs.length + if(!mintUrl) { + throw new AppError(Err.VALIDATION_ERROR, 'Mint url is not set') + } + + if(!tokenInfo) { + throw new AppError(Err.VALIDATION_ERROR, 'Token info is not set') + } + + let mintInstance = mint + if(!mintInstance) { + try { + mintInstance = await mintsStore.addMint(mintUrl) + } catch(e: any) { + return handleError(e) + } + } + + if(!mintInstance) { + return handleError(new AppError(Err.VALIDATION_ERROR, 'Could not establish mint instance')) + } const result = await WalletTask.receiveQueueAwaitable( - token as Token, - amountToReceiveInt, - memo, + mintInstance, + tokenInfo, encodedToken as string, ) @@ -204,8 +228,14 @@ export const ReceiveScreen = observer(function ReceiveScreen({ route }: Props) { setIsLoading(false) - const {error, message, transaction, receivedAmount, mintUrl} = result - const {status} = transaction as Transaction + const {error, message, transaction, receivedAmount} = result + + if(!transaction) { + setIsResultModalVisible(true) + return + } + + const {status} = transaction setTransactionStatus(status) setTransaction(transaction) @@ -264,14 +294,18 @@ export const ReceiveScreen = observer(function ReceiveScreen({ route }: Props) { const receiveOfflineToken = async function () { setIsLoading(true) - setIsReceiveTaskSentToQueue(true) + setIsReceiveTaskSentToQueue(true) + + if(!encodedToken) { + throw new AppError(Err.VALIDATION_ERROR, 'Encoded token is not set') + } + + if(!tokenInfo) { + throw new AppError(Err.VALIDATION_ERROR, 'Token info is not set') + } - const amountToReceiveInt = round(toNumber(amountToReceive) * getCurrency(unit).precision, 0) - const result = await WalletTask.receiveOfflinePrepareQueueAwaitable( - token as Token, - amountToReceiveInt, - memo, + tokenInfo, encodedToken as string, ) @@ -307,7 +341,7 @@ export const ReceiveScreen = observer(function ReceiveScreen({ route }: Props) { 0 ? receivedAmount : amountToReceive} + value={amountToReceive} onChangeText={() => {}} unit={unit} editable={false} @@ -358,7 +392,7 @@ export const ReceiveScreen = observer(function ReceiveScreen({ route }: Props) { )} - {token && toNumber(amountToReceive) > 0 && ( + {toNumber(amountToReceive) > 0 && mintUrl && ( <> {transactionStatus !== TransactionStatus.COMPLETED && ( - {CashuUtils.getMintsFromToken(token).map((mintUrl, index) => { - const mint = mintsStore.findByUrl(mintUrl) - if (!mint) { - return ( - } - /> - ) - } else { - return ( - - ) - } - })} + {isNewMint ? ( + } + /> + + ) : ( + + )} } /> diff --git a/src/screens/SendScreen.tsx b/src/screens/SendScreen.tsx index d5d58b27..b2ba6350 100644 --- a/src/screens/SendScreen.tsx +++ b/src/screens/SendScreen.tsx @@ -52,7 +52,7 @@ import { QRCodeBlock } from './Wallet/QRCode' import numbro from 'numbro' import { TranItem } from './TranDetailScreen' import { MemoInputCard } from '../components/MemoInputCard' -import { PaymentRequest as CashuPaymentRequest, PaymentRequestTransport, PaymentRequestTransportType, decodePaymentRequest, getDecodedToken, getTokenMetadata } from '@cashu/cashu-ts' +import { PaymentRequest as CashuPaymentRequest, PaymentRequestTransport, PaymentRequestTransportType, decodePaymentRequest, getDecodedToken } from '@cashu/cashu-ts' import { ProfilePointer } from 'nostr-tools/nip19' import { MINIBITS_NIP05_DOMAIN } from '@env' import FastImage from 'react-native-fast-image' @@ -714,82 +714,85 @@ export const SendScreen = observer(function SendScreen({ route }: Props) { const onMintBalanceConfirm = async function () { - - if(!mintBalanceToSendFrom) { - throw new AppError(Err.VALIDATION_ERROR, 'Please select mint balance to send from.') - } - - const { amount: amountToSendInt } = validateAndProcessAmount(amountToSend, unitRef.current) - const exactMatchProofs: Proof[] = [] - - if(!isOnlineRef.current) { - const availableProofs = proofsStore.getByMint(mintBalanceToSendFrom.mintUrl, { isPending: false, unit: unitRef.current }); - const autoSelectedProofs = CashuUtils.getProofsToSend(amountToSendInt, availableProofs) - const autoSelectedAmount = CashuUtils.getProofsAmount(autoSelectedProofs) - const isExactMatch = autoSelectedAmount === amountToSendInt - - log.trace("[onMintBalanceConfirm]", {isOnline: isOnlineRef.current, amountToSendInt, autoSelectedAmount, isExactMatch}) - - // setSelectedProofs(autoSelectedProofs) // - - if(!isExactMatch) { - // TODO need to improve algo auto-selected proofs - - /* setAmountToSend(numbro(selectedAmount / getCurrency(unitRef.current).precision).format({ - thousandSeparated: true, - mantissa: getCurrency(unitRef.current).mantissa - })) */ - resetSelectedProofs() - // show proof selector modal - setIsProofSelectorModalVisible(true) - - return - } else { - exactMatchProofs.push(...autoSelectedProofs) + try { + if(!mintBalanceToSendFrom) { + throw new AppError(Err.VALIDATION_ERROR, 'Please select mint balance to send from.') } - } - - setIsLoading(true) - - //@ts-ignore - const p2pk: { - pubkey: string; - locktime?: number; - refundKeys?: Array - } = {} - log.trace('[onMintBalanceConfirm] lockedPubkey', { lockedPubkey }) + const { amount: amountToSendInt } = validateAndProcessAmount(amountToSend, unitRef.current) + const exactMatchProofs: Proof[] = [] - if (lockedPubkey && lockedPubkey.length > 0) { - if (lockedPubkey.startsWith('npub')) { - p2pk.pubkey = '02' + NostrClient.getHexkey(lockedPubkey) - } else { - if (lockedPubkey.length === 64) { - p2pk.pubkey = '02' + lockedPubkey - } else if (lockedPubkey.length === 66) { - p2pk.pubkey = lockedPubkey + if(!isOnlineRef.current) { + const availableProofs = proofsStore.getByMint(mintBalanceToSendFrom.mintUrl, { isPending: false, unit: unitRef.current }); + const autoSelectedProofs = CashuUtils.getProofsToSend(amountToSendInt, availableProofs) + const autoSelectedAmount = CashuUtils.getProofsAmount(autoSelectedProofs) + const isExactMatch = autoSelectedAmount === amountToSendInt + + log.trace("[onMintBalanceConfirm]", {isOnline: isOnlineRef.current, amountToSendInt, autoSelectedAmount, isExactMatch}) + + // setSelectedProofs(autoSelectedProofs) // + + if(!isExactMatch) { + // TODO need to improve algo auto-selected proofs + + /* setAmountToSend(numbro(selectedAmount / getCurrency(unitRef.current).precision).format({ + thousandSeparated: true, + mantissa: getCurrency(unitRef.current).mantissa + })) */ + resetSelectedProofs() + // show proof selector modal + setIsProofSelectorModalVisible(true) + + return } else { - throw new AppError(Err.VALIDATION_ERROR, 'Invalid key. Please provide public key in NPUB or HEX format.') + exactMatchProofs.push(...autoSelectedProofs) } } - if (lockTime && lockTime > 0) { - p2pk.locktime = getUnixTime(new Date(Date.now() + lockTime * 24 * 60 * 60)) - log.trace('[onMintBalanceConfirm] Locktime', { pubkey: p2pk.pubkey, locktime: p2pk.locktime }) + setIsLoading(true) + + //@ts-ignore + const p2pk: { + pubkey: string; + locktime?: number; + refundKeys?: Array + } = {} + + log.trace('[onMintBalanceConfirm] lockedPubkey', { lockedPubkey }) + + if (lockedPubkey && lockedPubkey.length > 0) { + if (lockedPubkey.startsWith('npub')) { + p2pk.pubkey = '02' + NostrClient.getHexkey(lockedPubkey) + } else { + if (lockedPubkey.length === 64) { + p2pk.pubkey = '02' + lockedPubkey + } else if (lockedPubkey.length === 66) { + p2pk.pubkey = lockedPubkey + } else { + throw new AppError(Err.VALIDATION_ERROR, 'Invalid key. Please provide public key in NPUB or HEX format.') + } + } + + if (lockTime && lockTime > 0) { + p2pk.locktime = getUnixTime(new Date(Date.now() + lockTime * 24 * 60 * 60)) + log.trace('[onMintBalanceConfirm] Locktime', { pubkey: p2pk.pubkey, locktime: p2pk.locktime }) + } } + + const result = await WalletTask.sendQueueAwaitable( + mintBalanceToSendFrom as MintBalance, + amountToSendInt, + unitRef.current, + memo, + exactMatchProofs.length > 0 ? exactMatchProofs : selectedProofs, // autoSelected proofs are not yet in state + p2pk, + draftTransactionIdRef.current || undefined + ) + + await handleSendTaskResult(result) + } catch (e: any) { + handleError(e) } - - const result = await WalletTask.sendQueueAwaitable( - mintBalanceToSendFrom as MintBalance, - amountToSendInt, - unitRef.current, - memo, - exactMatchProofs.length > 0 ? exactMatchProofs : selectedProofs, // autoSelected proofs are not yet in state - p2pk, - draftTransactionIdRef.current || undefined - ) - - await handleSendTaskResult(result) } @@ -909,6 +912,10 @@ export const SendScreen = observer(function SendScreen({ route }: Props) { throw new AppError(Err.VALIDATION_ERROR, 'Missing token to send.') } + if(!mintBalanceToSendFrom) { + throw new AppError(Err.VALIDATION_ERROR, 'Missing mint balance context.') + } + setIsNostrDMSending(true) let messageContent: string | undefined = undefined @@ -923,16 +930,15 @@ export const SendScreen = observer(function SendScreen({ route }: Props) { } // keysetsV2 support - const tokenInfo = getTokenMetadata(encodedTokenToSend) - const mintKeysetIds = mintsStore.findByUrl(tokenInfo.mint)?.keysetIds + const mintKeysetIds = mintsStore.findByUrl(mintBalanceToSendFrom.mintUrl)?.keysetIds if(!mintKeysetIds || mintKeysetIds.length === 0) { throw new AppError(Err.NOTFOUND_ERROR, 'Missing keysetIds in the wallet state', { - mintUrl: tokenInfo.mint + mintUrl: mintBalanceToSendFrom.mintUrl }) } - + const decodedTokenToSend = getDecodedToken(encodedTokenToSend, mintKeysetIds) - + messageContent = JSON.stringify({ id: decodedCashuPaymentRequest.id, mint: decodedTokenToSend.mint, @@ -1011,17 +1017,20 @@ export const SendScreen = observer(function SendScreen({ route }: Props) { throw new AppError(Err.VALIDATION_ERROR, 'Missing payment request to pay.') } + if (!mintBalanceToSendFrom) { + throw new AppError(Err.VALIDATION_ERROR, 'Missing mint balance context.') + } + setIsPostSending(true) // keysetsV2 support - const tokenInfo = getTokenMetadata(encodedTokenToSend) - const mintKeysetIds = mintsStore.findByUrl(tokenInfo.mint)?.keysetIds + const mintKeysetIds = mintsStore.findByUrl(mintBalanceToSendFrom.mintUrl)?.keysetIds if(!mintKeysetIds || mintKeysetIds.length === 0) { throw new AppError(Err.NOTFOUND_ERROR, 'Missing keysetIds in the wallet state', { - mintUrl: tokenInfo.mint + mintUrl: mintBalanceToSendFrom.mintUrl }) } - + const decodedTokenToSend = getDecodedToken(encodedTokenToSend, mintKeysetIds) const payload = { diff --git a/src/screens/TranDetailScreen.tsx b/src/screens/TranDetailScreen.tsx index 5046b288..bfbb05ea 100644 --- a/src/screens/TranDetailScreen.tsx +++ b/src/screens/TranDetailScreen.tsx @@ -55,7 +55,7 @@ import { pollerExists } from '../utils/poller' import { CommonActions, StaticScreenProps, useFocusEffect, useNavigation } from '@react-navigation/native' import { QRCodeBlock } from './Wallet/QRCode' import { MintListItem } from './Mints/MintListItem' -import { Token, getDecodedToken } from '@cashu/cashu-ts' +import { Token, TokenMetadata, getDecodedToken, getTokenMetadata } from '@cashu/cashu-ts' import FastImage from 'react-native-fast-image' import { MintHeader } from './Mints/MintHeader' import { TransferOption } from './TransferScreen' @@ -625,11 +625,11 @@ const ReceiveInfoBlock = function (props: { setIsResultModalVisible(previousState => !previousState) - const increaseProofsCounter = async function (tokenToRetry: Token) { + const increaseProofsCounter = async function (unit: MintUnit) { const {mint} = transaction const walletInstance = await walletStore.getWallet( mint, - tokenToRetry.unit as MintUnit, + unit, {withSeed: true} ) @@ -647,25 +647,23 @@ const ReceiveInfoBlock = function (props: { try { // keysetsV2 support - const mintKeysetIds = mintsStore.findByUrl(transaction.mint)?.keysetIds - if(!mintKeysetIds || mintKeysetIds.length === 0) { - throw new AppError(Err.NOTFOUND_ERROR, 'Missing keysetIds in the wallet state', { - mintUrl: transaction.mint - }) - } - - const tokenToRetry = getDecodedToken(transaction.inputToken, mintKeysetIds) - const amountToReceive = CashuUtils.getProofsAmount(tokenToRetry.proofs) - const memo = tokenToRetry.memo || '' + const tokenInfo = getTokenMetadata(transaction.inputToken) if(isCounterIncreaseNeeded) { - increaseProofsCounter(tokenToRetry) + increaseProofsCounter(transaction.unit) + } + + const mintInstance = mintsStore.findByUrl(transaction.mint) + + if(!mintInstance) { + throw new AppError(Err.NOTFOUND_ERROR, 'Mint not found in the wallet state, cannot retry receive', { + mintUrl: transaction.mint + }) } const result = await WalletTask.receiveQueueAwaitable( - tokenToRetry, - amountToReceive, - memo, + mintInstance, + tokenInfo, transaction.inputToken ) diff --git a/src/services/cashu/cashuUtils.ts b/src/services/cashu/cashuUtils.ts index c20ba58a..6ef3abb9 100644 --- a/src/services/cashu/cashuUtils.ts +++ b/src/services/cashu/cashuUtils.ts @@ -382,8 +382,11 @@ const getP2PKLocktime = function (secret: string): number | undefined { } -const isTokenP2PKLocked = function (token: Token) { - const secrets = token.proofs.map((p) => p.secret) +const isTokenP2PKLocked = function (token: Token | TokenMetadata): boolean { + + const proofs = 'proofs' in token ? token.proofs : token.incompleteProofs + const secrets = proofs.map((p) => p.secret) + for (const secret of secrets) { try { if (getP2PKPubkeySecret(secret)) { diff --git a/src/services/wallet/receiveTask.ts b/src/services/wallet/receiveTask.ts index 0d0430c2..7d879a4a 100644 --- a/src/services/wallet/receiveTask.ts +++ b/src/services/wallet/receiveTask.ts @@ -29,7 +29,7 @@ export const RECEIVE_BY_CASHU_PAYMENT_REQUEST_TASK = 'receiveByCashuPaymentReque export const receiveTask = async function ( token: Token, - amountToReceive: number, + amountToReceive: number, memo: string, encodedToken: string, ): Promise { @@ -121,8 +121,7 @@ export const receiveTask = async function ( data: JSON.stringify(transactionData), keysetId: receivedProofs[0].id, outputToken, - balanceAfter, - ...(receivedAmount !== amountToReceive && {receivedAmount}), + balanceAfter, ...(swapFeePaid > 0 && {fee: swapFeePaid}) }) @@ -130,7 +129,7 @@ export const receiveTask = async function ( taskFunction: RECEIVE_TASK, mintUrl: mintToReceive, transaction, - message: `You've received ${formatCurrency(receivedAmount, getCurrency(unit).code)} ${getCurrency(unit).code} to your Minibits wallet.`, + message: `You've received ${formatCurrency(amountToReceive, getCurrency(unit).code)} ${getCurrency(unit).code} to your Minibits wallet.${swapFeePaid > 0 ? ` Swap fee paid was ${formatCurrency(swapFeePaid, getCurrency(unit).code)} ${getCurrency(unit).code}.` : ''}`, receivedAmount, receivedProofsCount: receivedProofs.length } as TransactionTaskResult @@ -164,25 +163,17 @@ export const receiveTask = async function ( export const receiveOfflinePrepareTask = async function ( - token: Token, - amountToReceive: number, + mintUrl: string, + unit: MintUnit, + amountToReceive: number, memo: string, encodedToken: string, ) { const transactionData: TransactionData[] = [] let transaction: Transaction | undefined = undefined - let mintToReceive = '' - const unit = token.unit as MintUnit || 'sat' + const mintToReceive = mintUrl try { - mintToReceive = token.mint - - if (!mintToReceive) { - throw new AppError( - Err.VALIDATION_ERROR, - 'Token is missing a mint param.', - ) - } // Let's create new draft receive transaction in database transactionData.push({ @@ -297,6 +288,11 @@ export const receiveOfflineCompleteTask = async function ( throw new AppError(Err.VALIDATION_ERROR, 'Could not find ecash token to redeem', {caller: 'receiveOfflineComplete'}) } + // Ensure mint is in wallet state — may be missing for offline receives from new mints + if(!mintsStore.alreadyExists(transaction.mint)) { + await mintsStore.addMint(transaction.mint) + } + // keysetsV2 support const mintKeysetIds = mintsStore.findByUrl(transaction.mint)?.keysetIds if(!mintKeysetIds || mintKeysetIds.length === 0) { @@ -304,7 +300,7 @@ export const receiveOfflineCompleteTask = async function ( mintUrl: transaction.mint }) } - + const token = getDecodedToken(transaction.inputToken, mintKeysetIds) mintToReceive = token.mint const unit = token.unit || 'sat' @@ -365,7 +361,6 @@ export const receiveOfflineCompleteTask = async function ( keysetId: receivedProofs[0].id, outputToken, balanceAfter, - ...(receivedAmount !== transaction.amount && {receivedAmount}), ...(swapFeePaid > 0 && {fee: swapFeePaid}) }) @@ -373,7 +368,7 @@ export const receiveOfflineCompleteTask = async function ( taskFunction: RECEIVE_OFFLINE_COMPLETE_TASK, mintUrl: mintToReceive, transaction, - message: `You've received ${formatCurrency(receivedAmount, getCurrency(unit).code)} ${getCurrency(unit).code} to your Minibits wallet.`, + message: `You've received ${formatCurrency(receivedAmount, getCurrency(unit).code)} ${getCurrency(unit).code} to your Minibits wallet.${swapFeePaid > 0 ? ` Swap fee paid was ${formatCurrency(swapFeePaid, getCurrency(unit).code)} ${getCurrency(unit).code}.` : ''}`, receivedAmount, } as TransactionTaskResult @@ -559,7 +554,7 @@ export const receiveSync = async function ( const unit = token.unit as MintUnit || 'sat' try { - // Handle missing mint, we add it automatically + // missing mint, should not happen as it is now added before const alreadyExists = mintsStore.alreadyExists(mintToReceive) if (!alreadyExists) { diff --git a/src/services/walletService.ts b/src/services/walletService.ts index 9a8fe84b..66ed866f 100644 --- a/src/services/walletService.ts +++ b/src/services/walletService.ts @@ -15,7 +15,7 @@ import {CashuProof, CashuUtils} from './cashu/cashuUtils' import {LightningUtils} from './lightning/lightningUtils' import AppError, {Err} from '../utils/AppError' import {MintBalance, MintStatus} from '../models/Mint' -import {MeltQuoteBaseResponse, MeltQuoteBolt11Response, MeltQuoteResponse, MeltQuoteState, MintQuoteState, PaymentRequestPayload, Token, getDecodedToken, getEncodedToken, getTokenMetadata} from '@cashu/cashu-ts' +import {MeltQuoteBaseResponse, MeltQuoteBolt11Response, MeltQuoteResponse, MeltQuoteState, MintQuoteState, PaymentRequestPayload, Token, TokenMetadata, getDecodedToken, getEncodedToken, getTokenMetadata} from '@cashu/cashu-ts' import {Mint} from '../models/Mint' import {pollerExists, stopPolling} from '../utils/poller' import { NostrClient, NostrEvent, NostrProfile } from './nostrService' @@ -103,15 +103,12 @@ type WalletTaskService = { draftTransactionId?: number ) => Promise receiveQueueAwaitable: ( - token: Token, - amountToReceive: number, - memo: string, + mint: Mint, + tokenMetadata: TokenMetadata, encodedToken: string, ) => Promise receiveOfflinePrepareQueueAwaitable: ( - token: Token, - amountToReceive: number, - memo: string, + tokenMetadata: TokenMetadata, encodedToken: string, ) => Promise receiveOfflineCompleteQueueAwaitable: ( @@ -288,13 +285,14 @@ const transferQueueAwaitable = function ( const receiveQueueAwaitable = function ( - token: Token, - amountToReceive: number, - memo: string, + mint: Mint, + tokenMetadata: TokenMetadata, encodedToken: string, ): Promise { + const now = Date.now() - const proofsCount = token.proofs.length + const {amount, memo, unit} = tokenMetadata + const proofsCount = tokenMetadata.incompleteProofs.length const taskId = proofsCount > MAX_SWAP_INPUT_SIZE ? `receiveBatchTask-${now}` @@ -326,15 +324,14 @@ const receiveQueueAwaitable = function ( EventEmitter.on(eventName, handler) - const taskPromise = proofsCount > MAX_SWAP_INPUT_SIZE - ? receiveBatchTask(token, amountToReceive, memo, encodedToken) - : receiveTask(token, amountToReceive, memo, encodedToken) - SyncQueue.addPrioritizedTask( taskId, async () => { try { - return await taskPromise + const token = getDecodedToken(encodedToken, mint.keysetIds) + return proofsCount > MAX_SWAP_INPUT_SIZE + ? await receiveBatchTask(token, amount, memo || '', encodedToken) + : await receiveTask(token, amount, memo || '', encodedToken) } catch (error) { rejectOnce(error) throw error @@ -431,13 +428,12 @@ const receiveBatchTask = async function ( const receiveOfflinePrepareQueueAwaitable = function ( - token: Token, - amountToReceive: number, - memo: string, + tokenMetadata: TokenMetadata, encodedToken: string, ): Promise { const now = Date.now() const taskId = `receiveOfflinePrepareTask-${now}` + const { mint: mintUrl, amount, memo, unit } = tokenMetadata return new Promise((resolve, reject) => { let resolved = false @@ -467,9 +463,10 @@ const receiveOfflinePrepareQueueAwaitable = function ( async () => { try { return await receiveOfflinePrepareTask( - token, - amountToReceive, - memo, + mintUrl, + unit as MintUnit, + amount, + memo || '', encodedToken ) } catch (error) { @@ -2393,15 +2390,13 @@ const handleClaimTask = async function (params: { mintUrl: tokenInfo.mint }) } - + decoded = getDecodedToken(encodedToken, mintKeysetIds) - const amountToReceive = CashuUtils.getProofsAmount(decoded.proofs) - const memo = decoded.memo || 'Received to Lightning address' const result: TransactionTaskResult = await receiveTask( decoded, - amountToReceive, - memo, + tokenInfo.amount, + tokenInfo.memo || 'Received to Lightning address', encodedToken, )