mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 19:28:26 +00:00
Merge branch 'cashu-ts-2.0'
This commit is contained in:
+2
-3
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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[] }
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'> {}
|
||||
|
||||
|
||||
@@ -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,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'
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'}`}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user