Skip to content

Commit

Permalink
Cache tx per each mint, profile create fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
minibits-cash committed Jan 12, 2024
1 parent 09b8f58 commit db81b24
Show file tree
Hide file tree
Showing 15 changed files with 214 additions and 112 deletions.
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "minibits_wallet",
"version": "0.1.5-beta.22",
"version": "0.1.5-beta.23",
"private": true,
"scripts": {
"android:clean": "cd android && ./gradlew clean",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Button.tsx
Expand Up @@ -182,7 +182,7 @@ const $textPresets: Record<Presets, StyleProp<TextStyle>> = {
const $pressedViewPresets: Record<Presets, StyleProp<ViewStyle>> = {
default: { backgroundColor: colors.palette.success100 },
secondary: { backgroundColor: colors.palette.neutral300 },
tertiary: { backgroundColor: colors.palette.neutral200 },
tertiary: { backgroundColor: colors.palette.neutral200, opacity: 0.4 },
}

const $pressedTextPresets: Record<Presets, StyleProp<TextStyle>> = {
Expand Down
70 changes: 61 additions & 9 deletions src/components/ErrorModal.tsx
@@ -1,9 +1,15 @@
import React, { FC, useState, useEffect } from 'react'
import { View, ScrollView } from 'react-native'
import { BottomModal, Icon, Text } from '../components'
import { View, ScrollView, LayoutAnimation, Platform, UIManager } from 'react-native'
import { BottomModal, Button, Icon, ListItem, Text } from '../components'
import AppError from '../utils/AppError'
import { spacing, useThemeColor, colors } from '../theme'
import { isObj } from '@cashu/cashu-ts/src/utils'
import JSONTree from 'react-native-json-tree'

if (Platform.OS === 'android' &&
UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true)
}

type ErrorModalProps = {
error: AppError
Expand All @@ -13,12 +19,17 @@ export const ErrorModal: FC<ErrorModalProps> = function ({ error }) {


const [isErrorVisible, setIsErrorVisible] = useState<boolean>(true)
const [isParamsVisible, setIsParamsVisible] = useState<boolean>(false)

// needed for error to re-appear
useEffect(() => {
setIsErrorVisible(true)
}, [error])

const toggleParams = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
setIsParamsVisible(previousState => !previousState)
}

const onClose = () => {
setIsErrorVisible(false)
Expand All @@ -38,15 +49,56 @@ export const ErrorModal: FC<ErrorModalProps> = function ({ error }) {
<Icon icon="faInfoCircle" size={spacing.large} color="white" />
<Text style={{ color: 'white', marginLeft: spacing.small }}>{error.name}</Text>
</View>
<ScrollView style={error.params ? {minHeight: 150} : {}}>
<View>
<Text style={{ color: 'white', marginBottom: spacing.small }}>{error.message}</Text>
{error.params && (
<Text style={{ color: colors.dark.textDim }} size="xs">{JSON.stringify(error.params).slice(0, 100)}</Text>
)}
</ScrollView>
</View>
{error.params && isObj(error.params) && (
<View>
{isParamsVisible ? (
<>
<View style={{borderRadius: spacing.small, backgroundColor: '#fff', padding: spacing.tiny}}>
<JSONTree
hideRoot
data={error.params}
theme={{
scheme: 'codeschool',
author: 'brettof86',
base00: '#000000',
base01: '#2e2f30',
base02: '#515253',
base03: '#737475',
base04: '#959697',
base05: '#b7b8b9',
base06: '#dadbdc',
base07: '#fcfdfe',
base08: '#e31a1c',
base09: '#e6550d',
base0A: '#dca060',
base0B: '#31a354',
base0C: '#80b1d3',
base0D: '#3182bd',
base0E: '#756bb1',
base0F: '#b15928'
}}
/>
</View>
<Button
onPress={toggleParams}
text='Hide details'
preset='tertiary'
/>
</>
) : (
<Button
onPress={toggleParams}
text='Show details'
preset='tertiary'
/>
)}
</View>
)}
</>
}
/>

/>
)
}
9 changes: 9 additions & 0 deletions src/models/MintsStore.ts
Expand Up @@ -13,6 +13,8 @@ import {log} from '../services/logService'
import { MINIBITS_MINT_URL } from '@env'
import { MintClient } from '../services'
import { GetInfoResponse, MintKeys } from '@cashu/cashu-ts'
import AppError, { Err } from '../utils/AppError'
import { stopPolling } from '../utils/poller'

export type MintsByHostname = {
hostname: string
Expand Down Expand Up @@ -42,6 +44,10 @@ export const MintsStoreModel = types
}))
.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')
}

const {keys, keyset} = yield self.getMintKeys(mintUrl)

const newMint: Mint = {
Expand Down Expand Up @@ -76,6 +82,9 @@ export const MintsStoreModel = types
detach(mintInstance)
destroy(mintInstance)
log.info('[removeMint]', 'Mint removed from MintsStore')

stopPolling('checkPendingTopupsPoller')
stopPolling('checkSpentByMintPoller')
}
},
blockMint(mintToBeBlocked: Mint) {
Expand Down
110 changes: 57 additions & 53 deletions src/models/TransactionsStore.ts
Expand Up @@ -5,6 +5,7 @@ import {
flow,
destroy,
isStateTreeNode,
detach,
} from 'mobx-state-tree'
import {withSetPropAction} from './helpers/withSetPropAction'
import {
Expand All @@ -13,44 +14,60 @@ import {
TransactionStatus,
TransactionRecord,
} from './Transaction'
import {Database} from '../services'
import {Database, MintClient} from '../services'
import {log} from '../services/logService'

export const maxTransactionsInModel = 10
export const maxTransactionsByMint = 10
export const maxTransactionsByHostname = 4

export const TransactionsStoreModel = types
.model('TransactionsStore', {
transactions: types.array(TransactionModel),
})
.actions(withSetPropAction)
.actions(self => ({
findById: (id: number) => {
.views(self => ({
get all() {
return self.transactions
.slice()
.sort((a, b) => {
// Sort by createdAt timestamp
if (a.createdAt && b.createdAt) {
return b.createdAt.getTime() - a.createdAt.getTime()
}
})
},
get count() {
return self.transactions.length
},
get recent() {
return this.all.slice(0, 3) // Return the first 3 transactions
},
get pending() {
return this.all.filter(t => t.status === TransactionStatus.PENDING)
},
findById(id: number) {
const tx = self.transactions.find(tx => tx.id === id)
return tx ? tx : undefined
}
}))
.actions(self => ({
removeTransaction: (removedTransaction: Transaction) => {
let transactionInstance: Transaction | undefined

if (isStateTreeNode(removedTransaction)) {
transactionInstance = removedTransaction
} else {
transactionInstance = self.findById((removedTransaction as Transaction).id as number)
}

if (transactionInstance) {
destroy(transactionInstance)
log.info('[removeTransaction]', 'Transaction removed from TransactionsStore')
}
return tx || undefined
},
recentByHostname(mintHostname: string) {
return this.all.filter(t => t.mint?.includes(mintHostname)).slice(0, maxTransactionsByHostname)
},
getByMint(mintUrl: string) {
return this.all.filter(t => t.mint === mintUrl)
},
removeOldTransactions: () => {
const numTransactions = self.transactions.length
countByMint(mintUrl: string) {
return this.getByMint(mintUrl).length
}
}))
.actions(self => ({
removeOldTransactions: () => { // not used
const numTransactions = self.count

// If there are more than 10 transactions, delete the older ones
if (numTransactions > maxTransactionsInModel) {
self.transactions
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) // Sort transactions by createdAt in descending order
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
.splice(maxTransactionsInModel) // Remove transactions beyond the desired number to keep

log.debug('[removeOldTransactions]', `${
Expand All @@ -59,6 +76,20 @@ export const TransactionsStoreModel = types
)
}
},
removeOldByMint: (mintUrl: string) => {
const numByMint = self.countByMint(mintUrl)

if (numByMint > maxTransactionsByMint) {
self.getByMint(mintUrl)
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
.splice(maxTransactionsByMint)

log.debug('[removeOldByMint]', `${
numByMint - maxTransactionsByMint
} transaction(s) removed from TransactionsStore`,
)
}
}
}))
.actions(self => ({
addTransaction: flow(function* addTransaction(newTransaction: Transaction) {
Expand All @@ -74,10 +105,8 @@ export const TransactionsStoreModel = types

log.debug('[addTransaction]', 'New transaction added to the TransactionsStore')

// Purge the oldest transaction from the model if the maximum number of transactions is reached
if (self.transactions.length > maxTransactionsInModel) {
self.removeOldTransactions()
}
// Purge the oldest transaction from cache, but keep some for each mint
self.removeOldByMint(newTransaction.mint)

return transactionInstance
}),
Expand All @@ -95,8 +124,7 @@ export const TransactionsStoreModel = types

self.transactions.push(...inStoreTransactions)

log.debug('[addTransactionsToModel]', `${inStoreTransactions.length} new transactions added to TransactionsStore`,
)
log.debug('[addTransactionsToModel]', `${inStoreTransactions.length} new transactions added to TransactionsStore`)
},
updateStatus: flow(function* updateStatus( // TODO append, not replace status to align behavior with updateStatuses
id: number,
Expand Down Expand Up @@ -225,30 +253,6 @@ export const TransactionsStoreModel = types
log.debug('[removeAllTransactions]', 'Removed all transactions from TransactionsStore')
},
}))
.views(self => ({
get count() {
return self.transactions.length
},
get recent() {
return this.all.slice(0, 3) // Return the first 3 transactions
},
get all() {
return self.transactions
.slice()
.sort((a, b) => {
// Sort by createdAt timestamp
if (a.createdAt && b.createdAt) {
return b.createdAt.getTime() - a.createdAt.getTime()
}
})
},
get pending() {
return this.all.filter(t => t.status === TransactionStatus.PENDING)
},
recentByHostname(mintHostname: string){
return this.all.filter(t => t.mint?.includes(mintHostname)).slice(0, 4)
}
}))

// refresh
export interface TransactionsStore
Expand Down

0 comments on commit db81b24

Please sign in to comment.