Merge branch 'cashu-ts-2.0'

This commit is contained in:
minibits-cash
2024-12-10 23:02:39 +01:00
42 changed files with 544 additions and 1174 deletions
+2 -3
View File
@@ -1,6 +1,6 @@
{
"name": "minibits_wallet",
"version": "0.1.10-beta.4",
"version": "0.1.10-beta.5",
"private": true,
"scripts": {
"android:clean": "cd android && ./gradlew clean",
@@ -19,8 +19,7 @@
"postinstall": "patch-package --exclude 'nothing'"
},
"dependencies": {
"@cashu/cashu-ts": "minibits-cash/cashu-ts#commit=8dcac7ce6d192c419aee5547f0db03c23b593a57",
"@exodus/borc": "^2.1.1",
"@cashu/cashu-ts": "minibits-cash/cashu-ts#cashu-ts-2.0",
"@fortawesome/fontawesome-svg-core": "^6.6.0",
"@fortawesome/free-brands-svg-icons": "^6.6.0",
"@fortawesome/free-regular-svg-icons": "^6.6.0",
+2 -2
View File
@@ -14,8 +14,8 @@ import AppError, { Err } from '../utils/AppError'
import { MintUnit, MintUnits } from '../services/wallet/currency'
import { getRootStore } from './helpers/getRootStore'
import { generateId } from '../utils/utils'
import { ProofV3 } from '../services/cashu/cashuUtils'
import { Proof } from './Proof'
import { CashuProof } from '../services/cashu/cashuUtils'
export type MintBalance = {
@@ -369,7 +369,7 @@ export const MintModel = types
self.proofsCounters = cast(self.proofsCounters)
},
getMintFeeReserve(proofs: ProofV3[] | Proof[]): number {
getMintFeeReserve(proofs: CashuProof[] | Proof[]): number {
// Find the corresponding keyset for each proof and sum the input fees
const totalInputFees = proofs.reduce((sum, proof) => {
const keyset = self.keysets.find(k => k.id === proof.id)
+65 -69
View File
@@ -1,30 +1,31 @@
import {Instance, SnapshotOut, types, flow, getRoot, getSnapshot} from 'mobx-state-tree'
import {
AmountPreference,
import {
CashuMint,
CashuWallet,
MeltQuoteResponse,
MeltTokensResponse,
setGlobalRequestOptions,
deriveSeedFromMnemonic,
MeltQuoteResponse,
setGlobalRequestOptions,
type MintKeys,
type MintKeyset,
MintAllKeysets,
MintActiveKeys
MintActiveKeys,
OutputAmounts,
Token,
MeltProofsResponse,
ProofState,
CheckStateEnum
} from '@cashu/cashu-ts'
import { debounce } from "lodash"
import { isObj } from '@cashu/cashu-ts/src/utils'
import { JS_BUNDLE_VERSION } from '@env'
import {KeyChain, MinibitsClient} from '../services'
import {log} from '../services/logService'
import AppError, { Err } from '../utils/AppError'
import { Currencies, CurrencyCode, MintUnit } from '../services/wallet/currency'
import { CashuUtils, ProofV3, TokenV3 } from '../services/cashu/cashuUtils'
import { CashuProof, CashuUtils } from '../services/cashu/cashuUtils'
import { Proof } from './Proof'
import { isObj } from '@cashu/cashu-ts/src/utils'
import { Mint } from './Mint'
import { getRootStore } from './helpers/getRootStore'
import { getUnixTime } from 'date-fns/getUnixTime'
import { boolean } from 'mobx-state-tree/dist/internal'
import {deriveSeedFromMnemonic} from '@cashu/crypto/modules/client/NUT09'
/*
Not persisted, in-memory only model of the cashu-ts wallet instances and seed.
@@ -136,7 +137,7 @@ export const WalletStoreModel = types
return new Uint8Array(Buffer.from(self.seedBase64, 'base64'))
}
const seed = yield KeyChain.loadSeed()
const seed: Uint8Array = yield KeyChain.loadSeed()
if (!seed) {
return undefined
@@ -218,8 +219,7 @@ export const WalletStoreModel = types
// syncs mint model in wallet state and returns cashu-ts mint class instance
const cashuMint = yield self.getMint(mintUrl)
// get uptodate mint model from wallet state
// const mintsStore = getRootStore(self).mintsStore
// get uptodate mint model from wallet state
const mintInstance = self.getMintModelInstance(mintUrl)
if(!mintInstance) {
throw new AppError(Err.NOTFOUND_ERROR, 'Mint not found in the wallet state.', {
@@ -234,7 +234,7 @@ export const WalletStoreModel = types
const requestedKeys = mintInstance.keys!.find((k: MintKeys) => k.id === options.keysetId)
if(!requestedKeys) {
throw new AppError(Err.NOTFOUND_ERROR, 'Wallet has not keys with provided keyset id.', {
throw new AppError(Err.NOTFOUND_ERROR, 'Mint has no keys with provided keyset id.', {
mintUrl,
keysetId: options.keysetId
})
@@ -253,7 +253,7 @@ export const WalletStoreModel = types
// if not we find active keyset with lowest fees and related keys
const activeKeyset: MintKeyset = self.getOptimalKeyset(mintInstance, unit) // throws
log.trace('[WalletStore.getWallet]', {activeKeyset, mintUrl})
log.trace('[WalletStore.getWallet] Optimal keyset for this unit', {activeKeyset, unit, mintUrl})
const activeKeys = mintInstance.keys!.find((k: MintKeys) => k.id === activeKeyset.id)
@@ -271,7 +271,7 @@ export const WalletStoreModel = types
const seedWallet: CashuWallet | undefined = self.seedWallets.find(
w => w.mint.mintUrl === mintUrl &&
w.keys.id === walletKeys.id
w.keysetId === walletKeys.id
)
if (seedWallet) {
@@ -283,10 +283,13 @@ export const WalletStoreModel = types
seed = yield self.getSeed()
const newSeedWallet = new CashuWallet(cashuMint, {
keys: walletKeys,
mnemonicOrSeed: seed
unit,
keys: mintInstance.keys,
keysets: mintInstance.keysets,
bip39seed: seed
})
newSeedWallet.keysetId = walletKeys.id
self.seedWallets.push(newSeedWallet)
log.trace('[WalletStore.getWallet]', 'Returning NEW cashuWallet instance with seed', {mintUrl})
@@ -296,7 +299,7 @@ export const WalletStoreModel = types
const wallet: CashuWallet | undefined = self.wallets.find(
w => w.mint.mintUrl === mintUrl &&
w.keys.id === walletKeys.id
w.keysetId === walletKeys.id
)
if (wallet) {
@@ -305,10 +308,13 @@ export const WalletStoreModel = types
}
const newWallet = new CashuWallet(cashuMint, {
keys: walletKeys,
mnemonicOrSeed: undefined
unit,
keys: mintInstance.keys,
keysets: mintInstance.keysets,
bip39seed: undefined
})
newWallet.keysetId = walletKeys.id
self.wallets.push(newWallet)
log.trace('[WalletStore.getWallet]', 'Returning NEW cashuWallet instance', {mintUrl})
@@ -347,10 +353,9 @@ export const WalletStoreModel = types
receive: flow(function* receive(
mintUrl: string,
unit: MintUnit,
decodedToken: TokenV3,
swapFeeReserve: number,
decodedToken: Token,
options: {
preference: AmountPreference[],
outputAmounts: OutputAmounts,
counter: number
}) {
@@ -362,17 +367,16 @@ export const WalletStoreModel = types
withSeed: true,
})
const amountToReceive: number = CashuUtils.getTokenAmounts(decodedToken).totalAmount
const amountToReceive: number = CashuUtils.getProofsAmount(decodedToken.proofs)
// log.trace('[WalletStore.receive]', {decodedToken, keysetId: cashuWallet.keysetId, outputAmounts: options.outputAmounts, counter: options.counter})
const proofs = yield cashuWallet.receive(
decodedToken,
swapFeeReserve,
decodedToken,
{
keysetId: cashuWallet.keys.id,
preference: options.preference,
keysetId: cashuWallet.keysetId,
outputAmounts: options.outputAmounts,
counter: options.counter,
pubkey: undefined,
privkey: undefined
})
const receivedAmount: number = CashuUtils.getProofsAmount(proofs as Proof[])
@@ -388,13 +392,12 @@ export const WalletStoreModel = types
}
}),
send: flow(function* send(mintUrl: string,
amountToSend: number,
swapFeeReserve: number,
amountToSend: number,
unit: MintUnit,
proofsToSendFrom: Proof[],
options: {
preference: AmountPreference[],
counter: number
outputAmounts: OutputAmounts,
counter: number,
} ) {
try {
const cashuWallet = yield self.getWallet(
@@ -406,25 +409,23 @@ export const WalletStoreModel = types
log.debug('[WalletStore.send] counter', options.counter)
const {returnChange, send} = yield cashuWallet.send(
amountToSend,
swapFeeReserve,
const {keep, send} = yield cashuWallet.swap(
amountToSend,
proofsToSendFrom,
{
keysetId: cashuWallet.keys.id,
preference: options.preference,
keysetId: cashuWallet.keysetId,
outputAmounts: options.outputAmounts,
counter: options.counter,
pubkey: undefined,
privkey: undefined
includeFees: false // fee reserve needs to be already in proofsToSendFrom
}
)
log.trace(`[WalletStore.send] ${returnChange.length} returnedProofs`)
log.trace(`[WalletStore.send] ${keep.length} returnedProofs`)
log.trace(`[WalletStore.send] ${send.length} proofsToSend`)
// do some basic validations that proof amounts from mints match
const totalAmountToSendFrom: number = CashuUtils.getProofsAmount(proofsToSendFrom)
const returnedAmount: number = CashuUtils.getProofsAmount(returnChange)
const returnedAmount: number = CashuUtils.getProofsAmount(keep)
const sendAmount: number = CashuUtils.getProofsAmount(send)
if (sendAmount !== amountToSend) {
@@ -437,8 +438,8 @@ export const WalletStoreModel = types
const swapFeePaid = totalAmountToSendFrom - amountToSend - returnedAmount
return {
returnedProofs: returnChange as ProofV3[],
proofsToSend: send as ProofV3[],
returnedProofs: keep as CashuProof[],
proofsToSend: send as CashuProof[],
swapFeePaid
}
} catch (e: any) {
@@ -455,23 +456,20 @@ export const WalletStoreModel = types
)
}
}),
getSpentOrPendingProofsFromMint: flow(function* getSpentOrPendingProofsFromMint(
getProofsStatesFromMint: flow(function* getProofsStatesFromMint(
mintUrl: string,
unit: MintUnit,
proofs: Proof[]
) {
try {
log.trace('[WalletStore.getSpentOrPendingProofsFromMint] start', {mintUrl, unit})
log.trace('[WalletStore.getProofsStatesFromMint] start', {mintUrl, unit})
const cashuWallet = yield self.getWallet(mintUrl, unit, {withSeed: false})
const spentPendingProofs = yield cashuWallet.checkProofsSpent(proofs) // seems to work for all units
const proofsByState: {[CheckStateEnum: string]: Proof[]} = yield cashuWallet.checkProofsStates(proofs)
log.trace('[WalletStore.getSpentOrPendingProofsFromMint]', {mintUrl, spentPendingProofs})
log.trace('[WalletStore.getProofsStatesFromMint]', {mintUrl, proofsByState})
return spentPendingProofs as {
spent: ProofV3[]
pending: ProofV3[]
}
return proofsByState
} catch (e: any) {
let message = 'Could not get response from the mint.'
@@ -481,7 +479,7 @@ export const WalletStoreModel = types
message,
{
message: e.message,
caller: 'Wallet.getSpentOrPendingProofsFromMint',
caller: 'WalletStore.getProofsStatesFromMint',
mintUrl
}
)
@@ -565,23 +563,22 @@ export const WalletStoreModel = types
unit: MintUnit,
mintQuote: string,
options: {
preference: AmountPreference[],
outputAmounts: OutputAmounts,
counter: number
}
) {
try {
const cashuWallet: CashuWallet = yield self.getWallet(mintUrl, unit, {withSeed: true}) // with seed
const {proofs} = yield cashuWallet.mintTokens(
const proofs = yield cashuWallet.mintProofs(
amount,
mintQuote,
{
keysetId: cashuWallet.keys.id,
preference: options.preference,
counter: options.counter,
pubkey: undefined
keysetId: cashuWallet.keysetId,
outputAmounts: options.outputAmounts,
counter: options.counter,
}
) as Promise<{proofs: ProofV3[]}>
)
log.info('[mintProofs]', {proofs})
@@ -637,7 +634,7 @@ export const WalletStoreModel = types
mintUrl: string,
unit: MintUnit,
lightningMeltQuote: MeltQuoteResponse, // invoice is stored by mint by quote
proofsToPayFrom: ProofV3[], // proofAmount >= amount + fee_reserve
proofsToPayFrom: CashuProof[], // proofAmount >= amount + fee_reserve
options: {
counter: number
}
@@ -645,12 +642,11 @@ export const WalletStoreModel = types
try {
const cashuWallet: CashuWallet = yield self.getWallet(mintUrl, unit, {withSeed: true}) // with seed
const meltResponse: MeltTokensResponse =
yield cashuWallet.meltTokens(
const meltResponse: MeltProofsResponse = yield cashuWallet.meltProofs(
lightningMeltQuote,
proofsToPayFrom,
{
keysetId: cashuWallet.keys.id,
keysetId: cashuWallet.keysetId,
counter: options.counter
}
)
@@ -689,7 +685,7 @@ export const WalletStoreModel = types
const seedWallet = new CashuWallet(cashuMint, {
unit: 'sat', // just use default unit as we restore by keyset
mnemonicOrSeed: seed
bip39seed: seed
})
const count = Math.abs(indexTo - indexFrom)
+4 -10
View File
@@ -9,9 +9,7 @@ import { Icon, Text } from "../components"
import { translate } from "../i18n"
import {
WalletScreen,
ReceiveOptionsScreen,
ReceiveScreen,
SendOptionsScreen,
SendScreen,
ScanScreen,
LightningPayScreen,
@@ -129,14 +127,12 @@ export function TabsNavigator() {
export type WalletStackParamList = {
Wallet: {scannedMintUrl? : string}
ReceiveOptions: {unit: MintUnit} // to be removed
Wallet: {scannedMintUrl? : string}
TokenReceive: {
unit: MintUnit
mintUrl?: string,
}
Receive: {unit: MintUnit, encodedToken? : string}
SendOptions: {unit: MintUnit}
Receive: {unit: MintUnit, encodedToken? : string}
Send: {
unit: MintUnit,
paymentOption?: SendOption,
@@ -189,11 +185,9 @@ const WalletNavigator = function WalletNavigator() {
headerShown: false,
}}
>
<WalletStack.Screen name="Wallet" component={WalletScreen} />
<WalletStack.Screen name="ReceiveOptions" component={ReceiveOptionsScreen} />
<WalletStack.Screen name="Wallet" component={WalletScreen} />
<WalletStack.Screen name="TokenReceive" component={TokenReceiveScreen} />
<WalletStack.Screen name="Receive" component={ReceiveScreen} />
<WalletStack.Screen name="SendOptions" component={SendOptionsScreen} />
<WalletStack.Screen name="Receive" component={ReceiveScreen} />
<WalletStack.Screen name="Send" component={SendScreen} />
<WalletStack.Screen name="Scan" component={ScanScreen} />
<WalletStack.Screen name="LightningPay" component={LightningPayScreen} />
+2 -2
View File
@@ -12,8 +12,8 @@ import { ContactType } from '../models/Contact'
import { NostrClient, log } from '../services'
import { getImageSource } from '../utils/utils'
import { verticalScale } from '@gocodingnow/rn-size-matters'
import { ReceiveOption } from './ReceiveOptionsScreen'
import { SendOption } from './SendOptionsScreen'
import { ReceiveOption } from './ReceiveScreen'
import { SendOption } from './SendScreen'
import { IncomingDataType, IncomingParser } from '../services/incomingParser'
import { translate } from '../i18n'
+4 -5
View File
@@ -1,11 +1,10 @@
import { formatDistance } from 'date-fns'
import { observer } from "mobx-react-lite"
import React from "react"
import { Image, ImageStyle, ScrollView, TextStyle, View, ViewStyle } from "react-native"
import { Button, Icon, ListItem, Screen, Text } from "../../components"
import { Contact, ContactType } from "../../models/Contact"
import { NostrClient } from "../../services"
import { colors, spacing, typography, useThemeColor } from "../../theme"
import { Image, ImageStyle, TextStyle, View, ViewStyle } from "react-native"
import { ListItem } from "../../components"
import { Contact } from "../../models/Contact"
import { spacing } from "../../theme"
import { getImageSource } from '../../utils/utils'
+1 -1
View File
@@ -11,7 +11,7 @@ import { TransactionStatus } from '../../models/Transaction'
import { poller, stopPolling } from '../../utils/poller'
import { ResultModalInfo } from '../Wallet/ResultModalInfo'
import { MINIBITS_NIP05_DOMAIN } from '@env'
import { SendOption } from '../SendOptionsScreen'
import { SendOption } from '../SendScreen'
import { translate } from '../../i18n'
import { CurrencyAmount } from '../Wallet/CurrencyAmount'
import { CurrencyCode } from '../../services/wallet/currency'
+4 -4
View File
@@ -13,8 +13,8 @@ import { log } from '../../services/logService'
import { ContactListItem } from './ContactListItem'
import { Contact, ContactType } from '../../models/Contact'
import { StackNavigationProp } from '@react-navigation/stack'
import { ReceiveOption } from '../ReceiveOptionsScreen'
import { SendOption } from '../SendOptionsScreen'
import { ReceiveOption } from '../ReceiveScreen'
import { SendOption } from '../SendScreen'
import { infoMessage, warningMessage } from '../../utils/utils'
import { IncomingDataType, IncomingParser } from '../../services/incomingParser'
import { translate } from '../../i18n'
@@ -422,8 +422,7 @@ const $newContainer: TextStyle = {
const $contactInput: TextStyle = {
flex: 1,
// borderRadius: 0,
// borderBottomRightRadius: spacing.extraSmall,
// borderTopLeftRadius: spacing.extraSmall,
borderRadius: spacing.extraSmall,
fontSize: verticalScale(16),
padding: spacing.small,
alignSelf: 'stretch',
@@ -433,6 +432,7 @@ const $contactInput: TextStyle = {
const $contactDomain: TextStyle = {
marginRight: spacing.small,
marginLeft: -spacing.small,
borderTopRightRadius: spacing.extraSmall,
borderBottomRightRadius: spacing.extraSmall,
padding: spacing.extraSmall,
+2 -2
View File
@@ -13,8 +13,8 @@ import { log } from '../../services/logService'
import { Contact, ContactType } from '../../models/Contact'
import { StackNavigationProp } from '@react-navigation/stack'
import { ContactsStackParamList } from '../../navigation'
import { SendOption } from '../SendOptionsScreen'
import { ReceiveOption } from '../ReceiveOptionsScreen'
import { SendOption } from '../SendScreen'
import { ReceiveOption } from '../ReceiveScreen'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { IncomingDataType, IncomingParser } from '../../services/incomingParser'
import { translate } from '../../i18n'
+2 -2
View File
@@ -10,8 +10,8 @@ import { PrivateContacts } from './Contacts/PrivateContacts'
import { PublicContacts } from './Contacts/PublicContacts'
import { log } from '../services/logService'
import { getImageSource } from '../utils/utils'
import { ReceiveOption } from './ReceiveOptionsScreen'
import { SendOption } from './SendOptionsScreen'
import { ReceiveOption } from './ReceiveScreen'
import { SendOption } from './SendScreen'
import { verticalScale } from '@gocodingnow/rn-size-matters'
+12 -15
View File
@@ -28,7 +28,7 @@ import AppError from '../utils/AppError'
import { Proof } from '../models/Proof'
import { useStores } from '../models'
import EventEmitter from '../utils/eventEmitter'
import { CashuUtils, ProofV3, TokenV3 } from '../services/cashu/cashuUtils'
import { CashuProof, CashuUtils } from '../services/cashu/cashuUtils'
import Clipboard from '@react-native-clipboard/clipboard'
import { translate } from '../i18n'
import { ProofsStoreSnapshot } from '../models/ProofsStore'
@@ -39,6 +39,7 @@ import { Database, TransactionTaskResult, WalletTask, WalletTaskResult } from '.
import { Transaction, TransactionStatus } from '../models/Transaction'
import { ResultModalInfo } from './Wallet/ResultModalInfo'
import { verticalScale } from '@gocodingnow/rn-size-matters'
import { Token, getDecodedToken, getEncodedToken } from '@cashu/cashu-ts'
interface ExportBackupScreenProps extends SettingsStackScreenProps<'ExportBackup'> {}
@@ -117,9 +118,9 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
// now we batch receive the pending encoded token
// this forces the proofs swap with the mint for standard denomination amounts
const encodedTokenToReceive: string = result.encodedTokenToSend
const tokenToReceive = CashuUtils.decodeToken(encodedTokenToReceive)
const {totalAmount: tokenAmount} = CashuUtils.getTokenAmounts(tokenToReceive)
const proofsCount = tokenToReceive.token[0].proofs.length
const tokenToReceive = getDecodedToken(encodedTokenToReceive)
const tokenAmount = CashuUtils.getProofsAmount(tokenToReceive.proofs)
const proofsCount = tokenToReceive.proofs.length
setTotalSentProofsCount(prev => prev + proofsCount)
setIsReceiveBatchSentToQueue(true)
@@ -366,11 +367,11 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
for (const keysetId in groupedByKeyset) {
const proofsByKeysetId = groupedByKeyset[keysetId]
const proofsToExport: ProofV3[] = []
const proofsToExport: CashuProof[] = []
for (const p of proofsByKeysetId) {
// clean private params
const proofToExport: ProofV3 = {
const proofToExport: CashuProof = {
id: p.id,
amount: p.amount,
secret: p.secret,
@@ -380,19 +381,15 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
proofsToExport.push(proofToExport)
}
const tokenByKeysetId: TokenV3 = {
token: [
{
mint,
proofs: proofsToExport
}
],
const tokenByKeysetId: Token = {
mint,
proofs: proofsToExport,
unit: proofsByKeysetId[0].unit
}
log.trace('[copyEncodedTokens]', {tokenByKeysetId})
const encodedByMint = CashuUtils.encodeToken(tokenByKeysetId)
const encodedByMint = getEncodedToken(tokenByKeysetId)
encodedTokens.push(encodedByMint)
}
}
@@ -501,7 +498,7 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
continue
}
const amount = CashuUtils.getProofsAmount(proofsToImport)
const amount = sumProofs(proofsToImport)
const unit = proofsByKeysetId[0].unit
log.trace('[doLocalRecovery] to be recovered', {mint, keysetId, unit, amount})
+2 -2
View File
@@ -25,9 +25,9 @@ import { Database, KeyChain, log, MinibitsClient, NostrClient } from '../service
import Clipboard from '@react-native-clipboard/clipboard'
import { rootStoreInstance, useStores } from '../models'
import {MnemonicInput} from './Recovery/MnemonicInput'
import { TransactionData, TransactionStatus } from '../models/Transaction'
import { TransactionStatus } from '../models/Transaction'
import { ResultModalInfo } from './Wallet/ResultModalInfo'
import { deriveSeedFromMnemonic } from '@cashu/cashu-ts'
import {deriveSeedFromMnemonic} from '@cashu/crypto/modules/client/NUT09'
import { MINIBITS_MINT_URL, MINIBITS_NIP05_DOMAIN } from '@env'
import { delay } from '../utils/utils'
import { applySnapshot} from 'mobx-state-tree'
+3 -4
View File
@@ -10,13 +10,12 @@ import {colors, spacing, useThemeColor} from '../theme'
import {log} from '../services/logService'
import { IncomingDataType, IncomingParser } from '../services/incomingParser'
import AppError, { Err } from '../utils/AppError'
import { Button, Card, ErrorModal, Header, Icon, InfoModal, ListItem, ScanIcon, Screen, Text } from '../components'
import { LnurlUtils } from '../services/lnurl/lnurlUtils'
import { Button, Card, ErrorModal, Icon, InfoModal, ListItem, ScanIcon, Screen, Text } from '../components'
import { infoMessage } from '../utils/utils'
import Clipboard from '@react-native-clipboard/clipboard'
import { SvgXml } from 'react-native-svg'
import { SendOption } from './SendOptionsScreen'
import { CurrencyCode, MintUnit } from '../services/wallet/currency'
import { SendOption } from './SendScreen'
import { MintUnit } from '../services/wallet/currency'
import { useStores } from '../models'
import { Mint } from '../models/Mint'
import { MintHeader } from './Mints/MintHeader'
+3 -4
View File
@@ -1,5 +1,5 @@
import React, { FC, useEffect, useMemo, useState } from 'react'
import { GetInfoResponse, NUT15Entry, NUT17Entry, SwapMethod } from '@cashu/cashu-ts'
import { GetInfoResponse, SwapMethod } from '@cashu/cashu-ts'
import { isObj } from '@cashu/cashu-ts/src/utils'
import Clipboard from '@react-native-clipboard/clipboard'
import { observer } from 'mobx-react-lite'
@@ -21,7 +21,6 @@ import {
Screen,
Text,
} from '../components'
import { AvatarHeader } from '../components/AvatarHeader'
import { CollapsibleText } from '../components/CollapsibleText'
import { translate } from '../i18n'
import { useStores } from '../models'
@@ -387,11 +386,11 @@ function NutsCard(props: {info: GetInfoResponse}) {
// detailed nuts are separated from simple ones if we want to show more info abt them in the future
for (const [nut, info] of Object.entries(props.info.nuts)) {
if (nut === '15' && (info as NUT15Entry[]).length > 0) {
if (nut === '15' && info) {
// see https://github.com/cashubtc/nuts/blob/main/15.md - multipath payments
// in the future, it might be nice to show for which currencies are multipath payments supported
// for example by extending NutItem
nutsSimple.push(['15', (info as NUT15Entry[])[0]?.mpp ?? false])
nutsSimple.push(['15', (info)[0]?.mpp ?? false])
continue;
}
// bug: nutshell 0.15.3 returns NUT17Entry[], instead of { supported: NUT17Entry[] }
+6 -22
View File
@@ -5,7 +5,7 @@ import Clipboard from '@react-native-clipboard/clipboard'
import {
MINIBITS_MINT_URL
} from '@env'
import {spacing, typography, useThemeColor} from '../theme'
import {colors, spacing, typography, useThemeColor} from '../theme'
import {SettingsStackScreenProps} from '../navigation'
import {
Button,
@@ -279,6 +279,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(
const iconColor = useThemeColor('textDim')
const inputBg = useThemeColor('background')
const headerTitle = useThemeColor('headerTitle')
const buttonBorder = useThemeColor('card')
return (
<Screen preset="scroll" contentContainerStyle={$screen}>
@@ -432,7 +433,9 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(
tx='common.paste'
style={{
borderRadius: 0,
marginLeft: 1,
marginLeft: -spacing.small,
borderLeftWidth: 1,
borderLeftColor: buttonBorder
}}
onPress={pasteMintUrl}
/>
@@ -532,32 +535,13 @@ const $card: ViewStyle = {
// paddingTop: 0,
}
const $cardHeading: TextStyle = {
fontFamily: typography.primary?.medium,
fontSize: 20,
}
const $cardContent: TextStyle = {
fontSize: 14,
}
const $bottomContainer: ViewStyle = {
flex: 1,
justifyContent: 'flex-end',
marginBottom: spacing.large,
alignSelf: 'stretch',
}
const $bottomModal: ViewStyle = {
alignItems: 'center',
}
const $mintInput: TextStyle = {
flex: 1,
borderTopLeftRadius: spacing.extraSmall,
borderBottomLeftRadius: spacing.extraSmall,
// fontSize: 16,
borderRadius: spacing.extraSmall,
padding: spacing.extraSmall,
alignSelf: 'stretch',
// textAlignVertical: 'top',
+1 -1
View File
@@ -107,7 +107,7 @@ export const MnemonicScreen: FC<SettingsStackScreenProps<'Mnemonic'>> = observer
const headerTitle = useThemeColor('headerTitle')
return (
<Screen style={$screen}>
<Screen contentContainerStyle={$screen}>
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
<Text preset="heading" tx='backupScreen.seedBackup' style={{color: headerTitle}} />
</View>
-2
View File
@@ -1,6 +1,5 @@
import {observer} from 'mobx-react-lite'
import { Observer } from 'mobx-react-lite'
import Clipboard from '@react-native-clipboard/clipboard'
import React, {FC, useEffect, useRef, useState} from 'react'
import {FlatList, TextInput, TextStyle, View, ViewStyle} from 'react-native'
import {colors, spacing, useThemeColor} from '../theme'
@@ -14,7 +13,6 @@ import { verticalScale } from '@gocodingnow/rn-size-matters'
import { NwcConnection } from '../models/NwcStore'
import { QRCodeBlock } from './Wallet/QRCode'
import { CollapsibleText } from '../components/CollapsibleText'
import { isSameDay } from 'date-fns/isSameDay'
interface NwcScreenProps extends SettingsStackScreenProps<'Nwc'> {}
+3 -2
View File
@@ -2,6 +2,7 @@ import {observer} from 'mobx-react-lite'
import React, {FC, useRef, useState} from 'react'
import {Image, LayoutAnimation, Platform, TextInput, TextStyle, UIManager, View, ViewStyle} from 'react-native'
import {getPublicKey} from 'nostr-tools'
import { hexToBytes } from '@noble/hashes/utils'
import {colors, spacing, typography, useThemeColor} from '../theme'
import {ContactsStackScreenProps} from '../navigation'
import {Icon, ListItem, Screen, Text, Card, BottomModal, Button, InfoModal, ErrorModal, Loading} from '../components'
@@ -169,7 +170,7 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
}
// validate that nsec matches profile pubkey
const privateKey = NostrClient.getHexkey(ownNsec)
const publicKey = getPublicKey(privateKey)
const publicKey = getPublicKey(hexToBytes(privateKey))
if(publicKey !== ownProfile.pubkey) {
throw new AppError(Err.VALIDATION_ERROR, translate("nsecPrivatePublicKeyMismatchError"), {publicKey})
@@ -397,7 +398,7 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
isVisible={isProfileChangeCompleted ? true : false}
ContentComponent={
<View style={$bottomModal}>
<ProfileHeader headerBgColor='transparent' />
<ProfileHeader headerBg='transparent' />
<Text
style={{color: textResult, textAlign: 'center', marginTop: spacing.small}}
tx="nostr.lastStandDialog.complete"
@@ -26,10 +26,6 @@ import AppError from '../../utils/AppError'
import { PaymentRequest } from '../../models/PaymentRequest'
import { StackNavigationProp } from '@react-navigation/stack'
import { PaymentRequestListItem } from './PaymentRequestListItem'
import QRCode from 'react-native-qrcode-svg'
import { infoMessage } from '../../utils/utils'
import Clipboard from '@react-native-clipboard/clipboard'
import { translate } from '../../i18n'
import { QRCodeBlock } from '../Wallet/QRCode'
@@ -19,7 +19,7 @@ import {
import {log} from '../../services'
import {colors, spacing, useThemeColor} from '../../theme'
import {getImageSource} from '../../utils/utils'
import {SendOption} from '../SendOptionsScreen'
import {SendOption} from '../SendScreen'
import {translate} from '../../i18n'
import { CurrencyAmount } from '../Wallet/CurrencyAmount'
-4
View File
@@ -4,7 +4,6 @@ import {Switch, TextStyle, View, ViewStyle} from 'react-native'
import {colors, spacing, useThemeColor} from '../theme'
import {SettingsStackScreenProps} from '../navigation'
import {
Icon,
ListItem,
Screen,
Text,
@@ -18,10 +17,7 @@ import {
import {useHeader} from '../utils/useHeader'
import {useStores} from '../models'
import AppError from '../utils/AppError'
import {ResultModalInfo} from './Wallet/ResultModalInfo'
import { KeyChain, MinibitsClient, } from '../services'
import { log } from '../services/logService'
import { OwnKeysScreen } from './OwnKeysScreen'
import { MINIBITS_NIP05_DOMAIN } from '@env'
import { CommonActions, StackActions } from '@react-navigation/native'
import { translate } from '../i18n'
-170
View File
@@ -1,170 +0,0 @@
import {observer} from 'mobx-react-lite'
import React, {FC, useState} from 'react'
import {TextStyle, View, ViewStyle} from 'react-native'
import { SvgXml } from 'react-native-svg'
import {spacing, useThemeColor, colors} from '../theme'
import {WalletStackScreenProps} from '../navigation'
import {
Card,
Screen,
InfoModal,
ErrorModal,
ListItem,
Text,
ScanIcon,
} from '../components'
import {useHeader} from '../utils/useHeader'
import {log} from '../services/logService'
import AppError from '../utils/AppError'
import useIsInternetReachable from '../utils/useIsInternetReachable'
export enum ReceiveOption {
// CREATE_AND_SEND_PAYMENT_REQUEST = 'CREATE_AND_SEND_PAYMENT_REQUEST',
SEND_PAYMENT_REQUEST = 'SEND_PAYMENT_REQUEST',
PASTE_OR_SCAN_TOKEN = 'PASTE_OR_SCAN_TOKEN',
SHOW_INVOICE = 'SHOW_INVOICE',
LNURL_WITHDRAW = 'LNURL_WITHDRAW'
}
export const ReceiveOptionsScreen: FC<WalletStackScreenProps<'ReceiveOptions'>> = observer(
function ReceiveOptionsScreen({route, navigation}) {
useHeader({
leftIcon: 'faArrowLeft',
onLeftPress: () => navigation.goBack(),
})
const isInternetReachable = useIsInternetReachable()
const [info, setInfo] = useState('')
const [error, setError] = useState<AppError | undefined>()
const gotoContacts = function () {
navigation.navigate('ContactsNavigator', {
screen: 'Contacts',
params: {
paymentOption: ReceiveOption.SEND_PAYMENT_REQUEST
}
})
}
const gotoTopup = function () {
navigation.navigate('Topup', { paymentOption: ReceiveOption.SHOW_INVOICE })
}
const onScan = async function () {
navigation.navigate('Scan')
}
const handleError = function (e: AppError): void {
log.error(e.name, e.message)
setError(e)
}
const headerBg = useThemeColor('header')
const iconColor = useThemeColor('textDim')
return (
<Screen preset="auto" contentContainerStyle={$screen}>
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
<Text
preset="heading"
tx="receiveScreen.title"
style={{color: 'white'}}
/>
</View>
<View style={$contentContainer}>
<Card
style={$optionsCard}
ContentComponent={
<>
<ListItem
tx="receiveScreen.sharePaymentRequest"
subTx="receiveScreen.sharePaymentRequestDescription"
leftIcon='faPaperPlane'
leftIconColor={colors.palette.secondary300}
leftIconInverse={true}
style={$item}
bottomSeparator={true}
onPress={gotoContacts}
/>
<ListItem
tx="receiveScreen.showOrShareInvoice"
subTx="receiveScreen.showOrShareInvoiceDescription"
leftIcon='faBolt'
leftIconColor={colors.palette.orange200}
leftIconInverse={true}
style={$item}
onPress={gotoTopup}
/>
<ListItem
tx="receiveScreen.scanToReceive"
subTx="receiveScreen.scanToReceiveDescription"
LeftComponent={
<View
style={{
marginEnd: spacing.small,
flex: 0,
borderRadius: spacing.small,
padding: spacing.extraSmall,
backgroundColor: colors.palette.success200
}}
>
<SvgXml
width={spacing.medium}
height={spacing.medium}
xml={ScanIcon}
fill='white'
/>
</View>
}
// leftIcon='faQrcode'
// leftIconColor={colors.palette.success200}
// leftIconInverse={true}
style={$item}
topSeparator={true}
onPress={onScan}
/>
</>
}
/>
</View>
{info && <InfoModal message={info} />}
{error && <ErrorModal error={error} />}
</Screen>
)
},
)
const $screen: ViewStyle = {
flex: 1,
}
const $headerContainer: TextStyle = {
alignItems: 'center',
padding: spacing.medium,
height: spacing.screenHeight * 0.20,
}
const $contentContainer: TextStyle = {
// flex: 1,
padding: spacing.extraSmall,
// alignItems: 'center',
}
const $optionsCard: ViewStyle = {
marginTop: -spacing.extraLarge * 2,
marginBottom: spacing.small,
//paddingTop: 0,
}
const $item: ViewStyle = {
paddingHorizontal: spacing.small,
paddingLeft: 0,
}
+21 -12
View File
@@ -24,24 +24,33 @@ import {log} from '../services/logService'
import AppError, { Err } from '../utils/AppError'
import EventEmitter from '../utils/eventEmitter'
import {CashuUtils, TokenV3} from '../services/cashu/cashuUtils'
import {CashuUtils, CashuProof} from '../services/cashu/cashuUtils'
import {ResultModalInfo} from './Wallet/ResultModalInfo'
import {MintListItem} from './Mints/MintListItem'
import useIsInternetReachable from '../utils/useIsInternetReachable'
import { verticalScale } from '@gocodingnow/rn-size-matters'
import { CurrencyCode, MintUnit, formatCurrency, getCurrency } from "../services/wallet/currency"
import { CurrencyCode, MintUnit, getCurrency } from "../services/wallet/currency"
import { MintHeader } from './Mints/MintHeader'
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'
export enum ReceiveOption {
// CREATE_AND_SEND_PAYMENT_REQUEST = 'CREATE_AND_SEND_PAYMENT_REQUEST',
SEND_PAYMENT_REQUEST = 'SEND_PAYMENT_REQUEST',
PASTE_OR_SCAN_TOKEN = 'PASTE_OR_SCAN_TOKEN',
SHOW_INVOICE = 'SHOW_INVOICE',
LNURL_WITHDRAW = 'LNURL_WITHDRAW'
}
export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
function ReceiveScreen({route, navigation}) {
const isInternetReachable = useIsInternetReachable()
const {mintsStore, walletStore} = useStores()
const [token, setToken] = useState<TokenV3 | undefined>()
const [token, setToken] = useState<Token | undefined>()
const [encodedToken, setEncodedToken] = useState<string | undefined>()
const [amountToReceive, setAmountToReceive] = useState<string>('0')
const [unit, setUnit] = useState<MintUnit>('sat')
@@ -165,11 +174,11 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
try {
navigation.setParams({encodedToken: undefined})
const decoded = CashuUtils.decodeToken(encoded)
const tokenAmounts = CashuUtils.getTokenAmounts(decoded)
const decoded = getDecodedToken(encoded)
const tokenAmount = CashuUtils.getProofsAmount(decoded.proofs)
log.trace('decoded token', {decoded})
log.trace('tokenAmounts', {tokenAmounts})
log.trace('tokenAmount', {tokenAmount})
if(!decoded.unit) {
setInfo(translate("decodedMissingCurrencyUnit", { unit: CurrencyCode.SAT }))
@@ -179,7 +188,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
const currency = getCurrency(decoded.unit as MintUnit)
setToken(decoded)
setAmountToReceive(numbro(tokenAmounts.totalAmount / currency.precision).format({thousandSeparated: true, mantissa: currency.mantissa}))
setAmountToReceive(numbro(tokenAmount / currency.precision).format({thousandSeparated: true, mantissa: currency.mantissa}))
setUnit(decoded.unit as MintUnit)
if (decoded.memo && decoded.memo.length > 0) {
@@ -197,12 +206,12 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
setIsReceiveTaskSentToQueue(true)
const amountToReceiveInt = round(toNumber(amountToReceive) * getCurrency(unit).precision, 0)
const proofsCount = token!.token.flatMap(entry => entry.proofs).length
const proofsCount = token!.proofs.length
if(proofsCount > MAX_SWAP_INPUT_SIZE) {
WalletTask.receiveBatch(
token as TokenV3,
token as Token,
amountToReceiveInt,
memo,
encodedToken as string,
@@ -211,7 +220,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
} else {
WalletTask.receive(
token as TokenV3,
token as Token,
amountToReceiveInt,
memo,
encodedToken as string,
@@ -234,7 +243,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
)
const mintInstance = mintsStore.findByUrl(mint)
const counter = mintInstance!.getProofsCounterByKeysetId!(walletInstance.keys.id)
const counter = mintInstance!.getProofsCounterByKeysetId!(walletInstance.keysetId)
counter!.increaseProofsCounter(20)
// retry receive
@@ -254,7 +263,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
const amountToReceiveInt = round(toNumber(amountToReceive) * getCurrency(unit).precision, 0)
WalletTask.receiveOfflinePrepare(
token as TokenV3,
token as Token,
amountToReceiveInt,
memo,
encodedToken as string,
+1 -3
View File
@@ -6,17 +6,15 @@ import {
ViewStyle,
View,
TextStyle,
TextInput,
} from 'react-native'
import {WalletStackScreenProps} from '../navigation'
import {Camera, CameraType} from 'react-native-camera-kit'
import { URDecoder } from '@gandlaf21/bc-ur'
import {spacing, typography, useThemeColor} from '../theme'
import {useHeader} from '../utils/useHeader'
import {log} from '../services/logService'
import { IncomingDataType, IncomingParser } from '../services/incomingParser'
import AppError, { Err } from '../utils/AppError'
import { BottomModal, Button, ErrorModal, Header, Icon, Screen, Text } from '../components'
import { Button, ErrorModal, Header, Icon, Screen } from '../components'
import { LnurlUtils } from '../services/lnurl/lnurlUtils'
import { infoMessage } from '../utils/utils'
import Clipboard from '@react-native-clipboard/clipboard'
+3 -3
View File
@@ -31,7 +31,7 @@ import { CashuUtils } from '../services/cashu/cashuUtils'
import { Proof } from '../models/Proof'
import { Transaction, TransactionData, TransactionRecord, TransactionStatus, TransactionType } from '../models/Transaction'
import { ResultModalInfo } from './Wallet/ResultModalInfo'
import { deriveSeedFromMnemonic } from '@cashu/cashu-ts'
import {deriveSeedFromMnemonic} from '@cashu/crypto/modules/client/NUT09'
import { MINIBITS_MINT_URL, MINIBITS_NIP05_DOMAIN } from '@env'
import { delay } from '../utils/utils'
import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
@@ -39,7 +39,7 @@ import { scale } from '@gocodingnow/rn-size-matters'
import { WalletUtils } from '../services/wallet/utils'
import { WalletScreen } from './WalletScreen'
import { MintUnit, formatCurrency, getCurrency } from '../services/wallet/currency'
import { isObj } from '@cashu/cashu-ts/src/utils'
import { isObj, sumProofs } from '@cashu/cashu-ts/src/utils'
import { WalletProfileRecord } from '../models/WalletProfileStore'
import { translate } from '../i18n'
@@ -271,7 +271,7 @@ export const SeedRecoveryScreen: FC<AppStackScreenProps<'SeedRecovery'>> = obser
setStatusMessage(translate("recovery.spentProofsAmount", { amount: spent.length }))
const spentAmount = CashuUtils.getProofsAmount(spent as Proof[])
const spentAmount = sumProofs(spent as Proof[])
alreadySpentAmount += spentAmount
const unspent = proofs.filter((proof: Proof) => !spent.includes(proof))
-153
View File
@@ -1,153 +0,0 @@
import {observer} from 'mobx-react-lite'
import React, {FC, useState, useCallback, useEffect} from 'react'
import {Alert, TextStyle, View, ViewStyle} from 'react-native'
import {spacing, useThemeColor, colors} from '../theme'
import {WalletStackScreenProps} from '../navigation'
import {
Button,
Icon,
Card,
Screen,
ListItem,
Text,
} from '../components'
import {useHeader} from '../utils/useHeader'
import {log} from '../services/logService'
import AppError from '../utils/AppError'
export enum SendOption {
SEND_TOKEN = 'SEND_TOKEN',
PASTE_OR_SCAN_INVOICE = 'PASTE_OR_SCAN_INVOICE',
SHOW_TOKEN = 'SHOW_TOKEN',
PAY_PAYMENT_REQUEST = 'PAY_PAYMENT_REQUEST',
LNURL_PAY = 'LNURL_PAY',
LNURL_ADDRESS = 'LNURL_ADDRESS',
DONATION = 'DONATION',
}
export const SendOptionsScreen: FC<WalletStackScreenProps<'SendOptions'>> = observer(
function SendOptionsScreen({route, navigation}) {
useHeader({
leftIcon: 'faArrowLeft',
onLeftPress: () => navigation.goBack(),
})
const [error, setError] = useState<AppError | undefined>()
const gotoContacts = function () {
navigation.navigate('ContactsNavigator', {
screen: 'Contacts',
params: {paymentOption: SendOption.SEND_TOKEN}})
}
const gotoSend = function () {
navigation.navigate('Send', {
paymentOption: SendOption.SHOW_TOKEN}
)
}
const gotoLightningPay = async function () {
navigation.navigate('LightningPay')
}
const handleError = function (e: AppError): void {
log.error(e.name, e.message)
setError(e)
}
const headerBg = useThemeColor('header')
const subtitleColor = useThemeColor('textDim')
return (
<Screen preset="auto" contentContainerStyle={$screen}>
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
<Text
preset="heading"
tx="sendScreen.title"
style={{color: 'white'}}
/>
</View>
<View style={$contentContainer}>
<Card
style={$ecashCard}
ContentComponent={
<>
<ListItem
tx="sendScreen.sendToContact"
subTx="sendScreen.sendToContactDescription"
leftIcon='faPaperPlane'
leftIconColor={colors.palette.secondary300}
leftIconInverse={true}
style={$item}
bottomSeparator={true}
onPress={gotoContacts}
/>
<ListItem
tx="sendScreen.showOrShareToken"
subTx="sendScreen.showOrShareTokenDescription"
leftIcon='faMoneyBill1'
leftIconColor={colors.palette.iconViolet300}
leftIconInverse={true}
style={$item}
onPress={gotoSend}
/>
</>
}
/>
<Card
style={$lightningCard}
ContentComponent={
<>
<ListItem
text="Pay with Lightning"
subText="Enter, scan or paste Lightning address, invoice or pay code."
leftIcon='faBolt'
leftIconColor={colors.palette.orange400}
leftIconInverse={true}
style={$item}
onPress={gotoLightningPay}
/>
</>
}
/>
</View>
</Screen>
)
},
)
const $screen: ViewStyle = {
flex: 1,
}
const $headerContainer: TextStyle = {
alignItems: 'center',
padding: spacing.medium,
height: spacing.screenHeight * 0.20,
}
const $contentContainer: TextStyle = {
// flex: 1,
padding: spacing.extraSmall,
// alignItems: 'center',
}
const $ecashCard: ViewStyle = {
marginTop: -spacing.extraLarge * 2,
marginBottom: spacing.small,
// paddingTop: 0,
}
const $lightningCard: ViewStyle = {
marginVertical: spacing.small,
// paddingTop: 0,
}
const $item: ViewStyle = {
paddingHorizontal: spacing.small,
paddingLeft: 0,
}
+12 -4
View File
@@ -53,7 +53,6 @@ import useIsInternetReachable from '../utils/useIsInternetReachable'
import { Proof } from '../models/Proof'
import { Contact, ContactType } from '../models/Contact'
import { getImageSource, infoMessage } from '../utils/utils'
import { SendOption } from './SendOptionsScreen'
import { verticalScale } from '@gocodingnow/rn-size-matters'
import { MintUnit, formatCurrency, getCurrency } from "../services/wallet/currency"
import { MintHeader } from './Mints/MintHeader'
@@ -64,6 +63,15 @@ import numbro from 'numbro'
import { TranItem } from './TranDetailScreen'
import { MemoInputCard } from '../components/MemoInputCard'
export enum SendOption {
SEND_TOKEN = 'SEND_TOKEN',
PASTE_OR_SCAN_INVOICE = 'PASTE_OR_SCAN_INVOICE',
SHOW_TOKEN = 'SHOW_TOKEN',
PAY_PAYMENT_REQUEST = 'PAY_PAYMENT_REQUEST',
LNURL_PAY = 'LNURL_PAY',
LNURL_ADDRESS = 'LNURL_ADDRESS',
DONATION = 'DONATION',
}
if (Platform.OS === 'android' &&
UIManager.setLayoutAnimationEnabledExperimental) {
@@ -449,8 +457,8 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
{withSeed: true}
)
const mintInstance = mintsStore.findByUrl(mintBalanceToSendFrom?.mintUrl as string)
const counter = mintInstance!.getProofsCounterByKeysetId!(walletInstance.keys.id)
counter!.increaseProofsCounter(50)
const counter = mintInstance!.getProofsCounterByKeysetId!(walletInstance.keysetId)
counter!.increaseProofsCounter(20)
// retry send
onMintBalanceConfirm()
@@ -698,7 +706,7 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
<QRCodeBlock
qrCodeData={encodedTokenToSend as string}
title='Ecash token to send'
type='EncodedV3Token'
type='EncodedV4Token'
/>
<TokenOptionsBlock
toggleNostrDMModal={toggleNostrDMModal}
+2 -2
View File
@@ -1,6 +1,6 @@
import {observer} from 'mobx-react-lite'
import React, {FC, useCallback, useEffect, useRef, useState} from 'react'
import {Alert, AppState, TextStyle, View, ViewStyle, useColorScheme} from 'react-native'
import React, {FC, useEffect, useRef, useState} from 'react'
import {AppState, TextStyle, View, ViewStyle, useColorScheme} from 'react-native'
import notifee from '@notifee/react-native'
import messaging from '@react-native-firebase/messaging'
import {
+1 -1
View File
@@ -48,7 +48,7 @@ import {ResultModalInfo} from './Wallet/ResultModalInfo'
import {useFocusEffect} from '@react-navigation/native'
import {Contact, ContactType} from '../models/Contact'
import {getImageSource, infoMessage} from '../utils/utils'
import {ReceiveOption} from './ReceiveOptionsScreen'
import {ReceiveOption} from './ReceiveScreen'
import {LNURLWithdrawParams} from 'js-lnurl'
import {round, roundDown, roundUp, toNumber} from '../utils/number'
import {LnurlClient, LnurlWithdrawResult} from '../services/lnurlService'
+6 -5
View File
@@ -46,7 +46,7 @@ import {Proof} from '../models/Proof'
import useColorScheme from '../theme/useThemeColor'
import useIsInternetReachable from '../utils/useIsInternetReachable'
import { ResultModalInfo } from './Wallet/ResultModalInfo'
import { CashuUtils, TokenV3 } from '../services/cashu/cashuUtils'
import { CashuUtils } from '../services/cashu/cashuUtils'
import { Mint, MintStatus } from '../models/Mint'
import { verticalScale } from '@gocodingnow/rn-size-matters'
import { CurrencySign } from './Wallet/CurrencySign'
@@ -56,6 +56,7 @@ import { pollerExists } from '../utils/poller'
import { useFocusEffect } from '@react-navigation/native'
import { QRCodeBlock } from './Wallet/QRCode'
import { MintListItem } from './Mints/MintListItem'
import { Token, getDecodedToken } from '@cashu/cashu-ts'
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true)
@@ -603,7 +604,7 @@ const ReceiveInfoBlock = function (props: {
setIsResultModalVisible(previousState => !previousState)
const increaseProofsCounter = async function (tokenToRetry: TokenV3) {
const increaseProofsCounter = async function (tokenToRetry: Token) {
const {mint} = transaction
const walletInstance = await walletStore.getWallet(
mint,
@@ -612,7 +613,7 @@ const ReceiveInfoBlock = function (props: {
)
const mintInstance = mintsStore.findByUrl(mint)
const counter = mintInstance!.getProofsCounterByKeysetId!(walletInstance.keys.id)
const counter = mintInstance!.getProofsCounterByKeysetId!(walletInstance.keysetId)
counter!.increaseProofsCounter(20)
}
@@ -624,8 +625,8 @@ const ReceiveInfoBlock = function (props: {
setIsLoading(true)
try {
const tokenToRetry: TokenV3 = CashuUtils.decodeToken(transaction.inputToken)
const amountToReceive = CashuUtils.getTokenAmounts(tokenToRetry).totalAmount
const tokenToRetry = getDecodedToken(transaction.inputToken)
const amountToReceive = CashuUtils.getProofsAmount(tokenToRetry.proofs)
const memo = tokenToRetry.memo || ''
if(isCounterIncreaseNeeded) {
@@ -210,7 +210,7 @@ export const TransactionListItem = observer(function (
amountStyle={{color: txReceiveColor}}
/>
)}
{[TransactionStatus.ERROR, TransactionStatus.BLOCKED, TransactionStatus.PREPARED_OFFLINE].includes(tx.status) && (
{[TransactionStatus.ERROR, TransactionStatus.BLOCKED, TransactionStatus.PREPARED_OFFLINE, TransactionStatus.EXPIRED].includes(tx.status) && (
<CurrencyAmount
amount={tx.amount}
mintUnit={tx.unit}
+2 -2
View File
@@ -36,7 +36,7 @@ import {ResultModalInfo} from './Wallet/ResultModalInfo'
import {addSeconds} from 'date-fns'
import { PaymentRequestStatus } from '../models/PaymentRequest'
import { DecodedLightningInvoice, LightningUtils } from '../services/lightning/lightningUtils'
import { SendOption } from './SendOptionsScreen'
import { SendOption } from './SendScreen'
import { round, roundDown, roundUp, toNumber } from '../utils/number'
import { LnurlClient, LNURLPayParams } from '../services/lnurlService'
import { verticalScale } from '@gocodingnow/rn-size-matters'
@@ -652,7 +652,7 @@ const increaseProofsCounterAndRetry = async function () {
{withSeed: true}
)
const mintInstance = mintsStore.findByUrl(mintBalanceToTransferFrom?.mintUrl as string)
const counter = mintInstance!.getProofsCounterByKeysetId!(walletInstance.keys.id)
const counter = mintInstance!.getProofsCounterByKeysetId!(walletInstance.keysetId)
counter!.increaseProofsCounter(20)
// retry transfer
+27 -26
View File
@@ -9,8 +9,9 @@ import { moderateScale, verticalScale } from "@gocodingnow/rn-size-matters"
import { colors, spacing } from "../../theme"
import { useEffect, useState } from "react"
import { translate } from "../../i18n"
import { CashuUtils, TokenV3 } from "../../services/cashu/cashuUtils"
import { CashuUtils, CashuProof } from "../../services/cashu/cashuUtils"
import { log } from "../../services"
import { Token, getDecodedToken, getEncodedToken } from '@cashu/cashu-ts';
export type QRCodeBlockTypes = 'EncodedV3Token' | 'EncodedV4Token' | 'Bolt11Invoice' | 'URL' | 'NWC'
@@ -26,9 +27,9 @@ export const QRCodeBlock = function (props: {
) {
const {qrCodeData, title, type, size} = props
const [qrError, setQrError] = useState<Error | undefined>()
const [encodedV4Token, setEncodedV4Token] = useState<string | undefined>()
const [decodedV3Token, setDecodedV3Token] = useState<TokenV3>()
const [qrError, setQrError] = useState<Error | undefined>()
const [encodedV3Token, setEncodedV3Token] = useState<string | undefined>()
const [decodedToken, setDecodedToken] = useState<Token>()
const [keysetFormat, setKeysetFormat] = useState<'hex' | 'base64' | undefined>()
const [isLoadingQRCode, setIsLoadingQRCode] = useState<boolean>(false)
const [isQRCodeError, setIsQRCodeError] = useState<boolean>(false)
@@ -37,20 +38,20 @@ export const QRCodeBlock = function (props: {
useEffect(() => {
const detectKeysetFormat = () => {
if(type === 'EncodedV3Token') {
const decoded = CashuUtils.decodeToken(qrCodeData) as TokenV3
setDecodedV3Token(decoded)
if(type === 'EncodedV4Token') {
const decoded = getDecodedToken(qrCodeData)
setDecodedToken(decoded)
if(decoded.token[0].proofs[0].id.startsWith('00')) {
setKeysetFormat('hex')
if(decoded.proofs[0].id.startsWith('00')) {
setKeysetFormat('hex')
} else {
try {
// make v4 default one
const encodedV4 = CashuUtils.encodeToken(decoded, 4)
setEncodedV4Token(encodedV4)
// make v3 legacy format
const encodedV3 = getEncodedToken(decoded, {version: 3})
setEncodedV3Token(encodedV3)
} catch (e: any) {
handleQrError(e)
}
} else {
setKeysetFormat('base64')
}
}
@@ -122,19 +123,19 @@ export const QRCodeBlock = function (props: {
const switchTokenEncoding = function () {
try {
if(encodedV4Token) {
setEncodedV4Token(undefined)
} else if(type === 'EncodedV3Token') {
log.trace('[v3]', qrCodeData)
if(encodedV3Token) {
setEncodedV3Token(undefined)
} else if(type === 'EncodedV4Token') {
log.trace('[v4]', qrCodeData)
if(!decodedV3Token) {
if(!decodedToken) {
return false
}
const encodedV4 = CashuUtils.encodeToken(decodedV3Token, 4)
const encodedV3 = getEncodedToken(decodedToken, {version: 3})
log.trace('[v4]', encodedV4)
setEncodedV4Token(encodedV4)
log.trace('[v3]', encodedV3)
setEncodedV3Token(encodedV3)
}
} catch (e: any) {
handleQrError(e)
@@ -163,7 +164,7 @@ export const QRCodeBlock = function (props: {
const onCopy = function () {
try {
Clipboard.setString(encodedV4Token || qrCodeData as string)
Clipboard.setString(encodedV3Token || qrCodeData as string)
} catch (e: any) {
handleQrError(e)
}
@@ -205,7 +206,7 @@ export const QRCodeBlock = function (props: {
/>
) : (
<QRCode
size={qrCodeSize} value={encodedV4Token || qrCodeData}
size={qrCodeSize} value={encodedV3Token || qrCodeData}
onError={switchToAnimatedQRcodeOnError}
/>
)}
@@ -240,10 +241,10 @@ export const QRCodeBlock = function (props: {
paddingVertical: verticalScale(spacing.tiny)
}}
/>
{type === 'EncodedV3Token' && keysetFormat === 'hex' && !isAnimating && (
{type === 'EncodedV4Token' && keysetFormat === 'hex' && !isAnimating && (
<Button
preset="tertiary"
text={`${encodedV4Token ? 'Old' : 'New'}`}
text={`${encodedV3Token ? 'New' : 'Old'}`}
onPress={switchTokenEncoding}
LeftAccessory={() => <Icon icon='faMoneyBill1' size={spacing.small} color={colors.light.text}/>}
textStyle={{color: colors.light.text, fontSize: 14}}
@@ -254,7 +255,7 @@ export const QRCodeBlock = function (props: {
}}
/>
)}
{type === 'EncodedV3Token' && !isQRCodeError && (
{type === 'EncodedV4Token' && !isQRCodeError && (
<Button
preset="tertiary"
text={`${isAnimating ? 'Static' : 'Animate'}`}
-2
View File
@@ -6,9 +6,7 @@ export * from './ImportBackupScreen'
// WalletNavigator
export * from './WalletScreen'
export * from './ReceiveOptionsScreen'
export * from './ReceiveScreen'
export * from './SendOptionsScreen'
export * from './SendScreen'
export * from './ScanScreen'
export * from './TranDetailScreen'
+20 -274
View File
@@ -1,47 +1,16 @@
import {Mint} from '../../models/Mint'
import {
AmountPreference,
getEncodedToken,
} from '@cashu/cashu-ts'
import cbor from '@exodus/borc'
import type {
Token as TokenV3,
TokenEntry as
TokenEntryV3,
Proof as ProofV3
getEncodedToken,
Token,
Proof as CashuProof,
} from '@cashu/cashu-ts'
import AppError, {Err} from '../../utils/AppError'
import {} from '@cashu/cashu-ts'
import { getDecodedToken } from '@cashu/cashu-ts'
import {Proof} from '../../models/Proof'
import { log } from '../logService'
import { encodeBase64ToJson, encodeBase64toUint8, encodeUint8toBase64 } from '@cashu/cashu-ts/src/base64'
import { bytesToHex, hexToBytes } from '@noble/curves/abstract/utils';
import { sumProofs } from '@cashu/cashu-ts/src/utils'
export type {TokenV3, TokenEntryV3, ProofV3}
interface ProofV4 {
a: number;
s: string;
c: Uint8Array;
d?: {
e: Uint8Array,
s: Uint8Array,
r: Uint8Array
},
w?: string
}
interface TokenEntryV4 {
i: Uint8Array;
p: ProofV4[];
}
interface TokenV4 {
m: string;
u: string;
d?: string;
t: TokenEntryV4[]
}
export {CashuProof}
const CASHU_URI_PREFIXES = [
'https://wallet.nutstash.app/#',
@@ -61,25 +30,16 @@ const findEncodedCashuToken = function (content: string) {
return maybeToken || null
}
const isValidCashuToken = function (text: string) {
for (const prefix of CASHU_URI_PREFIXES) {
if (text && text.startsWith(prefix)) {
text = text.slice(prefix.length)
break // necessary
}
}
return text && CASHU_TOKEN_PREFIXES.some(pref => text.startsWith(pref))
}
const extractEncodedCashuToken = function (maybeToken: string): string {
log.trace('[extractEncodedCashuToken] Extract token from', {maybeToken})
let encodedToken: string | undefined = undefined
let decoded: TokenV3 | undefined = undefined
let decoded: Token | undefined = undefined
if (maybeToken && CASHU_TOKEN_PREFIXES.some(pref => maybeToken.startsWith(pref))) {
decoded = decodeToken(maybeToken) // throws
decoded = getDecodedToken(maybeToken) // throws
return maybeToken
}
@@ -94,58 +54,13 @@ const extractEncodedCashuToken = function (maybeToken: string): string {
// try to decode
if(encodedToken) {
decoded = decodeToken(encodedToken) // throws
decoded = getDecodedToken(encodedToken) // throws
return encodedToken
}
throw new AppError(Err.NOTFOUND_ERROR, 'Could not extract ecash token from the provided string', {maybeToken, caller: 'extractEncodedCashuToken'})
}
/* const decodeToken = function (encoded: string): V3Token {
try {
const decoded = getDecodedToken(encoded)
return decoded as V3Token
} catch (e: any) {
throw new AppError(
Err.VALIDATION_ERROR,
`Provided ecash token is invalid.`,
{message: e.message, encoded},
)
}
} */
function mapToV4TokenEntries(tokenEntries: TokenEntryV3[]): TokenEntryV4[] {
const v4TokenEntries: TokenEntryV4[] = [];
tokenEntries.forEach(entry => {
const idMap: { [id: string]: ProofV4[] } = {};
entry.proofs.forEach(proof => {
if (!idMap[proof.id]) {
idMap[proof.id] = [];
}
idMap[proof.id].push({
a: proof.amount,
s: proof.secret,
c: hexToBytes(proof.C)
});
});
for (const id in idMap) {
v4TokenEntries.push({
i: hexToBytes(id),
p: idMap[id]
});
}
});
return v4TokenEntries
}
function base64urlFromBase64(str: string) {
return str.replace(/\+/g, '-').replace(/\//g, '_').split('=')[0];
// .replace(/=/g, '.');
@@ -156,175 +71,15 @@ function base64urlToBase64(str: string) {
// .replace(/./g, '=');
}
const encodeToken = function (token: TokenV3, version: 3 | 4 = 3): string {
try {
if(version === 3) {
return getEncodedToken(token)
} else if(version === 4) {
const v4tokenEntries = mapToV4TokenEntries(token.token)
const v4Token: TokenV4 = {
m: token.token[0].mint as string,
u: token.unit as string,
d: token.memo as string,
t: v4tokenEntries
}
// log.trace('[encodeToken]', {v4Token})
const encodedCbor = cbor.encode(v4Token)
return 'cashuB' + base64urlFromBase64(encodeUint8toBase64(encodedCbor))
} else {
throw new Error('Invalid version.')
}
} catch (e: any) {
throw new AppError(
Err.VALIDATION_ERROR,
`Error encoding token to version {${version}} format.`,
{message: e.message},
)
}
const getProofsAmount = function (proofs: Array<Proof | CashuProof>): number {
return sumProofs(proofs as CashuProof[])
}
/**
* Helper function to decode cashu tokens into object
* @param token an encoded cashu token (cashuAey...)
* @returns cashu token object
*/
function decodeToken(token: string) {
// remove prefixes
const uriPrefixes = [...CASHU_URI_PREFIXES, 'cashu'];
uriPrefixes.forEach((prefix) => {
if (!token.startsWith(prefix)) {
return;
}
token = token.slice(prefix.length);
});
return handleTokens(token);
// legacy method
const getMintsFromToken = function (token: Token): string[] {
return [token.mint]
}
/**
* @param token
* @returns
*/
function handleTokens(token: string): TokenV3 {
const version = token.slice(0, 1);
const encodedToken = token.slice(1);
if (version === 'A') {
return encodeBase64ToJson<TokenV3>(encodedToken);
} else if (version === 'B') {
const uInt8Token = encodeBase64toUint8(base64urlToBase64(encodedToken));
const tokenData = cbor.decodeFirst(uInt8Token) as TokenV4
const mergedTokenEntry: TokenEntryV3 = { mint: tokenData.m, proofs: [] };
tokenData.t.forEach((tokenEntry) =>
tokenEntry.p.forEach((p) => {
mergedTokenEntry.proofs.push({
secret: p.s,
C: bytesToHex(p.c),
amount: p.a,
id: bytesToHex(tokenEntry.i)
});
})
);
return { token: [mergedTokenEntry], memo: tokenData.d || '', unit: tokenData.u };
} else {
throw new Error('Token version is not supported');
}
}
const getTokenAmounts = function (token: TokenV3) {
const mintAmounts: {[k: string]: number} = {}
let totalAmount = 0
try {
for (const tokenEntry of token.token) {
const amount = getTokenEntryAmount(tokenEntry)
totalAmount += amount
const mint = tokenEntry.mint
if (mintAmounts[mint]) {
mintAmounts[mint] += amount
} else {
mintAmounts[mint] = amount
}
}
const mintAmountsArray = Object.entries(mintAmounts).map(
([mintUrl, amount]) => ({mintUrl, amount}),
)
return {totalAmount, mintAmounts: mintAmountsArray}
} catch (e: any) {
throw new AppError(
Err.VALIDATION_ERROR,
'Could not calculate total amount',
e.message,
)
}
}
const getTokenEntryAmount = function (tokenEntry: TokenEntryV3) {
try {
return getProofsAmount(tokenEntry.proofs)
} catch (e: any) {
throw new AppError(
Err.VALIDATION_ERROR,
'Could not calculate total TokenEntry amount',
e.message,
)
}
}
const getProofsAmount = function (proofs: Array<Proof | ProofV3>): number {
let totalAmount = 0
for (const proof of proofs) {
const amount = proof.amount
totalAmount += amount
}
return totalAmount
}
const getAmountPreferencesCount = function (amountPreferences: AmountPreference[]): number {
return amountPreferences.reduce((total, preference) => total + preference.count, 0);
}
const getMintsFromToken = function (token: TokenV3): string[] {
const mints = token.token.map(item => item.mint)
return Array.from(new Set(mints)) // make sure the mints are not duplicated
}
const getProofsFromTokenEntries = (tokenEntries: TokenEntryV3[]) => {
const proofs: ProofV3[] = []
for (const entry of tokenEntries) {
proofs.push(...entry.proofs)
}
return proofs
}
// Function to find the intersection of two arrays of Proof objects
/* const findIntersection = function (proofArray1: Proof[], proofArray2: Proof[]) {
// Convert the second array to a Set of secrets for fast lookup
const secretSet = new Set(proofArray2.map(proof => proof.secret))
// Filter the first array, keeping only objects whose secret exists in the second array
const intersection = proofArray1.filter(proof => secretSet.has(proof.secret))
return intersection
} */
const findExactMatch = function (requestedAmount: number, proofs: Proof[]): Proof[] | null {
const result: Proof[] = [];
@@ -418,9 +173,9 @@ const getProofsToSend = function (requestedAmount: number, proofs: Proof[]): Pro
* @returns
*/
const getProofsSubset = function (
proofs: Array<Proof | ProofV3>,
proofsToRemove: Array<Proof | ProofV3>,
): Array<Proof | ProofV3> {
proofs: Array<Proof | CashuProof>,
proofsToRemove: Array<Proof | CashuProof>,
): Array<Proof | CashuProof> {
return proofs.filter(proof => !proofsToRemove.some(p => p.secret === proof.secret))
}
@@ -439,10 +194,10 @@ const validateMintKeys = function (keys: object): boolean {
}
allKeys.forEach(k => {
//try parse int?
if (isNaN(k)) {
if (isNaN(Number(k))) {
isValid = false
}
if (!isPow2(k)) {
if (!isPow2(Number(k))) {
isValid = false
}
})
@@ -477,18 +232,9 @@ const getMintFromProof = function (
export const CashuUtils = {
findEncodedCashuToken,
extractEncodedCashuToken,
isValidCashuToken,
decodeToken,
encodeToken,
getTokenAmounts,
getTokenEntryAmount,
getProofsAmount,
getAmountPreferencesCount,
getProofsAmount,
getMintsFromToken,
findMinExcess,
// findIntersection,
// updateMintProofs,
getProofsFromTokenEntries,
getProofsToSend,
getProofsSubset,
validateMintKeys,
+46 -71
View File
@@ -7,14 +7,14 @@ import {
TransactionType,
} from '../../models/Transaction'
import {rootStoreInstance} from '../../models'
import {CashuUtils, ProofV3, TokenEntryV3, TokenV3} from '../cashu/cashuUtils'
import {CashuUtils, CashuProof} from '../cashu/cashuUtils'
import AppError, {Err} from '../../utils/AppError'
import { getDefaultAmountPreference } from '@cashu/cashu-ts/src/utils'
import { TransactionTaskResult } from '../walletService'
import { DEFAULT_DENOMINATION_TARGET, TransactionTaskResult } from '../walletService'
import { WalletUtils } from './utils'
import { MintUnit, formatCurrency, getCurrency } from './currency'
import { Proof } from '../../models/Proof'
import { MintProofsCounter } from '../../models/Mint'
import { Token, getDecodedToken } from '@cashu/cashu-ts'
import { getEncodedToken, getKeepAmounts } from '@cashu/cashu-ts/src/utils'
const {
mintsStore,
@@ -29,7 +29,7 @@ const RECEIVE_OFFLINE_PREPARE = 'receiveOfflinePrepareTask'
const RECEIVE_OFFLINE_COMPLETE = 'receiveOfflineCompleteTask'
export const receiveTask = async function (
token: TokenV3,
token: Token,
amountToReceive: number,
memo: string,
encodedToken: string,
@@ -40,24 +40,14 @@ export const receiveTask = async function (
const unit = token.unit as MintUnit || 'sat'
try {
const tokenMints: string[] = CashuUtils.getMintsFromToken(token)
log.trace('[receiveTask]', 'receiveToken tokenMints', tokenMints)
mintToReceive = token.mint
if (tokenMints.length === 0) {
if (!mintToReceive) {
throw new AppError(
Err.VALIDATION_ERROR,
'Could not get any mint information from the ecash token.',
'Token is missing a mint param.',
)
}
if (tokenMints.length > 1) {
throw new AppError(
Err.VALIDATION_ERROR,
'Wallet does not support receiving of tokens with entries from multiple mints.',
)
}
mintToReceive = tokenMints[0]
}
// Let's create new draft receive transaction in database
transactionData.push({
@@ -104,8 +94,7 @@ export const receiveTask = async function (
const {
receivedAmount,
outputToken,
swapFeeReserve,
outputToken,
swapFeePaid,
counter
} = await receiveSync(
@@ -123,8 +112,7 @@ export const receiveTask = async function (
// Finally, update completed transaction
transactionData.push({
status: TransactionStatus.COMPLETED,
swapFeeReserve,
status: TransactionStatus.COMPLETED,
swapFeePaid,
receivedAmount,
unit,
@@ -183,7 +171,7 @@ export const receiveTask = async function (
export const receiveOfflinePrepareTask = async function (
token: TokenV3,
token: Token,
amountToReceive: number,
memo: string,
encodedToken: string,
@@ -194,25 +182,14 @@ export const receiveOfflinePrepareTask = async function (
const unit = token.unit as MintUnit || 'sat'
try {
const tokenMints: string[] = CashuUtils.getMintsFromToken(token)
log.trace('[receiveOfflinePrepareTask]', 'receiveToken tokenMints', tokenMints)
mintToReceive = token.mint
if (tokenMints.length === 0) {
if (!mintToReceive) {
throw new AppError(
Err.VALIDATION_ERROR,
'Could not get any mint information from the ecash token.',
'Token is missing a mint param.',
)
}
if (tokenMints.length > 1) {
throw new AppError(
Err.VALIDATION_ERROR,
'Wallet does not support receiving of tokens with entries from multiple mints.',
)
}
mintToReceive = tokenMints[0]
}
// Let's create new draft receive transaction in database
transactionData.push({
@@ -240,7 +217,7 @@ export const receiveOfflinePrepareTask = async function (
const isBlocked = mintsStore.isBlocked(mintToReceive)
if (isBlocked) {
const blockedTransaction = await transaction.setStatus(
const blockedTransaction = transaction.setStatus(
TransactionStatus.BLOCKED,
JSON.stringify(transactionData),
)
@@ -314,16 +291,14 @@ export const receiveOfflineCompleteTask = async function (
throw new AppError(Err.VALIDATION_ERROR, 'Could not find ecash token to redeem', {caller: 'receiveOfflineComplete'})
}
const token = CashuUtils.decodeToken(transaction.inputToken)
const tokenMints: string[] = CashuUtils.getMintsFromToken(token as TokenV3)
const token = getDecodedToken(transaction.inputToken)
mintToReceive = token.mint
const unit = token.unit || 'sat'
if(unit !== transaction.unit) {
throw new AppError(Err.VALIDATION_ERROR, 'Transaction unit and token unit are not the same', {unit, transactionUnit: transaction.unit})
}
mintToReceive = tokenMints[0]
// Re-check blocked mint
const isBlocked = mintsStore.isBlocked(mintToReceive)
@@ -343,8 +318,7 @@ export const receiveOfflineCompleteTask = async function (
const {
receivedAmount,
outputToken,
swapFeeReserve,
outputToken,
swapFeePaid,
counter
} = await receiveSync(
@@ -363,7 +337,6 @@ export const receiveOfflineCompleteTask = async function (
transactionData.push({
status: TransactionStatus.COMPLETED,
receivedAmount,
swapFeeReserve,
swapFeePaid,
unit,
counter,
@@ -421,7 +394,7 @@ export const receiveOfflineCompleteTask = async function (
export const receiveSync = async function (
mintToReceive: string,
token: TokenV3,
token: Token,
memo: string,
transactionId: number
) {
@@ -445,20 +418,29 @@ export const receiveSync = async function (
// Increase the proofs counter before the mint call so that in case the response
// is not received our recovery index counts for sigs the mint has already issued
const amountToReceive = CashuUtils.getTokenAmounts(token).totalAmount
const amountPreferences = getDefaultAmountPreference(amountToReceive)
const countOfInFlightProofs = CashuUtils.getAmountPreferencesCount(amountPreferences)
const proofsToReceive = token.token.flatMap(entry => entry.proofs)
const swapFeeReserve = mintInstance.getMintFeeReserve(proofsToReceive)
const amountToReceive = CashuUtils.getProofsAmount(token.proofs)
const proofsByMint = proofsStore.getByMint(mintToReceive, {
isPending: false,
unit
})
const walletInstance = await walletStore.getWallet(mintToReceive, unit, {withSeed: true})
const amountPreference = getKeepAmounts(
proofsByMint,
amountToReceive,
(await walletInstance.getKeys()).keys,
DEFAULT_DENOMINATION_TARGET
)
log.trace('[receiveSync]', 'amountPreferences', {amountPreferences, transactionId})
log.trace('[receiveSync]', 'countOfInFlightProofs', {countOfInFlightProofs, transactionId})
log.trace('[receiveSync]', {amountPreference, transactionId})
// temp increase the counter + acquire lock and set inFlight values
lockedProofsCounter = await WalletUtils.lockAndSetInFlight(
mintInstance,
unit,
countOfInFlightProofs,
amountPreference.length,
transactionId
)
@@ -468,10 +450,9 @@ export const receiveSync = async function (
receivedResult = await walletStore.receive(
mintToReceive,
unit as MintUnit,
token,
swapFeeReserve,
token,
{
preference: amountPreferences,
outputAmounts: {keepAmounts: [], sendAmounts: amountPreference},
counter: lockedProofsCounter.inFlightFrom as number // MUST be counter value before increase
}
)
@@ -485,17 +466,16 @@ export const receiveSync = async function (
lockedProofsCounter = await WalletUtils.lockAndSetInFlight(
mintInstance,
unit,
countOfInFlightProofs,
amountPreference.length,
transactionId
)
receivedResult = await walletStore.receive(
mintToReceive,
unit as MintUnit,
token,
swapFeeReserve,
token,
{
preference: amountPreferences,
outputAmounts: {keepAmounts: [], sendAmounts: amountPreference},
counter: lockedProofsCounter.inFlightFrom as number
}
)
@@ -508,7 +488,7 @@ export const receiveSync = async function (
const swapFeePaid = receivedResult!.swapFeePaid
// If we've got valid response, decrease proofsCounter and let it be increased back in next step when adding proofs
lockedProofsCounter.decreaseProofsCounter(countOfInFlightProofs)
lockedProofsCounter.decreaseProofsCounter(amountPreference.length)
const { addedAmount: receivedAmount } = WalletUtils.addCashuProofs(
mintToReceive,
@@ -524,13 +504,9 @@ export const receiveSync = async function (
lockedProofsCounter.resetInFlight(transactionId)
// store swapped proofs as encoded token in tx data
const receivedTokenEntry = {
const outputToken = getEncodedToken({
mint: mintToReceive,
proofs: receivedProofs,
}
const outputToken = CashuUtils.encodeToken({
token: [receivedTokenEntry],
unit,
memo,
})
@@ -538,8 +514,7 @@ export const receiveSync = async function (
return {
receivedAmount,
receivedProofs,
outputToken,
swapFeeReserve,
outputToken,
swapFeePaid,
counter: lockedProofsCounter?.counter
}
+45 -50
View File
@@ -1,20 +1,17 @@
import {log} from '../logService'
import {
Transaction,
TransactionData,
TransactionRecord,
TransactionStatus,
TransactionType,
TransactionStatus,
} from '../../models/Transaction'
import {rootStoreInstance} from '../../models'
import {CashuUtils, ProofV3, TokenEntryV3, TokenV3} from '../cashu/cashuUtils'
import {CashuUtils, CashuProof} from '../cashu/cashuUtils'
import AppError, {Err} from '../../utils/AppError'
import { getDefaultAmountPreference } from '@cashu/cashu-ts/src/utils'
import { TransactionTaskResult } from '../walletService'
import { DEFAULT_DENOMINATION_TARGET, TransactionTaskResult } from '../walletService'
import { WalletUtils } from './utils'
import { MintUnit, formatCurrency, getCurrency } from './currency'
import { Proof } from '../../models/Proof'
import { MintUnit } from './currency'
import { MintProofsCounter } from '../../models/Mint'
import { Token } from '@cashu/cashu-ts'
import { getEncodedToken, getKeepAmounts } from '@cashu/cashu-ts/src/utils'
const {
mintsStore,
@@ -32,36 +29,41 @@ let lockedProofsCounter: MintProofsCounter | undefined = undefined
const transactionData = JSON.parse(transaction.data)
try {
const unit = transaction.unit as MintUnit
const pendingProofs = proofsStore.getByTransactionId(transaction.id!, true)
const unit = transaction.unit as MintUnit
const pendingProofs = proofsStore.getByTransactionId(transaction.id!, true)
if(pendingProofs.length === 0) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing proofs to swap')
}
if(pendingProofs.length === 0) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing proofs to swap')
}
const mintInstance = mintsStore.findByUrl(transaction.mint)
const mintInstance = mintsStore.findByUrl(transaction.mint)
if(!mintInstance) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing mint')
}
if(!mintInstance) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing mint')
}
const amountToRevert = CashuUtils.getProofsAmount(pendingProofs)
const amountPreferences = getDefaultAmountPreference(amountToRevert)
const countOfInFlightProofs = CashuUtils.getAmountPreferencesCount(amountPreferences)
const mintFeeReserve = mintInstance.getMintFeeReserve(pendingProofs)
const amountToRevert = CashuUtils.getProofsAmount(pendingProofs)
const proofsByMint = proofsStore.getByMint(mintInstance.mintUrl, {
isPending: false,
unit
})
const walletInstance = await walletStore.getWallet(mintInstance.mintUrl, unit, {withSeed: true})
const amountPreference = getKeepAmounts(
proofsByMint,
amountToRevert,
(await walletInstance.getKeys()).keys,
DEFAULT_DENOMINATION_TARGET
)
log.trace('[_revertTask]', 'amountPreferences', {amountPreferences, transactionId: transaction.id})
log.trace('[_revertTask]', 'countOfInFlightProofs', {countOfInFlightProofs, transactionId: transaction.id})
log.trace('[_revertTask]', {amountPreference, transactionId: transaction.id})
// We will swap pending proofs with the mint for fresh ones that we receive to the wallet.
// This will invalidate originally sent proofs effectively reverting the transaction.
const encodedToken: TokenV3 = {
token: [
{
mint: mintInstance.mintUrl,
proofs: pendingProofs
}
],
const encodedToken: Token = {
mint: mintInstance.mintUrl,
proofs: pendingProofs,
unit
}
@@ -69,39 +71,33 @@ try {
lockedProofsCounter = await WalletUtils.lockAndSetInFlight(
mintInstance,
unit,
countOfInFlightProofs,
amountPreference.length,
transaction.id!
)
const receivedResult = await walletStore.receive(
transaction.mint,
unit as MintUnit,
encodedToken,
mintFeeReserve,
encodedToken,
{
preference: amountPreferences,
outputAmounts: {keepAmounts: [], sendAmounts: amountPreference},
counter: lockedProofsCounter.inFlightFrom as number // MUST be counter value before increase
}
)
const receivedProofs = receivedResult.proofs
const mintFeePaid = receivedResult.mintFeePaid
// log.trace('[receiveTask]', {receivedProofs})
const mintFeePaid = receivedResult.swapFeePaid
// If we've got valid response, decrease proofsCounter and let it be increased back in next step when adding proofs
lockedProofsCounter.decreaseProofsCounter(countOfInFlightProofs)
lockedProofsCounter.decreaseProofsCounter(amountPreference.length)
// store freshed proofs as encoded token in tx data
const receivedTokenEntry = {
mint: mintInstance.mintUrl,
proofs: receivedProofs,
}
const outputToken = CashuUtils.encodeToken({
token: [receivedTokenEntry],
unit
const outputToken = getEncodedToken({
mint: mintInstance.mintUrl,
proofs: receivedProofs,
unit
})
// Remove original pending proofs
proofsStore.removeProofs(pendingProofs, true, false)
@@ -121,8 +117,7 @@ try {
// Update transaction status
transactionData.push({
status: TransactionStatus.REVERTED,
mintFeeReserve,
status: TransactionStatus.REVERTED,
mintFeePaid,
counter: lockedProofsCounter.counter,
createdAt: new Date(),
+35 -34
View File
@@ -6,19 +6,20 @@ import {
TransactionType,
} from '../../models/Transaction'
import {rootStoreInstance} from '../../models'
import {CashuUtils, ProofV3, TokenEntryV3} from '../cashu/cashuUtils'
import {CashuUtils, CashuProof} from '../cashu/cashuUtils'
import AppError, {Err} from '../../utils/AppError'
import {
MintKeyset,
getEncodedToken,
} from '@cashu/cashu-ts'
import { getDefaultAmountPreference } from '@cashu/cashu-ts/src/utils'
import { MAX_SWAP_INPUT_SIZE, TransactionTaskResult, WalletTask } from '../walletService'
import { DEFAULT_DENOMINATION_TARGET, MAX_SWAP_INPUT_SIZE, TransactionTaskResult, WalletTask } from '../walletService'
import { Mint, MintBalance, MintProofsCounter } from '../../models/Mint'
import { Proof } from '../../models/Proof'
import { poller } from '../../utils/poller'
import { WalletUtils } from './utils'
import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
import { MintUnit } from './currency'
import { getKeepAmounts } from '@cashu/cashu-ts/src/utils'
const {
mintsStore,
@@ -27,8 +28,6 @@ const {
walletStore
} = rootStoreInstance
// const {walletStore} = nonPersistedStores
export const sendTask = async function (
mintBalanceToSendFrom: MintBalance,
amountToSend: number,
@@ -41,7 +40,6 @@ export const sendTask = async function (
log.trace('[send]', 'mintBalanceToSendFrom', mintBalanceToSendFrom)
log.trace('[send]', 'amountToSend', {amountToSend, unit})
log.trace('[send]', 'memo', memo)
// create draft transaction
const transactionData: TransactionData[] = [
@@ -98,18 +96,14 @@ export const sendTask = async function (
JSON.stringify(transactionData),
)
// Create sendable encoded tokenV3
const tokenEntryToSend = {
mint: mintUrl,
proofs: proofsToSend,
}
// Create sendable encoded token
if (!memo || memo === '') {
memo = 'Sent from Minibits'
}
const outputToken = CashuUtils.encodeToken({
token: [tokenEntryToSend as TokenEntryV3],
const outputToken = getEncodedToken({
mint: mintUrl,
proofs: proofsToSend,
unit,
memo,
})
@@ -146,7 +140,7 @@ export const sendTask = async function (
WalletTask.syncStateWithMint,
{
interval: 6 * 1000,
maxPolls: 3,
maxPolls: 5,
maxErrors: 2
},
{proofsToSync, mintUrl, isPending: true}
@@ -309,8 +303,8 @@ export const sendFromMintSync = async function (
let swapFeeReserve: number = 0
let returnedAmount = 0
let swapFeePaid: number = 0
let proofsToSend: ProofV3[] | Proof[] = []
let returnedProofs: ProofV3[] = []
let proofsToSend: CashuProof[] | Proof[] = []
let returnedProofs: CashuProof[] = []
let isSwapNeeded: boolean = proofsToSendFromAmount - amountToSend > 0 ? true : false
@@ -321,7 +315,8 @@ export const sendFromMintSync = async function (
*/
if(isSwapNeeded) {
// Calculate feeReserve from mint fee rate
swapFeeReserve = mintInstance.getMintFeeReserve(proofsToSendFrom)
const walletInstance = await walletStore.getWallet(mintUrl, unit, {withSeed: true})
swapFeeReserve = walletInstance.getFeesForProofs(proofsToSendFrom)
const amountWithFees = amountToSend + swapFeeReserve
if (totalAmountFromMint < amountWithFees) {
@@ -353,14 +348,23 @@ export const sendFromMintSync = async function (
})
// Output denominations we ask for to get back
const amountPreferences = getDefaultAmountPreference(amountToSend)
// Output denominations we are about to get as a split remainder
const returnedAmountPreferences = getDefaultAmountPreference(returnedAmount)
const sendAmounts = getKeepAmounts(
proofsToSendFrom,
amountToSend,
(await walletInstance.getKeys()).keys,
DEFAULT_DENOMINATION_TARGET
)
const countOfInFlightProofs = CashuUtils.getAmountPreferencesCount(amountPreferences) +
CashuUtils.getAmountPreferencesCount(returnedAmountPreferences)
const keepAmounts = getKeepAmounts(
proofsToSendFrom,
returnedAmount,
(await walletInstance.getKeys()).keys,
DEFAULT_DENOMINATION_TARGET
)
const countOfInFlightProofs = sendAmounts.length + keepAmounts.length
log.trace('[sendFromMintSync]', 'countOfInFlightProofs', countOfInFlightProofs)
log.trace('[sendFromMintSync]', {sendAmounts, keepAmounts, countOfInFlightProofs})
// Increase the proofs counter before the mint call so that in case the response
// is not received our recovery index counts for sigs the mint has already issued (prevents duplicate b_b bug)
@@ -374,13 +378,12 @@ export const sendFromMintSync = async function (
const sendResult = await walletStore.send(
mintUrl,
amountToSend,
swapFeeReserve,
amountToSend,
unit,
proofsToSendFrom,
{
preference: amountPreferences,
counter: lockedProofsCounter.inFlightFrom as number // MUST be counter value before increase
outputAmounts: {sendAmounts, keepAmounts},
counter: lockedProofsCounter.inFlightFrom as number, // MUST be counter value before increase
}
)
@@ -436,7 +439,7 @@ export const sendFromMintSync = async function (
)
// Clean private properties to not to send them out. This returns plain js array, not model objects.
const cleanedProofsToSend: ProofV3[] = proofsToSend.map(proof => {
const cleanedProofsToSend: CashuProof[] = proofsToSend.map(proof => {
if (isStateTreeNode(proof)) {
const {mintUrl, unit, tId, ...rest} = getSnapshot(proof)
return rest
@@ -462,13 +465,11 @@ export const sendFromMintSync = async function (
// try to clean spent proofs if that was the swap error cause
if (e.params && e.params.message && e.params.message.includes('Token already spent')) {
await WalletTask.syncStateWithMintSync(
{
proofsToSync: proofsToSendFrom,
await WalletTask.syncStateWithMintSync({
proofsToSync: proofsStore.getByMint(mintUrl, {isPending: true}),
mintUrl,
isPending: false
}
)
})
}
if (e instanceof AppError) {
+53 -50
View File
@@ -1,6 +1,6 @@
import {CashuUtils, TokenEntryV3} from '../cashu/cashuUtils'
import {CashuUtils} from '../cashu/cashuUtils'
import AppError, {Err} from '../../utils/AppError'
import {MeltQuoteResponse, MeltQuoteState} from '@cashu/cashu-ts'
import {MeltProofsResponse, MeltQuoteResponse, MeltQuoteState, getEncodedToken} from '@cashu/cashu-ts'
import {rootStoreInstance} from '../../models'
import { TransactionTaskResult, WalletTask } from '../walletService'
import { MintBalance, MintProofsCounter } from '../../models/Mint'
@@ -59,7 +59,8 @@ export const transferTask = async function (
let transaction: Transaction | undefined = undefined
let proofsToMeltFrom: Proof[] = []
let proofsToMeltFromAmount: number = 0
let meltFeeReserve: number = 0
let meltFeeReserve: number = 0
let meltResponse: MeltProofsResponse
try {
const newTransaction = {
@@ -110,7 +111,9 @@ export const transferTask = async function (
)
proofsToMeltFromAmount = CashuUtils.getProofsAmount(proofsToMeltFrom)
meltFeeReserve = mintInstance.getMintFeeReserve(proofsToMeltFrom)
const walletInstance = await walletStore.getWallet(mintUrl, unit, {withSeed: true})
meltFeeReserve = walletInstance.getFeesForProofs(proofsToMeltFrom)
const amountWithFees = amountToTransfer + meltQuote.fee_reserve + meltFeeReserve
if (totalAmountFromMint < amountWithFees) {
@@ -161,23 +164,19 @@ export const transferTask = async function (
TransactionStatus.PREPARED,
JSON.stringify(transactionData),
)
const inputTokenEntry: TokenEntryV3 = {
const inputToken = getEncodedToken({
mint: mintUrl,
proofs: proofsToMeltFrom,
}
const inputToken = CashuUtils.encodeToken({
token: [inputTokenEntry],
unit,
unit
})
transaction.setInputToken(inputToken)
// number of outputs we can get back as a change
let countOfInFlightProofs = 1
let countOfInFlightProofs = proofsToMeltFrom.length
if(proofsToMeltFromAmount - amountToTransfer > 1) {
countOfInFlightProofs = Math.ceil(Math.log2(proofsToMeltFromAmount - amountToTransfer))
countOfInFlightProofs += Math.ceil(Math.log2(proofsToMeltFromAmount - amountToTransfer))
}
// temp increase the counter + acquire lock and set inFlight values
@@ -188,7 +187,7 @@ export const transferTask = async function (
transaction.id
)
const {state, preimage, change: returnedProofs} = await walletStore.payLightningMelt(
meltResponse = await walletStore.payLightningMelt(
mintUrl,
unit,
meltQuote,
@@ -200,7 +199,7 @@ export const transferTask = async function (
lockedProofsCounter.decreaseProofsCounter(countOfInFlightProofs)
if (state === MeltQuoteState.PAID) {
if (meltResponse.quote.state === MeltQuoteState.PAID) {
log.debug('[transfer] Invoice PAID', {
transactionId
@@ -210,33 +209,29 @@ export const transferTask = async function (
proofsStore.removeProofs(proofsToMeltFrom as Proof[], true, false)
// Save preimage asap
if(preimage) {
transaction.setProof(preimage)
if(meltResponse.quote.payment_preimage) {
transaction.setProof(meltResponse.quote.payment_preimage)
}
let totalFeePaid = proofsToMeltFromAmount - amountToTransfer
let lightningFeePaid = totalFeePaid - meltFeeReserve
let meltFeePaid = meltFeeReserve
let returnedAmount = CashuUtils.getProofsAmount(returnedProofs)
let returnedAmount = CashuUtils.getProofsAmount(meltResponse.change)
if(returnedProofs.length > 0) {
if(meltResponse.change.length > 0) {
WalletUtils.addCashuProofs(
mintUrl,
returnedProofs,
meltResponse.change,
{
unit,
transactionId: transaction.id,
isPending: false
}
)
const returnedTokenEntry: TokenEntryV3 = {
mint: mintUrl,
proofs: returnedProofs,
}
const outputToken = CashuUtils.encodeToken({
token: [returnedTokenEntry],
const outputToken = getEncodedToken({
mint: mintUrl,
proofs: meltResponse.change,
unit,
})
@@ -260,7 +255,7 @@ export const transferTask = async function (
lightningFeePaid,
meltFeePaid,
returnedAmount,
preimage,
preimage: meltResponse.quote.payment_preimage,
counter: lockedProofsCounter.counter,
createdAt: new Date(),
})
@@ -281,17 +276,15 @@ export const transferTask = async function (
lightningFeePaid,
meltFeePaid,
totalFeePaid,
meltQuote,
preimage,
meltQuote: meltResponse.quote,
preimage: meltResponse.quote.payment_preimage,
nwcEvent
} as TransactionTaskResult
} else if(state === MeltQuoteState.PENDING) {
} else if(meltResponse.quote.state === MeltQuoteState.PENDING) {
log.debug('[transfer] Invoice PENDING', {
state,
meltQuote,
preimage,
metResponseQuote: meltResponse.quote,
transactionId
})
@@ -307,7 +300,7 @@ export const transferTask = async function (
} else {
// throw so that proper state of proofs is synced inside the catch block
throw new AppError(Err.MINT_ERROR, 'Lightning payment has not been paid.', {
state,
metResponseQuote: meltResponse.quote,
transactionId
})
}
@@ -322,20 +315,21 @@ export const transferTask = async function (
let message = e.message
if (proofsToMeltFrom.length > 0) {
// check with the mint the real status of the proofs involved in transaction
// if unspent as a result of error, this returns proofs to spendable
await WalletTask.syncStateWithMintSync(
const walletInstance = await walletStore.getWallet(mintUrl, unit, {withSeed: true})
const refreshedMeltQuote = await walletInstance.checkMeltQuote(meltQuote.quote)
/* await WalletTask.syncStateWithMintSync(
{
proofsToSync: proofsToMeltFrom,
mintUrl,
isPending: true
}
)
)
// force refresh just in case above method did not update the model?
const refreshed = transactionsStore.findById(transaction.id)
const refreshedTransaction = transactionsStore.findById(transaction.id)*/
if(refreshed?.status === TransactionStatus.PENDING) {
if(refreshedMeltQuote.state = MeltQuoteState.PENDING) {
log.warn('[transfer]', 'proofsToMeltFrom from transfer with error are pending by mint', {
proofsToMeltFromAmount,
unit,
@@ -343,28 +337,37 @@ export const transferTask = async function (
})
message = 'Lightning payment did not complete in time. Your ecash will remain pending until the payment completes or fails.'
// needs to be returned as error
} else if(refreshed?.status === TransactionStatus.COMPLETED) {
// Likely receiving of change failed due to wallet error. We keep completed as status.
// needs to be returned as error so we do not return
} else if(refreshedMeltQuote.state === MeltQuoteState.PAID) {
log.error('[transfer]', 'Transfer throwed error but the payment suceeded', {
error: e.message,
proofsToMeltFromAmount,
refreshedMeltQuote,
unit,
transactionId: transaction.id
})
message = `Lightning invoice has been successfully paid, however some error occured: ${e.message}`
await WalletTask.syncStateWithMintSync({
proofsToSync: proofsStore.getByMint(mintUrl, {isPending: true}),
mintUrl,
isPending: true
})
message = `Lightning invoice has been successfully paid, however some error occured: ${e.message}`
return {
taskFunction: TRANSFER,
mintUrl,
transaction,
message,
meltQuote,
preimage: meltQuote?.payment_preimage,
meltQuote: refreshedMeltQuote,
preimage: refreshedMeltQuote.payment_preimage,
nwcEvent
} as TransactionTaskResult
} else {
// if melt quote is not paid return proofs from pending to spendable balance
proofsStore.removeProofs(proofsToMeltFrom, true, true)
proofsStore.addProofs(proofsToMeltFrom)
}
}
+3 -3
View File
@@ -3,7 +3,7 @@ import {rootStoreInstance} from '../../models'
import AppError, { Err } from "../../utils/AppError"
import { log } from '../logService'
import { isStateTreeNode } from 'mobx-state-tree'
import { CashuUtils, ProofV3 } from '../cashu/cashuUtils'
import { CashuUtils, CashuProof } from '../cashu/cashuUtils'
import { Mint, MintProofsCounter } from '../../models/Mint'
import { delay } from '../../utils/utils'
import { MintUnit } from './currency'
@@ -27,7 +27,7 @@ const lockAndSetInFlight = async function (
// Make sure to select the wallet instance keysetId
const walletInstance = await walletStore.getWallet(mint.mintUrl, unit, {withSeed: true})
const currentCounter = mint.getProofsCounterByKeysetId!(walletInstance.keys.id)
const currentCounter = mint.getProofsCounterByKeysetId!(walletInstance.keysetId)
log.info('[lockAndSetInFlight] Before lock', {
transactionId,
@@ -84,7 +84,7 @@ const lockAndSetInFlight = async function (
const addCashuProofs = function (
mintUrl: string,
proofsToAdd: ProofV3[] | Proof[],
proofsToAdd: CashuProof[] | Proof[],
options: {
unit: MintUnit,
transactionId: number,
+84 -98
View File
@@ -8,11 +8,11 @@ import {
TransactionStatus
} from '../models/Transaction'
import {rootStoreInstance} from '../models'
import {CashuUtils, ProofV3, TokenV3} from './cashu/cashuUtils'
import {CashuProof, CashuUtils} from './cashu/cashuUtils'
import {LightningUtils} from './lightning/lightningUtils'
import AppError, {Err} from '../utils/AppError'
import {MintBalance, MintProofsCounter, MintStatus} from '../models/Mint'
import {MeltQuoteResponse, MintQuoteState} from '@cashu/cashu-ts'
import {CheckStateEnum, MeltQuoteResponse, MintQuoteState, Token, getDecodedToken, getEncodedToken} from '@cashu/cashu-ts'
import {Mint} from '../models/Mint'
import {pollerExists, stopPolling} from '../utils/poller'
import { NostrClient, NostrEvent, NostrProfile } from './nostrService'
@@ -20,7 +20,6 @@ import { MINIBITS_NIP05_DOMAIN, MINIBITS_SERVER_API_HOST, MINIBIT_SERVER_NOSTR_P
import { PaymentRequest, PaymentRequestStatus, PaymentRequestType } from '../models/PaymentRequest'
import { IncomingDataType, IncomingParser } from './incomingParser'
import { Contact } from '../models/Contact'
import { getDefaultAmountPreference } from '@cashu/cashu-ts/src/utils'
import { SyncQueue } from './syncQueueService'
import { receiveTask, receiveOfflinePrepareTask, receiveOfflineCompleteTask} from './wallet/receiveTask'
import { sendTask } from './wallet/sendTask'
@@ -31,8 +30,14 @@ import { WalletUtils } from './wallet/utils'
import { NotificationService } from './notificationService'
import { MintUnit, formatCurrency, getCurrency } from './wallet/currency'
import { MinibitsClient } from './minibitsService'
import { getKeepAmounts } from '@cashu/cashu-ts/src/utils'
import { keys } from 'mobx'
/**
* The default number of proofs per denomination to keep in a wallet.
*/
export const DEFAULT_DENOMINATION_TARGET = 3
export const MAX_SWAP_INPUT_SIZE = 100
export const MAX_SYNC_INPUT_SIZE = 200 // 1000 hard mint limit
@@ -73,19 +78,19 @@ type WalletTaskService = {
nwcEvent?: NostrEvent
) => Promise<void>
receive: (
token: TokenV3,
token: Token,
amountToReceive: number,
memo: string,
encodedToken: string,
) => Promise<void>
receiveBatch: (
token: TokenV3,
token: Token,
amountToReceive: number,
memo: string,
encodedToken: string,
) => Promise<void>
receiveOfflinePrepare: (
token: TokenV3,
token: Token,
amountToReceive: number,
memo: string,
encodedToken: string,
@@ -156,7 +161,7 @@ export type ReceivedEventResult = {
memo?: string,
picture?: string
paymentRequest?: PaymentRequest
token?: TokenV3
token?: Token
}
@@ -202,7 +207,7 @@ const transfer = async function (
const receive = async function (
token: TokenV3,
token: Token,
amountToReceive: number,
memo: string,
encodedToken: string,
@@ -225,14 +230,14 @@ const receive = async function (
* Used when optimizing wallet proof amounts but might become the default.
*/
const receiveBatch = async function (
token: TokenV3,
token: Token,
amountToReceive: number,
memo: string,
encodedToken: string,
) {
const maxBatchSize = MAX_SWAP_INPUT_SIZE
const mintUrl = token.token[0].mint
const proofsToReceive = token.token.flatMap(entry => entry.proofs)
const mintUrl = token.mint
const proofsToReceive = token.proofs
const unit = token.unit
if (proofsToReceive.length > maxBatchSize) {
@@ -244,18 +249,14 @@ const receiveBatch = async function (
const batch = proofsToReceive.slice(i, i + maxBatchSize)
const batchAmount = CashuUtils.getProofsAmount(batch)
const batchToken: TokenV3 = {
token: [
{
mint: mintUrl,
proofs: batch
}
],
const batchToken: Token = {
mint: mintUrl,
proofs: batch,
memo: `${memo} #${index}`,
unit
}
const batchEncodedToken = CashuUtils.encodeToken(batchToken)
const batchEncodedToken = getEncodedToken(batchToken)
// Queued WalletTask
receive(
@@ -281,7 +282,7 @@ const receiveBatch = async function (
const receiveOfflinePrepare = async function (
token: TokenV3,
token: Token,
amountToReceive: number,
memo: string,
encodedToken: string,
@@ -585,24 +586,19 @@ const _syncStateWithMintTask = async function (
} as SyncStateTaskResult
}
const {
spent: spentByMintProofs,
pending: pendingByMintProofs
} = await walletStore.getSpentOrPendingProofsFromMint(
const proofsByState = await walletStore.getProofsStatesFromMint(
mintUrl,
mint && mint.units ? mint.units[0] : 'sat', // likely not to be unit-dependent
proofsToSync
)
const unspentByMintProofs = CashuUtils.getProofsSubset(proofsToSync, [...spentByMintProofs, ...pendingByMintProofs])
)
if(mint) {
mint.setStatus(MintStatus.ONLINE)
}
const spentByMintAmount = CashuUtils.getProofsAmount(spentByMintProofs)
const pendingByMintAmount = CashuUtils.getProofsAmount(pendingByMintProofs)
const unspentByMintAmount = CashuUtils.getProofsAmount(unspentByMintProofs)
const spentByMintAmount = CashuUtils.getProofsAmount(proofsByState.SPENT)
const pendingByMintAmount = CashuUtils.getProofsAmount(proofsByState.PENDING)
const unspentByMintAmount = CashuUtils.getProofsAmount(proofsByState.UNSPENT)
log.debug('[_syncStateWithMintTask]', `${isPending ? 'Pending' : ''} spent and pending by mint amounts`, {
spentByMintAmount,
@@ -611,23 +607,15 @@ const _syncStateWithMintTask = async function (
isPending
})
// If some of the pending by wallet proofs are neither pending nor spent by mint
/*if (unspentByMintProofs.length > 0 && isPending) {
// remove it from pending proofs in the wallet
proofsStore.removeProofs(unspentByMintProofs as Proof[], true, true)
// add proofs back to the spendable wallet
proofsStore.addProofs(unspentByMintProofs as Proof[])
}*/
// 1. Complete transactions with their proofs becoming spent by mint
if (spentByMintProofs.length > 0) {
if (proofsByState.SPENT.length > 0) {
// Remove spent proofs model instances
proofsStore.removeProofs(spentByMintProofs as Proof[], isPending)
proofsStore.removeProofs(proofsByState.SPENT as Proof[], isPending)
// Clean pendingByMint secrets from state if proofs came back as spent by mint
if(proofsStore.pendingByMintSecrets.length > 0) {
const spentByMintSecrets = new Set(spentByMintProofs.map(proof => proof.secret))
const spentByMintSecrets = new Set(proofsByState.SPENT.map(proof => proof.secret))
const tobeRemoved = proofsStore.pendingByMintSecrets.filter(secret => spentByMintSecrets.has(secret))
if(tobeRemoved.length > 0) {
@@ -638,7 +626,7 @@ const _syncStateWithMintTask = async function (
// Map to track spent amounts by transaction ID
const transactionStateMap: { [key: number]: number } = {}
for (const spent of spentByMintProofs) {
for (const spent of proofsByState.SPENT) {
// Find the matching proof
const spentProof = proofsToSync.find(proof => spent.secret === proof.secret)
@@ -668,7 +656,7 @@ const _syncStateWithMintTask = async function (
// return unspent proofs from pending back to spendable
if(isPending) {
const unspentProofs = proofsToSync.filter(proof =>
spentByMintProofs.find(spent => spent.secret !== proof.secret)
proofsByState.SPENT.find(spent => spent.secret !== proof.secret)
)
if(unspentProofs.length > 0) {
@@ -752,11 +740,11 @@ const _syncStateWithMintTask = async function (
// 2. Make sure that transactions with proofs pending by mint are pending
// and that we keep their secrets in pendingByMint state
if (pendingByMintProofs.length > 0) {
if (proofsByState.PENDING.length > 0) {
// To prevent multiple pending status updates we select only those
// pending by mint proofs that the wallet does not track yet
const newPendingByMintProofs = pendingByMintProofs.filter(
const newPendingByMintProofs = proofsByState.PENDING.filter(
proof => !proofsStore.pendingByMintSecrets.includes(proof.secret)
)
@@ -801,7 +789,7 @@ const _syncStateWithMintTask = async function (
transactionStateUpdates.push(...pendingStateUpdates)
// If we somehow found pending proofs inside spendable balance during cleanup from spent, move them to pending
// If we somehow found new pending by mint proofs inside spendable balance during cleanup from spent, move them to pending
if(!isPending) {
// remove it from spendable proofs in the wallet
proofsStore.removeProofs(newPendingByMintProofs as Proof[], false) // we clean spendable balance
@@ -839,9 +827,9 @@ const _syncStateWithMintTask = async function (
if(remainingSecrets.length > 0) {
let secretsTobeMovedToSpendable: string[] = []
if(pendingByMintProofs.length > 0) {
if(proofsByState.PENDING.length > 0) {
// Filter remainingSecrets to get those that do not exist in pendingByMintProofs
const pendingByMintSecrets = new Set(pendingByMintProofs.map(proof => proof.secret))
const pendingByMintSecrets = new Set(proofsByState.PENDING.map(proof => proof.secret))
secretsTobeMovedToSpendable = remainingSecrets.filter(secret => !pendingByMintSecrets.has(secret))
} else {
secretsTobeMovedToSpendable = remainingSecrets
@@ -1002,7 +990,7 @@ const _handleInFlightByMintTask = async function (mint: Mint): Promise<WalletTas
log.debug('[_handleInFlightByMintTask]', proofsCounter)
const { proofs } = await walletStore.restore(
const { proofs } : { proofs: Proof[]} = await walletStore.restore(
mint.mintUrl,
seed as Uint8Array,
{
@@ -1024,29 +1012,20 @@ const _handleInFlightByMintTask = async function (mint: Mint): Promise<WalletTas
} as WalletTaskResult
}
const {spent, pending} = await walletStore.getSpentOrPendingProofsFromMint(
const proofsByState = await walletStore.getProofsStatesFromMint(
mint.mintUrl,
mint.units ? mint.units[0] : 'sat',
proofs as Proof[]
)
const spentCount = spent.length
const pendingCount = pending.length
const spentAmount = CashuUtils.getProofsAmount(spent as Proof[])
const pendingAmount = CashuUtils.getProofsAmount(pending as Proof[])
const unspent = proofs.filter((proof: Proof) => !spent.includes(proof))
const unspentCount = unspent.length
const unspentAmount = CashuUtils.getProofsAmount(unspent as Proof[])
const spentCount = proofsByState.SPENT.length
const pendingCount = proofsByState.PENDING.length
const unspentCount = proofsByState.UNSEPNT.length
log.debug('[_handleInFlightByMintTask]', `Restored proofs`, {
spentCount,
spentAmount,
pendingCount,
pendingAmount,
unspentCount,
unspentAmount
unspentCount,
})
if(unspentCount === 0) {
@@ -1061,19 +1040,16 @@ const _handleInFlightByMintTask = async function (mint: Mint): Promise<WalletTas
proofsCount: 0,
proofsAmount: 0,
} as WalletTaskResult
}
}
const { addedAmount, addedProofs } = WalletUtils.addCashuProofs(
mint.mintUrl,
unspent,
proofsByState.UNSEPNT,
{
unit: proofsCounter.unit,
transactionId,
isPending: false
}
}
)
// release the lock
@@ -1182,14 +1158,7 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
if(!mintInstance || !mintQuote || !unit || !amount || !transaction) {
throw new AppError(Err.VALIDATION_ERROR, 'Missing mint, mint quote, mintUnit, amountToTopup or transaction', {mintUrl})
}
const amountPreferences = getDefaultAmountPreference(amount)
const countOfInFlightProofs = CashuUtils.getAmountPreferencesCount(amountPreferences)
log.trace('[_handlePendingTopupTask]', 'paymentHash', paymentHash)
log.trace('[_handlePendingTopupTask]', 'amountPreferences', amountPreferences)
log.trace('[_handlePendingTopupTask]', 'countOfInFlightProofs', countOfInFlightProofs)
// check is quote has been paid
const { state, mintQuote: quote } = await walletStore.checkLightningMintQuote(mintUrl!, mintQuote)
@@ -1232,16 +1201,33 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
message: `Quote ${mintQuote} has not yet been paid`,
} as WalletTaskResult
}
const proofsByMint = proofsStore.getByMint(mintUrl!, {
isPending: false,
unit
})
const walletInstance = await walletStore.getWallet(mintUrl!, unit, {withSeed: true})
const keepAmounts = getKeepAmounts(
proofsByMint,
amount,
(await walletInstance.getKeys()).keys,
DEFAULT_DENOMINATION_TARGET
)
log.trace('[_handlePendingTopupTask]', {paymentHash, keepAmounts})
// temp increase the counter + acquire lock and set inFlight values
lockedProofsCounter = await WalletUtils.lockAndSetInFlight(
mintInstance,
unit,
countOfInFlightProofs,
keepAmounts.length,
transactionId,
)
let proofs: ProofV3[] = []
let proofs: CashuProof[] = []
try {
proofs = (await walletStore.mintProofs(
@@ -1250,10 +1236,10 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
unit,
mintQuote,
{
preference: amountPreferences,
counter: lockedProofsCounter.inFlightFrom as number
outputAmounts: {keepAmounts, sendAmounts: []},
counter: lockedProofsCounter.inFlightFrom as number
}
)) as ProofV3[]
))
} catch (e: any) {
if(e.message.includes('outputs have already been signed before')) {
@@ -1263,7 +1249,7 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
lockedProofsCounter = await WalletUtils.lockAndSetInFlight(
mintInstance,
unit,
countOfInFlightProofs,
keepAmounts.length,
transactionId
)
@@ -1273,16 +1259,16 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
unit,
mintQuote,
{
preference: amountPreferences,
counter: lockedProofsCounter.inFlightFrom as number
outputAmounts: {keepAmounts, sendAmounts: []},
counter: lockedProofsCounter.inFlightFrom as number
}
)) as ProofV3[]
))
} else {
throw e
}
}
lockedProofsCounter.decreaseProofsCounter(countOfInFlightProofs)
lockedProofsCounter.decreaseProofsCounter(keepAmounts.length)
if (!proofs || proofs.length === 0) {
throw new AppError(Err.VALIDATION_ERROR, 'Mint did not return any proofs.')
@@ -1431,7 +1417,7 @@ const _handleClaimTask = async function (params: {
zapSenderProfile?: string,
zapRequest?: string,
}}) {
let decoded: TokenV3 | undefined = undefined
let decoded: Token | undefined = undefined
try {
const {claimedToken} = params
@@ -1447,8 +1433,8 @@ const _handleClaimTask = async function (params: {
log.debug('[_handleClaimTask] decrypted token', {encodedToken})
decoded = CashuUtils.decodeToken(encodedToken)
const amountToReceive = CashuUtils.getTokenAmounts(decoded).totalAmount
decoded = getDecodedToken(encodedToken)
const amountToReceive = CashuUtils.getProofsAmount(decoded.proofs)
const memo = decoded.memo || 'Received to Lightning address'
const result: TransactionTaskResult = await receiveTask(
@@ -1479,11 +1465,11 @@ const _handleClaimTask = async function (params: {
}
return {
mintUrl: decoded.token[0].mint,
mintUrl: decoded.mint,
taskFunction: '_handleClaimTask',
message: result.error ? result.error.message : 'Ecash sent to your lightning address has been received.',
error: result.error || undefined,
proofsCount: decoded.token[0].proofs.length,
proofsCount: decoded.proofs.length,
proofsAmount: result.transaction?.amount,
} as WalletTaskResult
@@ -1491,7 +1477,7 @@ const _handleClaimTask = async function (params: {
log.error(e.name, e.message)
return {
mintUrl: decoded ? decoded.token[0].mint : '',
mintUrl: decoded ? decoded.mint : '',
taskFunction: '_handleClaimTask',
message: e.message,
error: WalletUtils.formatError(e),
@@ -1672,8 +1658,8 @@ const _handleReceivedEventTask = async function (event: NostrEvent): Promise<Wal
//
if(incoming.type === IncomingDataType.CASHU) {
const decoded = CashuUtils.decodeToken(incoming.encoded)
const amountToReceive = CashuUtils.getTokenAmounts(decoded).totalAmount
const decoded = getDecodedToken(incoming.encoded)
const amountToReceive = CashuUtils.getProofsAmount(decoded.proofs)
const memo = decoded.memo || 'Received over Nostr'
const {transaction, receivedAmount} = await receiveTask(
@@ -1709,10 +1695,10 @@ const _handleReceivedEventTask = async function (event: NostrEvent): Promise<Wal
}
return {
mintUrl: decoded.token[0].mint,
mintUrl: decoded.mint,
taskFunction: '_handleReceivedEventTask',
message: 'Incoming ecash token has been received.',
proofsCount: decoded.token[0].proofs.length,
proofsCount: decoded.proofs.length,
proofsAmount: receivedAmount,
notificationInputs: {
event,
@@ -1851,4 +1837,4 @@ export const WalletTask: WalletTaskService = {
transfer,
topup,
revert
}
}
+63 -49
View File
@@ -1648,30 +1648,29 @@ __metadata:
languageName: node
linkType: hard
"@cashu/cashu-ts@minibits-cash/cashu-ts#commit=8dcac7ce6d192c419aee5547f0db03c23b593a57":
version: 1.0.0-minibits.6
resolution: "@cashu/cashu-ts@https://github.com/minibits-cash/cashu-ts.git#commit=8dcac7ce6d192c419aee5547f0db03c23b593a57"
"@cashu/cashu-ts@minibits-cash/cashu-ts#cashu-ts-2.0":
version: 2.0.0-minibits.1
resolution: "@cashu/cashu-ts@https://github.com/minibits-cash/cashu-ts.git#commit=0a9d4fca6ec8c9c221e5f3056013a91dbfb29e69"
dependencies:
"@cashu/crypto": ^0.2.6
"@cashu/crypto": ^0.3.4
"@noble/curves": ^1.3.0
"@noble/hashes": ^1.3.3
"@scure/bip32": ^1.3.3
"@scure/bip39": ^1.2.2
buffer: ^6.0.3
checksum: dfeebbfe10b99429da21848257eb8a320cb9936909487686d2847e2cdcc61390140ca4fd8da87227c66e3e1c972dc0c1e5689f7d197e55b873f202138a28189c
checksum: 27c53b4284d9a50ef3a5b86f6a6c87378d0f5247b8585af23c07f224392d8d601645aaa6e7cd4cd333b2e42c9f5d8d3bb9ecffcee7e68658d0a9b62a23f7308d
languageName: node
linkType: hard
"@cashu/crypto@npm:^0.2.6":
version: 0.2.7
resolution: "@cashu/crypto@npm:0.2.7"
"@cashu/crypto@npm:^0.3.4":
version: 0.3.4
resolution: "@cashu/crypto@npm:0.3.4"
dependencies:
"@noble/curves": ^1.3.0
"@noble/hashes": ^1.3.3
"@scure/bip32": ^1.3.3
"@scure/bip39": ^1.2.2
"@noble/curves": ^1.6.0
"@noble/hashes": ^1.5.0
"@scure/bip32": ^1.5.0
"@scure/bip39": ^1.4.0
buffer: ^6.0.3
checksum: b3ecb577844c8fec075e46d49bc06692ad1a81bdd0f8e0b0b93b069f6a6b434c38e259095c0efbec69777d5089a55cf80b6855662740886c56002dbd72f7f9fa
checksum: 634afdd9d8fb262cd3db415794661ba5810f4ba1e64d1af0972c9a9b0a8c45106710a00ee25949014a361458ef544bdfcafe926c548c95b05fff9a41600d34a3
languageName: node
linkType: hard
@@ -1736,17 +1735,6 @@ __metadata:
languageName: node
linkType: hard
"@exodus/borc@npm:^2.1.1":
version: 2.1.1
resolution: "@exodus/borc@npm:2.1.1"
dependencies:
bignumber.js: ^8.0.1
ieee754: ^1.1.8
iso-url: ~0.4.4
checksum: 3a9776fc97914793d6fc9b8efb63ba4a3593f9423dde54d1edac9821adf02ba65a8eef0bcd358272528ce09cb6e45b0574805eb97412908ca9217f66e1ea53f6
languageName: node
linkType: hard
"@fastify/busboy@npm:^2.0.0":
version: 2.1.1
resolution: "@fastify/busboy@npm:2.1.1"
@@ -2818,6 +2806,15 @@ __metadata:
languageName: node
linkType: hard
"@noble/curves@npm:^1.6.0, @noble/curves@npm:~1.7.0":
version: 1.7.0
resolution: "@noble/curves@npm:1.7.0"
dependencies:
"@noble/hashes": 1.6.0
checksum: e220b704f1e516f326fff985e794e840a267f5542e1388737142b08177672ebc41b460b5a5bf636d7622c68e8ae719bc042ccd8aed16dc14311450a94b5f2a05
languageName: node
linkType: hard
"@noble/curves@npm:~1.1.0":
version: 1.1.0
resolution: "@noble/curves@npm:1.1.0"
@@ -2848,6 +2845,20 @@ __metadata:
languageName: node
linkType: hard
"@noble/hashes@npm:1.6.0":
version: 1.6.0
resolution: "@noble/hashes@npm:1.6.0"
checksum: 07729b80108d2a9b862eb4e070d4f78ca7ee86b9a9c13a4f7c338ba47a15d4386dd283235da71f21ad515fa9f0b9429fc3da39d2f2b4a50e2442212d14cfd4a9
languageName: node
linkType: hard
"@noble/hashes@npm:^1.5.0, @noble/hashes@npm:~1.6.0":
version: 1.6.1
resolution: "@noble/hashes@npm:1.6.1"
checksum: 57c62f65ee217c0293b4321b547792aa6d79812bfe70a7d62dc83e0f936cc677b14ed981b4e88cf8fdad37cd6d3a0cbd3bd0908b0728adc9daf066e678be8901
languageName: node
linkType: hard
"@noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.1":
version: 1.3.3
resolution: "@noble/hashes@npm:1.3.3"
@@ -3563,13 +3574,20 @@ __metadata:
languageName: node
linkType: hard
"@scure/base@npm:~1.1.7, @scure/base@npm:~1.1.8":
"@scure/base@npm:~1.1.7":
version: 1.1.9
resolution: "@scure/base@npm:1.1.9"
checksum: 120820a37dfe9dfe4cab2b7b7460552d08e67dee8057ed5354eb68d8e3440890ae983ce3bee957d2b45684950b454a2b6d71d5ee77c1fd3fddc022e2a510337f
languageName: node
linkType: hard
"@scure/base@npm:~1.2.1":
version: 1.2.1
resolution: "@scure/base@npm:1.2.1"
checksum: 061e04e4f6ed7bada6cdad4c799e6a82f30dda3f4008895bdb2e556f333f9b41f44dc067d25c064357ed6c012ea9c8be1e7927caf8a083af865b8de27b52370c
languageName: node
linkType: hard
"@scure/bip32@npm:1.3.1":
version: 1.3.1
resolution: "@scure/bip32@npm:1.3.1"
@@ -3592,6 +3610,17 @@ __metadata:
languageName: node
linkType: hard
"@scure/bip32@npm:^1.5.0":
version: 1.6.0
resolution: "@scure/bip32@npm:1.6.0"
dependencies:
"@noble/curves": ~1.7.0
"@noble/hashes": ~1.6.0
"@scure/base": ~1.2.1
checksum: 1347477e28678a9bc4e2ec5e8e0f679263f2e3cb19c0e65849f76810c4c608461d4b283521c897249fa7dacc8c76e1b50e2a866b22467c8e93662a9c545cd42b
languageName: node
linkType: hard
"@scure/bip39@npm:1.2.1":
version: 1.2.1
resolution: "@scure/bip39@npm:1.2.1"
@@ -3602,13 +3631,13 @@ __metadata:
languageName: node
linkType: hard
"@scure/bip39@npm:^1.2.2":
version: 1.4.0
resolution: "@scure/bip39@npm:1.4.0"
"@scure/bip39@npm:^1.4.0":
version: 1.5.0
resolution: "@scure/bip39@npm:1.5.0"
dependencies:
"@noble/hashes": ~1.5.0
"@scure/base": ~1.1.8
checksum: 211f2c01361993bfe54c0e4949f290224381457c7f76d7cd51d6a983f3f4b6b9f85adfd0e623977d777ed80417a5fe729eb19dd34e657147810a0e58a8e7b9e0
"@noble/hashes": ~1.6.0
"@scure/base": ~1.2.1
checksum: 03d1888f5d0d514eebc5c3adc1e071d225963d434fcf789abea5ef2c8b4b99f3ad9ebee8a597c0c13d5415e6b2b380f55f61560c1643cd871961ab91cbcf5122
languageName: node
linkType: hard
@@ -4900,13 +4929,6 @@ __metadata:
languageName: node
linkType: hard
"bignumber.js@npm:^8.0.1":
version: 8.1.1
resolution: "bignumber.js@npm:8.1.1"
checksum: 71544348507cd16c2c152699a8e5146964d9cf4f46108075bcd16a964dffb3963256259fc6ffc9753b4d33dfd0cf863a6b564103a3a20e10002f12a1902e8075
languageName: node
linkType: hard
"bl@npm:^4.1.0":
version: 4.1.0
resolution: "bl@npm:4.1.0"
@@ -7444,7 +7466,7 @@ __metadata:
languageName: node
linkType: hard
"ieee754@npm:^1.1.13, ieee754@npm:^1.1.8, ieee754@npm:^1.2.1":
"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1":
version: 1.2.1
resolution: "ieee754@npm:1.2.1"
checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e
@@ -7963,13 +7985,6 @@ __metadata:
languageName: node
linkType: hard
"iso-url@npm:~0.4.4":
version: 0.4.7
resolution: "iso-url@npm:0.4.7"
checksum: c42ae615b462fec55ea7b480548fc76ef69af26103fcbb12a305dd929a4c18c6b22e29c666b0601280e552f56b0f144eab0b28b9a6fbb12ec58dc7c8ae053124
languageName: node
linkType: hard
"isobject@npm:^3.0.1":
version: 3.0.1
resolution: "isobject@npm:3.0.1"
@@ -9426,8 +9441,7 @@ __metadata:
"@babel/core": ^7.20.0
"@babel/preset-env": ^7.20.0
"@babel/runtime": ^7.20.0
"@cashu/cashu-ts": "minibits-cash/cashu-ts#commit=8dcac7ce6d192c419aee5547f0db03c23b593a57"
"@exodus/borc": ^2.1.1
"@cashu/cashu-ts": "minibits-cash/cashu-ts#cashu-ts-2.0"
"@fortawesome/fontawesome-svg-core": ^6.6.0
"@fortawesome/free-brands-svg-icons": ^6.6.0
"@fortawesome/free-regular-svg-icons": ^6.6.0