diff --git a/package.json b/package.json index fbbb099e..516283aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minibits_wallet", - "version": "0.4.2-beta.1", + "version": "0.4.2-beta.2", "private": true, "scripts": { "android:clean": "cd android && ./gradlew clean", diff --git a/src/models/MintsStore.ts b/src/models/MintsStore.ts index 51a9009a..509bd991 100644 --- a/src/models/MintsStore.ts +++ b/src/models/MintsStore.ts @@ -16,10 +16,6 @@ import { Mint as CashuMint, GetKeysetsResponse, GetKeysResponse, - MintActiveKeys, - MintAllKeysets, - type MintKeys as CashuMintKeys, - type MintKeyset as CashuMintKeyset } from '@cashu/cashu-ts' import { MintUnit } from '../services/wallet/currency' diff --git a/src/screens/NfcPayScreen.tsx b/src/screens/NfcPayScreen.tsx index 4f8e9df2..24222482 100644 --- a/src/screens/NfcPayScreen.tsx +++ b/src/screens/NfcPayScreen.tsx @@ -4,7 +4,7 @@ import { View, TextStyle, } from 'react-native' -import { PaymentRequest as CashuPaymentRequest, MeltQuoteBolt11Response, MeltQuoteResponse, PaymentRequestTransportType, decodePaymentRequest, getDecodedToken } from '@cashu/cashu-ts' +import { PaymentRequest as CashuPaymentRequest, MeltQuoteBolt11Response, MeltQuoteResponse, PaymentRequestTransportType, decodePaymentRequest, getDecodedToken, getTokenMetadata } from '@cashu/cashu-ts' import NfcManager, { Ndef, NfcEvents } from 'react-native-nfc-manager' import { colors, spacing, typography, useThemeColor } from '../theme' import EventEmitter from '../utils/eventEmitter' @@ -530,7 +530,16 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) { // Check for NOSTR transport first const nostrTransport = transports.find(t => t.type === PaymentRequestTransportType.NOSTR) - const decodedTokenToSend = getDecodedToken(encodedTokenToSend) + + // keysetsV2 support + const mintKeysetIds = mintsStore.findByUrl(result.mintUrl)?.keysetIds + if(!mintKeysetIds || mintKeysetIds.length === 0) { + throw new AppError(Err.NOTFOUND_ERROR, 'Missing keysetIds in the wallet state, payment failed', { + mintUrl: result.mintUrl + }) + } + + const decodedTokenToSend = getDecodedToken(encodedTokenToSend, mintKeysetIds) if (nostrTransport) { const decoded = NostrClient.decodeNprofile(nostrTransport.target) @@ -732,8 +741,18 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) { const handleCashuToken = async function (encodedToken: string) { + + // 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 decoded = getDecodedToken(encodedToken, mintKeysetIds) - const decoded = getDecodedToken(encodedToken) const amount = CashuUtils.getProofsAmount(decoded.proofs) const memo = decoded.memo || 'Received over NFC' const result = await WalletTask.receiveQueueAwaitable( diff --git a/src/screens/ReceiveScreen.tsx b/src/screens/ReceiveScreen.tsx index 71dc601b..6dedc49e 100644 --- a/src/screens/ReceiveScreen.tsx +++ b/src/screens/ReceiveScreen.tsx @@ -35,7 +35,7 @@ import { round, toNumber } from '../utils/number' import numbro from 'numbro' import { TranItem } from './TranDetailScreen' import { translate } from '../i18n' -import { Token, getDecodedToken } from '@cashu/cashu-ts' +import { Token, getDecodedToken, getTokenMetadata } from '@cashu/cashu-ts' import { RECEIVE_OFFLINE_PREPARE_TASK, RECEIVE_TASK } from '../services/wallet/receiveTask' import { CurrencyAmount } from './Wallet/CurrencyAmount' @@ -134,8 +134,18 @@ export const ReceiveScreen = observer(function ReceiveScreen({ route }: Props) { try { //@ts-ignore navigation.setParams({encodedToken: undefined}) + + // 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 decoded = getDecodedToken(encoded, mintKeysetIds) - const decoded = getDecodedToken(encoded) const tokenAmount = CashuUtils.getProofsAmount(decoded.proofs) const isLocked = CashuUtils.isTokenP2PKLocked(decoded) let isLockedToWallet = false diff --git a/src/screens/SendScreen.tsx b/src/screens/SendScreen.tsx index f7c7d599..d5d58b27 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 } from '@cashu/cashu-ts' +import { PaymentRequest as CashuPaymentRequest, PaymentRequestTransport, PaymentRequestTransportType, decodePaymentRequest, getDecodedToken, getTokenMetadata } from '@cashu/cashu-ts' import { ProfilePointer } from 'nostr-tools/nip19' import { MINIBITS_NIP05_DOMAIN } from '@env' import FastImage from 'react-native-fast-image' @@ -922,7 +922,16 @@ export const SendScreen = observer(function SendScreen({ route }: Props) { throw new AppError(Err.VALIDATION_ERROR, 'Missing payment request to pay.') } - const decodedTokenToSend = getDecodedToken(encodedTokenToSend) + // keysetsV2 support + const tokenInfo = getTokenMetadata(encodedTokenToSend) + 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 decodedTokenToSend = getDecodedToken(encodedTokenToSend, mintKeysetIds) messageContent = JSON.stringify({ id: decodedCashuPaymentRequest.id, @@ -1004,7 +1013,16 @@ export const SendScreen = observer(function SendScreen({ route }: Props) { setIsPostSending(true) - const decodedTokenToSend = getDecodedToken(encodedTokenToSend) + // keysetsV2 support + const tokenInfo = getTokenMetadata(encodedTokenToSend) + 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 decodedTokenToSend = getDecodedToken(encodedTokenToSend, mintKeysetIds) const payload = { id: decodedCashuPaymentRequest.id, diff --git a/src/screens/TranDetailScreen.tsx b/src/screens/TranDetailScreen.tsx index 643bf056..5046b288 100644 --- a/src/screens/TranDetailScreen.tsx +++ b/src/screens/TranDetailScreen.tsx @@ -645,8 +645,16 @@ const ReceiveInfoBlock = function (props: { setIsLoading(true) - try { - const tokenToRetry = getDecodedToken(transaction.inputToken) + 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 || '' diff --git a/src/screens/Wallet/QRCode.tsx b/src/screens/Wallet/QRCode.tsx index 0d1de177..403e4e6c 100644 --- a/src/screens/Wallet/QRCode.tsx +++ b/src/screens/Wallet/QRCode.tsx @@ -12,11 +12,13 @@ import { colors, spacing, ThemeCode } from "../../theme" import { translate, TxKeyPath } from "../../i18n" import { log } from "../../services" import { MMKVStorage } from "../../services/mmkvStorage" -import { Token, getDecodedToken, getEncodedToken } from '@cashu/cashu-ts' +import { Token, getDecodedToken, getEncodedToken, getTokenMetadata } from '@cashu/cashu-ts' import { NfcService } from '../../services/nfcService'; import { SvgXml } from 'react-native-svg'; import { NfcIcon } from '../../components/NfcIcon'; import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated'; +import { useStores } from '../../models'; +import AppError, { Err } from '../../utils/AppError'; export type QRCodeBlockTypes = 'EncodedV3Token' | 'EncodedV4Token' | 'Bolt11Invoice' | 'URL' | 'NWC' | 'PUBKEY' | 'PaymentRequest' @@ -35,6 +37,8 @@ export const QRCodeBlock = function (props: { ) { const { qrCodeData, title, titleTx, type, size, startNfcOnLoad = false } = props + const {mintsStore} = useStores() + const [qrError, setQrError] = useState() const [encodedV3Token, setEncodedV3Token] = useState() const [decodedToken, setDecodedToken] = useState() @@ -100,10 +104,20 @@ export const QRCodeBlock = function (props: { useEffect(() => { const detectKeysetFormat = () => { if(type === 'EncodedV4Token') { - const decoded = getDecodedToken(qrCodeData) + + // keysetsV2 support + const tokenInfo = getTokenMetadata(qrCodeData) + 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 decoded = getDecodedToken(qrCodeData, mintKeysetIds) setDecodedToken(decoded) - if(decoded.proofs[0].id.startsWith('00')) { + if(decoded.proofs[0].id.startsWith('00') || decoded.proofs[0].id.startsWith('01')) { setKeysetFormat('hex') } else { try { diff --git a/src/services/cashu/cashuUtils.ts b/src/services/cashu/cashuUtils.ts index 3f145ff2..c20ba58a 100644 --- a/src/services/cashu/cashuUtils.ts +++ b/src/services/cashu/cashuUtils.ts @@ -4,10 +4,11 @@ import type { Proof as CashuProof, PaymentRequest as CashuPaymentRequest, PaymentRequestPayload, + TokenMetadata, } from '@cashu/cashu-ts' import { bytesToHex } from '@noble/hashes/utils' import AppError, {Err} from '../../utils/AppError' -import { getDecodedToken } from '@cashu/cashu-ts' +import { getDecodedToken, getTokenMetadata } from '@cashu/cashu-ts' import {Proof} from '../../models/Proof' import { log } from '../logService' import { decodePaymentRequest } from '@cashu/cashu-ts' @@ -87,10 +88,10 @@ const extractEncodedCashuToken = function (maybeToken: string): string { log.trace('[extractEncodedCashuToken] Extract token from', {maybeToken}) let encodedToken: string | undefined = undefined - let decoded: Token | undefined = undefined + let tokenInfo: TokenMetadata | undefined = undefined if (maybeToken && CASHU_TOKEN_PREFIXES.some(pref => maybeToken.startsWith(pref))) { - decoded = getDecodedToken(maybeToken) // throws + tokenInfo = getTokenMetadata(maybeToken) // throws return maybeToken } @@ -105,7 +106,7 @@ const extractEncodedCashuToken = function (maybeToken: string): string { // try to decode if(encodedToken) { - decoded = getDecodedToken(encodedToken) // throws + tokenInfo = getTokenMetadata(encodedToken) // throws return encodedToken } diff --git a/src/services/wallet/receiveTask.ts b/src/services/wallet/receiveTask.ts index 20812981..0d0430c2 100644 --- a/src/services/wallet/receiveTask.ts +++ b/src/services/wallet/receiveTask.ts @@ -296,8 +296,16 @@ export const receiveOfflineCompleteTask = async function ( if (!transaction.inputToken) { throw new AppError(Err.VALIDATION_ERROR, 'Could not find ecash token to redeem', {caller: 'receiveOfflineComplete'}) } + + // 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 token = getDecodedToken(transaction.inputToken) + const token = getDecodedToken(transaction.inputToken, mintKeysetIds) mintToReceive = token.mint const unit = token.unit || 'sat' diff --git a/src/services/walletService.ts b/src/services/walletService.ts index cfbf6738..9a8fe84b 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} from '@cashu/cashu-ts' +import {MeltQuoteBaseResponse, MeltQuoteBolt11Response, MeltQuoteResponse, MeltQuoteState, MintQuoteState, PaymentRequestPayload, Token, getDecodedToken, getEncodedToken, getTokenMetadata} from '@cashu/cashu-ts' import {Mint} from '../models/Mint' import {pollerExists, stopPolling} from '../utils/poller' import { NostrClient, NostrEvent, NostrProfile } from './nostrService' @@ -692,7 +692,9 @@ const swapAllTask = async function (): Promise { ) const encodedTokenToReceive: string = sendResult.encodedTokenToSend - const tokenToReceive = getDecodedToken(encodedTokenToReceive) + + // keysetsV2 support + const tokenToReceive = getDecodedToken(encodedTokenToReceive, mint.keysetIds) const tokenAmount = CashuUtils.getProofsAmount(tokenToReceive.proofs) const receiveResult = await receiveBatchTask( @@ -727,7 +729,7 @@ const swapAllTask = async function (): Promise { ) const encodedTokenToReceive: string = sendResult.encodedTokenToSend - const tokenToReceive = getDecodedToken(encodedTokenToReceive) + const tokenToReceive = getDecodedToken(encodedTokenToReceive, mint.keysetIds) const tokenAmount = CashuUtils.getProofsAmount(tokenToReceive.proofs) const receiveResult = await receiveBatchTask( @@ -815,7 +817,7 @@ const swapByDenominationTask = async function (denomination: number): Promise