mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 19:28:26 +00:00
Support the update of mint URL if it had to be changed by mint operator
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "minibits_wallet",
|
||||
"version": "0.1.8-beta.29",
|
||||
"version": "0.1.8-beta.30",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android:clean": "cd android && ./gradlew clean",
|
||||
|
||||
@@ -91,13 +91,14 @@ import { faCircleQuestion } from '@fortawesome/free-solid-svg-icons/faCircleQues
|
||||
import { faEnvelope } from '@fortawesome/free-solid-svg-icons/faEnvelope'
|
||||
import { faCircleArrowUp } from "@fortawesome/free-solid-svg-icons/faCircleArrowUp"
|
||||
import { faCircleArrowDown } from "@fortawesome/free-solid-svg-icons/faCircleArrowDown"
|
||||
import { faGlobe } from "@fortawesome/free-solid-svg-icons/faGlobe"
|
||||
|
||||
|
||||
export type IconTypes = keyof typeof iconRegistry
|
||||
|
||||
// TODO remove need for manual iconregistry?
|
||||
// would be best to just import all of them, i guess, or figure out something smart
|
||||
export const iconRegistry = { faAddressCard, faAddressBook, faWallet, faQrcode, faClipboard, faSliders, faCoins, faEllipsisVertical, faEllipsis, faArrowUp, faArrowDown, faArrowLeft, faXmark, faInfoCircle, faBug, faCheckCircle, faArrowTurnUp, faArrowTurnDown, faPencil, faTags, faShareFromSquare, faRotate, faCode, faBan, faCircle, faPaperPlane, faBolt, faArrowUpFromBracket, faArrowRightToBracket, faPlus, faShieldHalved, faCloudArrowUp, faPaintbrush, faCopy, faBurst, faUserShield, faLock, faLockOpen, faTriangleExclamation, faDownload, faUpload, faRecycle, faListUl, faExpand, faFingerprint, faWandMagicSparkles, faCircleUser, faComment, faKey, faCircleNodes, faBullseye, faEyeSlash, faUpRightFromSquare, faShareNodes, faPaste, faKeyboard, faMoneyBill1, faGears, faTag, faBank, faChevronDown, faChevronUp, faCircleExclamation, faCircleQuestion, faEnvelope, faTwitter, faTelegramPlane, faDiscord, faGithub, faReddit, faCircleArrowUp, faCircleArrowDown }
|
||||
export const iconRegistry = { faAddressCard, faAddressBook, faWallet, faQrcode, faClipboard, faSliders, faCoins, faEllipsisVertical, faEllipsis, faArrowUp, faArrowDown, faArrowLeft, faXmark, faInfoCircle, faBug, faCheckCircle, faArrowTurnUp, faArrowTurnDown, faPencil, faTags, faShareFromSquare, faRotate, faCode, faBan, faCircle, faPaperPlane, faBolt, faArrowUpFromBracket, faArrowRightToBracket, faPlus, faShieldHalved, faCloudArrowUp, faPaintbrush, faCopy, faBurst, faUserShield, faLock, faLockOpen, faTriangleExclamation, faDownload, faUpload, faRecycle, faListUl, faExpand, faFingerprint, faWandMagicSparkles, faCircleUser, faComment, faKey, faCircleNodes, faBullseye, faEyeSlash, faUpRightFromSquare, faShareNodes, faPaste, faKeyboard, faMoneyBill1, faGears, faTag, faBank, faChevronDown, faChevronUp, faCircleExclamation, faCircleQuestion, faEnvelope, faTwitter, faTelegramPlane, faDiscord, faGithub, faReddit, faCircleArrowUp, faCircleArrowDown, faGlobe }
|
||||
|
||||
|
||||
interface IconProps extends TouchableOpacityProps {
|
||||
|
||||
@@ -70,7 +70,8 @@
|
||||
"success": "Success!",
|
||||
"copySuccessParam": "Copied %{param}",
|
||||
"hide": "Hide",
|
||||
"show": "Show"
|
||||
"show": "Show",
|
||||
"update": "Update"
|
||||
},
|
||||
"confirmBackupRecovery": "Do you really want to recover ecash from the backup?",
|
||||
"confirmMintRemoval": "Do you really want to remove %{hostname} - %{shortname} from the wallet?",
|
||||
@@ -164,6 +165,7 @@
|
||||
"addMint": "Add mint",
|
||||
"addMintMinibits": "Add Minibits mint",
|
||||
"addMintUrl": "Add mint URL",
|
||||
"updateMintUrl": "Update mint URL",
|
||||
"blockMint": "Block receiving from this mint",
|
||||
"copy": "Copy mint URL",
|
||||
"invalidUrl": "This is not a valid URL",
|
||||
|
||||
+32
-9
@@ -8,6 +8,7 @@ import { MINIBITS_MINT_URL } from '@env'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import { MintUnit } from '../services/wallet/currency'
|
||||
import { getRootStore } from './helpers/getRootStore'
|
||||
import { generateId } from '../utils/utils'
|
||||
|
||||
// used as a helper type across app
|
||||
/* export type Balance = {
|
||||
@@ -53,7 +54,8 @@ export type MintProofsCounter = {
|
||||
*/
|
||||
export const MintModel = types
|
||||
.model('Mint', {
|
||||
mintUrl: types.identifier,
|
||||
id: types.optional(types.identifier, generateId(8)),
|
||||
mintUrl: types.string,
|
||||
hostname: types.maybe(types.string),
|
||||
shortname: types.maybe(types.string),
|
||||
units: types.array(types.frozen<MintUnit>()),
|
||||
@@ -130,6 +132,14 @@ export const MintModel = types
|
||||
const counter = self.proofsCounters.find(c => c.inFlightTid === tId)
|
||||
return counter as MintProofsCounter | undefined
|
||||
},
|
||||
validateURL(url: string) {
|
||||
try {
|
||||
new URL(url)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
},
|
||||
}))
|
||||
.actions(self => ({
|
||||
getProofsCounterByUnit: flow(function* getProofsCounterByUnit(unit: MintUnit) {
|
||||
@@ -140,14 +150,6 @@ export const MintModel = types
|
||||
const counter = self.getOrCreateProofsCounter(keys.id, unit)
|
||||
return counter
|
||||
}),
|
||||
validateURL(url: string) {
|
||||
try {
|
||||
new URL(url)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
},
|
||||
setHostname() {
|
||||
try {
|
||||
self.hostname = new URL(self.mintUrl).hostname
|
||||
@@ -155,6 +157,27 @@ export const MintModel = types
|
||||
return false
|
||||
}
|
||||
},
|
||||
setMintUrl(url: string) {
|
||||
if(self.validateURL(url)) {
|
||||
const mintsStore = getRootStore(self).mintsStore
|
||||
|
||||
if(!mintsStore.alreadyExists(url)) {
|
||||
|
||||
const proofsStore = getRootStore(self).proofsStore
|
||||
proofsStore.updateMintUrl(self.mintUrl, url) // update mintUrl on mint's proofs
|
||||
self.mintUrl = url
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint URL already exists.', {url})
|
||||
} else {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Invalid Mint URL.', {url})
|
||||
}
|
||||
},
|
||||
setId() { // migration
|
||||
self.id = generateId(8)
|
||||
},
|
||||
setShortname: flow(function* setShortname() {
|
||||
// get name from URL as a fallback
|
||||
const lastSlashIndex = self.mintUrl.lastIndexOf('/')
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { MintClient } from '../services'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import { MintKeyset } from '@cashu/cashu-ts'
|
||||
import { getRootStore } from './helpers/getRootStore'
|
||||
|
||||
import { MintUnit, MintUnits } from '../services/wallet/currency'
|
||||
|
||||
export type MintsByHostname = {
|
||||
@@ -90,10 +90,6 @@ import { MintUnit, MintUnits } from '../services/wallet/currency'
|
||||
if(!activeKeysets || activeKeysets.length === 0) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint has no active keysets and is not operational', {mintUrl})
|
||||
}
|
||||
|
||||
const newMint: Mint = {
|
||||
mintUrl,
|
||||
}
|
||||
|
||||
for(const keyset of activeKeysets) {
|
||||
if(keyset.active === true) {
|
||||
|
||||
@@ -200,6 +200,20 @@ export const ProofsStoreModel = types
|
||||
|
||||
proofs.replace(proofs.filter(proof => !proofsToRemove.some(removed => removed.secret === proof.secret)))
|
||||
},
|
||||
updateMintUrl(currentMintUrl: string, updatedMintUrl: string) {
|
||||
log.trace('[proofStore.updateMintUrl] start')
|
||||
for (const proof of self.proofs) {
|
||||
if(proof.mintUrl === currentMintUrl) {
|
||||
proof.setMintUrl(updatedMintUrl)
|
||||
}
|
||||
}
|
||||
|
||||
for (const proof of self.pendingProofs) {
|
||||
if(proof.mintUrl === currentMintUrl) {
|
||||
proof.setMintUrl(updatedMintUrl)
|
||||
}
|
||||
}
|
||||
},
|
||||
}))
|
||||
.views(self => ({
|
||||
get proofsCount() {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {PaymentRequestsStoreModel} from './PaymentRequestsStore'
|
||||
import {ProofsStoreModel} from './ProofsStore'
|
||||
import {RelaysStoreModel} from './RelaysStore'
|
||||
|
||||
export const rootStoreModelVersion = 14 // Update this if model changes require migrations defined in setupRootStore.ts
|
||||
export const rootStoreModelVersion = 15 // Update this if model changes require migrations defined in setupRootStore.ts
|
||||
|
||||
/**
|
||||
* A RootStore model.
|
||||
|
||||
@@ -25,6 +25,7 @@ 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.
|
||||
@@ -262,7 +263,27 @@ async function _runMigrations(rootStore: RootStore) {
|
||||
} catch (e: any) {
|
||||
log.warn('[setupRootStore] Migration error', {message: e.name})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(currentVersion < 15) {
|
||||
log.trace(`Starting rootStore migrations from version v${currentVersion} -> v15`)
|
||||
try {
|
||||
|
||||
for (const mint of mintsStore.allMints) {
|
||||
try {
|
||||
mint.setId()
|
||||
} catch (e: any) {
|
||||
log.warn('[_runMigrations]', e.message)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.info(`Completed rootStore migrations to the version v${rootStoreModelVersion}`)
|
||||
rootStore.setVersion(rootStoreModelVersion)
|
||||
} catch (e: any) {
|
||||
log.warn('[setupRootStore] Migration error', {message: e.name})
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e: any) {
|
||||
throw new AppError(
|
||||
|
||||
+76
-15
@@ -24,10 +24,13 @@ import {Mint} from '../models/Mint'
|
||||
import {useStores} from '../models'
|
||||
import {useHeader} from '../utils/useHeader'
|
||||
import {log} from '../services/logService'
|
||||
import AppError from '../utils/AppError'
|
||||
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 { MintKeyset } from '@cashu/cashu-ts'
|
||||
import { MintClient } from '../services'
|
||||
|
||||
|
||||
|
||||
@@ -93,10 +96,10 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
}
|
||||
|
||||
if (mintsStore.alreadyExists(mintUrl)) {
|
||||
const msg = translate('mintsScreen.mintExists')
|
||||
log.trace(msg)
|
||||
setInfo(msg)
|
||||
return
|
||||
const msg = translate('mintsScreen.mintExists')
|
||||
log.trace(msg)
|
||||
setInfo(msg)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -109,23 +112,60 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
} finally {
|
||||
setMintUrl('')
|
||||
setIsLoading(false)
|
||||
toggleAddMintModal()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const updateMint = async function () {
|
||||
if (!selectedMint) {return}
|
||||
if (!selectedMint) {return}
|
||||
|
||||
try {
|
||||
setIsLoading(true)
|
||||
await mintsStore.updateMint(selectedMint.mintUrl)
|
||||
setInfo(translate("mintSettingsUpdated"))
|
||||
} catch (e: any) {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
await mintsStore.updateMint(selectedMint.mintUrl)
|
||||
setInfo(translate("mintSettingsUpdated"))
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
} finally {
|
||||
onMintUnselect()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const updateMintUrlStart = async function () {
|
||||
if (!selectedMint) {return}
|
||||
toggleAddMintModal() // open
|
||||
}
|
||||
|
||||
|
||||
const updateMintUrl = async function () {
|
||||
if (!selectedMint) {return}
|
||||
try {
|
||||
if (isStateTreeNode(selectedMint)) { // update URL of existing mint
|
||||
// checks if mint is reachable on new url, if it the same mint by checking keysets and syncs local data
|
||||
if(mintsStore.alreadyExists(mintUrl)) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint with this URL already exists.')
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
const activeKeysets: MintKeyset[] = await MintClient.getMintKeysets(mintUrl)
|
||||
const matchingKeyset = activeKeysets.find(keyset => selectedMint.keysets?.some(k => k === keyset.id))
|
||||
|
||||
if(!matchingKeyset) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'No keyset match, provided URL likely points to different mint.')
|
||||
}
|
||||
|
||||
selectedMint.setMintUrl!(mintUrl)
|
||||
}
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
} finally {
|
||||
setMintUrl('')
|
||||
setIsLoading(false)
|
||||
onMintUnselect() // close
|
||||
toggleAddMintModal() // close
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const addDefaultMint = async function () {
|
||||
@@ -343,6 +383,13 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
bottomSeparator={true}
|
||||
style={{paddingHorizontal: spacing.medium}}
|
||||
/>
|
||||
<ListItem
|
||||
leftIcon='faGlobe'
|
||||
onPress={updateMintUrlStart}
|
||||
text="Update mint URL"
|
||||
bottomSeparator={true}
|
||||
style={{paddingHorizontal: spacing.medium}}
|
||||
/>
|
||||
<ListItem
|
||||
leftIcon="faXmark"
|
||||
onPress={removeMint}
|
||||
@@ -361,7 +408,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
<View style={$bottomModal}>
|
||||
<Text
|
||||
preset="subheading"
|
||||
tx='mintsScreen.addMintUrl'
|
||||
tx={selectedMint ? 'mintsScreen.updateMintUrl' : 'mintsScreen.addMintUrl'}
|
||||
// style={{marginBottom: spacing.medium, textAlign: 'center'}}
|
||||
/>
|
||||
<View style={{flexDirection: 'row', alignItems: 'center', marginTop: spacing.small}}>
|
||||
@@ -397,7 +444,19 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
/>
|
||||
</View>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
{selectedMint ? (
|
||||
<Button
|
||||
tx='common.update'
|
||||
style={{
|
||||
// borderTopLeftRadius: 0,
|
||||
// borderBottomLeftRadius: 0,
|
||||
marginRight: spacing.small,
|
||||
minWidth: 80
|
||||
}}
|
||||
onPress={updateMintUrl}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
tx='common.save'
|
||||
style={{
|
||||
// borderTopLeftRadius: 0,
|
||||
@@ -406,7 +465,9 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
minWidth: 80
|
||||
}}
|
||||
onPress={addMint}
|
||||
/>
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
tx='common.cancel'
|
||||
onPress={toggleAddMintModal}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { showMessage } from "react-native-flash-message"
|
||||
import { colors, spacing, useThemeColor } from "../theme"
|
||||
import QuickCrypto from 'react-native-quick-crypto'
|
||||
import { fromByteArray } from "react-native-quick-base64"
|
||||
|
||||
/**
|
||||
* sleep statement.
|
||||
@@ -59,4 +61,11 @@ export const warningMessage = function(message: string, description?: string) {
|
||||
})
|
||||
}
|
||||
|
||||
export const generateId = function (lengthInBytes: number) {
|
||||
const random = QuickCrypto.randomBytes(lengthInBytes)
|
||||
const uint8Array = new Uint8Array(random)
|
||||
|
||||
return Buffer.from(uint8Array).toString('hex')
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user