mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 19:28:26 +00:00
- coin selection for faster and more frequent offline sends
Experimental, test-oriented changes: - fees support - binary v4 token format for smaller QR codes
This commit is contained in:
@@ -144,7 +144,7 @@ Minibits is a bare React Native app written in Typescript. The project structure
|
||||
|
||||
The code is derived from Ignite template, however with many libraries, notably Expo, stripped down to achieve fast startup times. Performance bottleneck on some Android devices is react-native-keychain. To overcome this, it has been patched not to warm-up on startup, caching for wallet operations is in place and its use to encrypt storage is opt-in.
|
||||
|
||||
Wallet state is managed by mobx-state-tree and persisted in fast MMKV storage. Only the basic mobx concepts are in place, whole model could be improved. All critical wallet code is in services/walletService.ts and all ecash state changes are in models/ProofsStore.ts. Wallet communication with the mints is in services/cashuMintClient.ts and uses [cashu-ts](https://github.com/cashubtc/cashu-ts) library.
|
||||
Wallet state is managed by mobx-state-tree and persisted in fast MMKV storage. Only the basic mobx concepts are in place, whole model could be improved. All critical wallet code is in services/walletService.ts and all ecash state changes are in models/ProofsStore.ts. Wallet communication with the mints is in model/Wallet.ts and uses [cashu-ts](https://github.com/cashubtc/cashu-ts) library.
|
||||
|
||||
Crypto operations are handled by react-native-quick-crypto, that is fast and does not require awful javascript shims. Transaction history and ecash backup is stored in sqlite, with fast react-native-quick-sqlite driver that enables to run lighter queries synchronously.
|
||||
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import {
|
||||
} from '@gocodingnow/rn-size-matters'
|
||||
import {AppNavigator} from './navigation'
|
||||
import {useInitialRootStore, useStores} from './models'
|
||||
import {Database, MinibitsClient} from './services'
|
||||
import {Database} from './services'
|
||||
import {ErrorBoundary} from './screens/ErrorScreen/ErrorBoundary'
|
||||
import Config from './config'
|
||||
import {log} from './services'
|
||||
|
||||
+17
-32
@@ -1,27 +1,20 @@
|
||||
import {cast, flow, getSnapshot, Instance, SnapshotIn, SnapshotOut, types} from 'mobx-state-tree'
|
||||
import {cast, flow, getRoot, getSnapshot, Instance, SnapshotIn, SnapshotOut, types} from 'mobx-state-tree'
|
||||
import {withSetPropAction} from './helpers/withSetPropAction'
|
||||
import type {
|
||||
CashuWallet,
|
||||
import type {
|
||||
GetInfoResponse,
|
||||
MintKeys as CashuMintKeys,
|
||||
MintKeyset as CashuMintKeyset
|
||||
} from '@cashu/cashu-ts'
|
||||
import {colors, getRandomIconColor} from '../theme'
|
||||
import { log, MintClient } from '../services'
|
||||
import { log } from '../services'
|
||||
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import { MintUnit, MintUnits } from '../services/wallet/currency'
|
||||
import { getRootStore } from './helpers/getRootStore'
|
||||
import { generateId } from '../utils/utils'
|
||||
import { Proof } from './Proof'
|
||||
import { ProofV3 } from '../services/cashu/cashuUtils'
|
||||
|
||||
|
||||
// used as a helper type across app
|
||||
/* export type Balance = {
|
||||
balance: number
|
||||
unit: MintUnit
|
||||
}*/
|
||||
|
||||
export type MintBalance = {
|
||||
mintUrl: string
|
||||
balances: {
|
||||
@@ -53,15 +46,15 @@ export type MintProofsCounter = {
|
||||
inFlightTid?: number // related tx id
|
||||
}
|
||||
|
||||
/* export const MintProofsCounterModel = types
|
||||
.model('MintProofsCounter', {
|
||||
keyset: types.string,
|
||||
unit: types.optional(types.frozen<MintUnit>(), 'sat'),
|
||||
counter: types.number,
|
||||
inFlightFrom: types.maybe(types.number),
|
||||
inFlightTo: types.maybe(types.number),
|
||||
inFlightTid: types.maybe(types.number)
|
||||
})*/
|
||||
export const MintProofsCounter = types.model('MintProofsCounter', {
|
||||
keyset: types.string,
|
||||
unit: types.optional(types.frozen<MintUnit>(), 'sat'),
|
||||
counter: types.number,
|
||||
inFlightFrom: types.maybe(types.number),
|
||||
inFlightTo: types.maybe(types.number),
|
||||
inFlightTid: types.maybe(types.number)
|
||||
})
|
||||
|
||||
/**
|
||||
* This represents a Cashu mint
|
||||
*/
|
||||
@@ -74,16 +67,7 @@ export const MintModel = types
|
||||
units: types.array(types.frozen<MintUnit>()),
|
||||
keysets: types.array(types.frozen<CashuMintKeyset>()),
|
||||
keys: types.array(types.frozen<CashuMintKeys>()),
|
||||
proofsCounters: types.array(
|
||||
types.model('MintProofsCounter', {
|
||||
keyset: types.string,
|
||||
unit: types.optional(types.frozen<MintUnit>(), 'sat'),
|
||||
counter: types.number,
|
||||
inFlightFrom: types.maybe(types.number),
|
||||
inFlightTo: types.maybe(types.number),
|
||||
inFlightTid: types.maybe(types.number)
|
||||
})
|
||||
),
|
||||
proofsCounters: types.array(MintProofsCounter),
|
||||
color: types.optional(types.string, colors.palette.iconBlue200),
|
||||
status: types.optional(types.frozen<MintStatus>(), MintStatus.ONLINE),
|
||||
createdAt: types.optional(types.Date, new Date()),
|
||||
@@ -334,7 +318,8 @@ export const MintModel = types
|
||||
let shortname = self.mintUrl.substring(lastSlashIndex + 1).slice(0, 25)
|
||||
|
||||
try {
|
||||
const info: GetInfoResponse = yield MintClient.getMintInfo(self.mintUrl)
|
||||
const {walletStore} = getRoot(self).nonPersistedStores
|
||||
const info: GetInfoResponse = yield walletStore.getMintInfo(self.mintUrl)
|
||||
|
||||
if(info.name.length > 0) {
|
||||
shortname = info.name
|
||||
@@ -421,7 +406,7 @@ export const MintModel = types
|
||||
|
||||
self.proofsCounters = cast(self.proofsCounters)
|
||||
},
|
||||
getMintFeeReserve(proofs: Proof[]): number {
|
||||
getMintFeeReserve(proofs: ProofV3[]): 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)
|
||||
|
||||
+82
-13
@@ -6,18 +6,22 @@ import {
|
||||
isStateTreeNode,
|
||||
detach,
|
||||
flow,
|
||||
getRoot,
|
||||
getSnapshot,
|
||||
getParent,
|
||||
} from 'mobx-state-tree'
|
||||
import {withSetPropAction} from './helpers/withSetPropAction'
|
||||
import {MintModel, Mint, MintBalance} from './Mint'
|
||||
import {MintModel, Mint, MintProofsCounter} from './Mint'
|
||||
import {log} from '../services/logService'
|
||||
import { MintClient } from '../services'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import type {
|
||||
MintKeys as CashuMintKeys,
|
||||
MintKeyset as CashuMintKeyset
|
||||
} from '@cashu/cashu-ts'
|
||||
|
||||
import { MintUnit, MintUnits } from '../services/wallet/currency'
|
||||
import { MintUnit } from '../services/wallet/currency'
|
||||
|
||||
|
||||
|
||||
export type MintsByHostname = {
|
||||
hostname: string
|
||||
@@ -29,10 +33,22 @@ export type MintsByUnit = {
|
||||
mints: Mint[]
|
||||
}
|
||||
|
||||
export type CounterBackup = {
|
||||
mintUrl: string
|
||||
proofCounters: MintProofsCounter
|
||||
}
|
||||
|
||||
// Define the CounterBackup model
|
||||
const CounterBackupModel = types.model('CounterBackup', {
|
||||
mintUrl: types.string,
|
||||
counters: types.array(MintProofsCounter)
|
||||
})
|
||||
|
||||
export const MintsStoreModel = types
|
||||
.model('MintsStore', {
|
||||
mints: types.array(MintModel),
|
||||
blockedMintUrls: types.array(types.string),
|
||||
blockedMintUrls: types.array(types.string),
|
||||
counterBackups: types.array(CounterBackupModel)
|
||||
})
|
||||
.views(self => ({
|
||||
findByUrl: (mintUrl: string | URL) => {
|
||||
@@ -41,15 +57,62 @@ export const MintsStoreModel = types
|
||||
},
|
||||
}))
|
||||
.actions(withSetPropAction)
|
||||
.actions(self => ({
|
||||
addOrUpdateCounterBackup(mintToRemove: Mint) {
|
||||
try {
|
||||
const existingIndex = self.counterBackups.findIndex(
|
||||
(backup) => backup.mintUrl === mintToRemove.mintUrl
|
||||
)
|
||||
|
||||
const newCounterBackup = CounterBackupModel.create({
|
||||
mintUrl: mintToRemove.mintUrl,
|
||||
counters: getSnapshot(mintToRemove.proofsCounters!)
|
||||
})
|
||||
|
||||
if (existingIndex !== -1) {
|
||||
// Replace existing backup
|
||||
self.counterBackups[existingIndex] = newCounterBackup
|
||||
} else {
|
||||
// Add new backup
|
||||
self.counterBackups.push(newCounterBackup)
|
||||
}
|
||||
} catch (e: any) {
|
||||
throw new AppError(Err.STORAGE_ERROR, e.message)
|
||||
}
|
||||
},
|
||||
updateMintCountersFromBackup(newMint: Mint) {
|
||||
const backup = self.counterBackups.find(
|
||||
(backup) => backup.mintUrl === newMint.mintUrl
|
||||
)
|
||||
|
||||
if (backup) {
|
||||
newMint.proofsCounters!.forEach((proofsCounter) => {
|
||||
const backupCounter = backup.counters.find(
|
||||
(counter) => counter.keyset === proofsCounter.keyset
|
||||
)
|
||||
|
||||
if (backupCounter) {
|
||||
newMint.increaseProofsCounter!(proofsCounter.keyset, backupCounter.counter)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
}))
|
||||
.actions(self => ({
|
||||
addMint: flow(function* addMint(mintUrl: string) {
|
||||
if(!mintUrl.includes('.onion') && !mintUrl.startsWith('https')) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint URL needs to start with https.')
|
||||
}
|
||||
|
||||
// create default wallet instance then download and cache up to date mint keys in that instance
|
||||
const keysets: CashuMintKeyset[] = yield MintClient.getMintKeysets(mintUrl)
|
||||
const keys: CashuMintKeys[] = yield MintClient.getMintKeys(mintUrl)
|
||||
log.trace('[addMint] start')
|
||||
|
||||
// create default wallet instance then download and cache up to date mint keys in that instance
|
||||
const {walletStore} = getRoot(self).nonPersistedStores
|
||||
|
||||
const keysets: CashuMintKeyset[] = yield walletStore.getMintKeysets(mintUrl)
|
||||
const keys: CashuMintKeys[] = yield walletStore.getMintKeys(mintUrl)
|
||||
|
||||
log.trace('[addMint]', {keysets})
|
||||
|
||||
if(!keysets || keysets.length === 0 || !keys || keys.length === 0) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint has no keysets and is not operational.', {mintUrl})
|
||||
@@ -73,8 +136,12 @@ export const MintsStoreModel = types
|
||||
mintInstance.initKeys(key)
|
||||
}
|
||||
|
||||
log.trace('[addMint] updateMintCountersFromBackup')
|
||||
|
||||
self.updateMintCountersFromBackup(mintInstance)
|
||||
|
||||
mintInstance.setHostname()
|
||||
yield mintInstance.setShortname()
|
||||
yield mintInstance.setShortname()
|
||||
|
||||
self.mints.push(mintInstance)
|
||||
}),
|
||||
@@ -85,8 +152,9 @@ export const MintsStoreModel = types
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Could not find mint to update', {mintUrl})
|
||||
}
|
||||
// refresh up to date mint keys
|
||||
const keysets: CashuMintKeyset[] = yield MintClient.getMintKeysets(mintUrl)
|
||||
const keys: CashuMintKeys[] = yield MintClient.getMintKeys(mintUrl)
|
||||
const {walletStore} = getRoot(self).nonPersistedStores
|
||||
const keysets: CashuMintKeyset[] = yield walletStore.getMintKeysets(mintUrl)
|
||||
const keys: CashuMintKeys[] = yield walletStore.getMintKeys(mintUrl)
|
||||
|
||||
if(!keysets || keysets.length === 0 || !keys || keys.length === 0) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint has no keysets and is not operational', {mintUrl})
|
||||
@@ -106,7 +174,7 @@ export const MintsStoreModel = types
|
||||
}
|
||||
|
||||
mintInstance.initKeys(key)
|
||||
}
|
||||
}
|
||||
|
||||
yield mintInstance.setShortname()
|
||||
|
||||
@@ -126,6 +194,7 @@ export const MintsStoreModel = types
|
||||
}
|
||||
|
||||
if (mintInstance) {
|
||||
self.addOrUpdateCounterBackup(mintInstance)
|
||||
detach(mintInstance)
|
||||
destroy(mintInstance)
|
||||
log.info('[removeMint]', 'Mint removed from MintsStore')
|
||||
@@ -142,7 +211,7 @@ export const MintsStoreModel = types
|
||||
unblockMint(blockedMint: Mint) {
|
||||
self.blockedMintUrls.remove(blockedMint.mintUrl)
|
||||
log.debug('[unblockMint]', 'Mint unblocked in MintsStore')
|
||||
}
|
||||
}
|
||||
}))
|
||||
.views(self => ({
|
||||
get mintCount() {
|
||||
@@ -155,7 +224,7 @@ export const MintsStoreModel = types
|
||||
const grouped: Record<string, MintsByHostname> = {}
|
||||
|
||||
self.mints.forEach((mint: Mint) => {
|
||||
const {hostname} = mint
|
||||
const hostname = mint.hostname!
|
||||
|
||||
if (!grouped[hostname as string]) {
|
||||
grouped[hostname as string] = {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {getRootStore} from './helpers/getRootStore'
|
||||
import AppError, {Err} from '../utils/AppError'
|
||||
import {Mint, MintBalance, UnitBalance} from './Mint'
|
||||
import {Database} from '../services'
|
||||
import { MintUnit, MintUnits } from '../services/wallet/currency'
|
||||
import { MintUnit } from '../services/wallet/currency'
|
||||
|
||||
export const ProofsStoreModel = types
|
||||
.model('Proofs', {
|
||||
|
||||
@@ -7,9 +7,15 @@ import {WalletProfileStoreModel} from './WalletProfileStore'
|
||||
import {PaymentRequestsStoreModel} from './PaymentRequestsStore'
|
||||
import {ProofsStoreModel} from './ProofsStore'
|
||||
import {RelaysStoreModel} from './RelaysStore'
|
||||
import {WalletStoreModel} from './WalletStore'
|
||||
|
||||
export const rootStoreModelVersion = 16 // Update this if model changes require migrations defined in setupRootStore.ts
|
||||
|
||||
// Ephemeral non-persisted stores
|
||||
const NonPersistedStoresModel = types.model('NonPersistedStores', {
|
||||
walletStore: types.optional(WalletStoreModel, {}),
|
||||
|
||||
});
|
||||
/**
|
||||
* A RootStore model.
|
||||
*/
|
||||
@@ -24,6 +30,7 @@ export const RootStoreModel = types
|
||||
paymentRequestsStore: types.optional(PaymentRequestsStoreModel, {}),
|
||||
proofsStore: types.optional(ProofsStoreModel, {}),
|
||||
relaysStore: types.optional(RelaysStoreModel, {}),
|
||||
nonPersistedStores: types.optional(NonPersistedStoresModel, {}),
|
||||
version: types.optional(types.number, rootStoreModelVersion),
|
||||
})
|
||||
.actions(self => ({
|
||||
@@ -32,6 +39,8 @@ export const RootStoreModel = types
|
||||
},
|
||||
}))
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The RootStore instance.
|
||||
*/
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import {Instance, types} from 'mobx-state-tree'
|
||||
import {TokenEntryModel} from './TokenEntry'
|
||||
import { MintUnit } from '../services'
|
||||
|
||||
/**
|
||||
* This represents sendable Cashu token V3
|
||||
*/
|
||||
|
||||
export const TokenModel = types.model('Token', {
|
||||
token: types.array(TokenEntryModel),
|
||||
unit: types.maybe(types.frozen<MintUnit>()),
|
||||
memo: types.maybe(types.string),
|
||||
})
|
||||
|
||||
export interface Token extends Instance<typeof TokenModel> {}
|
||||
@@ -1,24 +0,0 @@
|
||||
import {Instance, SnapshotIn, SnapshotOut, types} from 'mobx-state-tree'
|
||||
import {withSetPropAction} from './helpers/withSetPropAction'
|
||||
import {ProofModel} from './Proof'
|
||||
// import type { Proof } from '@cashu/cashu-ts'
|
||||
|
||||
/**
|
||||
* This represents a Cashu token V3
|
||||
*/
|
||||
|
||||
export const TokenEntryModel = types
|
||||
.model('TokenEntry', {
|
||||
mint: types.string,
|
||||
proofs: types.array(
|
||||
types.safeReference(ProofModel, {acceptsUndefined: false}),
|
||||
),
|
||||
transactionId: types.maybe(types.number),
|
||||
})
|
||||
|
||||
|
||||
export interface TokenEntry extends Instance<typeof TokenEntryModel> {}
|
||||
export interface TokenEntrySnapshotOut
|
||||
extends SnapshotOut<typeof TokenEntryModel> {}
|
||||
export interface TokenEntrySnapshotIn
|
||||
extends SnapshotIn<typeof TokenEntryModel> {}
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
TransactionStatus,
|
||||
TransactionRecord,
|
||||
} from './Transaction'
|
||||
import {Database, MintClient} from '../services'
|
||||
import {Database} from '../services'
|
||||
import {log} from '../services/logService'
|
||||
import { getRootStore } from './helpers/getRootStore'
|
||||
import { formatDistance } from 'date-fns'
|
||||
@@ -35,10 +35,6 @@ export type GroupedByTimeAgo = {
|
||||
export const TransactionsStoreModel = types
|
||||
.model('TransactionsStore', {
|
||||
transactions: types.array(TransactionModel),
|
||||
/*tranUnit: types.maybe(types.frozen<MintUnit>()),
|
||||
tranMint: types.maybe(types.frozen<Mint>()),
|
||||
tranOption: types.maybe(types.frozen<ReceiveOption | SendOption>()),
|
||||
tranContact: types.maybe(types.frozen<Contact>())*/
|
||||
})
|
||||
.actions(withSetPropAction)
|
||||
.views(self => ({
|
||||
@@ -98,7 +94,7 @@ export const TransactionsStoreModel = types
|
||||
recentByHostnameGroupedByTimeAgo(mintHostname: string) {
|
||||
const recentByHostname = this.recentByHostname(mintHostname)
|
||||
|
||||
return recentByHostname.reduce((groups: GroupedByTimeAgo, transaction: Transaction) => {
|
||||
return recentByHostname.reduce((groups: GroupedByTimeAgo, transaction: Transaction) => {
|
||||
const timeAgo = formatDistance(transaction.createdAt as Date, new Date(), {addSuffix: true})
|
||||
if (!groups[timeAgo]) {
|
||||
groups[timeAgo] = []
|
||||
@@ -156,7 +152,7 @@ export const TransactionsStoreModel = types
|
||||
|
||||
const transactionsToRemove = self.transactions.filter(transaction => {
|
||||
// Check if the mint property of the transaction does not exist in the mints array
|
||||
return !mintsStore.allMints.some(mint => mint.mintUrl === transaction.mint);
|
||||
return !mintsStore.allMints.some((mint: Mint) => mint.mintUrl === transaction.mint);
|
||||
});
|
||||
|
||||
self.transactions.replace(self.transactions.filter(t => !transactionsToRemove.some(removed => removed.id === t.id)))
|
||||
@@ -353,30 +349,6 @@ export const TransactionsStoreModel = types
|
||||
self.transactions.clear()
|
||||
log.debug('[removeAllTransactions]', 'Removed all transactions from TransactionsStore')
|
||||
},
|
||||
/* setTranUnit(unit: MintUnit) {
|
||||
self.tranUnit = unit
|
||||
},
|
||||
setTranMint(mint: Mint) {
|
||||
self.tranMint = mint
|
||||
},
|
||||
setTranOption(option: ReceiveOption | SendOption) {
|
||||
self.tranOption = option
|
||||
},
|
||||
setTranContact(contact: Contact) {
|
||||
self.tranContact = contact
|
||||
},
|
||||
unsetTranUnit() {
|
||||
self.tranUnit = undefined
|
||||
},
|
||||
unsetTranMint(mint: Mint) {
|
||||
self.tranMint = undefined
|
||||
},
|
||||
unsetTranOption(option: ReceiveOption | SendOption) {
|
||||
self.tranOption = undefined
|
||||
},
|
||||
unsetTranContact(contact: Contact) {
|
||||
self.tranContact = undefined
|
||||
},*/
|
||||
}))
|
||||
|
||||
const getHostname = function (mintUrl: string) {
|
||||
|
||||
@@ -0,0 +1,688 @@
|
||||
import {Instance, SnapshotOut, types, flow, getRoot} from 'mobx-state-tree'
|
||||
import {
|
||||
AmountPreference,
|
||||
CashuMint,
|
||||
CashuWallet,
|
||||
MeltQuoteResponse,
|
||||
MeltTokensResponse,
|
||||
setGlobalRequestOptions,
|
||||
deriveSeedFromMnemonic,
|
||||
type MintKeys,
|
||||
type MintKeyset
|
||||
} from '@cashu/cashu-ts'
|
||||
import { JS_BUNDLE_VERSION } from '@env'
|
||||
import {KeyChain} from '../services'
|
||||
import {log} from '../services/logService'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import { MintUnit } from '../services/wallet/currency'
|
||||
import { CashuUtils, ProofV3, TokenV3 } from '../services/cashu/cashuUtils'
|
||||
import { Proof } from './Proof'
|
||||
import { isObj } from '@cashu/cashu-ts/src/utils'
|
||||
import { Mint, MintModel } from './Mint'
|
||||
import { getRootStore } from './helpers/getRootStore'
|
||||
|
||||
/*
|
||||
Not persisted, in-memory only model of the cashu-ts wallet instances and seed.
|
||||
It is instantiated on first use so that wallet retrieves fresh mint keysets, then cached,
|
||||
so that new cashu-ts instances are re-used over app lifecycle. Seed is as well retrieved as needed and cached because retrieval from keychain might be slow.
|
||||
*/
|
||||
|
||||
export const WalletStoreModel = types
|
||||
.model('WalletStore', {
|
||||
mints: types.array(types.frozen<CashuMint>()),
|
||||
wallets: types.array(types.frozen<CashuWallet>()),
|
||||
seedWallets: types.array(types.frozen<CashuWallet>()),
|
||||
mnemonicPhrase: types.maybe(types.string),
|
||||
seedBase64: types.maybe(types.string),
|
||||
})
|
||||
.actions(self => ({
|
||||
getMnemonic: flow(function* getMnemonic() {
|
||||
if (self.mnemonicPhrase) {
|
||||
log.trace('[getMnemonic]', 'returning cached mnemonic')
|
||||
return self.mnemonicPhrase
|
||||
}
|
||||
|
||||
const mnemonic: string | undefined = yield KeyChain.loadMnemonic()
|
||||
|
||||
if (!mnemonic) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
self.mnemonicPhrase = mnemonic
|
||||
return mnemonic
|
||||
}),
|
||||
getSeed: flow(function* getSeed() {
|
||||
if (self.seedBase64) {
|
||||
log.trace('[getMnemonic]', 'returning cached mnemonic')
|
||||
return new Uint8Array(Buffer.from(self.seedBase64, 'base64'))
|
||||
}
|
||||
|
||||
const seed = yield KeyChain.loadSeed()
|
||||
|
||||
if (!seed) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
self.seedBase64 = Buffer.from(seed).toString('base64')
|
||||
return seed
|
||||
}),
|
||||
getMintModelInstance(mintUrl: string) {
|
||||
const mintsStore = getRoot(self).mintsStore
|
||||
return mintsStore.findByUrl(mintUrl)
|
||||
}
|
||||
}))
|
||||
.actions(self => ({
|
||||
getOrCreateMnemonic: flow(function* getOrCreateMnemonic() {
|
||||
let mnemonic: string | undefined = undefined
|
||||
|
||||
mnemonic = yield self.getMnemonic() // returns cached or saved mnemonic
|
||||
|
||||
if (!mnemonic) {
|
||||
mnemonic = KeyChain.generateMnemonic() as string
|
||||
const seed = deriveSeedFromMnemonic(mnemonic) // expensive
|
||||
|
||||
yield KeyChain.saveMnemonic(mnemonic)
|
||||
yield KeyChain.saveSeed(seed)
|
||||
|
||||
log.trace('[getOrCreateMnemonic]', 'Created and saved new mnemonic and seed')
|
||||
}
|
||||
|
||||
return mnemonic
|
||||
}),
|
||||
getMint: flow(function* getMint(mintUrl: string) {
|
||||
const mint = self.mints.find(m => m.mintUrl === mintUrl)
|
||||
|
||||
if (mint) {
|
||||
return mint
|
||||
}
|
||||
|
||||
setGlobalRequestOptions({
|
||||
headers: {'User-Agent': `Minibits/${JS_BUNDLE_VERSION}`}
|
||||
})
|
||||
// create cashu-ts mint instance
|
||||
const newMint = new CashuMint(mintUrl)
|
||||
|
||||
// get fresh keysets
|
||||
const {keysets} = yield newMint.getKeySets()
|
||||
|
||||
// get persisted mint model from wallet state
|
||||
const mintInstance = self.getMintModelInstance(mintUrl)
|
||||
|
||||
// skip checks if this is new mint being added
|
||||
if(mintInstance) {
|
||||
const newKeysets = keysets.filter((freshKeyset: MintKeyset) => {
|
||||
return !mintInstance.keysets.some((keyset: MintKeyset) => keyset.id === freshKeyset.id)
|
||||
})
|
||||
|
||||
if(newKeysets.length > 0) {
|
||||
// if we heve new keysets, get and sync new keys
|
||||
const {keysets} = yield newMint.getKeys()
|
||||
mintInstance.refreshKeys(keysets)
|
||||
}
|
||||
|
||||
// sync wallet state with fresh keysets, active statuses and keys
|
||||
mintInstance.refreshKeysets(keysets)
|
||||
}
|
||||
|
||||
// store cashu-ts mint instance in memory
|
||||
self.mints.push(newMint)
|
||||
|
||||
return newMint
|
||||
})
|
||||
}))
|
||||
.actions(self => ({
|
||||
getWallet: flow(function* getWallet(
|
||||
mintUrl: string,
|
||||
unit: MintUnit,
|
||||
options?: {
|
||||
keysetId?: string
|
||||
withSeed: boolean
|
||||
}
|
||||
) {
|
||||
log.trace('[WalletStore.getWallet] start')
|
||||
// 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
|
||||
const mintInstance = self.getMintModelInstance(mintUrl)
|
||||
if(!mintInstance) {
|
||||
throw new AppError(Err.NOTFOUND_ERROR, 'Mint not found in the wallet state.', {
|
||||
mintUrl
|
||||
})
|
||||
}
|
||||
|
||||
// select keys to be used to find or create new cashu-ts wallet instance
|
||||
let walletKeys: MintKeys
|
||||
if(options && options.keysetId) {
|
||||
|
||||
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.', {
|
||||
mintUrl,
|
||||
keysetId: options.keysetId
|
||||
})
|
||||
}
|
||||
|
||||
if(requestedKeys.unit !== unit) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Wallet unit and keys mismatch.', {
|
||||
mintUrl,
|
||||
keysetId: options.keysetId,
|
||||
unit
|
||||
})
|
||||
}
|
||||
|
||||
walletKeys = requestedKeys
|
||||
} else {
|
||||
// if not we find active keyset with lowest fees and related keys
|
||||
const activeKeyset: MintKeyset = mintInstance.keysets
|
||||
.filter((k: MintKeyset) => k.unit === unit && k.active)
|
||||
.sort((a: MintKeyset, b: MintKeyset) => (a.input_fee_ppk ?? 0) - (b.input_fee_ppk ?? 0))[0]
|
||||
|
||||
if(!activeKeyset) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Wallet has not any active keyset for the selected unit.', {
|
||||
mintUrl,
|
||||
unit
|
||||
})
|
||||
}
|
||||
|
||||
const activeKeys = mintInstance.keys.find((k: MintKeys) => k.id === activeKeyset.id)
|
||||
|
||||
if(!activeKeys) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Wallet has not any keys for the selected unit.', {
|
||||
mintUrl,
|
||||
unit
|
||||
})
|
||||
}
|
||||
|
||||
walletKeys = activeKeys
|
||||
}
|
||||
|
||||
if (options && options.withSeed) {
|
||||
|
||||
const seedWallet: CashuWallet | undefined = self.seedWallets.find(
|
||||
w => w.mint.mintUrl === mintUrl &&
|
||||
w.keys.id === walletKeys.id
|
||||
)
|
||||
|
||||
if (seedWallet) {
|
||||
return seedWallet
|
||||
}
|
||||
|
||||
let seed: Uint8Array | undefined = undefined
|
||||
seed = yield self.getSeed()
|
||||
|
||||
const newSeedWallet = new CashuWallet(cashuMint, {
|
||||
keys: walletKeys,
|
||||
mnemonicOrSeed: seed
|
||||
})
|
||||
|
||||
self.seedWallets.push(newSeedWallet)
|
||||
|
||||
log.trace('[getWallet]', 'Returning NEW cashuWallet instance with seed')
|
||||
|
||||
return newSeedWallet
|
||||
}
|
||||
|
||||
const wallet: CashuWallet | undefined = self.wallets.find(
|
||||
w => w.mint.mintUrl === mintUrl &&
|
||||
w.keys.id === walletKeys.id
|
||||
)
|
||||
|
||||
if (wallet) {
|
||||
return wallet
|
||||
}
|
||||
|
||||
const newWallet = new CashuWallet(cashuMint, {
|
||||
keys: walletKeys,
|
||||
mnemonicOrSeed: undefined
|
||||
})
|
||||
|
||||
self.wallets.push(newWallet)
|
||||
|
||||
log.trace('[getWallet]', 'Returning NEW cashuWallet instance')
|
||||
return newWallet
|
||||
}),
|
||||
getMintKeysets: flow(function* getMintKeysets(mintUrl: string) {
|
||||
log.trace('[getMintKeysets] start')
|
||||
const cashuMint: CashuMint = yield self.getMint(mintUrl)
|
||||
|
||||
try {
|
||||
const {keysets} = yield cashuMint.getKeySets() // all
|
||||
return keysets
|
||||
} catch (e: any) {
|
||||
let message = 'Could not connect to the selected mint.'
|
||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||
throw new AppError(Err.CONNECTION_ERROR, message, { message: e.message, mintUrl })
|
||||
}
|
||||
}),
|
||||
getMintKeys: flow(function* getMintKeys(mintUrl: string) {
|
||||
const cashuMint: CashuMint = yield self.getMint(mintUrl)
|
||||
|
||||
try {
|
||||
const {keysets} = yield cashuMint.getKeys() // all
|
||||
return keysets
|
||||
} catch (e: any) {
|
||||
let message = 'Could not connect to the selected mint.'
|
||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||
throw new AppError(Err.CONNECTION_ERROR, message, { message: e.message, mintUrl })
|
||||
}
|
||||
}),
|
||||
resetWallets() {
|
||||
self.seedWallets.clear()
|
||||
self.wallets.clear()
|
||||
}
|
||||
}))
|
||||
.actions(self => ({
|
||||
receive: flow(function* receive(mintUrl: string,
|
||||
unit: MintUnit,
|
||||
decodedToken: TokenV3,
|
||||
mintFeeReserve: number,
|
||||
options: {
|
||||
preference: AmountPreference[],
|
||||
counter: number
|
||||
}) {
|
||||
|
||||
try {
|
||||
const cashuWallet = yield self.getWallet(
|
||||
mintUrl,
|
||||
unit,
|
||||
{
|
||||
withSeed: true,
|
||||
})
|
||||
|
||||
const amountToReceive: number = CashuUtils.getTokenAmounts(decodedToken).totalAmount
|
||||
|
||||
const proofs = yield cashuWallet.receive(
|
||||
decodedToken,
|
||||
mintFeeReserve,
|
||||
{
|
||||
keysetId: cashuWallet.keys.id,
|
||||
preference: options.preference,
|
||||
counter: options.counter,
|
||||
pubkey: undefined,
|
||||
privkey: undefined
|
||||
})
|
||||
|
||||
const receivedAmount: number = CashuUtils.getProofsAmount(proofs as Proof[])
|
||||
const mintFeePaid = amountToReceive - receivedAmount
|
||||
|
||||
return {proofs, mintFeePaid}
|
||||
} catch (e: any) {
|
||||
throw new AppError(Err.MINT_ERROR, e.message)
|
||||
}
|
||||
}),
|
||||
send: flow(function* send(mintUrl: string,
|
||||
amountToSend: number,
|
||||
mintFeeReserve: number,
|
||||
unit: MintUnit,
|
||||
proofsToSendFrom: Proof[],
|
||||
options: {
|
||||
preference: AmountPreference[],
|
||||
counter: number
|
||||
} ) {
|
||||
try {
|
||||
const cashuWallet = yield self.getWallet(
|
||||
mintUrl,
|
||||
unit,
|
||||
{
|
||||
withSeed: true,
|
||||
})
|
||||
|
||||
log.debug('[WalletStore.send] counter', options.counter)
|
||||
|
||||
const {returnChange, send} = yield cashuWallet.send(
|
||||
amountToSend,
|
||||
mintFeeReserve,
|
||||
proofsToSendFrom,
|
||||
{
|
||||
keysetId: cashuWallet.keys.id,
|
||||
preference: options.preference,
|
||||
counter: options.counter,
|
||||
pubkey: undefined,
|
||||
privkey: undefined
|
||||
}
|
||||
)
|
||||
|
||||
log.debug('[WalletStore.send] returnedProofs', returnChange)
|
||||
log.debug('[WalletStore.send] proofsToSend', send)
|
||||
|
||||
// do some basic validations that proof amounts from mints match
|
||||
const totalAmountToSendFrom: number = CashuUtils.getProofsAmount(proofsToSendFrom)
|
||||
const returnedAmount: number = CashuUtils.getProofsAmount(returnChange as Proof[])
|
||||
const sendAmount: number = CashuUtils.getProofsAmount(send as Proof[])
|
||||
|
||||
if (sendAmount !== amountToSend) {
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
`Amount to be sent provided by mint does not equal requested amount. Original is ${amountToSend}, mint returned ${sendAmount}`,
|
||||
)
|
||||
}
|
||||
|
||||
const mintFeePaid = totalAmountToSendFrom - amountToSend - returnedAmount
|
||||
|
||||
// we normalize naming of returned parameters
|
||||
return {
|
||||
returnedProofs: returnChange as Proof[],
|
||||
proofsToSend: send as Proof[],
|
||||
mintFeePaid
|
||||
}
|
||||
} catch (e: any) {
|
||||
let message = 'The mint could not return signatures necessary for this transaction'
|
||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||
throw new AppError(
|
||||
Err.MINT_ERROR,
|
||||
message,
|
||||
{
|
||||
message: e.message,
|
||||
mintUrl,
|
||||
caller: 'WalletStore.sendFromMint',
|
||||
proofsToSendFrom,
|
||||
}
|
||||
)
|
||||
}
|
||||
}),
|
||||
getSpentOrPendingProofsFromMint: flow(function* getSpentOrPendingProofsFromMint(
|
||||
mintUrl: string,
|
||||
unit: MintUnit,
|
||||
proofs: Proof[]
|
||||
) {
|
||||
try {
|
||||
log.trace('*** [WalletStore.getSpentOrPendingProofsFromMint] start', {mintUrl, unit})
|
||||
const cashuWallet = yield self.getWallet(mintUrl, unit, {withSeed: true})
|
||||
const spentPendingProofs = yield cashuWallet.checkProofsSpent(proofs)
|
||||
|
||||
log.trace('*** [WalletStore.getSpentOrPendingProofsFromMint]', {mintUrl, spentPendingProofs})
|
||||
|
||||
return spentPendingProofs as {
|
||||
spent: ProofV3[]
|
||||
pending: ProofV3[]
|
||||
}
|
||||
|
||||
} catch (e: any) {
|
||||
let message = 'Could not get response from the mint.'
|
||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||
throw new AppError(
|
||||
Err.MINT_ERROR,
|
||||
message,
|
||||
{
|
||||
message: e.message,
|
||||
caller: 'Wallet.getSpentOrPendingProofsFromMint',
|
||||
mintUrl
|
||||
}
|
||||
)
|
||||
}
|
||||
}),
|
||||
createLightningMintQuote: flow(function* createLightningMintQuote(
|
||||
mintUrl: string,
|
||||
unit: MintUnit,
|
||||
amount: number,
|
||||
) {
|
||||
try {
|
||||
const cashuMint = yield self.getMint(mintUrl)
|
||||
const {
|
||||
request: encodedInvoice,
|
||||
quote: mintQuote,
|
||||
} = yield cashuMint.createMintQuote({
|
||||
unit,
|
||||
amount
|
||||
})
|
||||
|
||||
log.info('[createLightningMintQuote]', {encodedInvoice, mintQuote})
|
||||
|
||||
return {
|
||||
encodedInvoice,
|
||||
mintQuote,
|
||||
}
|
||||
} catch (e: any) {
|
||||
let message = 'The mint could not return an invoice.'
|
||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||
throw new AppError(
|
||||
Err.MINT_ERROR,
|
||||
message,
|
||||
{
|
||||
message: e.message,
|
||||
caller: 'createLightningMintQuote',
|
||||
mintUrl,
|
||||
}
|
||||
)
|
||||
}
|
||||
}),
|
||||
checkLightningMintQuote: flow(function* checkLightningMintQuote(
|
||||
mintUrl: string,
|
||||
quote: string,
|
||||
) {
|
||||
try {
|
||||
const cashuMint = yield self.getMint(mintUrl)
|
||||
const {
|
||||
request: encodedInvoice,
|
||||
quote: mintQuote,
|
||||
state,
|
||||
} = yield cashuMint.checkMintQuote(
|
||||
quote
|
||||
)
|
||||
|
||||
log.info('[checkLightningMintQuote]', {encodedInvoice, mintQuote, state})
|
||||
|
||||
return {
|
||||
encodedInvoice,
|
||||
mintQuote,
|
||||
state
|
||||
}
|
||||
} catch (e: any) {
|
||||
let message = 'The mint could not return the state of a mint quote.'
|
||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||
throw new AppError(
|
||||
Err.MINT_ERROR,
|
||||
message,
|
||||
{
|
||||
message: e.message,
|
||||
caller: 'checkLightningMintQuote',
|
||||
mintUrl,
|
||||
}
|
||||
)
|
||||
}
|
||||
}),
|
||||
mintProofs: flow(function* mintProofs(
|
||||
mintUrl: string,
|
||||
amount: number,
|
||||
unit: MintUnit,
|
||||
mintQuote: string,
|
||||
options: {
|
||||
preference: AmountPreference[],
|
||||
counter: number
|
||||
}
|
||||
) {
|
||||
try {
|
||||
const cashuWallet = yield self.getWallet(mintUrl, unit, {withSeed: true}) // with seed
|
||||
|
||||
const {proofs} = yield cashuWallet.mintTokens(
|
||||
amount,
|
||||
mintQuote,
|
||||
{
|
||||
keysetId: cashuWallet.keys.id,
|
||||
preference: options.preference,
|
||||
counter: options.counter,
|
||||
pubkey: undefined
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
log.info('[mintProofs]', {proofs})
|
||||
|
||||
return proofs
|
||||
|
||||
} catch (e: any) {
|
||||
log.info('[mintProofs]', {error: {name: e.name, message: e.message}})
|
||||
if(e.message.includes('quote not paid')) {
|
||||
return {
|
||||
proofs: [],
|
||||
}
|
||||
}
|
||||
let message = 'The mint returned error on request to mint new ecash.'
|
||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||
throw new AppError(
|
||||
Err.MINT_ERROR,
|
||||
message,
|
||||
{
|
||||
message: e.message,
|
||||
caller: 'mintProofs',
|
||||
mintUrl,
|
||||
}
|
||||
)
|
||||
}
|
||||
}),
|
||||
createLightningMeltQuote: flow(function* createLightningMeltQuote(
|
||||
mintUrl: string,
|
||||
unit: MintUnit,
|
||||
encodedInvoice: string,
|
||||
) {
|
||||
try {
|
||||
const cashuMint = yield self.getMint(mintUrl)
|
||||
const lightningQuote: MeltQuoteResponse = yield cashuMint.createMeltQuote({
|
||||
unit,
|
||||
request: encodedInvoice
|
||||
})
|
||||
|
||||
log.info('[createLightningMeltQuote]', {mintUrl, unit, encodedInvoice}, {lightningQuote})
|
||||
|
||||
return lightningQuote
|
||||
|
||||
} catch (e: any) {
|
||||
let message = 'The mint could not return the lightning quote.'
|
||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||
throw new AppError(
|
||||
Err.MINT_ERROR,
|
||||
message,
|
||||
{
|
||||
message: e.message,
|
||||
caller: 'createLightningMeltQuote',
|
||||
request: {mintUrl, unit, encodedInvoice},
|
||||
}
|
||||
)
|
||||
}
|
||||
}),
|
||||
payLightningMelt: flow(function* payLightningMelt(
|
||||
mintUrl: string,
|
||||
unit: MintUnit,
|
||||
lightningMeltQuote: MeltQuoteResponse, // invoice is stored by mint by quote
|
||||
proofsToPayFrom: ProofV3[], // proofAmount >= amount + fee_reserve
|
||||
options: {
|
||||
counter: number
|
||||
}
|
||||
) {
|
||||
try {
|
||||
const cashuWallet = yield self.getWallet(mintUrl, unit, {withSeed: true}) // with seed
|
||||
|
||||
const {isPaid, preimage, change: feeSavedProofs}: MeltTokensResponse =
|
||||
yield cashuWallet.meltTokens(
|
||||
lightningMeltQuote,
|
||||
proofsToPayFrom,
|
||||
{
|
||||
keysetId: cashuWallet.keys.id,
|
||||
counter: options.counter
|
||||
}
|
||||
)
|
||||
|
||||
log.trace('[payLightningMelt]', {isPaid, preimage, feeSavedProofs})
|
||||
// we normalize naming of returned parameters
|
||||
return {
|
||||
feeSavedProofs,
|
||||
isPaid,
|
||||
preimage,
|
||||
}
|
||||
} catch (e: any) {
|
||||
let message = 'Lightning payment failed.'
|
||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||
throw new AppError(
|
||||
Err.MINT_ERROR,
|
||||
message,
|
||||
{
|
||||
message: isObj(e.message) ? JSON.stringify(e.message) : e.message,
|
||||
caller: 'payLightningMelt',
|
||||
mintUrl
|
||||
}
|
||||
)
|
||||
}
|
||||
}),
|
||||
restore: flow(function* restore(
|
||||
mintUrl: string,
|
||||
seed: Uint8Array,
|
||||
options: {
|
||||
indexFrom: number,
|
||||
indexTo: number,
|
||||
keysetId: string
|
||||
}
|
||||
) {
|
||||
try {
|
||||
const {indexFrom, indexTo, keysetId} = options
|
||||
// need special wallet instance to pass seed and keysetId directly
|
||||
const cashuMint = yield self.getMint(mintUrl)
|
||||
|
||||
const seedWallet = new CashuWallet(cashuMint, {
|
||||
unit: 'sat', // just use default unit as we restore by keyset
|
||||
mnemonicOrSeed: seed
|
||||
})
|
||||
|
||||
const count = Math.abs(indexTo - indexFrom)
|
||||
|
||||
|
||||
const {proofs} = yield seedWallet.restore(
|
||||
indexFrom,
|
||||
count,
|
||||
{keysetId}
|
||||
)
|
||||
|
||||
|
||||
log.info('[restore]', 'Number of recovered proofs', {proofs: proofs.length})
|
||||
|
||||
return {
|
||||
proofs: proofs || []
|
||||
}
|
||||
} catch (e: any) {
|
||||
throw new AppError(Err.MINT_ERROR, isObj(e.message) ? JSON.stringify(e.message) : e.message, {mintUrl})
|
||||
}
|
||||
}),
|
||||
getMintInfo: flow(function* getMintInfo(mintUrl: string) {
|
||||
try {
|
||||
const cashuMint = yield self.getMint(mintUrl)
|
||||
const info = yield cashuMint.getInfo()
|
||||
log.trace('[getMintInfo]', {info})
|
||||
return info
|
||||
} catch (e: any) {
|
||||
let message = 'The mint could not return mint information.';
|
||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||
throw new AppError(
|
||||
Err.MINT_ERROR,
|
||||
message,
|
||||
{
|
||||
message: e.message,
|
||||
caller: 'getMintInfo',
|
||||
mintUrl
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}))
|
||||
.views(self => ({
|
||||
get mnemonic() {
|
||||
return self.mnemonicPhrase
|
||||
},
|
||||
get seed() {
|
||||
return self.seedBase64
|
||||
},
|
||||
}))
|
||||
|
||||
function isOnionMint(mintUrl: string) {
|
||||
return new URL(mintUrl).hostname.endsWith('.onion')
|
||||
}
|
||||
|
||||
const TorVPNSetupInstructions = `
|
||||
Is your Tor VPN running?
|
||||
Mints on Tor require a Tor VPN application like Orbot. You can get it on Google Play or Github.`
|
||||
|
||||
|
||||
export interface WalletStore extends Instance<typeof WalletStoreModel> {}
|
||||
export interface WalletStoreSnapshot
|
||||
extends SnapshotOut<typeof WalletStoreModel> {}
|
||||
@@ -17,15 +17,13 @@ import {
|
||||
import * as Sentry from '@sentry/react-native'
|
||||
import RNExitApp from 'react-native-exit-app'
|
||||
import type {RootStore} from '../RootStore'
|
||||
import {KeyChain, MinibitsClient, MintClient, MMKVStorage, NostrClient} from '../../services'
|
||||
import {KeyChain, MinibitsClient, MMKVStorage, NostrClient} from '../../services'
|
||||
import {Database} from '../../services'
|
||||
import { log } from '../../services/logService'
|
||||
import { rootStoreModelVersion } from '../RootStore'
|
||||
import AppError, { Err } from '../../utils/AppError'
|
||||
import { MINIBITS_NIP05_DOMAIN, MINIBITS_RELAY_URL } from '@env'
|
||||
import { LogLevel } from '../../services/log/logTypes'
|
||||
import { MintStatus } from '../Mint'
|
||||
import { generateId } from '../../utils/utils'
|
||||
|
||||
/**
|
||||
* The key we'll be saving our state as within storage.
|
||||
@@ -80,7 +78,10 @@ export async function setupRootStore(rootStore: RootStore) {
|
||||
}
|
||||
|
||||
// load the last known state from storage
|
||||
restoredState = MMKVStorage.load(ROOT_STORAGE_KEY) || {}
|
||||
restoredState = MMKVStorage.load(ROOT_STORAGE_KEY) || {}
|
||||
|
||||
// log.trace('[setupRootStore]', {restoredState})
|
||||
|
||||
applySnapshot(rootStore, restoredState)
|
||||
|
||||
} catch (e: any) {
|
||||
@@ -93,9 +94,15 @@ export async function setupRootStore(rootStore: RootStore) {
|
||||
}
|
||||
|
||||
// track changes & save to storage
|
||||
_disposer = onSnapshot(rootStore, snapshot =>
|
||||
MMKVStorage.save(ROOT_STORAGE_KEY, snapshot),
|
||||
)
|
||||
_disposer = onSnapshot(rootStore, snapshot => {
|
||||
// log.trace('[setupRootStore] snapshot listener', {snapshot})
|
||||
const persistedSnapshot = {
|
||||
...snapshot,
|
||||
nonPersistedStores: undefined // Exclude non-persisted stores
|
||||
}
|
||||
|
||||
MMKVStorage.save(ROOT_STORAGE_KEY, persistedSnapshot)
|
||||
})
|
||||
|
||||
// run migrations if needed, needs to be after onSnapshot to be persisted
|
||||
try {
|
||||
|
||||
@@ -26,7 +26,7 @@ import { translate } from '../i18n'
|
||||
import { useStores } from '../models'
|
||||
import { Mint, MintStatus } from '../models/Mint'
|
||||
import { SettingsStackScreenProps } from '../navigation'
|
||||
import { MintClient, log } from '../services'
|
||||
import { log } from '../services'
|
||||
import { colors, spacing, typography, useThemeColor } from '../theme'
|
||||
import useColorScheme from '../theme/useThemeColor'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
@@ -361,7 +361,8 @@ export const MintInfoScreen: FC<SettingsStackScreenProps<'MintInfo'>> = observer
|
||||
},
|
||||
})
|
||||
|
||||
const { mintsStore } = useStores()
|
||||
const { mintsStore, nonPersistedStores } = useStores()
|
||||
const { walletStore } = nonPersistedStores
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [mintInfo, setMintInfo] = useState<GetInfoResponse | undefined>()
|
||||
@@ -384,7 +385,7 @@ export const MintInfoScreen: FC<SettingsStackScreenProps<'MintInfo'>> = observer
|
||||
const mint = mintsStore.findByUrl(route.params.mintUrl)
|
||||
|
||||
if (mint) {
|
||||
const info: GetInfoResponse = await MintClient.getMintInfo(mint.mintUrl)
|
||||
const info: GetInfoResponse = await walletStore.getMintInfo(mint.mintUrl)
|
||||
mint.setStatus(MintStatus.ONLINE)
|
||||
if(info.name && info.name !== mint.shortname) {
|
||||
await mint.setShortname()
|
||||
|
||||
@@ -28,9 +28,8 @@ import AppError, { Err } from '../utils/AppError'
|
||||
import {translate} from '../i18n'
|
||||
import {MintListItem} from './Mints/MintListItem'
|
||||
import { SvgXml } from 'react-native-svg'
|
||||
import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
|
||||
import { isStateTreeNode } from 'mobx-state-tree'
|
||||
import { MintKeyset } from '@cashu/cashu-ts'
|
||||
import { MintClient } from '../services'
|
||||
|
||||
|
||||
|
||||
@@ -40,7 +39,8 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
onLeftPress: () => navigation.goBack(),
|
||||
})
|
||||
|
||||
const {mintsStore, proofsStore, userSettingsStore} = useStores()
|
||||
const {mintsStore, proofsStore, nonPersistedStores} = useStores()
|
||||
const {walletStore} = nonPersistedStores
|
||||
const mintInputRef = useRef<TextInput>(null)
|
||||
|
||||
const [mintUrl, setMintUrl] = useState('')
|
||||
@@ -142,7 +142,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
|
||||
toggleAddMintModal() // close
|
||||
setIsLoading(true)
|
||||
const keysets: MintKeyset[] = await MintClient.getMintKeysets(mintUrl)
|
||||
const keysets: MintKeyset[] = await walletStore.getMintKeysets(mintUrl)
|
||||
const matchingKeyset = keysets.find(keyset => selectedMint.keysets?.some(k => k.id === keyset.id))
|
||||
|
||||
if(!matchingKeyset) {
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
Text,
|
||||
} from '../components'
|
||||
import {Mint} from '../models/Mint'
|
||||
import {Token} from '../models/Token'
|
||||
import {Transaction, TransactionStatus} from '../models/Transaction'
|
||||
import {useStores} from '../models'
|
||||
import {TransactionTaskResult, WalletTask} from '../services'
|
||||
@@ -25,7 +24,7 @@ import {log} from '../services/logService'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import EventEmitter from '../utils/eventEmitter'
|
||||
|
||||
import {CashuUtils} from '../services/cashu/cashuUtils'
|
||||
import {CashuUtils, TokenV3} from '../services/cashu/cashuUtils'
|
||||
import {ResultModalInfo} from './Wallet/ResultModalInfo'
|
||||
import {MintListItem} from './Mints/MintListItem'
|
||||
import useIsInternetReachable from '../utils/useIsInternetReachable'
|
||||
@@ -42,7 +41,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
const isInternetReachable = useIsInternetReachable()
|
||||
const {mintsStore} = useStores()
|
||||
|
||||
const [token, setToken] = useState<Token | undefined>()
|
||||
const [token, setToken] = useState<TokenV3 | undefined>()
|
||||
const [encodedToken, setEncodedToken] = useState<string | undefined>()
|
||||
const [amountToReceive, setAmountToReceive] = useState<string>('0')
|
||||
const [unit, setUnit] = useState<MintUnit>('sat')
|
||||
@@ -146,7 +145,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
try {
|
||||
navigation.setParams({encodedToken: undefined})
|
||||
|
||||
const decoded: Token = CashuUtils.decodeToken(encoded)
|
||||
const decoded = CashuUtils.decodeToken(encoded)
|
||||
const tokenAmounts = CashuUtils.getTokenAmounts(decoded)
|
||||
|
||||
log.trace('decoded token', {decoded})
|
||||
@@ -154,17 +153,14 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
|
||||
if(!decoded.unit) {
|
||||
setInfo(translate("decodedMissingCurrencyUnit", { unit: CurrencyCode.SATS }))
|
||||
decoded.unit = CurrencyCode.SATS
|
||||
}
|
||||
|
||||
const currency = getCurrency(decoded.unit)
|
||||
const currency = getCurrency(decoded.unit as MintUnit)
|
||||
|
||||
setToken(decoded)
|
||||
setAmountToReceive(numbro(tokenAmounts.totalAmount / currency.precision).format({thousandSeparated: true, mantissa: currency.mantissa}))
|
||||
|
||||
if(decoded.unit) {
|
||||
log.trace('Token unit', decoded.unit)
|
||||
setUnit(decoded.unit)
|
||||
}
|
||||
setUnit(decoded.unit as MintUnit)
|
||||
|
||||
if (decoded.memo && decoded.memo.length > 0) {
|
||||
setMemo(decoded.memo as string)
|
||||
@@ -182,7 +178,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
const amountToReceiveInt = round(toNumber(amountToReceive) * getCurrency(unit).precision, 0)
|
||||
|
||||
WalletTask.receive(
|
||||
token as Token,
|
||||
token as TokenV3,
|
||||
amountToReceiveInt,
|
||||
memo,
|
||||
encodedToken as string,
|
||||
@@ -197,7 +193,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
const amountToReceiveInt = round(toNumber(amountToReceive) * getCurrency(unit).precision, 0)
|
||||
|
||||
WalletTask.receiveOfflinePrepare(
|
||||
token as Token,
|
||||
token as TokenV3,
|
||||
amountToReceiveInt,
|
||||
memo,
|
||||
encodedToken as string,
|
||||
|
||||
@@ -20,10 +20,10 @@ import {
|
||||
} from '../components'
|
||||
import {useHeader} from '../utils/useHeader'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import { KeyChain, log, MintClient } from '../services'
|
||||
import { scale } from '@gocodingnow/rn-size-matters'
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import { translate } from '../i18n'
|
||||
import { useStores } from '../models'
|
||||
|
||||
|
||||
export const RemoteBackupScreen: FC<SettingsStackScreenProps<'RemoteBackup'>> = observer(function RemoteBackupScreen(_props) {
|
||||
@@ -35,7 +35,8 @@ export const RemoteBackupScreen: FC<SettingsStackScreenProps<'RemoteBackup'>> =
|
||||
},
|
||||
})
|
||||
|
||||
// const {userSettingsStore} = useStores()
|
||||
const {nonPersistedStores} = useStores()
|
||||
const {walletStore} = nonPersistedStores
|
||||
|
||||
const [info, setInfo] = useState('')
|
||||
const [mnemonic, setMnemonic] = useState<string>()
|
||||
@@ -50,12 +51,12 @@ export const RemoteBackupScreen: FC<SettingsStackScreenProps<'RemoteBackup'>> =
|
||||
setIsLoading(true)
|
||||
let mnemonic: string | undefined = undefined
|
||||
|
||||
mnemonic = await MintClient.getMnemonic()
|
||||
mnemonic = await walletStore.getMnemonic()
|
||||
|
||||
if(!mnemonic) {
|
||||
// wallets upgraded from 0.1.4 with no generated seed
|
||||
mnemonic = await MintClient.getOrCreateMnemonic() // expensive, derives seed
|
||||
MintClient.resetCachedWallets() // force all cached wallet instances to be recreated with seed
|
||||
mnemonic = await walletStore.getOrCreateMnemonic() // expensive, derives seed
|
||||
walletStore.resetWallets() // force all cached wallet instances to be recreated with seed
|
||||
setIsNewMnemonic(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from '../components'
|
||||
import {useHeader} from '../utils/useHeader'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import { KeyChain, log, MinibitsClient, MintClient, NostrClient } from '../services'
|
||||
import { KeyChain, log, MinibitsClient, NostrClient } from '../services'
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import { useStores } from '../models'
|
||||
import { MintListItem } from './Mints/MintListItem'
|
||||
@@ -58,7 +58,8 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
},
|
||||
})
|
||||
|
||||
const {mintsStore, proofsStore, userSettingsStore, transactionsStore, walletProfileStore} = useStores()
|
||||
const {mintsStore, proofsStore, userSettingsStore, transactionsStore, walletProfileStore, nonPersistedStores} = useStores()
|
||||
const {walletStore} = nonPersistedStores
|
||||
const mnemonicInputRef = useRef<TextInput>(null)
|
||||
const indexInputRef = useRef<TextInput>(null)
|
||||
|
||||
@@ -91,7 +92,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
const getMnemonic = async () => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
const existing = await MintClient.getMnemonic()
|
||||
const existing = await walletStore.getMnemonic()
|
||||
|
||||
if(existing) {
|
||||
setMnemonicExists(true)
|
||||
@@ -174,7 +175,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
const onMintSelect = async function (mint: Mint) {
|
||||
try {
|
||||
setSelectedMintUrl(mint.mintUrl)
|
||||
const allActiveKeysets = await MintClient.getMintKeysets(mint.mintUrl) // mint api call
|
||||
const allActiveKeysets = await walletStore.getMintKeysets(mint.mintUrl) // mint api call
|
||||
|
||||
// Default is the last one as it seems to be the newest one
|
||||
const lastKeyset = allActiveKeysets.slice(-1)[0]
|
||||
@@ -240,7 +241,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
setStatusMessage(translate("recovery.restoringFromParam", { hostname: recoveredMint.hostname }))
|
||||
log.info('[restore]', `Restoring from ${recoveredMint.hostname}...`)
|
||||
|
||||
const { proofs } = await MintClient.restore(
|
||||
const { proofs } = await walletStore.restore(
|
||||
recoveredMint.mintUrl,
|
||||
seed as Uint8Array,
|
||||
{
|
||||
@@ -257,7 +258,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
// need to move counter by whole interval to avoid duplicate _B!!!
|
||||
recoveredMint.increaseProofsCounter(selectedKeyset.id as string, Math.abs(endIndex - startIndex))
|
||||
|
||||
const {spent, pending} = await MintClient.getSpentOrPendingProofsFromMint(
|
||||
const {spent, pending} = await walletStore.getSpentOrPendingProofsFromMint(
|
||||
recoveredMint.mintUrl,
|
||||
selectedKeyset.unit as MintUnit,
|
||||
proofs as Proof[],
|
||||
@@ -270,7 +271,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
const spentAmount = CashuUtils.getProofsAmount(spent as Proof[])
|
||||
alreadySpentAmount += spentAmount
|
||||
|
||||
const unspent = proofs.filter(proof => !spent.includes(proof))
|
||||
const unspent = proofs.filter((proof: Proof) => !spent.includes(proof))
|
||||
|
||||
if(unspent && unspent.length > 0) {
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import JSONTree from 'react-native-json-tree'
|
||||
import {colors, spacing, useThemeColor} from '../theme'
|
||||
import {TransactionsStackScreenProps, WalletStackScreenProps} from '../navigation'
|
||||
import {TransactionsStackScreenProps} from '../navigation'
|
||||
import EventEmitter from '../utils/eventEmitter'
|
||||
import {
|
||||
Button,
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
Loading,
|
||||
Header,
|
||||
} from '../components'
|
||||
import {useHeader} from '../utils/useHeader'
|
||||
import {useStores} from '../models'
|
||||
import {translate, TxKeyPath} from '../i18n'
|
||||
import {
|
||||
@@ -41,19 +40,15 @@ import {BackupProof, Proof} from '../models/Proof'
|
||||
import useColorScheme from '../theme/useThemeColor'
|
||||
import useIsInternetReachable from '../utils/useIsInternetReachable'
|
||||
import { ResultModalInfo } from './Wallet/ResultModalInfo'
|
||||
import QRCode from 'react-native-qrcode-svg'
|
||||
import { getDecodedToken, Token as CashuToken } from '@cashu/cashu-ts'
|
||||
import { CashuUtils } from '../services/cashu/cashuUtils'
|
||||
import { CashuUtils, TokenV3 } from '../services/cashu/cashuUtils'
|
||||
import { MintStatus } from '../models/Mint'
|
||||
import { moderateVerticalScale } from '@gocodingnow/rn-size-matters'
|
||||
import { CurrencySign } from './Wallet/CurrencySign'
|
||||
import { MintUnit, formatCurrency, getCurrency } from "../services/wallet/currency"
|
||||
import { Token } from '../models/Token'
|
||||
import { PaymentRequest } from '../models/PaymentRequest'
|
||||
import { pollerExists } from '../utils/poller'
|
||||
import { StackActions, useFocusEffect } from '@react-navigation/native'
|
||||
import { CurrencyAmount } from './Wallet/CurrencyAmount'
|
||||
import { toNumber } from '../utils/number'
|
||||
import { useFocusEffect } from '@react-navigation/native'
|
||||
import { QRCodeBlock } from './Wallet/QRCode'
|
||||
|
||||
type ProofsByStatus = {
|
||||
@@ -500,12 +495,12 @@ const ReceiveInfoBlock = function (props: {
|
||||
|
||||
try {
|
||||
const tokenToRetry: CashuToken = getDecodedToken(encodedTokenToRetry)
|
||||
const amountToReceive = CashuUtils.getTokenAmounts(tokenToRetry as Token).totalAmount
|
||||
const amountToReceive = CashuUtils.getTokenAmounts(tokenToRetry).totalAmount
|
||||
const memo = tokenToRetry.memo || ''
|
||||
|
||||
setIsReceiveTaskSentToQueue(true)
|
||||
WalletTask.receive(
|
||||
tokenToRetry as Token,
|
||||
tokenToRetry,
|
||||
amountToReceive,
|
||||
memo,
|
||||
encodedTokenToRetry
|
||||
|
||||
@@ -25,25 +25,22 @@ import {
|
||||
BottomModal,
|
||||
Text,
|
||||
} from '../components'
|
||||
import {Mint} from '../models/Mint'
|
||||
import {Transaction, TransactionStatus} from '../models/Transaction'
|
||||
import {useStores} from '../models'
|
||||
import {MintClient, TransactionTaskResult, WalletTask} from '../services'
|
||||
import {TransactionTaskResult, WalletTask} from '../services'
|
||||
import EventEmitter from '../utils/eventEmitter'
|
||||
import {log} from '../services/logService'
|
||||
import AppError, {Err} from '../utils/AppError'
|
||||
import {MintBalance} from '../models/Mint'
|
||||
import {MintListItem} from './Mints/MintListItem'
|
||||
import {ResultModalInfo} from './Wallet/ResultModalInfo'
|
||||
import {addSeconds} from 'date-fns'
|
||||
import { PaymentRequestStatus } from '../models/PaymentRequest'
|
||||
import { infoMessage } from '../utils/utils'
|
||||
import { DecodedLightningInvoice, LightningUtils } from '../services/lightning/lightningUtils'
|
||||
import { SendOption } from './SendOptionsScreen'
|
||||
import { round, roundDown, roundUp, toNumber } from '../utils/number'
|
||||
import { LnurlClient, LNURLPayParams } from '../services/lnurlService'
|
||||
import { moderateVerticalScale } from '@gocodingnow/rn-size-matters'
|
||||
import { Currencies, CurrencyCode, MintUnit, getCurrency } from "../services/wallet/currency"
|
||||
import { CurrencyCode, MintUnit, getCurrency } from "../services/wallet/currency"
|
||||
import { FeeBadge } from './Wallet/FeeBadge'
|
||||
import { MeltQuoteResponse } from '@cashu/cashu-ts'
|
||||
import { MintHeader } from './Mints/MintHeader'
|
||||
@@ -68,7 +65,8 @@ export const TransferScreen: FC<WalletStackScreenProps<'Transfer'>> = observer(
|
||||
const amountInputRef = useRef<TextInput>(null)
|
||||
const lnurlCommentInputRef = useRef<TextInput>(null)
|
||||
|
||||
const {proofsStore, mintsStore, paymentRequestsStore, transactionsStore} = useStores()
|
||||
const {proofsStore, mintsStore, paymentRequestsStore, transactionsStore, nonPersistedStores} = useStores()
|
||||
const {walletStore} = nonPersistedStores
|
||||
|
||||
const isInternetReachable = useIsInternetReachable()
|
||||
|
||||
@@ -290,7 +288,7 @@ useEffect(() => {
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
const quote = await MintClient.createLightningMeltQuote(
|
||||
const quote = await walletStore.createLightningMeltQuote(
|
||||
mintBalanceToTransferFrom.mintUrl,
|
||||
unit,
|
||||
encodedInvoice,
|
||||
|
||||
@@ -88,7 +88,7 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
const pagerRef = useRef<PagerView>(null)
|
||||
const appState = useRef(AppState.currentState)
|
||||
const isInternetReachable = useIsInternetReachable()
|
||||
const groupedMints = mintsStore.groupedByUnit
|
||||
const groupedMints: MintsByUnit[] = mintsStore.groupedByUnit
|
||||
|
||||
const [currentUnit, setCurrentUnit] = useState<MintUnit>(groupedMints.length > 0 ? groupedMints[0].unit : 'sat')
|
||||
const [info, setInfo] = useState<string>('')
|
||||
@@ -162,7 +162,7 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
WalletTask.receiveEventsFromRelays().catch(e => false)
|
||||
// log.trace('[getInitialData]', 'walletProfile', walletProfileStore)
|
||||
const preferredUnit: MintUnit = userSettingsStore.preferredUnit
|
||||
const pageIndex = groupedMints.findIndex(m => m.unit === preferredUnit)
|
||||
const pageIndex = groupedMints.findIndex((m) => m.unit === preferredUnit)
|
||||
pagerRef.current && pagerRef.current.setPage(pageIndex)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ import {
|
||||
} from '../components'
|
||||
import {TxKeyPath, translate} from '../i18n'
|
||||
import AppError from '../utils/AppError'
|
||||
import { MintClient } from '../services'
|
||||
7
|
||||
|
||||
|
||||
const AnimatedPagerView = Animated.createAnimatedComponent(PagerView)
|
||||
|
||||
const PAGES = [
|
||||
@@ -81,7 +81,8 @@ export const WelcomeScreen: FC<AppStackScreenProps<'Welcome'>> =
|
||||
//StatusBarProps: {barStyle: 'dark-content'},
|
||||
})
|
||||
|
||||
const {userSettingsStore, relaysStore, walletProfileStore} = useStores()
|
||||
const {userSettingsStore, relaysStore, walletProfileStore, nonPersistedStores} = useStores()
|
||||
const {walletStore} = nonPersistedStores
|
||||
const [error, setError] = useState<AppError | undefined>()
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||
|
||||
@@ -90,7 +91,7 @@ export const WelcomeScreen: FC<AppStackScreenProps<'Welcome'>> =
|
||||
try {
|
||||
// do not overwrite if one was set during recovery
|
||||
setIsLoading(true)
|
||||
const mnemonic = await MintClient.getOrCreateMnemonic()
|
||||
const mnemonic = await walletStore.getOrCreateMnemonic()
|
||||
|
||||
// move new profile creation to the app start so that device token can register
|
||||
if(!walletProfileStore.pubkey || !walletProfileStore.picture) {
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import {Mint} from '../../models/Mint'
|
||||
import {
|
||||
AmountPreference,
|
||||
getDecodedToken,
|
||||
AmountPreference,
|
||||
getEncodedToken,
|
||||
} from '@cashu/cashu-ts'
|
||||
import cbor from '@exodus/borc'
|
||||
import type {Token as TokenV3, TokenEntry as TokenEntryV3, Proof as ProofV3} from '@cashu/cashu-ts'
|
||||
import type {
|
||||
Token as TokenV3,
|
||||
TokenEntry as
|
||||
TokenEntryV3,
|
||||
Proof as ProofV3
|
||||
} from '@cashu/cashu-ts'
|
||||
import AppError, {Err} from '../../utils/AppError'
|
||||
import {} from '@cashu/cashu-ts'
|
||||
import {Proof} from '../../models/Proof'
|
||||
import { log } from '../logService'
|
||||
// import { encodeCBOR } from '@cashu/cashu-ts/src/cbor'
|
||||
import { encodeBase64ToJson, encodeBase64toUint8, encodeUint8toBase64 } from '@cashu/cashu-ts/src/base64'
|
||||
import { bytesToHex, hexToBytes } from '@noble/curves/abstract/utils';
|
||||
|
||||
export type {TokenV3, TokenEntryV3, ProofV3}
|
||||
|
||||
interface ProofV4 {
|
||||
a: number;
|
||||
s: string;
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from '@cashu/cashu-ts'
|
||||
import {rootStoreInstance} from '../models'
|
||||
import { KeyChain } from '../services'
|
||||
import {CashuUtils} from './cashu/cashuUtils'
|
||||
import {CashuUtils, TokenV3} from './cashu/cashuUtils'
|
||||
import AppError, {Err} from '../utils/AppError'
|
||||
import {log} from './logService'
|
||||
import {
|
||||
@@ -24,7 +24,7 @@ import { isObj } from '@cashu/cashu-ts/src/utils'
|
||||
import { JS_BUNDLE_VERSION } from '@env'
|
||||
import { MintUnit } from './wallet/currency'
|
||||
import { getSnapshot } from 'mobx-state-tree'
|
||||
import { Token } from '../models/Token'
|
||||
|
||||
|
||||
let _mints: CashuMint[] = []
|
||||
let _wallets: CashuWallet[] = [] // used where seed is not required (perf)
|
||||
@@ -303,7 +303,7 @@ const receive = async function (
|
||||
withSeed: true,
|
||||
})
|
||||
|
||||
const amountToReceive = CashuUtils.getTokenAmounts(decodedToken as Token).totalAmount
|
||||
const amountToReceive = CashuUtils.getTokenAmounts(decodedToken as TokenV3).totalAmount
|
||||
|
||||
const proofs = await cashuWallet.receive(
|
||||
decodedToken,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from "./cashuMintClient"
|
||||
export * from "./mmkvStorage"
|
||||
export * from "./sqlite"
|
||||
export * from "./keyChain"
|
||||
@@ -8,4 +7,4 @@ export * from "./nostrService"
|
||||
export * from "./logService"
|
||||
export * from "./torService"
|
||||
export * from "./syncQueueService"
|
||||
// export * from "./restoreService"
|
||||
|
||||
|
||||
@@ -7,9 +7,6 @@ import {btoa, fromByteArray} from 'react-native-quick-base64'
|
||||
import {rootStoreInstance} from '../models'
|
||||
import {log} from './logService'
|
||||
|
||||
const {
|
||||
userSettingsStore
|
||||
} = rootStoreInstance
|
||||
|
||||
export enum KeyChainServiceName {
|
||||
MMKV = 'app.minibits.mmkv',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {log} from '../logService'
|
||||
import {MintClient} from '../cashuMintClient'
|
||||
import {
|
||||
Transaction,
|
||||
TransactionData,
|
||||
@@ -8,14 +7,8 @@ import {
|
||||
TransactionType,
|
||||
} from '../../models/Transaction'
|
||||
import {rootStoreInstance} from '../../models'
|
||||
import {CashuUtils} from '../cashu/cashuUtils'
|
||||
import {CashuUtils, ProofV3, TokenEntryV3, TokenV3} from '../cashu/cashuUtils'
|
||||
import AppError, {Err} from '../../utils/AppError'
|
||||
import {Token} from '../../models/Token'
|
||||
import {
|
||||
TokenEntry,
|
||||
type Token as CashuToken,
|
||||
type Proof as CashuProof
|
||||
} from '@cashu/cashu-ts'
|
||||
import { getDefaultAmountPreference } from '@cashu/cashu-ts/src/utils'
|
||||
import { TransactionTaskResult } from '../walletService'
|
||||
import { WalletUtils } from './utils'
|
||||
@@ -26,15 +19,19 @@ const {
|
||||
mintsStore,
|
||||
proofsStore,
|
||||
transactionsStore,
|
||||
nonPersistedStores
|
||||
} = rootStoreInstance
|
||||
|
||||
const {walletStore} = nonPersistedStores
|
||||
|
||||
|
||||
// function names to pass to task results
|
||||
const RECEIVE = 'receiveTask'
|
||||
const RECEIVE_OFFLINE_PREPARE = 'receiveOfflinePrepareTask'
|
||||
const RECEIVE_OFFLINE_COMPLETE = 'receiveOfflineCompleteTask'
|
||||
|
||||
export const receiveTask = async function (
|
||||
token: Token,
|
||||
token: TokenV3,
|
||||
amountToReceive: number,
|
||||
memo: string,
|
||||
encodedToken: string,
|
||||
@@ -42,10 +39,10 @@ export const receiveTask = async function (
|
||||
const transactionData: TransactionData[] = []
|
||||
let transactionId: number = 0
|
||||
let mintToReceive: string | undefined = undefined
|
||||
const unit = token.unit || 'sat'
|
||||
const unit = token.unit as MintUnit || 'sat'
|
||||
|
||||
try {
|
||||
const tokenMints: string[] = CashuUtils.getMintsFromToken(token as Token)
|
||||
const tokenMints: string[] = CashuUtils.getMintsFromToken(token)
|
||||
log.trace('[receiveTask]', 'receiveToken tokenMints', tokenMints)
|
||||
|
||||
if (tokenMints.length === 0) {
|
||||
@@ -126,8 +123,8 @@ export const receiveTask = async function (
|
||||
// is not received our recovery index counts for sigs the mint has already issued
|
||||
const amountPreferences = getDefaultAmountPreference(amountToReceive)
|
||||
const countOfInFlightProofs = CashuUtils.getAmountPreferencesCount(amountPreferences)
|
||||
const tokenEntries: Array<TokenEntry> = token.token
|
||||
const proofsToReceive = tokenEntries[0].proofs as Proof[]
|
||||
const tokenEntries: TokenEntryV3[] = token.token
|
||||
const proofsToReceive = tokenEntries[0].proofs as ProofV3[]
|
||||
const mintFeeReserve = mintInstance.getMintFeeReserve(proofsToReceive)
|
||||
|
||||
log.trace('[receiveTask]', 'proofsCounter initial state', {proofsCounter: mintInstance.getProofsCounterByUnit?.(unit)})
|
||||
@@ -140,7 +137,7 @@ export const receiveTask = async function (
|
||||
// get locked counter values
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit?.(unit)!
|
||||
|
||||
const receivedResult = await MintClient.receive(
|
||||
const receivedResult = await walletStore.receive(
|
||||
mintToReceive,
|
||||
unit as MintUnit,
|
||||
token,
|
||||
@@ -272,7 +269,7 @@ export const receiveTask = async function (
|
||||
|
||||
|
||||
export const receiveOfflinePrepareTask = async function (
|
||||
token: Token,
|
||||
token: TokenV3,
|
||||
amountToReceive: number,
|
||||
memo: string,
|
||||
encodedToken: string,
|
||||
@@ -280,10 +277,10 @@ export const receiveOfflinePrepareTask = async function (
|
||||
const transactionData: TransactionData[] = []
|
||||
let transactionId: number = 0
|
||||
let mintToReceive = ''
|
||||
const unit = token.unit || 'sat'
|
||||
const unit = token.unit as MintUnit || 'sat'
|
||||
|
||||
try {
|
||||
const tokenMints: string[] = CashuUtils.getMintsFromToken(token as Token)
|
||||
const tokenMints: string[] = CashuUtils.getMintsFromToken(token)
|
||||
log.trace('[receiveOfflinePrepareTask]', 'receiveToken tokenMints', tokenMints)
|
||||
|
||||
if (tokenMints.length === 0) {
|
||||
@@ -409,7 +406,7 @@ export const receiveOfflineCompleteTask = async function (
|
||||
}
|
||||
|
||||
const token = CashuUtils.decodeToken(encodedToken)
|
||||
const tokenMints: string[] = CashuUtils.getMintsFromToken(token as Token)
|
||||
const tokenMints: string[] = CashuUtils.getMintsFromToken(token as TokenV3)
|
||||
const unit = token.unit || 'sat'
|
||||
|
||||
if(unit !== transaction.unit) {
|
||||
@@ -452,7 +449,7 @@ export const receiveOfflineCompleteTask = 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 (prevents duplicate b_b bug)
|
||||
|
||||
const tokenEntries: Array<TokenEntry> = token.token
|
||||
const tokenEntries: TokenEntryV3[] = token.token
|
||||
const proofsToReceive = tokenEntries[0].proofs as Proof[]
|
||||
const mintFeeReserve = mintInstance.getMintFeeReserve(proofsToReceive)
|
||||
// We will receive amount less fees paid for a swap for fresh ecash
|
||||
@@ -468,7 +465,7 @@ export const receiveOfflineCompleteTask = async function (
|
||||
// get locked counter values
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit?.(unit)!
|
||||
|
||||
const receivedResult = await MintClient.receive(
|
||||
const receivedResult = await walletStore.receive(
|
||||
mintToReceive,
|
||||
unit as MintUnit,
|
||||
token,
|
||||
@@ -486,7 +483,7 @@ export const receiveOfflineCompleteTask = async function (
|
||||
mintInstance.decreaseProofsCounter(lockedProofsCounter.keyset, countOfInFlightProofs)
|
||||
|
||||
// store swapped proofs as encoded token in tx data
|
||||
const tokenEntryToSend: TokenEntry = {
|
||||
const tokenEntryToSend: TokenEntryV3 = {
|
||||
mint: mintToReceive,
|
||||
proofs: receivedProofs,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {log} from '../logService'
|
||||
import {MintClient} from '../cashuMintClient'
|
||||
import {
|
||||
Transaction,
|
||||
TransactionData,
|
||||
@@ -8,12 +7,10 @@ import {
|
||||
TransactionType,
|
||||
} from '../../models/Transaction'
|
||||
import {rootStoreInstance} from '../../models'
|
||||
import {CashuUtils} from '../cashu/cashuUtils'
|
||||
import {CashuUtils, ProofV3, TokenEntryV3} from '../cashu/cashuUtils'
|
||||
import AppError, {Err} from '../../utils/AppError'
|
||||
import {
|
||||
type Token as CashuToken,
|
||||
type TokenEntry as CashuTokenEntry,
|
||||
type Proof as CashuProof,
|
||||
import {
|
||||
MintKeyset,
|
||||
} from '@cashu/cashu-ts'
|
||||
import { getDefaultAmountPreference, isObj } from '@cashu/cashu-ts/src/utils'
|
||||
import { TransactionTaskResult, WalletTask } from '../walletService'
|
||||
@@ -28,9 +25,11 @@ import { boolean } from 'mobx-state-tree/dist/internal'
|
||||
const {
|
||||
mintsStore,
|
||||
proofsStore,
|
||||
transactionsStore,
|
||||
transactionsStore,
|
||||
nonPersistedStores
|
||||
} = rootStoreInstance
|
||||
|
||||
const {walletStore} = nonPersistedStores
|
||||
|
||||
export const sendTask = async function (
|
||||
mintBalanceToSendFrom: MintBalance,
|
||||
@@ -108,7 +107,7 @@ export const sendTask = async function (
|
||||
}
|
||||
|
||||
const encodedTokenToSend = CashuUtils.encodeToken({
|
||||
token: [tokenEntryToSend as CashuTokenEntry],
|
||||
token: [tokenEntryToSend as TokenEntryV3],
|
||||
unit,
|
||||
memo,
|
||||
})
|
||||
@@ -270,8 +269,8 @@ export const sendFromMint = async function (
|
||||
|
||||
// Prioritize send from inactive keysets
|
||||
let proofsToSendFrom: Proof[] = []
|
||||
const inactiveKeysetIds = mintInstance?.keysets.filter(k => k.active === false).map(k => k.id)
|
||||
const activeKeysetIds = mintInstance?.keysets.filter(k => k.active === true).map(k => k.id)
|
||||
const inactiveKeysetIds = mintInstance?.keysets.filter((k: MintKeyset) => k.active === false).map((k: MintKeyset) => k.id)
|
||||
const activeKeysetIds = mintInstance?.keysets.filter((k: MintKeyset) => k.active === true).map((k: MintKeyset) => k.id)
|
||||
|
||||
log.trace('[sendFromMint]', {inactiveKeysetIds, activeKeysetIds})
|
||||
|
||||
@@ -382,7 +381,7 @@ export const sendFromMint = async function (
|
||||
|
||||
// if split to required denominations was necessary, this gets it done with the mint and we get the return
|
||||
|
||||
const sendResult = await MintClient.send(
|
||||
const sendResult = await walletStore.send(
|
||||
mintUrl,
|
||||
amountToSend,
|
||||
mintFeeReserve,
|
||||
@@ -454,7 +453,7 @@ export const sendFromMint = async function (
|
||||
|
||||
// We return cleaned proofs to be encoded as a sendable token + fees
|
||||
return {
|
||||
proofs: cleanedProofsToSend as CashuProof[],
|
||||
proofs: cleanedProofsToSend as ProofV3[],
|
||||
mintFeeReserve,
|
||||
mintFeePaid
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import AppError, {Err} from '../../utils/AppError'
|
||||
import {rootStoreInstance} from '../../models'
|
||||
import { TransactionTaskResult, WalletTask } from '../walletService'
|
||||
import { MintBalance } from '../../models/Mint'
|
||||
@@ -6,19 +5,21 @@ import { poller } from '../../utils/poller'
|
||||
import { Transaction, TransactionData, TransactionRecord, TransactionStatus, TransactionType } from '../../models/Transaction'
|
||||
import { log } from '../logService'
|
||||
import { Contact } from '../../models/Contact'
|
||||
import { MintClient } from '../cashuMintClient'
|
||||
import { LightningUtils } from '../lightning/lightningUtils'
|
||||
import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
|
||||
import { PaymentRequest, PaymentRequestStatus, PaymentRequestType } from '../../models/PaymentRequest'
|
||||
import { WalletUtils } from './utils'
|
||||
import { MintUnit, MintUnits } from './currency'
|
||||
import { MintUnit } from './currency'
|
||||
|
||||
const {
|
||||
transactionsStore,
|
||||
walletProfileStore,
|
||||
paymentRequestsStore
|
||||
paymentRequestsStore,
|
||||
nonPersistedStores
|
||||
} = rootStoreInstance
|
||||
|
||||
const {walletStore} = nonPersistedStores
|
||||
|
||||
const TOPUP = 'topupTask'
|
||||
|
||||
export const topupTask = async function (
|
||||
@@ -59,7 +60,7 @@ export const topupTask = async function (
|
||||
const storedTransaction: TransactionRecord = await transactionsStore.addTransaction(newTransaction)
|
||||
transactionId = storedTransaction.id as number
|
||||
|
||||
const {encodedInvoice, mintQuote} = await MintClient.createLightningMintQuote(mintUrl, unit, amountToTopup)
|
||||
const {encodedInvoice, mintQuote} = await walletStore.createLightningMintQuote(mintUrl, unit, amountToTopup)
|
||||
|
||||
const decodedInvoice = LightningUtils.decodeInvoice(encodedInvoice)
|
||||
const {amount, payment_hash, expiry, timestamp} = LightningUtils.getInvoiceData(decodedInvoice)
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import {CashuUtils} from '../cashu/cashuUtils'
|
||||
import {CashuUtils, ProofV3} from '../cashu/cashuUtils'
|
||||
import AppError, {Err} from '../../utils/AppError'
|
||||
import {MeltQuoteResponse, type Proof as CashuProof} from '@cashu/cashu-ts'
|
||||
import {MeltQuoteResponse} from '@cashu/cashu-ts'
|
||||
import {rootStoreInstance} from '../../models'
|
||||
import { TransactionTaskResult, WalletTask } from '../walletService'
|
||||
import { MintBalance } from '../../models/Mint'
|
||||
import { Proof } from '../../models/Proof'
|
||||
import { Transaction, TransactionData, TransactionRecord, TransactionStatus, TransactionType } from '../../models/Transaction'
|
||||
import { log } from '../logService'
|
||||
import { MintClient } from '../cashuMintClient'
|
||||
import { WalletUtils } from './utils'
|
||||
import {isBefore} from 'date-fns'
|
||||
import { sendFromMint } from './sendTask'
|
||||
@@ -16,9 +15,12 @@ import { MintUnit, formatCurrency, getCurrency } from './currency'
|
||||
const {
|
||||
transactionsStore,
|
||||
mintsStore,
|
||||
proofsStore
|
||||
proofsStore,
|
||||
nonPersistedStores,
|
||||
} = rootStoreInstance
|
||||
|
||||
const {walletStore} = nonPersistedStores
|
||||
|
||||
const TRANSFER = 'transferTask'
|
||||
|
||||
export const transferTask = async function (
|
||||
@@ -51,7 +53,7 @@ export const transferTask = async function (
|
||||
]
|
||||
|
||||
let transactionId: number = 0
|
||||
let proofsToPay: CashuProof[] = []
|
||||
let proofsToPay: Proof[] = []
|
||||
|
||||
try {
|
||||
if (amountToTransfer + meltQuote.fee_reserve > mintBalanceToTransferFrom.balances[unit]!) {
|
||||
@@ -129,7 +131,7 @@ export const transferTask = async function (
|
||||
// get locked counter values
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit?.(unit)!
|
||||
|
||||
const {isPaid, preimage, feeSavedProofs} = await MintClient.payLightningMelt(
|
||||
const {isPaid, preimage, feeSavedProofs} = await walletStore.payLightningMelt(
|
||||
mintUrl,
|
||||
unit,
|
||||
meltQuote,
|
||||
@@ -316,7 +318,7 @@ export const transferTask = async function (
|
||||
|
||||
|
||||
const _moveProofsFromPending = async function (
|
||||
proofsToMove: CashuProof[],
|
||||
proofsToMove: ProofV3[],
|
||||
mintUrl: string,
|
||||
unit: MintUnit,
|
||||
transactionId: number,
|
||||
@@ -339,7 +341,7 @@ const _moveProofsFromPending = async function (
|
||||
// Check with the mint if the proofs are not marked as pending. This happens when lightning payment fails
|
||||
// due to the timeout but mint's node keeps the payment as in-flight (e.g. receiving node holds the invoice)
|
||||
// In this case we need to keep such proofs as pending and not move them back to wallet as in other payment failures.
|
||||
const {pending: pendingByMint} = await MintClient.getSpentOrPendingProofsFromMint(
|
||||
const {pending: pendingByMint} = await walletStore.getSpentOrPendingProofsFromMint(
|
||||
mintUrl,
|
||||
unit,
|
||||
proofsToMove as Proof[]
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import {
|
||||
type Token as CashuToken,
|
||||
type TokenEntry as CashuTokenEntry,
|
||||
type Proof as CashuProof,
|
||||
} from '@cashu/cashu-ts'
|
||||
import {Proof} from '../../models/Proof'
|
||||
import {rootStoreInstance} from '../../models'
|
||||
import AppError from "../../utils/AppError"
|
||||
import { log } from '../logService'
|
||||
import { isStateTreeNode } from 'mobx-state-tree'
|
||||
import { CashuUtils } from '../cashu/cashuUtils'
|
||||
import { CashuUtils, ProofV3 } from '../cashu/cashuUtils'
|
||||
import { Mint, MintProofsCounter } from '../../models/Mint'
|
||||
import { delay } from '../../utils/utils'
|
||||
import { MintUnit } from './currency'
|
||||
@@ -79,7 +74,7 @@ const lockAndSetInFlight = async function (
|
||||
|
||||
const addCashuProofs = function (
|
||||
mintUrl: string,
|
||||
proofsToAdd: CashuProof[] | Proof[],
|
||||
proofsToAdd: ProofV3[] | Proof[],
|
||||
options: {
|
||||
unit: MintUnit,
|
||||
transactionId: number,
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import {isBefore} from 'date-fns'
|
||||
import {getSnapshot} from 'mobx-state-tree'
|
||||
import {log} from './logService'
|
||||
import {MintClient} from './cashuMintClient'
|
||||
import {Proof} from '../models/Proof'
|
||||
import {
|
||||
Transaction,
|
||||
TransactionStatus
|
||||
} from '../models/Transaction'
|
||||
import {rootStoreInstance} from '../models'
|
||||
import {CashuUtils} from './cashu/cashuUtils'
|
||||
import {CashuUtils, TokenV3} from './cashu/cashuUtils'
|
||||
import {LightningUtils} from './lightning/lightningUtils'
|
||||
import AppError, {Err} from '../utils/AppError'
|
||||
import {MintBalance, MintStatus} from '../models/Mint'
|
||||
import {Token} from '../models/Token'
|
||||
import {MeltQuoteResponse, MintQuoteState, type Proof as CashuProof} from '@cashu/cashu-ts'
|
||||
import {CashuMint, MeltQuoteResponse, MintQuoteState, type Proof as CashuProof} from '@cashu/cashu-ts'
|
||||
import {Mint} from '../models/Mint'
|
||||
import {pollerExists, stopPolling} from '../utils/poller'
|
||||
import EventEmitter from '../utils/eventEmitter'
|
||||
@@ -22,7 +20,7 @@ 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 { getDecodedToken, getDefaultAmountPreference } from '@cashu/cashu-ts/src/utils'
|
||||
import { getDefaultAmountPreference } from '@cashu/cashu-ts/src/utils'
|
||||
import { SyncQueue } from './syncQueueService'
|
||||
import { receiveTask, receiveOfflinePrepareTask, receiveOfflineCompleteTask} from './wallet/receiveTask'
|
||||
import { sendTask } from './wallet/sendTask'
|
||||
@@ -30,7 +28,7 @@ import { topupTask } from './wallet/topupTask'
|
||||
import { transferTask } from './wallet/transferTask'
|
||||
import { WalletUtils } from './wallet/utils'
|
||||
import { NotificationService } from './notificationService'
|
||||
import { CurrencyCode, MintUnit, formatCurrency, getCurrency } from './wallet/currency'
|
||||
import { MintUnit, formatCurrency, getCurrency } from './wallet/currency'
|
||||
import { MinibitsClient } from './minibitsService'
|
||||
|
||||
|
||||
@@ -60,13 +58,13 @@ type WalletTaskService = {
|
||||
encodedInvoice: string,
|
||||
) => Promise<void>
|
||||
receive: (
|
||||
token: Token,
|
||||
token: TokenV3,
|
||||
amountToReceive: number,
|
||||
memo: string,
|
||||
encodedToken: string,
|
||||
) => Promise<void>
|
||||
receiveOfflinePrepare: (
|
||||
token: Token,
|
||||
token: TokenV3,
|
||||
amountToReceive: number,
|
||||
memo: string,
|
||||
encodedToken: string,
|
||||
@@ -111,7 +109,7 @@ export type ReceivedEventResult = {
|
||||
memo?: string,
|
||||
picture?: string
|
||||
paymentRequest?: PaymentRequest
|
||||
token?: Token
|
||||
token?: TokenV3
|
||||
}
|
||||
|
||||
|
||||
@@ -123,8 +121,11 @@ const {
|
||||
paymentRequestsStore,
|
||||
contactsStore,
|
||||
relaysStore,
|
||||
nonPersistedStores,
|
||||
} = rootStoreInstance
|
||||
|
||||
const { walletStore } = nonPersistedStores
|
||||
|
||||
|
||||
const transfer = async function (
|
||||
mintBalanceToTransferFrom: MintBalance,
|
||||
@@ -153,7 +154,7 @@ const transfer = async function (
|
||||
|
||||
|
||||
const receive = async function (
|
||||
token: Token,
|
||||
token: TokenV3,
|
||||
amountToReceive: number,
|
||||
memo: string,
|
||||
encodedToken: string,
|
||||
@@ -173,7 +174,7 @@ const receive = async function (
|
||||
|
||||
|
||||
const receiveOfflinePrepare = async function (
|
||||
token: Token,
|
||||
token: TokenV3,
|
||||
amountToReceive: number,
|
||||
memo: string,
|
||||
encodedToken: string,
|
||||
@@ -325,6 +326,7 @@ const handleSpentByMint = async function (
|
||||
* @isPending whether to work on proofs in spendable or pending state by the wallet
|
||||
* @returns WalletTaskResult
|
||||
*/
|
||||
|
||||
const _handleSpentByMintTask = async function (
|
||||
options: {
|
||||
mintUrl: string,
|
||||
@@ -363,7 +365,7 @@ const _handleSpentByMintTask = async function (
|
||||
const {
|
||||
spent: spentProofs,
|
||||
pending: pendingProofs
|
||||
} = await MintClient.getSpentOrPendingProofsFromMint(
|
||||
} = await walletStore.getSpentOrPendingProofsFromMint(
|
||||
mintUrl,
|
||||
mint && mint.units ? mint.units[0] : 'sat',
|
||||
proofsFromMint
|
||||
@@ -405,7 +407,7 @@ const _handleSpentByMintTask = async function (
|
||||
log.trace('[_handleSpentByMintTask]', 'Starting sweep of spent pendingByMint proofs')
|
||||
|
||||
for (const spent of spentProofs) {
|
||||
const secret = proofsStore.pendingByMintSecrets.find((s => s === spent.secret))
|
||||
const secret = proofsStore.pendingByMintSecrets.find((s: string) => s === spent.secret)
|
||||
if(secret) {
|
||||
proofsStore.removeFromPendingByMint(spent as Proof)
|
||||
}
|
||||
@@ -439,7 +441,7 @@ const _handleSpentByMintTask = async function (
|
||||
// Check if remaining pendingByMint secrets are still pending with the mint.
|
||||
// If not, move related wallet's pending proofs back to spendable as the payment failed
|
||||
// and proofs did not come as spent (those are handled above).
|
||||
const remainingSecrets = getSnapshot(proofsStore.pendingByMintSecrets)
|
||||
const remainingSecrets: string[] = getSnapshot(proofsStore.pendingByMintSecrets)
|
||||
|
||||
log.trace('[_handleSpentByMintTask]', 'Remaining pendingByMintSecrets', remainingSecrets)
|
||||
|
||||
@@ -454,7 +456,7 @@ const _handleSpentByMintTask = async function (
|
||||
|
||||
// only move if it is from current mint
|
||||
if(proofToMove && proofToMove.mintUrl === mintUrl) {
|
||||
const stillPendingByMint = pendingProofs.find((p => p.secret === secret))
|
||||
const stillPendingByMint = pendingProofs.find(p => p.secret === secret)
|
||||
|
||||
if(!stillPendingByMint) {
|
||||
// move to spendable if it is not pending by mint anymore
|
||||
@@ -542,16 +544,12 @@ const handleInFlight = async function (): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
const seed = await MintClient.getSeed()
|
||||
|
||||
if(!seed) {return}
|
||||
|
||||
for (const mint of mintsStore.allMints) {
|
||||
const now = new Date().getTime()
|
||||
|
||||
SyncQueue.addTask(
|
||||
`_handleInFlightByMintTask-${now}`,
|
||||
async () => await _handleInFlightByMintTask(mint, seed)
|
||||
async () => await _handleInFlightByMintTask(mint)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -561,7 +559,7 @@ const handleInFlight = async function (): Promise<void> {
|
||||
/*
|
||||
* Recover proofs that were issued by mint, but wallet failed to receive them if split did not complete.
|
||||
*/
|
||||
const _handleInFlightByMintTask = async function (mint: Mint, seed: Uint8Array): Promise<WalletTaskResult> {
|
||||
const _handleInFlightByMintTask = async function (mint: Mint): Promise<WalletTaskResult> {
|
||||
|
||||
const mintUrl = mint.mintUrl
|
||||
const proofsCounter = mint.findInFlightProofsCounter?.()
|
||||
@@ -579,10 +577,16 @@ const _handleInFlightByMintTask = async function (mint: Mint, seed: Uint8Array):
|
||||
}
|
||||
|
||||
try {
|
||||
const seed = await walletStore.getSeed()
|
||||
if(!seed) {
|
||||
throw new Error('Missing seed')
|
||||
}
|
||||
|
||||
log.info('[_handleInFlightByMintTask]', `Restoring from ${mint.hostname}...`)
|
||||
log.debug('[_handleInFlightByMintTask]', proofsCounter)
|
||||
log.debug('[_handleInFlightByMintTask]', proofsCounter)
|
||||
|
||||
|
||||
const { proofs } = await MintClient.restore(
|
||||
const { proofs } = await walletStore.restore(
|
||||
mint.mintUrl,
|
||||
seed as Uint8Array,
|
||||
{
|
||||
@@ -604,7 +608,7 @@ const _handleInFlightByMintTask = async function (mint: Mint, seed: Uint8Array):
|
||||
} as WalletTaskResult
|
||||
}
|
||||
|
||||
const {spent, pending} = await MintClient.getSpentOrPendingProofsFromMint(
|
||||
const {spent, pending} = await walletStore.getSpentOrPendingProofsFromMint(
|
||||
mint.mintUrl,
|
||||
mint.units ? mint.units[0] : 'sat',
|
||||
proofs as Proof[]
|
||||
@@ -616,7 +620,7 @@ const _handleInFlightByMintTask = async function (mint: Mint, seed: Uint8Array):
|
||||
const spentAmount = CashuUtils.getProofsAmount(spent as Proof[])
|
||||
const pendingAmount = CashuUtils.getProofsAmount(pending as Proof[])
|
||||
|
||||
const unspent = proofs.filter(proof => !spent.includes(proof))
|
||||
const unspent = proofs.filter((proof: Proof) => !spent.includes(proof))
|
||||
const unspentCount = unspent.length
|
||||
const unspentAmount = CashuUtils.getProofsAmount(unspent as Proof[])
|
||||
|
||||
@@ -770,7 +774,7 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
|
||||
log.trace('[_handlePendingTopupTask]', 'countOfInFlightProofs', countOfInFlightProofs)
|
||||
|
||||
// check is quote has been paid
|
||||
const { state, mintQuote: quote } = await MintClient.checkLightningMintQuote(mint!, mintQuote)
|
||||
const { state, mintQuote: quote } = await walletStore.checkLightningMintQuote(mint!, mintQuote)
|
||||
|
||||
if (quote !== mintQuote) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Returned quote is different then the one requested', {mintUrl: mint, quote, mintQuote})
|
||||
@@ -825,7 +829,7 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
|
||||
|
||||
let proofs: CashuProof[] = []
|
||||
|
||||
proofs = (await MintClient.mintProofs(
|
||||
proofs = (await walletStore.mintProofs(
|
||||
mint as string,
|
||||
amount,
|
||||
unit,
|
||||
@@ -979,7 +983,7 @@ const _handleClaimTask = async function (params: {claimedInvoice: {token: string
|
||||
|
||||
log.debug('[_handleClaimTask] decrypted token', {encodedToken})
|
||||
|
||||
const decoded: Token = CashuUtils.decodeToken(encodedToken)
|
||||
const decoded = CashuUtils.decodeToken(encodedToken)
|
||||
const amountToReceive = CashuUtils.getTokenAmounts(decoded).totalAmount
|
||||
const memo = decoded.memo || 'Received to Lightning address'
|
||||
|
||||
@@ -1175,12 +1179,12 @@ const _handleReceivedEventTask = async function (event: NostrEvent): Promise<Wal
|
||||
//
|
||||
if(incoming.type === IncomingDataType.CASHU) {
|
||||
|
||||
const decoded: Token = CashuUtils.decodeToken(incoming.encoded)
|
||||
const decoded = CashuUtils.decodeToken(incoming.encoded)
|
||||
const amountToReceive = CashuUtils.getTokenAmounts(decoded).totalAmount
|
||||
const memo = decoded.memo || 'Received over Nostr'
|
||||
|
||||
const {transaction, receivedAmount} = await receiveTask(
|
||||
decoded as Token,
|
||||
decoded,
|
||||
amountToReceive,
|
||||
memo,
|
||||
incoming.encoded as string,
|
||||
|
||||
Reference in New Issue
Block a user