mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 11:18:24 +00:00
Finalize keysets V2, bug fixes
This commit is contained in:
@@ -31,7 +31,7 @@
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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,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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user