Finalize keysets V2, bug fixes

This commit is contained in:
minibits-cash
2026-04-13 12:51:18 +02:00
parent 2c9d5c1cf9
commit e3ec278e28
15 changed files with 262 additions and 234 deletions
@@ -31,7 +31,7 @@
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
+1 -1
View File
@@ -133,7 +133,7 @@
"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",
"decodedMissingCurrencyUnit": "Currency unit is missing in the received token. Wallet will assume token amount is in bitcoin $%{unit}. Do not continue if you are not sure this is correct.",
"decodedMissingCurrencyUnit": "Currency unit is missing in the received token.",
"deleteContact": "Delete contact",
"deleteContactDesc": "Remove this contact from your wallet.",
"derivingSeedStatus": "Deriving seed, this takes a while...",
+1 -1
View File
@@ -133,7 +133,7 @@
"copyContactPublicKey": "Copiar la clave pública del contacto",
"copyMnemonicBackupWorkaround": "Para aplicar una copia de seguridad a sus fondos existentes, envíese todo el saldo a usted mismo. De lo contrario, solo se podrán restaurar los fondos a partir de ese momento.",
"copyProofs": "Pruebas de copia",
"decodedMissingCurrencyUnit": "Falta la unidad monetaria en el token recibido. La billetera asumirá que el monto del token está en bitcoin $ %{unit} . No continúe si no está seguro de que sea correcto.",
"decodedMissingCurrencyUnit": "Falta la unidad monetaria en el token recibido.",
"deleteContact": "Eliminar contacto",
"deleteContactDesc": "Elimina este contacto de tu billetera.",
"derivingSeedStatus": "Derivar la semilla, esto lleva un tiempo...",
+1 -1
View File
@@ -133,7 +133,7 @@
"copyContactPublicKey": "Copiar pubkey do contato",
"copyMnemonicBackupWorkaround": "Para aplicar backup aos fundos existentes, envie todo saldo para si mesmo. Caso contrário, apenas fundos futuros poderão ser restaurados.",
"copyProofs": "Copiar proofs",
"decodedMissingCurrencyUnit": "Unidade monetária faltando no token recebido. A carteira assumirá que o valor está em bitcoin $%{unit}. Não continue se incerto.",
"decodedMissingCurrencyUnit": "Unidade monetária faltando no token recebido.",
"deleteContact": "Excluir contato",
"deleteContactDesc": "Remova este contato da sua carteira.",
"derivingSeedStatus": "Derivando seed, isso leva um tempo...",
+1 -1
View File
@@ -133,7 +133,7 @@
"copyContactPublicKey": "Skopíruj verejný kľúč kontaktu",
"copyMnemonicBackupWorkaround": "Aby si odzálohoval svoj existujúci ecash, pošli celý zostatok sáme sebe.",
"copyProofs": "Skopíruj potvrdenia",
"decodedMissingCurrencyUnit": "Mena chýba v prijatom ecash tokene. Peňaženka bude predpokladať, že token je v bitcoin $%{unit}. Nepokračuj ak si si nie istý, že to je správne.",
"decodedMissingCurrencyUnit": "Mena chýba v prijatom ecash tokene.",
"deleteContact": "Vymaž kontakt",
"deleteContactDesc": "Vymaž tento kontakt zo zoznamu.",
"derivingSeedStatus": "Derivujem seed kľúč, môže to cvíľu trvať.",
+2 -5
View File
@@ -2,10 +2,8 @@ import {
Instance,
SnapshotOut,
types,
flow,
destroy,
isStateTreeNode,
detach,
getSnapshot,
} from 'mobx-state-tree'
import {withSetPropAction} from './helpers/withSetPropAction'
@@ -81,7 +79,7 @@ import { MINIBITS_NIP05_DOMAIN } from '@env'
}
},
removeContact(contactToBeRemoved: Contact) {
let contactInstance: Contact | undefined
let contactInstance: Contact | undefined
if (isStateTreeNode(contactToBeRemoved)) {
contactInstance = contactToBeRemoved
@@ -90,9 +88,8 @@ import { MINIBITS_NIP05_DOMAIN } from '@env'
}
if (contactInstance) {
detach(contactInstance) // needed
destroy(contactInstance)
log.debug('[removeContact]', 'Contact removed from MintsStore')
log.debug('[removeContact]', 'Contact removed from ContactsStore')
}
},
selectContact(contact: Contact) {
+2
View File
@@ -168,6 +168,8 @@ export const MintsStoreModel = types
yield mintInstance.setShortname()
self.mints.push(mintInstance)
return mintInstance
}),
updateMint: flow(function* updateMint(mintUrl: string) {
const mintInstance = self.findByUrl(mintUrl)
+7 -2
View File
@@ -1,4 +1,5 @@
import { formatDistance } from 'date-fns'
import { isAlive, isStateTreeNode } from 'mobx-state-tree'
import { observer } from "mobx-react-lite"
import React from "react"
import { Image, ImageStyle, TextStyle, View, ViewStyle } from "react-native"
@@ -16,9 +17,13 @@ export interface ContactListProps {
}
export const ContactListItem = observer(function (props: ContactListProps) {
const { contact } = props
if (isStateTreeNode(contact) && !isAlive(contact as any)) {
return null
}
return (
<ListItem
key={contact.pubkey}
+9 -13
View File
@@ -744,21 +744,17 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
// keysetsV2 support
const tokenInfo = getTokenMetadata(encodedToken)
const mintKeysetIds = mintsStore.findByUrl(tokenInfo.mint)?.keysetIds
if(!mintKeysetIds || mintKeysetIds.length === 0) {
throw new AppError(Err.NOTFOUND_ERROR, 'Missing keysetIds in the wallet state, payment failed', {
mintUrl: tokenInfo.mint
})
}
const {mint: mintUrl, amount, unit, memo} = tokenInfo
const decoded = getDecodedToken(encodedToken, mintKeysetIds)
const amount = CashuUtils.getProofsAmount(decoded.proofs)
const memo = decoded.memo || 'Received over NFC'
let mintInstance = mintsStore.findByUrl(mintUrl)
if(!mintInstance) {
mintInstance = await mintsStore.addMint(mintUrl)
}
const result = await WalletTask.receiveQueueAwaitable(
decoded,
amount,
memo,
mintInstance,
tokenInfo,
encodedToken
)
+94 -66
View File
@@ -35,8 +35,7 @@ import { round, toNumber } from '../utils/number'
import numbro from 'numbro'
import { TranItem } from './TranDetailScreen'
import { translate } from '../i18n'
import { Token, getDecodedToken, getTokenMetadata } from '@cashu/cashu-ts'
import { RECEIVE_OFFLINE_PREPARE_TASK, RECEIVE_TASK } from '../services/wallet/receiveTask'
import { TokenMetadata, getTokenMetadata } from '@cashu/cashu-ts'
import { CurrencyAmount } from './Wallet/CurrencyAmount'
export enum ReceiveOption {
@@ -58,10 +57,12 @@ export const ReceiveScreen = observer(function ReceiveScreen({ route }: Props) {
const {mintsStore, walletStore, userSettingsStore} = useStores()
const [token, setToken] = useState<Token | undefined>()
const [tokenInfo, setTokenInfo] = useState<TokenMetadata | undefined>()
const [encodedToken, setEncodedToken] = useState<string | undefined>()
const [amountToReceive, setAmountToReceive] = useState<string>('0')
const [unit, setUnit] = useState<MintUnit>('sat')
const [mintUrl, setMintUrl] = useState<string | undefined>(undefined)
const [mint, setMint] = useState<Mint | undefined>(undefined)
const [totalReceived, setTotalReceived] = useState<number>(0)
const [receivedAmount, setReceivedAmount] = useState<string>('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<AppError | undefined>()
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) {
<View style={$amountContainer}>
<AmountInput
ref={amountInputRef}
value={toNumber(receivedAmount) > 0 ? receivedAmount : amountToReceive}
value={amountToReceive}
onChangeText={() => {}}
unit={unit}
editable={false}
@@ -358,7 +392,7 @@ export const ReceiveScreen = observer(function ReceiveScreen({ route }: Props) {
)}
</View>
<View style={$contentContainer}>
{token && toNumber(amountToReceive) > 0 && (
{toNumber(amountToReceive) > 0 && mintUrl && (
<>
{transactionStatus !== TransactionStatus.COMPLETED && (
<Card
@@ -385,28 +419,22 @@ export const ReceiveScreen = observer(function ReceiveScreen({ route }: Props) {
label={'Receive to'}
ContentComponent={
<>
{CashuUtils.getMintsFromToken(token).map((mintUrl, index) => {
const mint = mintsStore.findByUrl(mintUrl)
if (!mint) {
return (
<ListItem
key={mintUrl}
text={new URL(mintUrl).hostname}
topSeparator={true}
RightComponent={<Text size='xs' tx="newMint" style={$newBadge}/>}
/>
)
} else {
return (
<MintListItem
key={mintUrl}
mint={mint as Mint}
isSelected={true}
isSelectable={true}
/>
)
}
})}
{isNewMint ? (
<ListItem
key={mintUrl}
text={new URL(mintUrl).hostname}
//topSeparator={true}
RightComponent={<Text size='xs' tx="newMint" style={$newBadge}/>}
/>
) : (
<MintListItem
key={mintUrl}
mint={mint as Mint}
isSelected={true}
isSelectable={true}
/>
)}
</>
}
/>
+86 -77
View File
@@ -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<string>
} = {}
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<string>
} = {}
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 = {
+15 -17
View File
@@ -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
)
+5 -2
View File
@@ -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)) {
+15 -20
View File
@@ -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<TransactionTaskResult> {
@@ -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) {
+22 -27
View File
@@ -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<TransactionTaskResult>
receiveQueueAwaitable: (
token: Token,
amountToReceive: number,
memo: string,
mint: Mint,
tokenMetadata: TokenMetadata,
encodedToken: string,
) => Promise<TransactionTaskResult>
receiveOfflinePrepareQueueAwaitable: (
token: Token,
amountToReceive: number,
memo: string,
tokenMetadata: TokenMetadata,
encodedToken: string,
) => Promise<TransactionTaskResult>
receiveOfflineCompleteQueueAwaitable: (
@@ -288,13 +285,14 @@ const transferQueueAwaitable = function (
const receiveQueueAwaitable = function (
token: Token,
amountToReceive: number,
memo: string,
mint: Mint,
tokenMetadata: TokenMetadata,
encodedToken: string,
): Promise<TransactionTaskResult> {
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<TransactionTaskResult>(
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<TransactionTaskResult> {
const now = Date.now()
const taskId = `receiveOfflinePrepareTask-${now}`
const { mint: mintUrl, amount, memo, unit } = tokenMetadata
return new Promise<TransactionTaskResult>((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,
)