Load proofs always from DB, theme app icon
@@ -11,47 +11,62 @@
|
||||
android:supportsRtl="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="lightning" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="lnurlw" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="lnurlp" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="cashu" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="lightning"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="lnurlw"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="lnurlp"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="cashu"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity-alias
|
||||
android:name=".MainActivityDefault"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:targetActivity=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
<activity-alias
|
||||
android:name=".MainActivityGolden"
|
||||
android:enabled="false"
|
||||
android:exported="true"
|
||||
android:icon="@mipmap/ic_launcher_golden"
|
||||
android:targetActivity=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_golden_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_golden_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_golden_monochrome"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@@ -38,6 +38,7 @@
|
||||
"@react-navigation/native-stack": "^6.11.0",
|
||||
"@react-navigation/stack": "^6.4.1",
|
||||
"@sentry/react-native": "^5.32.0",
|
||||
"@synonymdev/react-native-change-icon": "^5.0.0",
|
||||
"date-fns": "^4.0.0",
|
||||
"i18n-js": "^4.4.3",
|
||||
"js-lnurl": "^0.6.0",
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
"missingMnemonicOrSeedError": "Missing mnemonic or seed.",
|
||||
"mnemonicDesc": "This 12 word sequence represents your seed and allows you to recover your ecash balance in case of device loss. Keep them in safe place.",
|
||||
"mnemonicTitle": "Your mnemonic phrase",
|
||||
"recoveryTool": "Local recovery tool",
|
||||
"recoveryToolDescription": "Show ecash backed up in the local database and attempt to recover unspent ones into the wallet in case the wallet storage gets corrupted or a transaction fails due to an unexpected error.",
|
||||
"recoveryTool": "Export ecash",
|
||||
"recoveryToolDescription": "Filter and export all ecash from local database grouped by mint keysets.",
|
||||
"remoteBackupDescription": "Mnemonic phrase encodes your seed and allows you to recover your ecash balance in case of device loss.",
|
||||
"removeSpentCoins": "Remove spent ecash",
|
||||
"removeSpentCoinsDescription": "In case of a SEND or TRANSFER transaction error, spent ecash may remain in your wallet. This blocks further transactions with the mint, making the wallet unusable. This tool removes spent ecash from the wallet.",
|
||||
@@ -231,7 +231,7 @@
|
||||
"nsecMissingProfileError": "Missing profile to update",
|
||||
"nsecPasteError": "Copy your nsec key first, then paste.",
|
||||
"nsecPrivatePublicKeyMismatchError": "Provided private key does not match your new profile public key.",
|
||||
"numberOfBackedupProofs": "Number of backed up proofs",
|
||||
"numberOfBackedupProofs": "Number of stored proofs",
|
||||
"onDeviceInfo": "On-device information",
|
||||
"pasteEcashTokenDesc": "Paste ecash token you want to receive.",
|
||||
"payCommon": {
|
||||
@@ -370,7 +370,7 @@
|
||||
"recoveryInsertMnemonicDesc": "Paste or rewrite 12 word phrase to recover your ecash balance and wallet address on this device. Separate words by blank spaces.",
|
||||
"recoveryInsertMnemonicDescAddrOnly": "Paste or rewrite 12 word phrase to recover your wallet address on this device. Separate words by blank spaces.",
|
||||
"recoveryInvalidMnemonicError": "Invalid mnemonic phrase. Provide 12 word sequence separated by blank spaces.",
|
||||
"recoveryTool": "Recovery tool",
|
||||
"exportEcash": "Export ecash",
|
||||
"relayExists": "Relay already exists",
|
||||
"relays": "Relays",
|
||||
"relayurlPasteError": "Copy your relay URL key before pasting",
|
||||
|
||||
@@ -4,9 +4,10 @@ import {
|
||||
types,
|
||||
isStateTreeNode,
|
||||
detach,
|
||||
flow,
|
||||
} from 'mobx-state-tree'
|
||||
import {withSetPropAction} from './helpers/withSetPropAction'
|
||||
import {ProofModel, Proof} from './Proof'
|
||||
import {ProofModel, Proof, BackupProof} from './Proof'
|
||||
import {log} from '../services/logService'
|
||||
import {getRootStore} from './helpers/getRootStore'
|
||||
import AppError, {Err} from '../utils/AppError'
|
||||
@@ -87,6 +88,43 @@ export const ProofsStoreModel = types
|
||||
},
|
||||
}))
|
||||
.actions(self => ({
|
||||
// Proofs are not more persisted in mmkv storage but loaded from database on afterCreate model
|
||||
loadProofsFromDatabase: flow(function* laodProofs() {
|
||||
|
||||
const unspentAndPendingProofs = yield Database.getProofs(true, true, false)
|
||||
|
||||
// Group into isUnspent and isPending
|
||||
const groupedProofs = unspentAndPendingProofs.reduce(
|
||||
(acc: {isPending: BackupProof[], isUnspent: BackupProof[]}, proof: BackupProof) => {
|
||||
if (proof.isPending) {
|
||||
acc.isPending.push(proof);
|
||||
} else {
|
||||
acc.isUnspent.push(proof);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ isUnspent: [], isPending: [] } // Initialize the groups
|
||||
)
|
||||
|
||||
const cleanProofs = (proofs: BackupProof[]) => {
|
||||
return proofs.map(p => {
|
||||
// Destructure the unwanted properties and collect the rest in `cleaned`
|
||||
const { isPending, isSpent, updatedAt, ...cleaned } = p
|
||||
// Return the cleaned object
|
||||
return cleaned as Proof
|
||||
})
|
||||
}
|
||||
|
||||
self.proofs.replace(cleanProofs(groupedProofs.isUnspent))
|
||||
self.pendingProofs.replace(cleanProofs(groupedProofs.isPending))
|
||||
}),
|
||||
}))
|
||||
.actions(self => ({
|
||||
/*afterCreate() {
|
||||
// not used to be able to load proofs without blocking app start
|
||||
// Trigger the load proofs action when the store is initialized
|
||||
self.loadProofsFromDatabase()
|
||||
},*/
|
||||
addProofs(newProofs: Proof[], isPending: boolean = false): {addedAmount: number, addedProofs: Proof[]} {
|
||||
try {
|
||||
const proofs = isPending ? self.pendingProofs : self.proofs
|
||||
@@ -405,7 +443,13 @@ export const ProofsStoreModel = types
|
||||
getProofsSubset: (proofs: Proof[], proofsToRemove: Proof[]) => {
|
||||
return proofs.filter(proof => !proofsToRemove.includes(proof))
|
||||
},
|
||||
}))
|
||||
})).postProcessSnapshot((snapshot) => { // NOT persisted to storage except last pendingByMintSecrets!
|
||||
return {
|
||||
proofs: [],
|
||||
pendingProofs: [],
|
||||
pendingByMintSecrets: snapshot.pendingByMintSecrets
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
export interface Proofs extends Instance<typeof ProofsStoreModel> {}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {WalletStoreModel} from './WalletStore'
|
||||
import {NwcStoreModel} from './NwcStore'
|
||||
import { log } from '../services'
|
||||
|
||||
export const rootStoreModelVersion = 26 // Update this if model changes require migrations defined in setupRootStore.ts
|
||||
export const rootStoreModelVersion = 28 // Update this if model changes require migrations defined in setupRootStore.ts
|
||||
|
||||
/**
|
||||
* A RootStore model.
|
||||
|
||||
@@ -103,7 +103,7 @@ export const UserSettingsStoreModel = types
|
||||
self.isLocalBackupOn = isLocalBackupOn
|
||||
return isLocalBackupOn
|
||||
},
|
||||
setIsStorageEncrypted: flow(function* setIsStorageEncryptedvalue(
|
||||
setIsStorageEncrypted: flow(function* setIsStorageEncryptedvalue( // legacy, tobe removed
|
||||
isEncrypted: boolean,
|
||||
) {
|
||||
if (isEncrypted) {
|
||||
@@ -115,7 +115,7 @@ export const UserSettingsStoreModel = types
|
||||
self.isStorageEncrypted = isEncrypted
|
||||
return isEncrypted
|
||||
}),
|
||||
setIsTorDaemonOn: (isTorDaemonOn: boolean) => {
|
||||
setIsTorDaemonOn: (isTorDaemonOn: boolean) => { // legacy, tobe removed
|
||||
Database.updateUserSettings({...self, isTorDaemonOn})
|
||||
self.isTorDaemonOn = isTorDaemonOn
|
||||
return isTorDaemonOn
|
||||
|
||||
@@ -54,6 +54,7 @@ export async function setupRootStore(rootStore: RootStore) {
|
||||
Sentry.setUser({ id: userSettings.walletId })
|
||||
}
|
||||
|
||||
// legacy, encryption is sunset, to be replaced by opt-in biometric auth to read the seed
|
||||
if (userSettings.isStorageEncrypted) {
|
||||
try {
|
||||
await MMKVStorage.initEncryption()
|
||||
@@ -77,21 +78,27 @@ export async function setupRootStore(rootStore: RootStore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // legacy end
|
||||
|
||||
// load the last known state from storage
|
||||
const start = performance.now()
|
||||
restoredState = MMKVStorage.load(ROOT_STORAGE_KEY) || {}
|
||||
|
||||
// dirty mobx fix
|
||||
restoredState.walletProfileStore.isBatchClaimOn = undefined
|
||||
const mmkvLoaded = performance.now()
|
||||
const dataSize = Buffer.byteLength(JSON.stringify(restoredState), 'utf8')
|
||||
|
||||
const dataSize = Buffer.byteLength(JSON.stringify(restoredState), 'utf8')
|
||||
log.trace('[setupRootStore]', `Loading ${dataSize.toLocaleString()} bytes of state from MMKV took ${(mmkvLoaded - start).toLocaleString()} ms.`)
|
||||
|
||||
log.trace('[setupRootStore]', `restored state has ${dataSize} bytes.`)
|
||||
// log.trace(restoredState)
|
||||
|
||||
applySnapshot(rootStore, restoredState)
|
||||
|
||||
const stateHydrated = performance.now()
|
||||
log.trace(`[setupRootStore] Hydrating rooStoreModel took ${stateHydrated - mmkvLoaded} ms.`)
|
||||
|
||||
const {proofsStore} = rootStore
|
||||
await proofsStore.loadProofsFromDatabase()
|
||||
|
||||
const proofsLoaded = performance.now()
|
||||
log.trace(`[setupRootStore] Loading proofs from DB and hydrating took ${proofsLoaded - stateHydrated} ms.`)
|
||||
|
||||
} catch (e: any) {
|
||||
log.error('[setupRootStore]', Err.STORAGE_ERROR, {message: e.message, params: e.params})
|
||||
}
|
||||
@@ -326,6 +333,30 @@ async function _runMigrations(rootStore: RootStore) {
|
||||
rootStore.setVersion(rootStoreModelVersion)
|
||||
log.info(`Completed rootStore migrations to the version v${rootStoreModelVersion}`)
|
||||
}
|
||||
|
||||
if(currentVersion < 28) {
|
||||
log.trace(`Starting rootStore migrations from version v${currentVersion} -> v27`)
|
||||
if(userSettingsStore.isStorageEncrypted) {
|
||||
await userSettingsStore.setIsStorageEncrypted(false)
|
||||
}
|
||||
if(!userSettingsStore.isLocalBackupOn) {
|
||||
const proofs = proofsStore.allProofs
|
||||
const pendingProofs = proofsStore.pendingProofs
|
||||
|
||||
Database.addOrUpdateProofs(proofs, false, false)
|
||||
Database.addOrUpdateProofs(pendingProofs, true, false)
|
||||
userSettingsStore.setIsLocalBackupOn(true)
|
||||
}
|
||||
|
||||
for (const mint of mintsStore.allMints) {
|
||||
for(const keysetId of mint.keysetIds) {
|
||||
Database.updateProofsMintUrl(keysetId, mint.mintUrl)
|
||||
}
|
||||
}
|
||||
|
||||
rootStore.setVersion(rootStoreModelVersion)
|
||||
log.info(`Completed rootStore migrations to the version v${rootStoreModelVersion}`)
|
||||
}
|
||||
} catch (e: any) {
|
||||
throw new AppError(
|
||||
Err.STORAGE_ERROR,
|
||||
@@ -334,5 +365,4 @@ async function _runMigrations(rootStore: RootStore) {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
UpdateScreen,
|
||||
BackupScreen,
|
||||
RemoteBackupScreen,
|
||||
LocalRecoveryScreen,
|
||||
ExportEcashScreen,
|
||||
RelaysScreen,
|
||||
TranDetailScreen,
|
||||
TranHistoryScreen,
|
||||
@@ -287,7 +287,7 @@ export type SettingsStackParamList = {
|
||||
}
|
||||
Backup: undefined
|
||||
RemoteBackup: undefined
|
||||
LocalRecovery: undefined
|
||||
ExportEcash: undefined
|
||||
Developer: undefined
|
||||
Relays: undefined
|
||||
RemoteRecovery: {isAddressOnlyRecovery: boolean}
|
||||
@@ -320,7 +320,7 @@ const SettingsNavigator = function SettingsNavigator() {
|
||||
<SettingsStack.Screen name="Update" component={UpdateScreen} />
|
||||
<SettingsStack.Screen name="Backup" component={BackupScreen} />
|
||||
<SettingsStack.Screen name="RemoteBackup" component={RemoteBackupScreen} />
|
||||
<SettingsStack.Screen name="LocalRecovery" component={LocalRecoveryScreen} />
|
||||
<SettingsStack.Screen name="ExportEcash" component={ExportEcashScreen} />
|
||||
<SettingsStack.Screen name="Developer" component={DeveloperScreen} />
|
||||
<SettingsStack.Screen name="Relays" component={RelaysScreen} />
|
||||
<SettingsStack.Screen name="RemoteRecovery" component={RemoteRecoveryScreen} />
|
||||
|
||||
@@ -20,7 +20,7 @@ import {useStores} from '../models'
|
||||
import AppError from '../utils/AppError'
|
||||
import EventEmitter from '../utils/eventEmitter'
|
||||
import {ResultModalInfo} from './Wallet/ResultModalInfo'
|
||||
import {Database, log, WalletTask, WalletTaskResult} from '../services'
|
||||
import {Database, log, SyncStateTaskResult, WalletTask, WalletTaskResult} from '../services'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(function BackupScreen(_props) {
|
||||
@@ -41,22 +41,32 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
const [totalSpentAmount, setTotalSpentAmount] = useState<number>(0)
|
||||
|
||||
useEffect(() => {
|
||||
const removeSpentByMintTaskResult = async (result: WalletTaskResult) => {
|
||||
const removeSpentByMintTaskResult = async (result: SyncStateTaskResult) => {
|
||||
log.trace('removeSpentByMintTaskResult event handler triggered')
|
||||
|
||||
if (!isSyncStateSentToQueue) { return false }
|
||||
|
||||
setIsLoading(false)
|
||||
// runs per each mint
|
||||
if (result && result.spentAmount > 0) {
|
||||
if (result && result.transactionStateUpdates.length > 0) {
|
||||
|
||||
let totalSpentPerMint = 0
|
||||
|
||||
for (const update of result.transactionStateUpdates) {
|
||||
if(update.spentByMintAmount && update.spentByMintAmount > 0) {
|
||||
totalSpentPerMint += update.spentByMintAmount
|
||||
}
|
||||
}
|
||||
|
||||
setTotalSpentAmount(prev => prev + result.spentAmount)
|
||||
setTotalSpentCount(prev => prev + result.spentCount)
|
||||
setInfo(
|
||||
translate("backupScreen.result", { proofCount: totalSpentCount, amount: totalSpentAmount })
|
||||
translate("backupScreen.result", { proofCount: totalSpentCount, amount: totalSpentAmount })
|
||||
)
|
||||
return
|
||||
}
|
||||
setInfo(translate("noSpentEcashFound"))
|
||||
|
||||
setInfo(translate("noSpentEcashFound"))
|
||||
}
|
||||
|
||||
EventEmitter.on('ev__syncStateWithMintTask_result', removeSpentByMintTaskResult)
|
||||
@@ -66,7 +76,7 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
}
|
||||
}, [isSyncStateSentToQueue])
|
||||
|
||||
const toggleBackupSwitch = () => {
|
||||
/* const toggleBackupSwitch = () => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
const result = userSettingsStore.setIsLocalBackupOn(!isLocalBackupOn)
|
||||
@@ -97,13 +107,13 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
const toggleBackupModal = () =>
|
||||
setIsBackupModalVisible(previousState => !previousState)
|
||||
|
||||
const gotoLocalRecovery = function () {
|
||||
navigation.navigate('LocalRecovery')
|
||||
const gotoExportEcash = function () {
|
||||
navigation.navigate('ExportEcash')
|
||||
}
|
||||
|
||||
const gotoRemoteBackup = function () {
|
||||
@@ -180,7 +190,7 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
style={$card}
|
||||
HeadingComponent={
|
||||
<>
|
||||
<ListItem
|
||||
{/*<ListItem
|
||||
tx="backupScreen.localBackup"
|
||||
subTx="backupScreen.localBackupDescription"
|
||||
leftIcon='faDownload'
|
||||
@@ -199,7 +209,7 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
</View>
|
||||
}
|
||||
style={$item}
|
||||
/>
|
||||
/>*/}
|
||||
{isLocalBackupOn && (
|
||||
<ListItem
|
||||
tx="backupScreen.recoveryTool"
|
||||
@@ -208,8 +218,8 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
leftIconColor={colors.palette.focus300}
|
||||
leftIconInverse={true}
|
||||
style={$item}
|
||||
onPress={gotoLocalRecovery}
|
||||
topSeparator={true}
|
||||
onPress={gotoExportEcash}
|
||||
// topSeparator={true}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -4,10 +4,8 @@ import {
|
||||
TextStyle,
|
||||
ViewStyle,
|
||||
View,
|
||||
useColorScheme,
|
||||
Alert,
|
||||
} from 'react-native'
|
||||
import {useThemeColor, spacing, colors, typography} from '../theme'
|
||||
import {useThemeColor, spacing, typography} from '../theme'
|
||||
import {
|
||||
Button,
|
||||
Icon,
|
||||
@@ -28,28 +26,24 @@ import {BackupProof} from '../models/Proof'
|
||||
import { useStores } from '../models'
|
||||
import { CashuUtils, ProofV3, TokenV3 } from '../services/cashu/cashuUtils'
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import { Transaction, TransactionData, TransactionRecord, TransactionStatus, TransactionType } from '../models/Transaction'
|
||||
import { WalletUtils } from '../services/wallet/utils'
|
||||
import { MintUnit, MintUnits, getCurrency } from '../services/wallet/currency'
|
||||
import { MintUnits, getCurrency } from '../services/wallet/currency'
|
||||
import { CurrencyAmount } from './Wallet/CurrencyAmount'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
interface LocalRecoveryScreenProps
|
||||
extends SettingsStackScreenProps<'LocalRecovery'> {}
|
||||
interface ExportEcashScreenProps extends SettingsStackScreenProps<'ExportEcash'> {}
|
||||
|
||||
|
||||
export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
function LocalRecoveryScreen(_props) {
|
||||
export const ExportEcashScreen: FC<ExportEcashScreenProps> =
|
||||
function ExportEcash(_props) {
|
||||
|
||||
const { navigation } = _props
|
||||
const { mintsStore, proofsStore, transactionsStore } = useStores()
|
||||
const { mintsStore } = useStores()
|
||||
|
||||
useHeader({
|
||||
leftIcon: 'faArrowLeft',
|
||||
onLeftPress: () => navigation.goBack(),
|
||||
})
|
||||
|
||||
|
||||
const [showUnspentOnly, setShowUnspentOnly] = useState<boolean>(true)
|
||||
const [showPendingOnly, setShowPendingOnly] = useState<boolean>(false)
|
||||
const [showSpentOnly, setShowSpentOnly] = useState<boolean>(false)
|
||||
@@ -72,7 +66,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
try {
|
||||
setIsLoading(true)
|
||||
// update empty unit fields to resolve v0.1.7 upgrade issue of backed up proofs not having unit migrated
|
||||
await Database.updateProofsToDefaultUnit()
|
||||
// await Database.updateProofsToDefaultUnit()
|
||||
const backupProofs = await Database.getProofs(isUnspent, isPending, isDeleted)
|
||||
setProofs(backupProofs)
|
||||
setIsLoading(false)
|
||||
@@ -188,7 +182,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
}
|
||||
|
||||
|
||||
const onRecovery = async function () {
|
||||
/* const onRecovery = async function () {
|
||||
if (!showUnspentOnly) {
|
||||
setInfo(translate("unspentOnlyRecoverable"))
|
||||
return
|
||||
@@ -337,7 +331,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
const groupProofsByMint = function (proofs: BackupProof[]) {
|
||||
return proofs.reduce((acc: Record<string, BackupProof[]>, proof) => {
|
||||
@@ -383,7 +377,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
|
||||
const headerBg = useThemeColor('header')
|
||||
const iconColor = useThemeColor('textDim')
|
||||
const activeIconColor = useThemeColor('button')
|
||||
const activeIconColor = useThemeColor('tint')
|
||||
const headerTitle = useThemeColor('headerTitle')
|
||||
|
||||
|
||||
@@ -392,7 +386,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
<Text
|
||||
preset="heading"
|
||||
tx="recoveryTool"
|
||||
tx="exportEcash"
|
||||
style={{color: headerTitle}}
|
||||
/>
|
||||
</View>
|
||||
@@ -474,44 +468,36 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
))}
|
||||
</>
|
||||
}
|
||||
FooterComponent={
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="tertiary"
|
||||
onPress={copyBackupProofs}
|
||||
tx="copyProofs"
|
||||
style={{
|
||||
minHeight: 25,
|
||||
paddingVertical: spacing.extraSmall,
|
||||
marginTop: spacing.small,
|
||||
marginRight: spacing.small
|
||||
}}
|
||||
textStyle={{fontSize: 14}}
|
||||
/>
|
||||
<Button
|
||||
preset="tertiary"
|
||||
onPress={copyEncodedTokens}
|
||||
tx="copyAsEncodedTokens"
|
||||
style={{
|
||||
minHeight: 25,
|
||||
paddingVertical: spacing.extraSmall,
|
||||
marginTop: spacing.small,
|
||||
}}
|
||||
textStyle={{fontSize: 14}}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
style={[$card]}
|
||||
/>
|
||||
)}
|
||||
<View style={$bottomContainer}>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
// preset="secondary"
|
||||
onPress={copyBackupProofs}
|
||||
tx="copyProofs"
|
||||
style={{
|
||||
marginRight: spacing.small
|
||||
}}
|
||||
textStyle={{fontSize: 14}}
|
||||
/>
|
||||
<Button
|
||||
// preset="secondary"
|
||||
onPress={copyEncodedTokens}
|
||||
tx="copyAsEncodedTokens"
|
||||
textStyle={{fontSize: 14}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{/* <View style={$bottomContainer}>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
onPress={onRecovery}
|
||||
tx="recoverToWallet"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View> */}
|
||||
{isLoading && <Loading />}
|
||||
{error && <ErrorModal error={error} />}
|
||||
{info && <InfoModal message={info} />}
|
||||
@@ -16,9 +16,9 @@ import { QRCodeBlock } from './Wallet/QRCode'
|
||||
import { CollapsibleText } from '../components/CollapsibleText'
|
||||
import { isSameDay } from 'date-fns/isSameDay'
|
||||
|
||||
interface SettingsScreenProps extends SettingsStackScreenProps<'Nwc'> {}
|
||||
interface NwcScreenProps extends SettingsStackScreenProps<'Nwc'> {}
|
||||
|
||||
export const NwcScreen: FC<SettingsScreenProps> = observer(
|
||||
export const NwcScreen: FC<NwcScreenProps> = observer(
|
||||
function NwcScreen(_props) {
|
||||
|
||||
const {navigation} = _props
|
||||
|
||||
@@ -109,7 +109,7 @@ export const SecurityScreen: FC<SettingsStackScreenProps<'Security'>> = observer
|
||||
style={$card}
|
||||
ContentComponent={
|
||||
<>
|
||||
<ListItem
|
||||
{/*<ListItem
|
||||
tx="securityScreen.encryptStorage"
|
||||
subTx="securityScreen.encryptStorageDescription"
|
||||
leftIcon={isStorageEncrypted ? 'faLock' : 'faLockOpen'}
|
||||
@@ -128,7 +128,7 @@ export const SecurityScreen: FC<SettingsStackScreenProps<'Security'>> = observer
|
||||
</View>
|
||||
}
|
||||
style={$item}
|
||||
/>
|
||||
/>*/}
|
||||
{isStorageEncrypted && (
|
||||
<ListItem
|
||||
tx="securityScreen.biometry"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import React, {FC, useCallback, useEffect, useRef, useState} from 'react'
|
||||
import {Alert, AppState, TextStyle, View, ViewStyle} from 'react-native'
|
||||
import {Alert, AppState, TextStyle, View, ViewStyle, useColorScheme} from 'react-native'
|
||||
import notifee from '@notifee/react-native'
|
||||
import messaging from '@react-native-firebase/messaging'
|
||||
import {
|
||||
@@ -9,9 +9,10 @@ import {
|
||||
CODEPUSH_PRODUCTION_DEPLOYMENT_KEY,
|
||||
} from '@env'
|
||||
import codePush, { RemotePackage } from 'react-native-code-push'
|
||||
import { changeIcon, getIcon, resetIcon } from '@synonymdev/react-native-change-icon'
|
||||
import {ThemeCode, Themes, colors, spacing, useThemeColor} from '../theme'
|
||||
import {SettingsStackScreenProps} from '../navigation' // @demo remove-current-line
|
||||
import {ListItem, Screen, Text, Card, NwcIcon, Button, BottomModal, InfoModal} from '../components'
|
||||
import {ListItem, Screen, Text, Card, NwcIcon, Button, BottomModal, InfoModal, Icon} from '../components'
|
||||
import {useHeader} from '../utils/useHeader'
|
||||
import {useStores} from '../models'
|
||||
import {translate} from '../i18n'
|
||||
@@ -44,7 +45,8 @@ export const SettingsScreen: FC<SettingsScreenProps> = observer(
|
||||
} = useStores()
|
||||
|
||||
const [isUpdateAvailable, setIsUpdateAvailable] = useState<boolean>(false)
|
||||
const [updateDescription, setUpdateDescription] = useState<string>('')
|
||||
const [updateDescription, setUpdateDescription] = useState<string>('')
|
||||
const [currentTheme, setCurrentTheme] = useState<ThemeCode>(userSettingsStore.theme)
|
||||
|
||||
const [updateSize, setUpdateSize] = useState<string>('')
|
||||
const [isCurrencyModalVisible, setIsCurrencyModalVisible] = useState<boolean>(false)
|
||||
@@ -180,9 +182,9 @@ export const SettingsScreen: FC<SettingsScreenProps> = observer(
|
||||
await notifee.openNotificationSettings()
|
||||
}
|
||||
|
||||
const gotoPreferredUnit = function() {
|
||||
/* const gotoPreferredUnit = function() {
|
||||
Alert.alert('Preferred unit is set based on your Wallet screen.')
|
||||
}
|
||||
} */
|
||||
|
||||
|
||||
const getRateColor = function () {
|
||||
@@ -221,19 +223,42 @@ export const SettingsScreen: FC<SettingsScreenProps> = observer(
|
||||
toggleCurrencyModal()
|
||||
}
|
||||
|
||||
const onSelectTheme = function(theme: ThemeCode) {
|
||||
const currentTheme = userSettingsStore.theme
|
||||
const onSelectTheme = async function(theme: ThemeCode) {
|
||||
if(currentTheme !== theme) {
|
||||
try {
|
||||
// state update causes crash because of hooks
|
||||
Database.updateUserSettings({...userSettingsStore, theme})
|
||||
setCurrentTheme(theme)
|
||||
|
||||
const currentIcon = await getIcon()
|
||||
log.trace('[onSelectTheme]', {currentIcon})
|
||||
|
||||
if(theme === ThemeCode.GOLDEN) {
|
||||
await changeIcon('Golden')
|
||||
const updatedIcon = await getIcon()
|
||||
log.trace('[onSelectTheme]', {updatedIcon})
|
||||
}
|
||||
|
||||
if(currentIcon !== 'Default') {
|
||||
await changeIcon('Default')
|
||||
const updatedIcon = await getIcon()
|
||||
log.trace('[onSelectTheme]', {updatedIcon})
|
||||
}
|
||||
|
||||
setInfo('Restart the wallet to apply new theme.')
|
||||
} catch (e: any) {
|
||||
log.warn('[onSelectTheme]', e.message)
|
||||
}
|
||||
}
|
||||
|
||||
toggleThemeModal()
|
||||
}
|
||||
|
||||
const $itemRight = {color: useThemeColor('textDim')}
|
||||
const headerBg = useThemeColor('header')
|
||||
const headerTitle = useThemeColor('headerTitle')
|
||||
const headerTitle = useThemeColor('headerTitle')
|
||||
const colorScheme = useColorScheme()
|
||||
const defaultThemeColor = colorScheme === 'dark' ? Themes[ThemeCode.DARK]?.color : Themes[ThemeCode.LIGHT]?.color
|
||||
|
||||
return (
|
||||
<Screen contentContainerStyle={$screen} preset='auto'>
|
||||
@@ -279,19 +304,20 @@ export const SettingsScreen: FC<SettingsScreenProps> = observer(
|
||||
style={$itemRight}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
}
|
||||
bottomSeparator={true}
|
||||
onPress={toggleCurrencyModal}
|
||||
/>
|
||||
<ListItem
|
||||
tx='settingsScreen.theme'
|
||||
leftIcon='faPaintbrush'
|
||||
leftIconColor={headerBg as string}
|
||||
leftIconColor={currentTheme === ThemeCode.DEFAULT ? defaultThemeColor as string : Themes[currentTheme as ThemeCode]?.color as string}
|
||||
leftIconInverse={true}
|
||||
style={$item}
|
||||
RightComponent={
|
||||
<View style={$rightContainer}>
|
||||
<Text
|
||||
text={Themes[userSettingsStore.theme]!.title}
|
||||
text={Themes[currentTheme]!.title}
|
||||
style={$itemRight}
|
||||
/>
|
||||
</View>
|
||||
@@ -462,19 +488,26 @@ export const SettingsScreen: FC<SettingsScreenProps> = observer(
|
||||
/>
|
||||
<BottomModal
|
||||
isVisible={isThemeModalVisible ? true : false}
|
||||
style={{alignItems: 'stretch'}}
|
||||
style={{alignItems: 'stretch', padding: spacing.small}}
|
||||
ContentComponent={
|
||||
<>
|
||||
{[ThemeCode.DEFAULT, ThemeCode.DARK, ThemeCode.LIGHT, ThemeCode.GOLDEN].map(code =>
|
||||
{[ThemeCode.DEFAULT, ThemeCode.DARK, ThemeCode.LIGHT, ThemeCode.GOLDEN].map(code =>
|
||||
<ListItem
|
||||
key={code}
|
||||
leftIconColor={Themes[code as ThemeCode]?.color as string}
|
||||
leftIconColor={code === ThemeCode.DEFAULT ? defaultThemeColor as string : Themes[code as ThemeCode]?.color as string}
|
||||
leftIconInverse={true}
|
||||
leftIcon='faPaintbrush'
|
||||
text={Themes[code as ThemeCode]!.title}
|
||||
onPress={() => onSelectTheme(code as ThemeCode)}
|
||||
RightComponent={
|
||||
<View style={$rightContainer}>
|
||||
{currentTheme === code && (
|
||||
<Icon icon='faCheckCircle' color={$itemRight.color} />
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
bottomSeparator={true}
|
||||
/>
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ import {useStores} from '../models'
|
||||
import {WalletStackScreenProps} from '../navigation'
|
||||
import {Mint, UnitBalance} from '../models/Mint'
|
||||
import {MintsByUnit} from '../models/MintsStore'
|
||||
import {log, MinibitsClient, NostrClient} from '../services'
|
||||
import {Database, log, MinibitsClient, NostrClient} from '../services'
|
||||
import {Env} from '../utils/envtypes'
|
||||
import {Transaction} from '../models/Transaction'
|
||||
import {TransactionListItem} from './Transactions/TransactionListItem'
|
||||
@@ -63,6 +63,8 @@ import { LeftProfileHeader } from './ContactsScreen'
|
||||
import { maxTransactionsByUnit } from '../models/TransactionsStore'
|
||||
import { NavigationState, Route, TabBar, TabView } from 'react-native-tab-view'
|
||||
import { NwcConnection, NwcConnectionModel } from '../models/NwcStore'
|
||||
import { getSnapshot } from 'mobx-state-tree'
|
||||
import { BackupProof, Proof } from '../models/Proof'
|
||||
|
||||
const deploymentKey = APP_ENV === Env.PROD ? CODEPUSH_PRODUCTION_DEPLOYMENT_KEY : CODEPUSH_STAGING_DEPLOYMENT_KEY
|
||||
|
||||
@@ -97,7 +99,6 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
|
||||
const [info, setInfo] = useState<string>('')
|
||||
const [defaultMintUrl, setDefaultMintUrl] = useState<string>(MINIBITS_MINT_URL)
|
||||
const [lastClaimCheck, setLastClaimCheck] = useState<number>(getUnixTime(new Date()))
|
||||
const [error, setError] = useState<AppError | undefined>()
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||
|
||||
@@ -149,7 +150,8 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
// On app start
|
||||
useEffect(() => {
|
||||
// get deeplink if any
|
||||
const getInitialData = async () => {
|
||||
const getInitialData = async () => {
|
||||
// get deeplink data if any
|
||||
const url = await Linking.getInitialURL()
|
||||
|
||||
if (url) {
|
||||
@@ -525,6 +527,7 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
renderLabel={({ route, focused, color }) => (
|
||||
<CurrencySign
|
||||
mintUnit={route.key as MintUnit}
|
||||
textStyle={{color: 'white'}}
|
||||
containerStyle={focused ? {} : {opacity: 0.5}}
|
||||
/>
|
||||
)}
|
||||
@@ -796,7 +799,7 @@ const UnitBalanceBlock = observer(function (props: {
|
||||
<CurrencyAmount
|
||||
amount={getConvertedBalance() ?? 0}
|
||||
currencyCode={unitBalance.unit === 'sat' ? userSettingsStore.exchangeCurrency : CurrencyCode.SAT}
|
||||
symbolStyle={{color: convertedBalanceColor, marginTop: spacing.tiny}}
|
||||
symbolStyle={{color: convertedBalanceColor, marginTop: spacing.tiny, fontSize: moderateVerticalScale(10)}}
|
||||
amountStyle={{color: convertedBalanceColor}}
|
||||
size='small'
|
||||
/>
|
||||
|
||||
@@ -33,7 +33,7 @@ export * from './UpdateScreen'
|
||||
export * from './BackupScreen'
|
||||
export * from './RemoteBackupScreen'
|
||||
export * from './RemoteRecoveryScreen'
|
||||
export * from './LocalRecoveryScreen'
|
||||
export * from './ExportEcashScreen'
|
||||
export * from './DeveloperScreen'
|
||||
export * from './RelaysScreen'
|
||||
export * from './NwcScreen'
|
||||
|
||||
@@ -10,6 +10,7 @@ import { NostrClient, NostrEvent, NostrProfile } from './nostrService'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import { Platform } from 'react-native'
|
||||
import { RootStoreModel, rootStoreInstance, setupRootStore } from '../models';
|
||||
import { ProofsStoreModel } from '../models/ProofsStore';
|
||||
|
||||
export type NotifyReceiveToLnurlData = {
|
||||
type: 'NotifyReceiveToLnurlData',
|
||||
@@ -90,11 +91,11 @@ const _receiveToLnurlHandler = async function(remoteData: NotifyReceiveToLnurlDa
|
||||
|
||||
const _nwcRequestHandler = async function(remoteData: NotifyNwcRequestData) {
|
||||
const {requestEvent} = remoteData.data
|
||||
const {nwcStore} = rootStoreInstance
|
||||
const {nwcStore, proofsStore} = rootStoreInstance
|
||||
|
||||
// If app is killed, state is not loaded
|
||||
if(nwcStore.all.length === 0) {
|
||||
await setupRootStore(rootStoreInstance)
|
||||
await setupRootStore(rootStoreInstance)
|
||||
}
|
||||
|
||||
log.trace('[_nwcRequestHandler]', {connections: nwcStore.all})
|
||||
|
||||
@@ -21,7 +21,7 @@ import { ThemeCode } from '../theme'
|
||||
|
||||
let _db: QuickSQLiteConnection
|
||||
|
||||
const _dbVersion = 15 // Update this if db changes require migrations
|
||||
const _dbVersion = 16 // Update this if db changes require migrations
|
||||
|
||||
const getInstance = function () {
|
||||
if (!_db) {
|
||||
@@ -99,6 +99,7 @@ const _createOrUpdateSchema = function (db: QuickSQLiteConnection) {
|
||||
C TEXT NOT NULL,
|
||||
unit TEXT,
|
||||
tId INTEGER,
|
||||
mintUrl TEXT,
|
||||
isPending BOOLEAN,
|
||||
isSpent BOOLEAN,
|
||||
updatedAt TEXT
|
||||
@@ -294,6 +295,15 @@ const _runMigrations = function (db: QuickSQLiteConnection) {
|
||||
log.info(`Prepared database migrations from ${currentVersion} -> 15`)
|
||||
}
|
||||
|
||||
if (currentVersion < 16) {
|
||||
migrationQueries.push([
|
||||
`ALTER TABLE proofs
|
||||
ADD COLUMN mintUrl`,
|
||||
])
|
||||
|
||||
log.info(`Prepared database migrations from ${currentVersion} -> 16`)
|
||||
}
|
||||
|
||||
// Update db version as a part of migration sqls
|
||||
migrationQueries.push([
|
||||
`INSERT OR REPLACE INTO dbversion (id, version, createdAt)
|
||||
@@ -903,7 +913,7 @@ const updateOutputToken = function (id: number, outputToken: string) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// proof of payment, e.g. preimage
|
||||
const updateProof = function (id: number, proof: string) {
|
||||
try {
|
||||
const query = `
|
||||
@@ -1073,8 +1083,8 @@ const addOrUpdateProof = function (
|
||||
const now = new Date()
|
||||
|
||||
const query = `
|
||||
INSERT OR REPLACE INTO proofs (id, amount, secret, C, tId, isPending, isSpent, updatedAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT OR REPLACE INTO proofs (id, amount, secret, C, tId, mintUrl, isPending, isSpent, updatedAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`
|
||||
const params = [
|
||||
proof.id,
|
||||
@@ -1082,6 +1092,7 @@ const addOrUpdateProof = function (
|
||||
proof.secret,
|
||||
proof.C,
|
||||
proof.tId,
|
||||
proof.mintUrl,
|
||||
isPending,
|
||||
isSpent,
|
||||
now.toISOString(),
|
||||
@@ -1090,7 +1101,7 @@ const addOrUpdateProof = function (
|
||||
const db = getInstance()
|
||||
const result = db.execute(query, params)
|
||||
// DO NOT log proof secrets to Sentry
|
||||
log.info('[addOrUpdateProof]', `${isPending ? ' Pending' : ''} proof added or updated in the database backup`,
|
||||
log.info('[addOrUpdateProof]', `${isPending ? ' Pending' : ''} proof added or updated in the database`,
|
||||
{id: result.insertId, tId: proof.tId, isPending, isSpent},
|
||||
)
|
||||
|
||||
@@ -1118,8 +1129,8 @@ const addOrUpdateProofs = function (
|
||||
|
||||
for (const proof of proofs) {
|
||||
insertQueries.push([
|
||||
` INSERT OR REPLACE INTO proofs (id, amount, secret, C, unit, tId, isPending, isSpent, updatedAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
` INSERT OR REPLACE INTO proofs (id, amount, secret, C, unit, tId, mintUrl, isPending, isSpent, updatedAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`,
|
||||
[
|
||||
proof.id,
|
||||
@@ -1128,6 +1139,7 @@ const addOrUpdateProofs = function (
|
||||
proof.C,
|
||||
proof.unit,
|
||||
proof.tId,
|
||||
proof.mintUrl,
|
||||
isPending,
|
||||
isSpent,
|
||||
now.toISOString(),
|
||||
@@ -1145,7 +1157,7 @@ const addOrUpdateProofs = function (
|
||||
// DO NOT log proof secrets to Sentry
|
||||
log.info('[addOrUpdateProofs]',
|
||||
`${rowsAffected}${isPending ? ' pending' : ''
|
||||
} proofs were added or updated in the database backup`,
|
||||
} proofs were added or updated in the database`,
|
||||
{isPending, isSpent}
|
||||
)
|
||||
|
||||
@@ -1159,6 +1171,31 @@ const addOrUpdateProofs = function (
|
||||
}
|
||||
}
|
||||
|
||||
// migration
|
||||
const updateProofsMintUrl = function (id: string, mintUrl: string) {
|
||||
try {
|
||||
const query = `
|
||||
UPDATE proofs
|
||||
SET mintUrl = ?
|
||||
WHERE id = ?
|
||||
`
|
||||
const params = [mintUrl, id]
|
||||
|
||||
const db = getInstance()
|
||||
db.execute(query, params)
|
||||
|
||||
log.debug('[updateMintUrl]', 'Proof mintUrl updated', {id, mintUrl})
|
||||
|
||||
|
||||
} catch (e: any) {
|
||||
throw new AppError(
|
||||
Err.DATABASE_ERROR,
|
||||
'Could not update proof mintUrl in database',
|
||||
e.message,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const removeAllProofs = async function () {
|
||||
try {
|
||||
const query = `
|
||||
@@ -1229,10 +1266,20 @@ const getProofs = async function (
|
||||
ORDER BY id DESC
|
||||
`
|
||||
}
|
||||
if (isUnspent && isPending) {
|
||||
if (isPending) {
|
||||
query = `
|
||||
SELECT *
|
||||
FROM proofs
|
||||
WHERE isSpent = 0
|
||||
ORDER BY id DESC
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
const db = getInstance()
|
||||
const {rows} = await db.executeAsync(query)
|
||||
|
||||
|
||||
return rows?._array as BackupProof[]
|
||||
|
||||
} catch (e: any) {
|
||||
@@ -1315,6 +1362,7 @@ export const Database = {
|
||||
getPendingAmount,
|
||||
addOrUpdateProof,
|
||||
addOrUpdateProofs,
|
||||
updateProofsMintUrl,
|
||||
removeAllProofs,
|
||||
getProofById,
|
||||
getProofs,
|
||||
|
||||
@@ -14,7 +14,6 @@ import {MintBalance, MintProofsCounter, MintStatus} from '../models/Mint'
|
||||
import {MeltQuoteResponse, MintQuoteState} from '@cashu/cashu-ts'
|
||||
import {Mint} from '../models/Mint'
|
||||
import {pollerExists, stopPolling} from '../utils/poller'
|
||||
import EventEmitter from '../utils/eventEmitter'
|
||||
import { NostrClient, NostrEvent, NostrProfile } from './nostrService'
|
||||
import { MINIBITS_NIP05_DOMAIN, MINIBITS_SERVER_API_HOST, MINIBIT_SERVER_NOSTR_PUBKEY } from '@env'
|
||||
import { PaymentRequest, PaymentRequestStatus, PaymentRequestType } from '../models/PaymentRequest'
|
||||
|
||||
@@ -23,7 +23,7 @@ const palette = {
|
||||
neutral500: '#978F8A',
|
||||
neutral600: '#564E4A',
|
||||
neutral700: '#2e2e2e',
|
||||
neutral800: '#232323',
|
||||
neutral800: '#181818',
|
||||
neutral900: '#050505',
|
||||
|
||||
primary100: '#D6E4FF',
|
||||
|
||||
@@ -3867,6 +3867,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@synonymdev/react-native-change-icon@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "@synonymdev/react-native-change-icon@npm:5.0.0"
|
||||
peerDependencies:
|
||||
react-native: "*"
|
||||
checksum: 01e049715dc01ffdb081badd1803fbbb3630037be526224ff0ccec8d7aea8495b49bbafc0ace0926c8fbc1ac416ec5976626527b786586334fa3a964805db2c8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tootallnate/quickjs-emscripten@npm:^0.23.0":
|
||||
version: 0.23.0
|
||||
resolution: "@tootallnate/quickjs-emscripten@npm:0.23.0"
|
||||
@@ -9381,6 +9390,7 @@ __metadata:
|
||||
"@react-navigation/native-stack": ^6.11.0
|
||||
"@react-navigation/stack": ^6.4.1
|
||||
"@sentry/react-native": ^5.32.0
|
||||
"@synonymdev/react-native-change-icon": ^5.0.0
|
||||
"@tsconfig/react-native": ^3.0.0
|
||||
"@types/lodash.clonedeep": ^4.5.9
|
||||
"@types/react": ^18.2.6
|
||||
|
||||