Merge branch 'master' into listtsx

This commit is contained in:
Marcos Rodriguez Velez 2024-04-18 18:14:15 -04:00
commit ecf9226654
No known key found for this signature in database
GPG Key ID: 6030B2F48CCE86D7
50 changed files with 1165 additions and 850 deletions

View File

@ -424,7 +424,7 @@ export class BlueReplaceFeeSuggestions extends Component {
}
}
export function BlueBigCheckmark({ style }) {
export function BlueBigCheckmark({ style = {} }) {
const defaultStyles = {
backgroundColor: '#ccddf9',
width: 120,

View File

@ -55,7 +55,7 @@ import AztecoRedeem from './screen/receive/aztecoRedeem';
import ReceiveDetails from './screen/receive/details';
import ScanQRCode from './screen/send/ScanQRCode';
import Broadcast from './screen/send/broadcast';
import Broadcast from './screen/send/Broadcast';
import CoinControl from './screen/send/coinControl';
import Confirm from './screen/send/confirm';
import SendCreate from './screen/send/create';
@ -165,7 +165,8 @@ const WalletsRoot = () => {
component={LNDViewAdditionalInvoicePreImage}
options={LNDViewAdditionalInvoicePreImage.navigationOptions(theme)}
/>
<WalletsStack.Screen name="Broadcast" component={Broadcast} options={Broadcast.navigationOptions(theme)} />
<WalletsStack.Screen name="Broadcast" component={Broadcast} options={navigationStyle({ title: loc.send.create_broadcast })(theme)} />
<WalletsStack.Screen name="IsItMyAddress" component={IsItMyAddress} options={IsItMyAddress.navigationOptions(theme)} />
<WalletsStack.Screen name="GenerateWord" component={GenerateWord} options={GenerateWord.navigationOptions(theme)} />
<WalletsStack.Screen name="LnurlPay" component={LnurlPay} options={LnurlPay.navigationOptions(theme)} />

View File

@ -1,6 +1,8 @@
import { getUniqueId } from 'react-native-device-info';
import Bugsnag from '@bugsnag/react-native';
import BlueApp from '../BlueApp';
import { BlueApp as BlueAppClass } from '../class';
const BlueApp = BlueAppClass.getInstance();
/**
* in case Bugsnag was started, but user decided to opt out while using the app, we have this

View File

@ -0,0 +1,69 @@
import { Platform } from 'react-native';
import Biometric from '../class/biometrics';
import prompt from '../helpers/prompt';
import loc from '../loc';
import { BlueApp as BlueAppClass } from '../class/';
const BlueApp = BlueAppClass.getInstance();
// If attempt reaches 10, a wipe keychain option will be provided to the user.
let unlockAttempt = 0;
export const startAndDecrypt = async (retry?: boolean): Promise<boolean> => {
console.log('startAndDecrypt');
if (BlueApp.getWallets().length > 0) {
console.log('App already has some wallets, so we are in already started state, exiting startAndDecrypt');
return true;
}
await BlueApp.migrateKeys();
let password: undefined | string;
if (await BlueApp.storageIsEncrypted()) {
do {
password = await prompt((retry && loc._.bad_password) || loc._.enter_password, loc._.storage_is_encrypted, false);
} while (!password);
}
let success = false;
let wasException = false;
try {
success = await BlueApp.loadFromDisk(password);
} catch (error) {
// in case of exception reading from keystore, lets retry instead of assuming there is no storage and
// proceeding with no wallets
console.warn('exception loading from disk:', error);
wasException = true;
}
if (wasException) {
// retrying, but only once
try {
await new Promise(resolve => setTimeout(resolve, 3000)); // sleep
success = await BlueApp.loadFromDisk(password);
} catch (error) {
console.warn('second exception loading from disk:', error);
}
}
if (success) {
console.log('loaded from disk');
// We want to return true to let the UnlockWith screen that its ok to proceed.
return true;
}
if (password) {
// we had password and yet could not load/decrypt
unlockAttempt++;
if (unlockAttempt < 10 || Platform.OS !== 'ios') {
return startAndDecrypt(true);
} else {
unlockAttempt = 0;
Biometric.showKeychainWipeAlert();
// We want to return false to let the UnlockWith screen that it is NOT ok to proceed.
return false;
}
} else {
unlockAttempt = 0;
// Return true because there was no wallet data in keychain. Proceed.
return true;
}
};
export default BlueApp;

View File

@ -1,18 +1,20 @@
import React, { createContext, useEffect, useState } from 'react';
import { useAsyncStorage } from '@react-native-async-storage/async-storage';
import BlueApp, { TTXMetadata, startAndDecrypt } from '../BlueApp';
import { startAndDecrypt } from './start-and-decrypt';
import Notifications from '../blue_modules/notifications';
import { LegacyWallet, WatchOnlyWallet } from '../class';
import { LegacyWallet, TTXMetadata, WatchOnlyWallet, BlueApp as BlueAppClass } from '../class';
import type { TWallet } from '../class/wallets/types';
import presentAlert from '../components/Alert';
import loc, { STORAGE_KEY as LOC_STORAGE_KEY } from '../loc';
import { FiatUnit, TFiatUnit } from '../models/fiatUnit';
import * as BlueElectrum from './BlueElectrum';
import { PREFERRED_CURRENCY_STORAGE_KEY } from './currency';
import { initCurrencyDaemon, PREFERRED_CURRENCY_STORAGE_KEY } from './currency';
import triggerHapticFeedback, { HapticFeedbackTypes } from './hapticFeedback';
import A from '../blue_modules/analytics';
const BlueApp = BlueAppClass.getInstance();
// hashmap of timestamps we _started_ refetching some wallet
const _lastTimeTriedToRefetchWallet: { [walletID: string]: number } = {};
@ -97,6 +99,7 @@ export const BlueStorageProvider = ({ children }: { children: React.ReactNode })
useEffect(() => {
if (walletsInitialized) {
initCurrencyDaemon();
BlueElectrum.connectMain();
}
}, [walletsInitialized]);

View File

@ -1,38 +1,29 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import createHash from 'create-hash';
import { Platform } from 'react-native';
import DefaultPreference from 'react-native-default-preference';
import * as Keychain from 'react-native-keychain';
import { Transaction, TWallet } from './wallets/types';
import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store';
import Realm from 'realm';
import { initCurrencyDaemon } from './blue_modules/currency';
import * as encryption from './blue_modules/encryption';
import {
HDAezeedWallet,
HDLegacyBreadwalletWallet,
HDLegacyElectrumSeedP2PKHWallet,
HDLegacyP2PKHWallet,
HDSegwitBech32Wallet,
HDSegwitElectrumSeedP2WPKHWallet,
HDSegwitP2SHWallet,
LegacyWallet,
LightningCustodianWallet,
LightningLdkWallet,
MultisigHDWallet,
SLIP39LegacyP2PKHWallet,
SLIP39SegwitBech32Wallet,
SLIP39SegwitP2SHWallet,
SegwitBech32Wallet,
SegwitP2SHWallet,
WatchOnlyWallet,
} from './class/';
import Biometric from './class/biometrics';
import { randomBytes } from './class/rng';
import { TWallet, Transaction } from './class/wallets/types';
import presentAlert from './components/Alert';
import prompt from './helpers/prompt';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as encryption from '../blue_modules/encryption';
import createHash from 'create-hash';
import RNFS from 'react-native-fs';
import loc from './loc';
import Realm from 'realm';
import Keychain from 'react-native-keychain';
import { randomBytes } from './rng';
import presentAlert from '../components/Alert';
import { SegwitBech32Wallet } from './wallets/segwit-bech32-wallet';
import { SegwitP2SHWallet } from './wallets/segwit-p2sh-wallet';
import { WatchOnlyWallet } from './wallets/watch-only-wallet';
import { HDLegacyP2PKHWallet } from './wallets/hd-legacy-p2pkh-wallet';
import { HDSegwitP2SHWallet } from './wallets/hd-segwit-p2sh-wallet';
import { HDSegwitBech32Wallet } from './wallets/hd-segwit-bech32-wallet';
import { HDLegacyBreadwalletWallet } from './wallets/hd-legacy-breadwallet-wallet';
import { HDLegacyElectrumSeedP2PKHWallet } from './wallets/hd-legacy-electrum-seed-p2pkh-wallet';
import { HDSegwitElectrumSeedP2WPKHWallet } from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet';
import { MultisigHDWallet } from './wallets/multisig-hd-wallet';
import { HDAezeedWallet } from './wallets/hd-aezeed-wallet';
import { LightningLdkWallet } from './wallets/lightning-ldk-wallet';
import { SLIP39LegacyP2PKHWallet, SLIP39SegwitBech32Wallet, SLIP39SegwitP2SHWallet } from './wallets/slip39-wallets';
import { LightningCustodianWallet } from './wallets/lightning-custodian-wallet';
import { LegacyWallet } from './wallets/legacy-wallet';
import DefaultPreference from 'react-native-default-preference';
let usedBucketNum: boolean | number = false;
let savingInProgress = 0; // its both a flag and a counter of attempts to write to disk
@ -52,14 +43,16 @@ type TRealmTransaction = {
const isReactNative = typeof navigator !== 'undefined' && navigator?.product === 'ReactNative';
export class AppStorage {
export class BlueApp {
static FLAG_ENCRYPTED = 'data_encrypted';
static LNDHUB = 'lndhub';
static ADVANCED_MODE_ENABLED = 'advancedmodeenabled';
static DO_NOT_TRACK = 'donottrack';
static HANDOFF_STORAGE_KEY = 'HandOff';
static keys2migrate = [AppStorage.HANDOFF_STORAGE_KEY, AppStorage.DO_NOT_TRACK, AppStorage.ADVANCED_MODE_ENABLED];
private static _instance: BlueApp | null = null;
static keys2migrate = [BlueApp.HANDOFF_STORAGE_KEY, BlueApp.DO_NOT_TRACK, BlueApp.ADVANCED_MODE_ENABLED];
public cachedPassword?: false | string;
public tx_metadata: TTXMetadata;
@ -71,13 +64,21 @@ export class AppStorage {
this.cachedPassword = false;
}
static getInstance(): BlueApp {
if (!BlueApp._instance) {
BlueApp._instance = new BlueApp();
}
return BlueApp._instance;
}
async migrateKeys() {
// do not migrate keys if we are not in RN env
if (!isReactNative) {
return;
}
for (const key of AppStorage.keys2migrate) {
for (const key of BlueApp.keys2migrate) {
try {
const value = await RNSecureKeyStore.get(key);
if (value) {
@ -135,9 +136,9 @@ export class AppStorage {
storageIsEncrypted = async (): Promise<boolean> => {
let data;
try {
data = await this.getItemWithFallbackToRealm(AppStorage.FLAG_ENCRYPTED);
data = await this.getItemWithFallbackToRealm(BlueApp.FLAG_ENCRYPTED);
} catch (error: any) {
console.warn('error reading `' + AppStorage.FLAG_ENCRYPTED + '` key:', error.message);
console.warn('error reading `' + BlueApp.FLAG_ENCRYPTED + '` key:', error.message);
return false;
}
@ -199,7 +200,7 @@ export class AppStorage {
data = JSON.stringify(data);
this.cachedPassword = password;
await this.setItem('data', data);
await this.setItem(AppStorage.FLAG_ENCRYPTED, '1');
await this.setItem(BlueApp.FLAG_ENCRYPTED, '1');
};
/**
@ -414,7 +415,7 @@ export class AppStorage {
unserializedWallet = LightningCustodianWallet.fromJson(key) as unknown as LightningCustodianWallet;
let lndhub: false | any = false;
try {
lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB);
lndhub = await AsyncStorage.getItem(BlueApp.LNDHUB);
} catch (error) {
console.warn(error);
}
@ -683,12 +684,12 @@ export class AppStorage {
}
await this.setItem('data', JSON.stringify(data));
await this.setItem(AppStorage.FLAG_ENCRYPTED, this.cachedPassword ? '1' : '');
await this.setItem(BlueApp.FLAG_ENCRYPTED, this.cachedPassword ? '1' : '');
// now, backing up same data in realm:
const realmkeyValue = await this.openRealmKeyValue();
this.saveToRealmKeyValue(realmkeyValue, 'data', JSON.stringify(data));
this.saveToRealmKeyValue(realmkeyValue, AppStorage.FLAG_ENCRYPTED, this.cachedPassword ? '1' : '');
this.saveToRealmKeyValue(realmkeyValue, BlueApp.FLAG_ENCRYPTED, this.cachedPassword ? '1' : '');
realmkeyValue.close();
} catch (error: any) {
console.error('save to disk exception:', error.message);
@ -843,50 +844,50 @@ export class AppStorage {
isAdvancedModeEnabled = async (): Promise<boolean> => {
try {
return !!(await AsyncStorage.getItem(AppStorage.ADVANCED_MODE_ENABLED));
return !!(await AsyncStorage.getItem(BlueApp.ADVANCED_MODE_ENABLED));
} catch (_) {}
return false;
};
setIsAdvancedModeEnabled = async (value: boolean) => {
await AsyncStorage.setItem(AppStorage.ADVANCED_MODE_ENABLED, value ? '1' : '');
await AsyncStorage.setItem(BlueApp.ADVANCED_MODE_ENABLED, value ? '1' : '');
};
isHandoffEnabled = async (): Promise<boolean> => {
try {
return !!(await AsyncStorage.getItem(AppStorage.HANDOFF_STORAGE_KEY));
return !!(await AsyncStorage.getItem(BlueApp.HANDOFF_STORAGE_KEY));
} catch (_) {}
return false;
};
setIsHandoffEnabled = async (value: boolean): Promise<void> => {
await AsyncStorage.setItem(AppStorage.HANDOFF_STORAGE_KEY, value ? '1' : '');
await AsyncStorage.setItem(BlueApp.HANDOFF_STORAGE_KEY, value ? '1' : '');
};
isDoNotTrackEnabled = async (): Promise<boolean> => {
try {
const keyExists = await AsyncStorage.getItem(AppStorage.DO_NOT_TRACK);
const keyExists = await AsyncStorage.getItem(BlueApp.DO_NOT_TRACK);
if (keyExists !== null) {
const doNotTrackValue = !!keyExists;
if (doNotTrackValue) {
await DefaultPreference.setName('group.io.bluewallet.bluewallet');
await DefaultPreference.set(AppStorage.DO_NOT_TRACK, '1');
AsyncStorage.removeItem(AppStorage.DO_NOT_TRACK);
await DefaultPreference.set(BlueApp.DO_NOT_TRACK, '1');
AsyncStorage.removeItem(BlueApp.DO_NOT_TRACK);
} else {
return Boolean(await DefaultPreference.get(AppStorage.DO_NOT_TRACK));
return Boolean(await DefaultPreference.get(BlueApp.DO_NOT_TRACK));
}
}
} catch (_) {}
const doNotTrackValue = await DefaultPreference.get(AppStorage.DO_NOT_TRACK);
const doNotTrackValue = await DefaultPreference.get(BlueApp.DO_NOT_TRACK);
return doNotTrackValue === '1' || false;
};
setDoNotTrack = async (value: boolean) => {
await DefaultPreference.setName('group.io.bluewallet.bluewallet');
if (value) {
await DefaultPreference.set(AppStorage.DO_NOT_TRACK, '1');
await DefaultPreference.set(BlueApp.DO_NOT_TRACK, '1');
} else {
await DefaultPreference.clear(AppStorage.DO_NOT_TRACK);
await DefaultPreference.clear(BlueApp.DO_NOT_TRACK);
}
};
@ -938,69 +939,3 @@ export class AppStorage {
}
}
}
const BlueApp = new AppStorage();
// If attempt reaches 10, a wipe keychain option will be provided to the user.
let unlockAttempt = 0;
export const startAndDecrypt = async (retry?: boolean): Promise<boolean> => {
console.log('startAndDecrypt');
if (BlueApp.getWallets().length > 0) {
console.log('App already has some wallets, so we are in already started state, exiting startAndDecrypt');
return true;
}
await BlueApp.migrateKeys();
let password: undefined | string;
if (await BlueApp.storageIsEncrypted()) {
do {
password = await prompt((retry && loc._.bad_password) || loc._.enter_password, loc._.storage_is_encrypted, false);
} while (!password);
}
let success = false;
let wasException = false;
try {
success = await BlueApp.loadFromDisk(password);
} catch (error) {
// in case of exception reading from keystore, lets retry instead of assuming there is no storage and
// proceeding with no wallets
console.warn('exception loading from disk:', error);
wasException = true;
}
if (wasException) {
// retrying, but only once
try {
await new Promise(resolve => setTimeout(resolve, 3000)); // sleep
success = await BlueApp.loadFromDisk(password);
} catch (error) {
console.warn('second exception loading from disk:', error);
}
}
if (success) {
console.log('loaded from disk');
// We want to return true to let the UnlockWith screen that its ok to proceed.
return true;
}
if (password) {
// we had password and yet could not load/decrypt
unlockAttempt++;
if (unlockAttempt < 10 || Platform.OS !== 'ios') {
return startAndDecrypt(true);
} else {
unlockAttempt = 0;
Biometric.showKeychainWipeAlert();
// We want to return false to let the UnlockWith screen that it is NOT ok to proceed.
return false;
}
} else {
unlockAttempt = 0;
// Return true because there was no wallet data in keychain. Proceed.
return true;
}
};
initCurrencyDaemon();
export default BlueApp;

View File

@ -5,11 +5,10 @@ import URL from 'url';
import { readFileOutsideSandbox } from '../blue_modules/fs';
import { Chain } from '../models/bitcoinUnits';
import { LightningCustodianWallet, WatchOnlyWallet } from './';
import { BlueApp, LightningCustodianWallet, WatchOnlyWallet } from './';
import Azteco from './azteco';
import Lnurl from './lnurl';
import type { TWallet } from './wallets/types';
import { AppStorage } from '../BlueApp';
type TCompletionHandlerParams = [string, object];
type TContext = {
@ -232,7 +231,7 @@ class DeeplinkSchemaMatch {
w.setLabel(w.typeReadable);
try {
const lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB);
const lndhub = await AsyncStorage.getItem(BlueApp.LNDHUB);
if (lndhub) {
w.setBaseURI(lndhub);
w.init();

View File

@ -18,3 +18,4 @@ export * from './wallets/multisig-hd-wallet';
export * from './wallets/slip39-wallets';
export * from './hd-segwit-bech32-transaction';
export * from './multisig-cosigner';
export * from './blue-app';

View File

@ -4,14 +4,14 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useTheme } from './themes';
const BORDER_RADIUS = 30;
const PADDINGS = 8;
const BORDER_RADIUS = 8;
const PADDINGS = 24;
const ICON_MARGIN = 7;
const cStyles = StyleSheet.create({
root: {
alignSelf: 'center',
height: '6.3%',
height: '6.9%',
minHeight: 44,
},
rootAbsolute: {
@ -23,7 +23,6 @@ const cStyles = StyleSheet.create({
bottom: -1000,
},
rootPost: {
borderRadius: BORDER_RADIUS,
flexDirection: 'row',
overflow: 'hidden',
},
@ -138,6 +137,7 @@ export const FButton = ({ text, icon, width, first, last, ...props }: FButtonPro
const bStylesHook = StyleSheet.create({
root: {
backgroundColor: colors.buttonBackgroundColor,
borderRadius: BORDER_RADIUS,
},
text: {
color: colors.buttonAlternativeTextColor,
@ -145,19 +145,25 @@ export const FButton = ({ text, icon, width, first, last, ...props }: FButtonPro
textDisabled: {
color: colors.formBorder,
},
marginRight: {
marginRight: 10,
},
});
const style: Record<string, any> = {};
const additionalStyles = !last ? bStylesHook.marginRight : {};
if (width) {
const paddingLeft = first ? BORDER_RADIUS / 2 : PADDINGS;
const paddingRight = last ? BORDER_RADIUS / 2 : PADDINGS;
style.paddingRight = paddingRight;
style.paddingLeft = paddingLeft;
style.width = width + paddingRight + paddingLeft;
style.paddingHorizontal = PADDINGS;
style.width = width + PADDINGS * 2;
}
return (
<TouchableOpacity accessibilityLabel={text} accessibilityRole="button" style={[bStyles.root, bStylesHook.root, style]} {...props}>
<TouchableOpacity
accessibilityLabel={text}
accessibilityRole="button"
style={[bStyles.root, bStylesHook.root, style, additionalStyles]}
{...props}
>
<View style={bStyles.icon}>{icon}</View>
<Text numberOfLines={1} style={[bStyles.text, props.disabled ? bStylesHook.textDisabled : bStylesHook.text]}>
{text}

View File

@ -208,9 +208,9 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
// @ts-ignore: Ugh
key={balance} // force component recreation on balance change. To fix right-to-left languages, like Farsi
numberOfLines={1}
minimumFontScale={0.5}
adjustsFontSizeToFit
style={styles.walletBalanceText}
ellipsizeMode="middle"
>
{balance}
</Text>
@ -250,24 +250,23 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
)}
{wallet.allowBIP47() && wallet.isBIP47Enabled() && (
<TouchableOpacity accessibilityRole="button" onPress={handleOnPaymentCodeButtonPressed}>
<View style={styles.manageFundsButton}>
<Text style={styles.manageFundsButtonText}>{loc.bip47.payment_code}</Text>
</View>
<TouchableOpacity style={styles.manageFundsButton} accessibilityRole="button" onPress={handleOnPaymentCodeButtonPressed}>
<Text style={styles.manageFundsButtonText}>{loc.bip47.payment_code}</Text>
</TouchableOpacity>
)}
{wallet.type === LightningLdkWallet.type && (
<TouchableOpacity accessibilityRole="button" accessibilityLabel={loc.lnd.title} onPress={handleManageFundsPressed}>
<View style={styles.manageFundsButton}>
<Text style={styles.manageFundsButtonText}>{loc.lnd.title}</Text>
</View>
<TouchableOpacity
style={styles.manageFundsButton}
accessibilityRole="button"
accessibilityLabel={loc.lnd.title}
onPress={handleManageFundsPressed}
>
<Text style={styles.manageFundsButtonText}>{loc.lnd.title}</Text>
</TouchableOpacity>
)}
{wallet.type === MultisigHDWallet.type && (
<TouchableOpacity accessibilityRole="button" onPress={handleManageFundsPressed}>
<View style={styles.manageFundsButton}>
<Text style={styles.manageFundsButtonText}>{loc.multisig.manage_keys}</Text>
</View>
<TouchableOpacity style={styles.manageFundsButton} accessibilityRole="button" onPress={handleManageFundsPressed}>
<Text style={styles.manageFundsButtonText}>{loc.multisig.manage_keys}</Text>
</TouchableOpacity>
)}
</LinearGradient>
@ -297,6 +296,7 @@ const styles = StyleSheet.create({
walletBalance: {
flexShrink: 1,
marginRight: 6,
height: 34,
},
manageFundsButton: {
marginTop: 14,

View File

@ -103,6 +103,9 @@ const iStyles = StyleSheet.create({
borderRadius: 12,
minHeight: 164,
},
balanceContainer: {
height: 40,
},
image: {
width: 99,
height: 94,
@ -218,21 +221,23 @@ export const WalletCarouselItem = ({ item, _, onPress, handleLongPress, isSelect
<Text numberOfLines={1} style={[iStyles.label, { color: colors.inverseForegroundColor }]}>
{item.getLabel()}
</Text>
{item.hideBalance ? (
<>
<BlueSpacing10 />
<BlurredBalanceView />
</>
) : (
<Text
numberOfLines={1}
key={balance} // force component recreation on balance change. To fix right-to-left languages, like Farsi
ellipsizeMode="middle"
style={[iStyles.balance, { color: colors.inverseForegroundColor }]}
>
{`${balance} `}
</Text>
)}
<View style={iStyles.balanceContainer}>
{item.hideBalance ? (
<>
<BlueSpacing10 />
<BlurredBalanceView />
</>
) : (
<Text
numberOfLines={1}
adjustsFontSizeToFit
key={balance} // force component recreation on balance change. To fix right-to-left languages, like Farsi
style={[iStyles.balance, { color: colors.inverseForegroundColor }]}
>
{`${balance} `}
</Text>
)}
</View>
<Text style={iStyles.br} />
<Text numberOfLines={1} style={[iStyles.latestTx, { color: colors.inverseForegroundColor }]}>
{loc.wallets.list_latest_transaction}

View File

@ -15,9 +15,8 @@
6D2A6464258BA92D0092292B /* Stickers.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D2A6463258BA92D0092292B /* Stickers.xcassets */; };
6D2A6468258BA92D0092292B /* Stickers.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6D2A6461258BA92C0092292B /* Stickers.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
6D32C5C62596CE3A008C077C /* EventEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D32C5C52596CE3A008C077C /* EventEmitter.m */; };
6D4AF15925D21172009DD853 /* WidgetAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */; };
6D4AF16325D21185009DD853 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */; };
6D4AF16D25D21192009DD853 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */; };
6D4AF15925D21172009DD853 /* MarketAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* MarketAPI.swift */; };
6D4AF16D25D21192009DD853 /* Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Placeholders.swift */; };
6D4AF17825D211A3009DD853 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; };
6D4AF18425D215D1009DD853 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; };
6DD4109D266CADF10087DE03 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3A252FE1A3004D72DF /* WidgetKit.framework */; };
@ -25,14 +24,12 @@
6DD410A1266CADF10087DE03 /* Widgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DD410A0266CADF10087DE03 /* Widgets.swift */; };
6DD410A7266CADF40087DE03 /* WidgetsExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6DD4109C266CADF10087DE03 /* WidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
6DD410AC266CAE470087DE03 /* PriceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA4BC255872E3009312A5 /* PriceWidget.swift */; };
6DD410AE266CAF1F0087DE03 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */; };
6DD410AF266CAF5C0087DE03 /* WalletInformationAndMarketWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E06254BA347007B5B82 /* WalletInformationAndMarketWidget.swift */; };
6DD410B0266CAF5C0087DE03 /* WalletInformationWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4AB1254FB59C00E9F9AA /* WalletInformationWidget.swift */; };
6DD410B1266CAF5C0087DE03 /* WidgetAPI+Electrum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */; };
6DD410B1266CAF5C0087DE03 /* MarketAPI+Electrum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5142558EBA3009312A5 /* MarketAPI+Electrum.swift */; };
6DD410B2266CAF5C0087DE03 /* WalletInformationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F2225525053003792DF /* WalletInformationView.swift */; };
6DD410B3266CAF5C0087DE03 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */; };
6DD410B4266CAF5C0087DE03 /* WidgetAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */; };
6DD410B5266CAF5C0087DE03 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */; };
6DD410B4266CAF5C0087DE03 /* MarketAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* MarketAPI.swift */; };
6DD410B6266CAF5C0087DE03 /* PriceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5272558EC52009312A5 /* PriceView.swift */; };
6DD410B7266CAF5C0087DE03 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D9A2E08254BA348007B5B82 /* Assets.xcassets */; };
6DD410B8266CAF5C0087DE03 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; };
@ -40,7 +37,7 @@
6DD410BA266CAF5C0087DE03 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; };
6DD410BB266CAF5C0087DE03 /* MarketView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F17255226DA003792DF /* MarketView.swift */; };
6DD410BE266CAF5C0087DE03 /* SendReceiveButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F3425526311003792DF /* SendReceiveButtons.swift */; };
6DD410BF266CB13D0087DE03 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */; };
6DD410BF266CB13D0087DE03 /* Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Placeholders.swift */; };
6DD410C0266CB1460087DE03 /* MarketWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9946622555A660000E52E8 /* MarketWidget.swift */; };
6DF25A9F249DB97E001D06F5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6DF25A9E249DB97E001D06F5 /* LaunchScreen.storyboard */; };
6DFC807024EA0B6C007B8700 /* EFQRCode in Frameworks */ = {isa = PBXBuildFile; productRef = 6DFC806F24EA0B6C007B8700 /* EFQRCode */; };
@ -75,6 +72,70 @@
B43D037B225847C500FBAA95 /* TransactionTableRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0375225847C500FBAA95 /* TransactionTableRow.swift */; };
B43D037C225847C500FBAA95 /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0376225847C500FBAA95 /* Wallet.swift */; };
B43D037D225847C500FBAA95 /* WalletInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0377225847C500FBAA95 /* WalletInformation.swift */; };
B44033BF2BCC32F800162242 /* BitcoinUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033BE2BCC32F800162242 /* BitcoinUnit.swift */; };
B44033C02BCC32F800162242 /* BitcoinUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033BE2BCC32F800162242 /* BitcoinUnit.swift */; };
B44033C12BCC32F800162242 /* BitcoinUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033BE2BCC32F800162242 /* BitcoinUnit.swift */; };
B44033C22BCC32F800162242 /* BitcoinUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033BE2BCC32F800162242 /* BitcoinUnit.swift */; };
B44033C42BCC332400162242 /* Balance.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033C32BCC332400162242 /* Balance.swift */; };
B44033C52BCC332400162242 /* Balance.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033C32BCC332400162242 /* Balance.swift */; };
B44033C62BCC332400162242 /* Balance.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033C32BCC332400162242 /* Balance.swift */; };
B44033C72BCC332400162242 /* Balance.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033C32BCC332400162242 /* Balance.swift */; };
B44033CA2BCC350A00162242 /* Currency.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033C92BCC350A00162242 /* Currency.swift */; };
B44033CB2BCC350A00162242 /* Currency.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033C92BCC350A00162242 /* Currency.swift */; };
B44033CC2BCC350A00162242 /* Currency.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033C92BCC350A00162242 /* Currency.swift */; };
B44033CD2BCC350A00162242 /* Currency.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033C92BCC350A00162242 /* Currency.swift */; };
B44033CE2BCC352900162242 /* UserDefaultsGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */; };
B44033CF2BCC352C00162242 /* UserDefaultsGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */; };
B44033D02BCC352F00162242 /* UserDefaultsGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */; };
B44033D32BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033D22BCC368800162242 /* UserDefaultsGroupKey.swift */; };
B44033D42BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033D22BCC368800162242 /* UserDefaultsGroupKey.swift */; };
B44033D52BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033D22BCC368800162242 /* UserDefaultsGroupKey.swift */; };
B44033D62BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033D22BCC368800162242 /* UserDefaultsGroupKey.swift */; };
B44033D72BCC369400162242 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; };
B44033D82BCC369500162242 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; };
B44033D92BCC369900162242 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */; };
B44033DA2BCC369A00162242 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */; };
B44033DB2BCC369B00162242 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */; };
B44033DD2BCC36C300162242 /* LatestTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033DC2BCC36C300162242 /* LatestTransaction.swift */; };
B44033DE2BCC36C300162242 /* LatestTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033DC2BCC36C300162242 /* LatestTransaction.swift */; };
B44033DF2BCC36C300162242 /* LatestTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033DC2BCC36C300162242 /* LatestTransaction.swift */; };
B44033E02BCC36C300162242 /* LatestTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033DC2BCC36C300162242 /* LatestTransaction.swift */; };
B44033E12BCC36CA00162242 /* Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Placeholders.swift */; };
B44033E22BCC36CB00162242 /* Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Placeholders.swift */; };
B44033E42BCC36FF00162242 /* WalletData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E32BCC36FF00162242 /* WalletData.swift */; };
B44033E52BCC36FF00162242 /* WalletData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E32BCC36FF00162242 /* WalletData.swift */; };
B44033E62BCC36FF00162242 /* WalletData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E32BCC36FF00162242 /* WalletData.swift */; };
B44033E72BCC36FF00162242 /* WalletData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E32BCC36FF00162242 /* WalletData.swift */; };
B44033E92BCC371A00162242 /* MarketData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E82BCC371A00162242 /* MarketData.swift */; };
B44033EA2BCC371A00162242 /* MarketData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E82BCC371A00162242 /* MarketData.swift */; };
B44033EB2BCC371A00162242 /* MarketData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E82BCC371A00162242 /* MarketData.swift */; };
B44033EC2BCC371A00162242 /* MarketData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E82BCC371A00162242 /* MarketData.swift */; };
B44033EE2BCC374500162242 /* Numeric+abbreviated.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033ED2BCC374500162242 /* Numeric+abbreviated.swift */; };
B44033EF2BCC374500162242 /* Numeric+abbreviated.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033ED2BCC374500162242 /* Numeric+abbreviated.swift */; };
B44033F02BCC374500162242 /* Numeric+abbreviated.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033ED2BCC374500162242 /* Numeric+abbreviated.swift */; };
B44033F12BCC374500162242 /* Numeric+abbreviated.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033ED2BCC374500162242 /* Numeric+abbreviated.swift */; };
B44033F42BCC377F00162242 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033F32BCC377F00162242 /* WidgetData.swift */; };
B44033F52BCC377F00162242 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033F32BCC377F00162242 /* WidgetData.swift */; };
B44033F62BCC377F00162242 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033F32BCC377F00162242 /* WidgetData.swift */; };
B44033F72BCC377F00162242 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033F32BCC377F00162242 /* WidgetData.swift */; };
B44033F92BCC379200162242 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033F82BCC379200162242 /* WidgetDataStore.swift */; };
B44033FA2BCC379200162242 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033F82BCC379200162242 /* WidgetDataStore.swift */; };
B44033FB2BCC379200162242 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033F82BCC379200162242 /* WidgetDataStore.swift */; };
B44033FC2BCC379200162242 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033F82BCC379200162242 /* WidgetDataStore.swift */; };
B44033FD2BCC37D600162242 /* MarketAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* MarketAPI.swift */; };
B44033FE2BCC37D700162242 /* MarketAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* MarketAPI.swift */; };
B44034002BCC37F800162242 /* Bundle+decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033FF2BCC37F800162242 /* Bundle+decode.swift */; };
B44034012BCC37F800162242 /* Bundle+decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033FF2BCC37F800162242 /* Bundle+decode.swift */; };
B44034022BCC37F800162242 /* Bundle+decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033FF2BCC37F800162242 /* Bundle+decode.swift */; };
B44034032BCC37F800162242 /* Bundle+decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033FF2BCC37F800162242 /* Bundle+decode.swift */; };
B44034042BCC389100162242 /* XMLParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */; };
B44034052BCC389200162242 /* XMLParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */; };
B44034062BCC389F00162242 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; };
B44034072BCC38A000162242 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; };
B440340F2BCC40A400162242 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = B440340E2BCC40A400162242 /* fiatUnits.json */; };
B44034102BCC40A400162242 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = B440340E2BCC40A400162242 /* fiatUnits.json */; };
B44034112BCC40A400162242 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = B440340E2BCC40A400162242 /* fiatUnits.json */; };
B44034122BCC40A400162242 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = B440340E2BCC40A400162242 /* fiatUnits.json */; };
B4549F362B82B10D002E3153 /* ci_post_clone.sh in Resources */ = {isa = PBXBuildFile; fileRef = B4549F352B82B10D002E3153 /* ci_post_clone.sh */; };
B461B852299599F800E431AA /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = B461B851299599F800E431AA /* AppDelegate.mm */; };
B47B21EC2B2128B8001F6690 /* BlueWalletUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B47B21EB2B2128B8001F6690 /* BlueWalletUITests.swift */; };
@ -97,8 +158,6 @@
B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; };
C59F90CE0D04D3E4BB39BC5D /* libPods-BlueWalletUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F02C2F7CA3591E4E0B06EBA /* libPods-BlueWalletUITests.a */; };
C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
E5D4794B26781FC0007838C1 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */; };
E5D4794C26781FC1007838C1 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -151,13 +210,6 @@
remoteGlobalIDString = B4A29A212B55C990002A67DF;
remoteInfo = "BlueWallet-NoLDK";
};
B4A29A232B55C990002A67DF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = B40D4E2F225841EC00428FCC;
remoteInfo = BlueWalletWatch;
};
B4A29A252B55C990002A67DF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
@ -310,21 +362,19 @@
6D641F2225525053003792DF /* WalletInformationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletInformationView.swift; sourceTree = "<group>"; };
6D641F3425526311003792DF /* SendReceiveButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendReceiveButtons.swift; sourceTree = "<group>"; };
6D6CA4BC255872E3009312A5 /* PriceWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceWidget.swift; sourceTree = "<group>"; };
6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WidgetAPI+Electrum.swift"; sourceTree = "<group>"; };
6D6CA5142558EBA3009312A5 /* MarketAPI+Electrum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MarketAPI+Electrum.swift"; sourceTree = "<group>"; };
6D6CA5272558EC52009312A5 /* PriceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceView.swift; sourceTree = "<group>"; };
6D9946622555A660000E52E8 /* MarketWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketWidget.swift; sourceTree = "<group>"; };
6D9A2E06254BA347007B5B82 /* WalletInformationAndMarketWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletInformationAndMarketWidget.swift; sourceTree = "<group>"; };
6D9A2E08254BA348007B5B82 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WidgetAPI.swift; sourceTree = "<group>"; };
6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WidgetDataStore.swift; sourceTree = "<group>"; };
6D9A2E6A254BAB1B007B5B82 /* MarketAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarketAPI.swift; sourceTree = "<group>"; };
6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsGroup.swift; sourceTree = "<group>"; };
6DD4109C266CADF10087DE03 /* WidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
6DD410A0266CADF10087DE03 /* Widgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widgets.swift; sourceTree = "<group>"; };
6DD410A4266CADF40087DE03 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
6DD410AD266CAF1F0087DE03 /* fiatUnits.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = fiatUnits.json; path = ../../../../models/fiatUnits.json; sourceTree = "<group>"; };
6DD410C3266CCB780087DE03 /* WidgetsExtension.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = WidgetsExtension.entitlements; sourceTree = SOURCE_ROOT; };
6DEB4AB1254FB59C00E9F9AA /* WalletInformationWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletInformationWidget.swift; sourceTree = "<group>"; };
6DEB4BFA254FBA0E00E9F9AA /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = "<group>"; };
6DEB4BFA254FBA0E00E9F9AA /* Placeholders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Placeholders.swift; sourceTree = "<group>"; };
6DEB4C3A254FBF4800E9F9AA /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
6DF25A9E249DB97E001D06F5 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
6DFC807124EA2FA9007B8700 /* ViewQRCodefaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewQRCodefaceController.swift; sourceTree = "<group>"; };
@ -375,6 +425,18 @@
B43D0376225847C500FBAA95 /* Wallet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
B43D0377225847C500FBAA95 /* WalletInformation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletInformation.swift; sourceTree = "<group>"; };
B43D046E22584C1B00FBAA95 /* libRNWatch.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libRNWatch.a; sourceTree = BUILT_PRODUCTS_DIR; };
B44033BE2BCC32F800162242 /* BitcoinUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitcoinUnit.swift; sourceTree = "<group>"; };
B44033C32BCC332400162242 /* Balance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Balance.swift; sourceTree = "<group>"; };
B44033C92BCC350A00162242 /* Currency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Currency.swift; sourceTree = "<group>"; };
B44033D22BCC368800162242 /* UserDefaultsGroupKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsGroupKey.swift; sourceTree = "<group>"; };
B44033DC2BCC36C300162242 /* LatestTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestTransaction.swift; sourceTree = "<group>"; };
B44033E32BCC36FF00162242 /* WalletData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletData.swift; sourceTree = "<group>"; };
B44033E82BCC371A00162242 /* MarketData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketData.swift; sourceTree = "<group>"; };
B44033ED2BCC374500162242 /* Numeric+abbreviated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Numeric+abbreviated.swift"; sourceTree = "<group>"; };
B44033F32BCC377F00162242 /* WidgetData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetData.swift; sourceTree = "<group>"; };
B44033F82BCC379200162242 /* WidgetDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataStore.swift; sourceTree = "<group>"; };
B44033FF2BCC37F800162242 /* Bundle+decode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+decode.swift"; sourceTree = "<group>"; };
B440340E2BCC40A400162242 /* fiatUnits.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = fiatUnits.json; path = ../../../models/fiatUnits.json; sourceTree = "<group>"; };
B4549F352B82B10D002E3153 /* ci_post_clone.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = ci_post_clone.sh; sourceTree = "<group>"; };
B461B850299599F800E431AA /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = BlueWallet/AppDelegate.h; sourceTree = "<group>"; };
B461B851299599F800E431AA /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = BlueWallet/AppDelegate.mm; sourceTree = "<group>"; };
@ -567,7 +629,8 @@
6D2AA8062568B8E50090B089 /* Fiat */ = {
isa = PBXGroup;
children = (
6DD410AD266CAF1F0087DE03 /* fiatUnits.json */,
B440340E2BCC40A400162242 /* fiatUnits.json */,
B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */,
6D2AA8072568B8F40090B089 /* FiatUnit.swift */,
);
path = Fiat;
@ -625,16 +688,7 @@
6DEB4BC1254FB98300E9F9AA /* Shared */ = {
isa = PBXGroup;
children = (
B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */,
6D2AA8062568B8E50090B089 /* Fiat */,
6DEB4DD82552260200E9F9AA /* Views */,
6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */,
6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */,
6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */,
6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */,
6DEB4BFA254FBA0E00E9F9AA /* Models.swift */,
6DEB4C3A254FBF4800E9F9AA /* Colors.swift */,
6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */,
6D4AF18225D215D0009DD853 /* BlueWalletWatch-Bridging-Header.h */,
B40FC3F829CCD1AC0007EBAC /* SwiftTCPClient.swift */,
);
@ -655,6 +709,7 @@
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
B44033C82BCC34AC00162242 /* Shared */,
B41C2E552BB3DCB8000FE097 /* PrivacyInfo.xcprivacy */,
B4549F2E2B80FEA1002E3153 /* ci_scripts */,
13B07FAE1A68108700A75B9A /* BlueWallet */,
@ -766,6 +821,31 @@
path = Objects;
sourceTree = "<group>";
};
B44033C82BCC34AC00162242 /* Shared */ = {
isa = PBXGroup;
children = (
6D2AA8062568B8E50090B089 /* Fiat */,
6D9A2E6A254BAB1B007B5B82 /* MarketAPI.swift */,
6D6CA5142558EBA3009312A5 /* MarketAPI+Electrum.swift */,
B44033C92BCC350A00162242 /* Currency.swift */,
6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */,
6DEB4C3A254FBF4800E9F9AA /* Colors.swift */,
6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */,
B44033D22BCC368800162242 /* UserDefaultsGroupKey.swift */,
B44033DC2BCC36C300162242 /* LatestTransaction.swift */,
B44033E32BCC36FF00162242 /* WalletData.swift */,
B44033E82BCC371A00162242 /* MarketData.swift */,
B44033ED2BCC374500162242 /* Numeric+abbreviated.swift */,
B44033F32BCC377F00162242 /* WidgetData.swift */,
B44033F82BCC379200162242 /* WidgetDataStore.swift */,
B44033FF2BCC37F800162242 /* Bundle+decode.swift */,
6DEB4BFA254FBA0E00E9F9AA /* Placeholders.swift */,
B44033BE2BCC32F800162242 /* BitcoinUnit.swift */,
B44033C32BCC332400162242 /* Balance.swift */,
);
path = Shared;
sourceTree = "<group>";
};
B4549F2E2B80FEA1002E3153 /* ci_scripts */ = {
isa = PBXGroup;
children = (
@ -951,7 +1031,6 @@
buildRules = (
);
dependencies = (
B4A29A222B55C990002A67DF /* PBXTargetDependency */,
B4A29A242B55C990002A67DF /* PBXTargetDependency */,
B4A29A262B55C990002A67DF /* PBXTargetDependency */,
B4A29A282B55C990002A67DF /* PBXTargetDependency */,
@ -1061,6 +1140,7 @@
buildActionMask = 2147483647;
files = (
6DF25A9F249DB97E001D06F5 /* LaunchScreen.storyboard in Resources */,
B440340F2BCC40A400162242 /* fiatUnits.json in Resources */,
84E05A842721191B001A0D3A /* Settings.bundle in Resources */,
B4549F362B82B10D002E3153 /* ci_post_clone.sh in Resources */,
B41C2E562BB3DCB8000FE097 /* PrivacyInfo.xcprivacy in Resources */,
@ -1081,8 +1161,8 @@
buildActionMask = 2147483647;
files = (
B41C2E582BB3DCB8000FE097 /* PrivacyInfo.xcprivacy in Resources */,
B44034112BCC40A400162242 /* fiatUnits.json in Resources */,
6DD410B7266CAF5C0087DE03 /* Assets.xcassets in Resources */,
6DD410AE266CAF1F0087DE03 /* fiatUnits.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1091,7 +1171,6 @@
buildActionMask = 2147483647;
files = (
B40D4E36225841ED00428FCC /* Assets.xcassets in Resources */,
E5D4794C26781FC1007838C1 /* fiatUnits.json in Resources */,
B40D4E34225841EC00428FCC /* Interface.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1101,8 +1180,8 @@
buildActionMask = 2147483647;
files = (
B41C2E572BB3DCB8000FE097 /* PrivacyInfo.xcprivacy in Resources */,
B44034102BCC40A400162242 /* fiatUnits.json in Resources */,
B4EE583C226703320003363C /* Assets.xcassets in Resources */,
E5D4794B26781FC0007838C1 /* fiatUnits.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1118,6 +1197,7 @@
buildActionMask = 2147483647;
files = (
B41C2E592BB3DCB8000FE097 /* PrivacyInfo.xcprivacy in Resources */,
B44034122BCC40A400162242 /* fiatUnits.json in Resources */,
B4A29A352B55C990002A67DF /* LaunchScreen.storyboard in Resources */,
B4A29A372B55C990002A67DF /* Images.xcassets in Resources */,
);
@ -1420,12 +1500,30 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B44033E92BCC371A00162242 /* MarketData.swift in Sources */,
B44033CA2BCC350A00162242 /* Currency.swift in Sources */,
B4AB21092B61DC3F0080440C /* SplashScreen.m in Sources */,
B44033EE2BCC374500162242 /* Numeric+abbreviated.swift in Sources */,
B4AB21072B61D8CA0080440C /* SplashScreen.swift in Sources */,
B44033DD2BCC36C300162242 /* LatestTransaction.swift in Sources */,
6D32C5C62596CE3A008C077C /* EventEmitter.m in Sources */,
B44033FE2BCC37D700162242 /* MarketAPI.swift in Sources */,
B44033CE2BCC352900162242 /* UserDefaultsGroup.swift in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
B461B852299599F800E431AA /* AppDelegate.mm in Sources */,
B44033F42BCC377F00162242 /* WidgetData.swift in Sources */,
B44033C42BCC332400162242 /* Balance.swift in Sources */,
B44034072BCC38A000162242 /* FiatUnit.swift in Sources */,
B44034002BCC37F800162242 /* Bundle+decode.swift in Sources */,
B44033E22BCC36CB00162242 /* Placeholders.swift in Sources */,
B44033DA2BCC369A00162242 /* Colors.swift in Sources */,
B44033D32BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */,
32B5A32A2334450100F8D608 /* Bridge.swift in Sources */,
B44033D82BCC369500162242 /* UserDefaultsExtension.swift in Sources */,
B44033E42BCC36FF00162242 /* WalletData.swift in Sources */,
B44033BF2BCC32F800162242 /* BitcoinUnit.swift in Sources */,
B44034052BCC389200162242 /* XMLParserDelegate.swift in Sources */,
B44033F92BCC379200162242 /* WidgetDataStore.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1434,22 +1532,32 @@
buildActionMask = 2147483647;
files = (
6DD410BE266CAF5C0087DE03 /* SendReceiveButtons.swift in Sources */,
6DD410B4266CAF5C0087DE03 /* WidgetAPI.swift in Sources */,
6DD410B4266CAF5C0087DE03 /* MarketAPI.swift in Sources */,
B40FC3FA29CCD1D00007EBAC /* SwiftTCPClient.swift in Sources */,
6DD410A1266CADF10087DE03 /* Widgets.swift in Sources */,
6DD410AC266CAE470087DE03 /* PriceWidget.swift in Sources */,
B44033D52BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */,
6DD410B2266CAF5C0087DE03 /* WalletInformationView.swift in Sources */,
B44034022BCC37F800162242 /* Bundle+decode.swift in Sources */,
B44033CC2BCC350A00162242 /* Currency.swift in Sources */,
6DD410B6266CAF5C0087DE03 /* PriceView.swift in Sources */,
6DD410B3266CAF5C0087DE03 /* Colors.swift in Sources */,
B44033C12BCC32F800162242 /* BitcoinUnit.swift in Sources */,
6DD410BB266CAF5C0087DE03 /* MarketView.swift in Sources */,
6DD410B5266CAF5C0087DE03 /* WidgetDataStore.swift in Sources */,
B44033F02BCC374500162242 /* Numeric+abbreviated.swift in Sources */,
B44033DF2BCC36C300162242 /* LatestTransaction.swift in Sources */,
6DD410C0266CB1460087DE03 /* MarketWidget.swift in Sources */,
B4AB225E2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */,
B44033F62BCC377F00162242 /* WidgetData.swift in Sources */,
6DD410BA266CAF5C0087DE03 /* FiatUnit.swift in Sources */,
B44033FB2BCC379200162242 /* WidgetDataStore.swift in Sources */,
B44033EB2BCC371A00162242 /* MarketData.swift in Sources */,
6DD410AF266CAF5C0087DE03 /* WalletInformationAndMarketWidget.swift in Sources */,
6DD410BF266CB13D0087DE03 /* Models.swift in Sources */,
B44033C62BCC332400162242 /* Balance.swift in Sources */,
B44033E62BCC36FF00162242 /* WalletData.swift in Sources */,
6DD410BF266CB13D0087DE03 /* Placeholders.swift in Sources */,
6DD410B0266CAF5C0087DE03 /* WalletInformationWidget.swift in Sources */,
6DD410B1266CAF5C0087DE03 /* WidgetAPI+Electrum.swift in Sources */,
6DD410B1266CAF5C0087DE03 /* MarketAPI+Electrum.swift in Sources */,
6DD410B9266CAF5C0087DE03 /* UserDefaultsGroup.swift in Sources */,
6DD410B8266CAF5C0087DE03 /* UserDefaultsExtension.swift in Sources */,
);
@ -1465,23 +1573,35 @@
32F0A29A2311DBB20095C559 /* ComplicationController.swift in Sources */,
B40D4E602258425500428FCC /* SpecifyInterfaceController.swift in Sources */,
B43D0379225847C500FBAA95 /* WatchDataSource.swift in Sources */,
B44033D42BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */,
B44034012BCC37F800162242 /* Bundle+decode.swift in Sources */,
849047CA2702A32A008EE567 /* Handoff.swift in Sources */,
6D4AF16325D21185009DD853 /* WidgetDataStore.swift in Sources */,
B44033EA2BCC371A00162242 /* MarketData.swift in Sources */,
B44033CB2BCC350A00162242 /* Currency.swift in Sources */,
6DFC807224EA2FA9007B8700 /* ViewQRCodefaceController.swift in Sources */,
B40D4E46225841ED00428FCC /* NotificationController.swift in Sources */,
B40D4E5D2258425500428FCC /* InterfaceController.swift in Sources */,
B44033FA2BCC379200162242 /* WidgetDataStore.swift in Sources */,
B44033DE2BCC36C300162242 /* LatestTransaction.swift in Sources */,
B43D037B225847C500FBAA95 /* TransactionTableRow.swift in Sources */,
B43D037D225847C500FBAA95 /* WalletInformation.swift in Sources */,
6D4AF15925D21172009DD853 /* WidgetAPI.swift in Sources */,
6D4AF15925D21172009DD853 /* MarketAPI.swift in Sources */,
B40D4E642258425500428FCC /* WalletDetailsInterfaceController.swift in Sources */,
B40D4E44225841ED00428FCC /* ExtensionDelegate.swift in Sources */,
B40D4E682258426B00428FCC /* KeychainSwiftDistrib.swift in Sources */,
6D4AF16D25D21192009DD853 /* Models.swift in Sources */,
6D4AF16D25D21192009DD853 /* Placeholders.swift in Sources */,
B44033DB2BCC369B00162242 /* Colors.swift in Sources */,
B40D4E632258425500428FCC /* ReceiveInterfaceController.swift in Sources */,
B43D0378225847C500FBAA95 /* WalletGradient.swift in Sources */,
B44033C02BCC32F800162242 /* BitcoinUnit.swift in Sources */,
B44033E52BCC36FF00162242 /* WalletData.swift in Sources */,
B44033EF2BCC374500162242 /* Numeric+abbreviated.swift in Sources */,
B44033D02BCC352F00162242 /* UserDefaultsGroup.swift in Sources */,
B44033C52BCC332400162242 /* Balance.swift in Sources */,
6D4AF18425D215D1009DD853 /* UserDefaultsExtension.swift in Sources */,
B4AB225D2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */,
B40D4E5E2258425500428FCC /* NumericKeypadInterfaceController.swift in Sources */,
B44033F52BCC377F00162242 /* WidgetData.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1498,10 +1618,28 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B44033D72BCC369400162242 /* UserDefaultsExtension.swift in Sources */,
B44033F72BCC377F00162242 /* WidgetData.swift in Sources */,
B44033E12BCC36CA00162242 /* Placeholders.swift in Sources */,
B44033E72BCC36FF00162242 /* WalletData.swift in Sources */,
B44033E02BCC36C300162242 /* LatestTransaction.swift in Sources */,
B44033D92BCC369900162242 /* Colors.swift in Sources */,
B4A29A2C2B55C990002A67DF /* EventEmitter.m in Sources */,
B44033D62BCC368800162242 /* UserDefaultsGroupKey.swift in Sources */,
B44033FD2BCC37D600162242 /* MarketAPI.swift in Sources */,
B4A29A2D2B55C990002A67DF /* main.m in Sources */,
B4A29A2E2B55C990002A67DF /* AppDelegate.mm in Sources */,
B44033C72BCC332400162242 /* Balance.swift in Sources */,
B44033FC2BCC379200162242 /* WidgetDataStore.swift in Sources */,
B44033C22BCC32F800162242 /* BitcoinUnit.swift in Sources */,
B44033F12BCC374500162242 /* Numeric+abbreviated.swift in Sources */,
B44034032BCC37F800162242 /* Bundle+decode.swift in Sources */,
B44033CD2BCC350A00162242 /* Currency.swift in Sources */,
B44034062BCC389F00162242 /* FiatUnit.swift in Sources */,
B44033CF2BCC352C00162242 /* UserDefaultsGroup.swift in Sources */,
B4A29A2F2B55C990002A67DF /* Bridge.swift in Sources */,
B44034042BCC389100162242 /* XMLParserDelegate.swift in Sources */,
B44033EC2BCC371A00162242 /* MarketData.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1544,12 +1682,6 @@
target = B4A29A212B55C990002A67DF /* BlueWallet-NoLDK */;
targetProxy = B49038D62B8FBA2500A8164A /* PBXContainerItemProxy */;
};
B4A29A222B55C990002A67DF /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
platformFilter = ios;
target = B40D4E2F225841EC00428FCC /* BlueWalletWatch */;
targetProxy = B4A29A232B55C990002A67DF /* PBXContainerItemProxy */;
};
B4A29A242B55C990002A67DF /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
targetProxy = B4A29A252B55C990002A67DF /* PBXContainerItemProxy */;
@ -2074,7 +2206,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 5.0;
WATCHOS_DEPLOYMENT_TARGET = 6.0;
};
name = Debug;
};
@ -2123,7 +2255,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 5.0;
WATCHOS_DEPLOYMENT_TARGET = 6.0;
};
name = Release;
};
@ -2170,7 +2302,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 5.0;
WATCHOS_DEPLOYMENT_TARGET = 6.0;
};
name = Debug;
};
@ -2217,7 +2349,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 5.0;
WATCHOS_DEPLOYMENT_TARGET = 6.0;
};
name = Release;
};

View File

@ -102,7 +102,7 @@
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetFamily"
value = "systemMedium"
value = "systemSmall"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>

View File

@ -36,7 +36,7 @@ class ExtensionDelegate: NSObject, WKExtensionDelegate {
}
private func updateMarketData(for fiatUnit: FiatUnit) {
WidgetAPI.fetchPrice(currency: fiatUnit.endPointKey) { (data, error) in
MarketAPI.fetchPrice(currency: fiatUnit.endPointKey) { (data, error) in
guard let data = data, let encodedData = try? PropertyListEncoder().encode(data) else { return }
let groupUserDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue)
groupUserDefaults?.set(encodedData, forKey: MarketData.string)

View File

@ -1,6 +1,6 @@
PODS:
- boost (1.76.0)
- BugsnagReactNative (7.22.6):
- BugsnagReactNative (7.22.7):
- React-Core
- BVLinearGradient (2.8.3):
- React-Core
@ -352,7 +352,7 @@ PODS:
- react-native-tcp-socket (6.0.6):
- CocoaAsyncSocket
- React-Core
- react-native-webview (13.8.4):
- react-native-webview (13.8.6):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- react-native-widget-center (0.0.9):
@ -469,7 +469,7 @@ PODS:
- React-perflogger (= 0.72.12)
- ReactNativeCameraKit (13.0.0):
- React-Core
- RealmJS (12.6.2):
- RealmJS (12.7.0):
- React
- rn-ldk (0.8.4):
- React-Core
@ -782,7 +782,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost: 7dcd2de282d72e344012f7d6564d024930a6a440
BugsnagReactNative: 366a7e11c0bcf34842e54f40b15dd937cb267aa7
BugsnagReactNative: 7cc5c927f6a0b00a8e3cc7157dab4cc94a4bc575
BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
@ -822,7 +822,7 @@ SPEC CHECKSUMS:
react-native-safe-area-context: b97eb6f9e3b7f437806c2ce5983f479f8eb5de4b
react-native-secure-key-store: 910e6df6bc33cb790aba6ee24bc7818df1fe5898
react-native-tcp-socket: e724380c910c2e704816ec817ed28f1342246ff7
react-native-webview: 007d5c5a74de7243be6331221727639ed01ef760
react-native-webview: 2ce07db2190ee7b3af77e8fac75ff47abe47089f
react-native-widget-center: 12dfba20a4fa995850b52cf0afecf734397f4b9c
React-NativeModulesApple: 694679e4193a49c09f0a76ee27ec09b2c466d59c
React-perflogger: 63606aeab27683112e1bd4ef25bd099ec1cb03f8
@ -842,7 +842,7 @@ SPEC CHECKSUMS:
React-utils: 9a24cb88f950d1020ee55bddacbc8c16a611e2dc
ReactCommon: 76843a9bb140596351ac2786257ac9fe60cafabb
ReactNativeCameraKit: 9d46a5d7dd544ca64aa9c03c150d2348faf437eb
RealmJS: 385df5ee940d96f1de26b1dab153e325633d3052
RealmJS: 55c8cbedc3ef719a942e370f8c94e04cb7e6781c
rn-ldk: 0d8749d98cc5ce67302a32831818c116b67f7643
RNCAsyncStorage: 826b603ae9c0f88b5ac4e956801f755109fa4d5c
RNCClipboard: 090462274cc05b02628bd158baf6d73c3abe8441

35
ios/Shared/Balance.swift Normal file
View File

@ -0,0 +1,35 @@
import Foundation
class Balance {
static func formatBalance(_ balance: Decimal, toUnit: BitcoinUnit, withFormatting: Bool = false, completion: @escaping (String) -> Void) {
switch toUnit {
case .BTC:
let value = balance / Decimal(100_000_000)
completion("\(value) BTC") // Localize unit names as needed.
case .SATS:
if withFormatting {
completion(NumberFormatter.localizedString(from: balance as NSNumber, number: .decimal) + " SATS")
} else {
completion("\(balance) SATS")
}
case .LOCAL_CURRENCY:
fetchLocalCurrencyEquivalent(satoshi: balance, completion: completion)
}
}
private static func fetchLocalCurrencyEquivalent(satoshi: Decimal, completion: @escaping (String) -> Void) {
let currency = Currency.getUserPreferredCurrency() // Ensure this method retrieves the correct currency code.
MarketAPI.fetchPrice(currency: currency) { dataStore, error in
DispatchQueue.main.async {
guard let dataStore = dataStore, error == nil else {
completion("Error: \(error?.localizedDescription ?? "Unknown error")")
return
}
let rate = Decimal(string: dataStore.rate) ?? Decimal(0)
let convertedAmount = (satoshi / Decimal(100_000_000)) * rate
completion("\(convertedAmount) \(currency)")
}
}
}
}

View File

@ -0,0 +1,15 @@
//
// BitcoinUnit.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 4/14/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
import Foundation
enum BitcoinUnit: String {
case BTC = "BTC"
case SATS = "SATS"
case LOCAL_CURRENCY = "LOCAL_CURRENCY"
}

View File

@ -1,24 +1,13 @@
//
// FiatUnit.swift
// Bundle+decode.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 11/20/20.
// Copyright © 2020 BlueWallet. All rights reserved.
// Created by Marcos Rodriguez on 4/14/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
import Foundation
struct FiatUnit: Codable {
let endPointKey: String
let symbol: String
let locale: String
let source: String
}
func fiatUnit(currency: String) -> FiatUnit? {
return Bundle.main.decode([String: FiatUnit].self, from: "fiatUnits.json").first(where: {$1.endPointKey == currency})?.value
}
extension Bundle {
func decode<T: Decodable>(_ type: T.Type, from file: String, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> T {
guard let url = self.url(forResource: file, withExtension: nil) else {

57
ios/Shared/Currency.swift Normal file
View File

@ -0,0 +1,57 @@
//
// Currency.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 4/14/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
import Foundation
struct CurrencyError: LocalizedError {
var errorDescription: String = "Failed to parse response"
}
class Currency {
static func getUserPreferredCurrency() -> String {
guard let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue),
let preferredCurrency = userDefaults.string(forKey: "preferredCurrency")
else {
return "USD"
}
if preferredCurrency != Currency.getLastSelectedCurrency() {
UserDefaults.standard.removeObject(forKey: WidgetData.WidgetCachedDataStoreKey)
UserDefaults.standard.removeObject(forKey: WidgetData.WidgetDataStoreKey)
UserDefaults.standard.synchronize()
}
return preferredCurrency
}
static func getUserPreferredCurrencyLocale() -> String {
guard let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue),
let preferredCurrency = userDefaults.string(forKey: "preferredCurrencyLocale")
else {
return "en_US"
}
return preferredCurrency
}
static func getLastSelectedCurrency() -> String {
guard let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue), let dataStore = userDefaults.string(forKey: "currency") else {
return "USD"
}
return dataStore
}
static func saveNewSelectedCurrency() {
UserDefaults.standard.setValue(Currency.getUserPreferredCurrency(), forKey: "currency")
}
}

View File

@ -0,0 +1,20 @@
//
// FiatUnit.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 11/20/20.
// Copyright © 2020 BlueWallet. All rights reserved.
//
import Foundation
struct FiatUnit: Codable {
let endPointKey: String
let symbol: String
let locale: String
let source: String
}
func fiatUnit(currency: String) -> FiatUnit? {
return Bundle.main.decode([String: FiatUnit].self, from: "fiatUnits.json").first(where: {$1.endPointKey == currency})?.value
}

View File

@ -0,0 +1,14 @@
//
// LatestTransaction.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 4/14/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
import Foundation
struct LatestTransaction {
let isUnconfirmed: Bool?
let epochValue: Int?
}

View File

@ -12,7 +12,7 @@ struct APIError: LocalizedError {
var errorDescription: String = "Failed to fetch Electrum data..."
}
extension WidgetAPI {
extension MarketAPI {
static func fetchNextBlockFee(completion: @escaping ((MarketData?, Error?) -> Void), userElectrumSettings: UserDefaultsElectrumSettings = UserDefaultsGroup.getElectrumSettings()) {
let settings = userElectrumSettings
@ -73,12 +73,12 @@ extension WidgetAPI {
static func fetchMarketData(currency: String, completion: @escaping ((MarketData?, Error?) -> Void)) {
var marketDataEntry = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0)
WidgetAPI.fetchPrice(currency: currency, completion: { (result, error) in
MarketAPI.fetchPrice(currency: currency, completion: { (result, error) in
if let result = result {
marketDataEntry.rate = result.rateDouble
marketDataEntry.price = result.formattedRate ?? "!"
}
WidgetAPI.fetchNextBlockFee { (marketData, error) in
MarketAPI.fetchNextBlockFee { (marketData, error) in
if let nextBlock = marketData?.nextBlock {
marketDataEntry.nextBlock = nextBlock
} else {

199
ios/Shared/MarketAPI.swift Normal file
View File

@ -0,0 +1,199 @@
//
// WidgetAPI.swift
// TodayExtension
//
// Created by Marcos Rodriguez on 11/2/19.
//
import Foundation
var numberFormatter: NumberFormatter {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 0
formatter.locale = Locale.current
return formatter
}
class MarketAPI {
private static func buildURLString(source: String, endPointKey: String) -> String {
switch source {
case "Yadio":
return "https://api.yadio.io/json/\(endPointKey)"
case "YadioConvert":
return "https://api.yadio.io/convert/1/BTC/\(endPointKey)"
case "Exir":
return "https://api.exir.io/v1/ticker?symbol=btc-irt"
case "wazirx":
return "https://api.wazirx.com/api/v2/tickers/btcinr"
case "Bitstamp":
return "https://www.bitstamp.net/api/v2/ticker/btc\(endPointKey.lowercased())"
case "Coinbase":
return "https://api.coinbase.com/v2/prices/BTC-\(endPointKey.uppercased())/buy"
case "CoinGecko":
return "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=\(endPointKey.lowercased())"
case "BNR":
return "https://www.bnr.ro/nbrfxrates.xml"
default:
return "https://api.coindesk.com/v1/bpi/currentprice/\(endPointKey).json"
}
}
private static func handleDefaultData(data: Data, source: String, endPointKey: String, completion: @escaping ((WidgetDataStore?, Error?) -> Void)) {
guard let json = (try? JSONSerialization.jsonObject(with: data, options: [])) as? Dictionary<String, Any> else {
completion(nil, CurrencyError(errorDescription: "JSON parsing error."))
return
}
// Parse the JSON based on the source and format the response
parseJSONBasedOnSource(json: json, source: source, endPointKey: endPointKey, completion: completion)
}
private static func parseJSONBasedOnSource(json: Dictionary<String, Any>, source: String, endPointKey: String, completion: @escaping ((WidgetDataStore?, Error?) -> Void)) {
var latestRateDataStore: WidgetDataStore?
switch source {
case "Yadio":
if let rateDict = json[endPointKey] as? [String: Any],
let rateDouble = rateDict["price"] as? Double,
let lastUpdated = rateDict["timestamp"] as? Int {
let unix = Double(lastUpdated / 1_000)
let lastUpdatedString = ISO8601DateFormatter().string(from: Date(timeIntervalSince1970: unix))
latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble)
completion(latestRateDataStore, nil)
} else {
completion(nil, CurrencyError(errorDescription: "Data formatting error for source: \(source)"))
}
case "YadioConvert":
guard let rateDouble = json["rate"] as? Double,
let lastUpdated = json["timestamp"] as? Int
else { break }
let unix = Double(lastUpdated / 1_000)
let lastUpdatedString = ISO8601DateFormatter().string(from: Date(timeIntervalSince1970: unix))
latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble)
completion(latestRateDataStore, nil)
case "CoinGecko":
if let bitcoinDict = json["bitcoin"] as? [String: Any],
let rateDouble = bitcoinDict[endPointKey.lowercased()] as? Double {
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble)
completion(latestRateDataStore, nil)
} else {
completion(nil, CurrencyError(errorDescription: "Data formatting error for source: \(source)"))
}
case "Exir":
if let rateDouble = json["last"] as? Double {
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble)
completion(latestRateDataStore, nil)
} else {
completion(nil, CurrencyError(errorDescription: "Data formatting error for source: \(source)"))
}
case "Bitstamp":
if let rateString = json["last"] as? String, let rateDouble = Double(rateString) {
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble)
completion(latestRateDataStore, nil)
} else {
completion(nil, CurrencyError(errorDescription: "Data formatting error for source: \(source)"))
}
case "wazirx":
if let tickerDict = json["ticker"] as? [String: Any],
let rateString = tickerDict["buy"] as? String,
let rateDouble = Double(rateString) {
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble)
completion(latestRateDataStore, nil)
} else {
completion(nil, CurrencyError(errorDescription: "Data formatting error for source: \(source)"))
}
case "Coinbase":
if let data = json["data"] as? [String: Any],
let rateString = data["amount"] as? String,
let rateDouble = Double(rateString) {
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble)
completion(latestRateDataStore, nil)
} else {
completion(nil, CurrencyError(errorDescription: "Data formatting error for source: \(source)"))
}
case "BNR":
// Handle BNR source differently if needed, perhaps requiring XML parsing
// Placeholder for potential XML parsing logic or alternative JSON structure
completion(nil, CurrencyError(errorDescription: "BNR data source is not yet implemented"))
default:
completion(nil, CurrencyError(errorDescription: "Unsupported data source \(source)"))
}
}
// Handles XML data for BNR source
private static func handleBNRData(data: Data, completion: @escaping ((WidgetDataStore?, Error?) -> Void)) {
let parser = XMLParser(data: data)
let delegate = BNRXMLParserDelegate()
parser.delegate = delegate
if parser.parse(), let usdToRonRate = delegate.usdRate {
let coinGeckoUrl = URL(string: "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd")!
URLSession.shared.dataTask(with: coinGeckoUrl) { data, _, error in
guard let data = data, error == nil else {
completion(nil, error ?? CurrencyError())
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let bitcoinDict = json["bitcoin"] as? [String: Double],
let btcToUsdRate = bitcoinDict["usd"] {
let btcToRonRate = btcToUsdRate * usdToRonRate
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
let latestRateDataStore = WidgetDataStore(rate: String(btcToRonRate), lastUpdate: lastUpdatedString, rateDouble: btcToRonRate)
completion(latestRateDataStore, nil)
} else {
completion(nil, CurrencyError())
}
} catch {
completion(nil, error)
}
}.resume()
} else {
completion(nil, CurrencyError(errorDescription: "XML parsing error."))
}
}
static func fetchPrice(currency: String, completion: @escaping ((WidgetDataStore?, Error?) -> Void)) {
let currencyToFiatUnit = fiatUnit(currency: currency)
guard let source = currencyToFiatUnit?.source, let endPointKey = currencyToFiatUnit?.endPointKey else {
completion(nil, CurrencyError(errorDescription: "Invalid currency unit or endpoint."))
return
}
let urlString = buildURLString(source: source, endPointKey: endPointKey)
guard let url = URL(string: urlString) else {
completion(nil, CurrencyError(errorDescription: "Invalid URL."))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
completion(nil, error ?? CurrencyError(errorDescription: "Network error or data not found."))
return
}
if source == "BNR" {
handleBNRData(data: data, completion: completion)
} else {
handleDefaultData(data: data, source: source, endPointKey: endPointKey, completion: completion)
}
}.resume()
}
}

View File

@ -0,0 +1,37 @@
//
// MarketData.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 4/14/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
import Foundation
struct MarketData:Codable {
var nextBlock: String
var sats: String
var price: String
var rate: Double
var formattedNextBlock: String {
return nextBlock == "..." ? "..." : #"\#(nextBlock) sat/b"#
}
var dateString: String = ""
var formattedDate: String? {
let isoDateFormatter = ISO8601DateFormatter()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current
dateFormatter.timeStyle = .short
if let date = isoDateFormatter.date(from: dateString) {
return dateFormatter.string(from: date)
}
return nil
}
static let string = "MarketData"
}
enum MarketDataTimeline: String {
case Previous = "previous"
case Current = "current"
}

View File

@ -0,0 +1,27 @@
//
// Numeric+abbreviated.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 4/14/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
import Foundation
extension Numeric {
var abbreviated: String {
let bytecountFormatter = ByteCountFormatter()
bytecountFormatter.zeroPadsFractionDigits = true
bytecountFormatter.countStyle = .decimal
bytecountFormatter.isAdaptive = false
let bytesString = bytecountFormatter.string(fromByteCount: (self as! NSNumber).int64Value)
let numericString = bytesString
.replacingOccurrences(of: "bytes", with: "")
.replacingOccurrences(of: "B", with: "") // removes B (bytes) in 'KB'/'MB'/'GB'
.replacingOccurrences(of: "G", with: "B") // replace G (Giga) to just B (billions)
return numericString.replacingOccurrences(of: " ", with: "")
}
}

View File

@ -0,0 +1,16 @@
//
// Models.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 11/1/20.
// Copyright © 2020 BlueWallet. All rights reserved.
//
import Foundation
let emptyMarketData = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0)
let emptyWalletData = WalletData(balance: 0, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: Int(Date().timeIntervalSince1970)))

View File

@ -0,0 +1,20 @@
//
// UserDefaultsGroupKeys.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 4/14/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
import Foundation
enum UserDefaultsGroupKey: String {
case GroupName = "group.io.bluewallet.bluewallet"
case PreferredCurrency = "preferredCurrency"
case ElectrumSettingsHost = "electrum_host"
case ElectrumSettingsTCPPort = "electrum_tcp_port"
case ElectrumSettingsSSLPort = "electrum_ssl_port"
case AllWalletsBalance = "WidgetCommunicationAllWalletsSatoshiBalance"
case AllWalletsLatestTransactionTime = "WidgetCommunicationAllWalletsLatestTransactionTime"
case LatestTransactionIsUnconfirmed = "\"WidgetCommunicationLatestTransactionIsUnconfirmed\""
}

View File

@ -0,0 +1,27 @@
//
// WalletData.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 4/14/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
import Foundation
struct WalletData {
var balance: Double
var latestTransactionTime: LatestTransaction = LatestTransaction(isUnconfirmed: false, epochValue: 0)
var formattedBalanceBTC: String {
let formatter = NumberFormatter()
formatter.numberStyle = .none
formatter.usesSignificantDigits = true
formatter.maximumSignificantDigits = 9
formatter.roundingMode = .up
let value = NSNumber(value: balance / 100000000);
if let valueString = formatter.string(from: value) {
return "\(String(describing: valueString)) \(BitcoinUnit.BTC.rawValue)"
} else {
return "0 \(BitcoinUnit.BTC.rawValue)"
}
}
}

View File

@ -0,0 +1,22 @@
//
// WidgetData.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 4/14/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
import Foundation
class WidgetData {
static let WidgetDataStoreKey = "WidgetDataStoreKey"
static let WidgetCachedDataStoreKey = "WidgetCachedDataStoreKey"
static func savePriceRateAndLastUpdate(rate: String, lastUpdate: String) {
guard let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue) else { return }
userDefaults.setValue(["rate": rate, "lastUpdate": lastUpdate], forKey: WidgetDataStoreKey)
userDefaults.synchronize()
}
}

View File

@ -0,0 +1,62 @@
//
// WidgetDataStore.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 4/14/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
import Foundation
struct WidgetDataStore: Codable {
let rate: String
let lastUpdate: String
let rateDouble: Double
var formattedRate: String? {
let numberFormatter = NumberFormatter()
numberFormatter.locale = Locale(identifier: Currency.getUserPreferredCurrencyLocale())
numberFormatter.numberStyle = .currency
numberFormatter.maximumFractionDigits = 0
numberFormatter.minimumFractionDigits = 0
if let rateString = numberFormatter.string(from: NSNumber(value: rateDouble)) {
return rateString
}
return rate
}
var formattedRateForSmallComplication: String? {
return rateDouble.abbreviated
}
var formattedRateForComplication: String? {
let numberFormatter = NumberFormatter()
numberFormatter.locale = Locale(identifier: Currency.getUserPreferredCurrencyLocale())
numberFormatter.numberStyle = .currency
numberFormatter.currencySymbol = ""
if let rateString = numberFormatter.string(from: NSNumber(value: rateDouble)) {
return rateString
}
return rate
}
var date: Date? {
let isoDateFormatter = ISO8601DateFormatter()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current
dateFormatter.timeStyle = .short
return isoDateFormatter.date(from: lastUpdate)
}
var formattedDate: String? {
let isoDateFormatter = ISO8601DateFormatter()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current
dateFormatter.timeStyle = .short
if let date = isoDateFormatter.date(from: lastUpdate) {
return dateFormatter.string(from: date)
}
return nil
}
}

View File

@ -29,10 +29,10 @@ struct WalletInformationWidgetProvider: TimelineProvider {
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [WalletInformationWidgetEntry] = []
let userPreferredCurrency = WidgetAPI.getUserPreferredCurrency()
let userPreferredCurrency = Currency.getUserPreferredCurrency()
let allwalletsBalance = WalletData(balance: UserDefaultsGroup.getAllWalletsBalance(), latestTransactionTime: UserDefaultsGroup.getAllWalletsLatestTransactionTime())
WidgetAPI.fetchPrice(currency: userPreferredCurrency) { (result, error) in
MarketAPI.fetchPrice(currency: userPreferredCurrency) { (result, error) in
let entry: WalletInformationWidgetEntry
if let result = result {

View File

@ -34,8 +34,8 @@ struct MarketWidgetProvider: TimelineProvider {
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
} else {
let userPreferredCurrency = WidgetAPI.getUserPreferredCurrency()
WidgetAPI.fetchMarketData(currency: userPreferredCurrency) { (result, error) in
let userPreferredCurrency = Currency.getUserPreferredCurrency()
MarketAPI.fetchMarketData(currency: userPreferredCurrency) { (result, error) in
let entry: MarketWidgetEntry
if let result = result {

View File

@ -36,14 +36,14 @@ struct PriceWidgetProvider: TimelineProvider {
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
} else {
let userPreferredCurrency = WidgetAPI.getUserPreferredCurrency()
if userPreferredCurrency != WidgetAPI.getLastSelectedCurrency() {
let userPreferredCurrency = Currency.getUserPreferredCurrency()
if userPreferredCurrency != Currency.getLastSelectedCurrency() {
marketData[.Current] = nil
marketData[.Previous] = nil
WidgetAPI.saveNewSelectedCurrency()
Currency.saveNewSelectedCurrency()
}
WidgetAPI.fetchPrice(currency: userPreferredCurrency) { (data, error) in
MarketAPI.fetchPrice(currency: userPreferredCurrency) { (data, error) in
let entry: PriceWidgetEntry
if let data = data, let formattedRate = data.formattedRate {

View File

@ -1,74 +0,0 @@
//
// Models.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 11/1/20.
// Copyright © 2020 BlueWallet. All rights reserved.
//
import Foundation
struct MarketData:Codable {
var nextBlock: String
var sats: String
var price: String
var rate: Double
var formattedNextBlock: String {
return nextBlock == "..." ? "..." : #"\#(nextBlock) sat/b"#
}
var dateString: String = ""
var formattedDate: String? {
let isoDateFormatter = ISO8601DateFormatter()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current
dateFormatter.timeStyle = .short
if let date = isoDateFormatter.date(from: dateString) {
return dateFormatter.string(from: date)
}
return nil
}
static let string = "MarketData"
}
struct WalletData {
var balance: Double
var latestTransactionTime: LatestTransaction = LatestTransaction(isUnconfirmed: false, epochValue: 0)
var formattedBalanceBTC: String {
let formatter = NumberFormatter()
formatter.numberStyle = .none
formatter.usesSignificantDigits = true
formatter.maximumSignificantDigits = 9
formatter.roundingMode = .up
let value = NSNumber(value: balance / 100000000);
if let valueString = formatter.string(from: value) {
return "\(String(describing: valueString)) BTC"
} else {
return "0 BTC"
}
}
}
struct LatestTransaction {
let isUnconfirmed: Bool?
let epochValue: Int?
}
let emptyMarketData = MarketData(nextBlock: "...", sats: "...", price: "...", rate: 0)
let emptyWalletData = WalletData(balance: 0, latestTransactionTime: LatestTransaction(isUnconfirmed: false, epochValue: Int(Date().timeIntervalSince1970)))
enum MarketDataTimeline: String {
case Previous = "previous"
case Current = "current"
}
enum UserDefaultsGroupKey: String {
case GroupName = "group.io.bluewallet.bluewallet"
case PreferredCurrency = "preferredCurrency"
case ElectrumSettingsHost = "electrum_host"
case ElectrumSettingsTCPPort = "electrum_tcp_port"
case ElectrumSettingsSSLPort = "electrum_ssl_port"
case AllWalletsBalance = "WidgetCommunicationAllWalletsSatoshiBalance"
case AllWalletsLatestTransactionTime = "WidgetCommunicationAllWalletsLatestTransactionTime"
case LatestTransactionIsUnconfirmed = "\"WidgetCommunicationLatestTransactionIsUnconfirmed\""
}

View File

@ -28,7 +28,7 @@ struct MarketView: View {
Spacer()
HStack(alignment: .center, spacing: 0, content: {
Text("Sats/\(WidgetAPI.getUserPreferredCurrency())").bold().lineLimit(1).font(Font.system(size:11, weight: .medium, design: .default)).foregroundColor(.textColor)
Text("Sats/\(Currency.getUserPreferredCurrency())").bold().lineLimit(1).font(Font.system(size:11, weight: .medium, design: .default)).foregroundColor(.textColor)
Spacer()
Text(marketData.sats == "..." ? "..." : marketData.sats).padding(EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4)).lineLimit(1).minimumScaleFactor(0.1).foregroundColor(.widgetBackground).font(Font.system(size:11, weight: .semibold, design: .default)).background(Color(red: 0.97, green: 0.21, blue: 0.38)).overlay(
RoundedRectangle(cornerRadius: 4.0)

View File

@ -16,7 +16,7 @@ struct WalletInformationView: View {
var formattedBalance: String {
let numberFormatter = NumberFormatter()
numberFormatter.locale = Locale(identifier: WidgetAPI.getUserPreferredCurrencyLocale())
numberFormatter.locale = Locale(identifier: Currency.getUserPreferredCurrencyLocale())
numberFormatter.numberStyle = .currency
let amount = numberFormatter.string(from: NSNumber(value: ((allWalletsBalance.balance / 100000000) * marketData.rate))) ?? ""
return amount

View File

@ -1,218 +0,0 @@
//
// WidgetAPI.swift
// TodayExtension
//
// Created by Marcos Rodriguez on 11/2/19.
//
import Foundation
struct CurrencyError: LocalizedError {
var errorDescription: String = "Failed to parse response"
}
var numberFormatter: NumberFormatter {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 0
formatter.locale = Locale.current
return formatter
}
class WidgetAPI {
static func fetchPrice(currency: String, completion: @escaping ((WidgetDataStore?, Error?) -> Void)) {
let currencyToFiatUnit = fiatUnit(currency: currency)
guard let source = currencyToFiatUnit?.source, let endPointKey = currencyToFiatUnit?.endPointKey else { return }
var urlString: String
switch source {
case "Yadio":
urlString = "https://api.yadio.io/json/\(endPointKey)"
case "YadioConvert":
urlString = "https://api.yadio.io/convert/1/BTC/\(endPointKey)"
case "Exir":
urlString = "https://api.exir.io/v1/ticker?symbol=btc-irt"
case "wazirx":
urlString = "https://api.wazirx.com/api/v2/tickers/btcinr"
case "Bitstamp":
urlString = "https://www.bitstamp.net/api/v2/ticker/btc\(endPointKey.lowercased())"
case "Coinbase":
urlString = "https://api.coinbase.com/v2/prices/BTC-\(endPointKey.uppercased())/buy"
case "CoinGecko":
urlString = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=\(endPointKey.lowercased())"
case "BNR":
urlString = "https://www.bnr.ro/nbrfxrates.xml"
default:
urlString = "https://api.coindesk.com/v1/bpi/currentprice/\(endPointKey).json"
}
guard let url = URL(string:urlString) else { return }
if source == "BNR" {
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print("Error fetching data: \(error.localizedDescription)")
completion(nil, error)
return
}
guard let data = data else {
print("No data received")
completion(nil, nil)
return
}
// Parse XML data for USD to RON rate
let parser = XMLParser(data: data)
let delegate = BNRXMLParserDelegate()
parser.delegate = delegate
if parser.parse(), let usdToRonRate = delegate.usdRate {
// Fetch BTC to USD rate using CoinGecko
let coinGeckoUrl = URL(string: "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd")!
URLSession.shared.dataTask(with: coinGeckoUrl) { (data, _, error) in
guard let data = data, error == nil else {
completion(nil, error ?? CurrencyError())
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
let bitcoinDict = json["bitcoin"] as? [String: Double],
let btcToUsdRate = bitcoinDict["usd"] {
let btcToRonRate = btcToUsdRate * usdToRonRate
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
let latestRateDataStore = WidgetDataStore(rate: String(btcToRonRate), lastUpdate: lastUpdatedString, rateDouble: btcToRonRate)
completion(latestRateDataStore, nil)
} else {
completion(nil, CurrencyError())
}
} catch {
completion(nil, error)
}
}.resume()
} else {
print("Error parsing XML")
completion(nil, CurrencyError())
}
}.resume()
} else {
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let dataResponse = data,
let json = (try? JSONSerialization.jsonObject(with: dataResponse, options: .mutableContainers) as? Dictionary<String, Any>),
error == nil
else {
print(error?.localizedDescription ?? "Response Error")
completion(nil, error)
return
}
var latestRateDataStore: WidgetDataStore?
switch source {
case "Yadio":
guard let rateDict = json[endPointKey] as? [String: Any],
let rateDouble = rateDict["price"] as? Double,
let lastUpdated = json["timestamp"] as? Int
else { break }
let unix = Double(lastUpdated / 1_000)
let lastUpdatedString = ISO8601DateFormatter().string(from: Date(timeIntervalSince1970: unix))
latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble)
case "CoinGecko":
guard let rateDict = json["bitcoin"] as? [String: Any],
let rateDouble = rateDict[endPointKey.lowercased()] as? Double
else { break }
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble)
case "YadioConvert":
guard let rateDict = json as? [String: Any],
let rateDouble = rateDict["rate"] as? Double,
let lastUpdated = json["timestamp"] as? Int
else { break }
let unix = Double(lastUpdated / 1_000)
let lastUpdatedString = ISO8601DateFormatter().string(from: Date(timeIntervalSince1970: unix))
latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble)
case "Exir":
guard let rateDouble = json["last"] as? Double else { break }
let rateString = String(rateDouble)
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble)
case "Bitstamp":
guard let rateString = json["last"] as? String, let rateDouble = Double(rateString) else { break }
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble)
case "wazirx":
guard let tickerDict = json["ticker"] as? [String: Any],
let rateString = tickerDict["buy"] as? String,
let rateDouble = Double(rateString)
else { break }
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble)
case "Coinbase":
guard let data = json["data"] as? Dictionary<String, Any>,
let rateString = data["amount"] as? String,
let rateDouble = Double(rateString)
else { break }
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble)
default:
guard let bpi = json["bpi"] as? Dictionary<String, Any>,
let preferredCurrency = bpi[endPointKey] as? Dictionary<String, Any>,
let rateString = preferredCurrency["rate"] as? String,
let rateDouble = preferredCurrency["rate_float"] as? Double,
let time = json["time"] as? Dictionary<String, Any>,
let lastUpdatedString = time["updatedISO"] as? String
else { break }
latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble)
}
if (latestRateDataStore == nil) {
completion(nil, CurrencyError())
return
}
completion(latestRateDataStore, nil)
}.resume()
}
}
static func getUserPreferredCurrency() -> String {
guard let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue),
let preferredCurrency = userDefaults.string(forKey: "preferredCurrency")
else {
return "USD"
}
if preferredCurrency != WidgetAPI.getLastSelectedCurrency() {
UserDefaults.standard.removeObject(forKey: WidgetData.WidgetCachedDataStoreKey)
UserDefaults.standard.removeObject(forKey: WidgetData.WidgetDataStoreKey)
UserDefaults.standard.synchronize()
}
return preferredCurrency
}
static func getUserPreferredCurrencyLocale() -> String {
guard let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue),
let preferredCurrency = userDefaults.string(forKey: "preferredCurrencyLocale")
else {
return "en_US"
}
return preferredCurrency
}
static func getLastSelectedCurrency() -> String {
guard let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue), let dataStore = userDefaults.string(forKey: "currency") else {
return "USD"
}
return dataStore
}
static func saveNewSelectedCurrency() {
UserDefaults.standard.setValue(WidgetAPI.getUserPreferredCurrency(), forKey: "currency")
}
}

View File

@ -1,86 +0,0 @@
//
// Created by Marcos Rodriguez on 11/3/19.
//
import Foundation
extension Numeric {
var abbreviated: String {
let bytecountFormatter = ByteCountFormatter()
bytecountFormatter.zeroPadsFractionDigits = true
bytecountFormatter.countStyle = .decimal
bytecountFormatter.isAdaptive = false
let bytesString = bytecountFormatter.string(fromByteCount: (self as! NSNumber).int64Value)
let numericString = bytesString
.replacingOccurrences(of: "bytes", with: "")
.replacingOccurrences(of: "B", with: "") // removes B (bytes) in 'KB'/'MB'/'GB'
.replacingOccurrences(of: "G", with: "B") // replace G (Giga) to just B (billions)
return numericString.replacingOccurrences(of: " ", with: "")
}
}
struct WidgetDataStore: Codable {
let rate: String
let lastUpdate: String
let rateDouble: Double
var formattedRate: String? {
let numberFormatter = NumberFormatter()
numberFormatter.locale = Locale(identifier: WidgetAPI.getUserPreferredCurrencyLocale())
numberFormatter.numberStyle = .currency
numberFormatter.maximumFractionDigits = 0
numberFormatter.minimumFractionDigits = 0
if let rateString = numberFormatter.string(from: NSNumber(value: rateDouble)) {
return rateString
}
return rate
}
var formattedRateForSmallComplication: String? {
return rateDouble.abbreviated
}
var formattedRateForComplication: String? {
let numberFormatter = NumberFormatter()
numberFormatter.locale = Locale(identifier: WidgetAPI.getUserPreferredCurrencyLocale())
numberFormatter.numberStyle = .currency
numberFormatter.currencySymbol = ""
if let rateString = numberFormatter.string(from: NSNumber(value: rateDouble)) {
return rateString
}
return rate
}
var date: Date? {
let isoDateFormatter = ISO8601DateFormatter()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current
dateFormatter.timeStyle = .short
return isoDateFormatter.date(from: lastUpdate)
}
var formattedDate: String? {
let isoDateFormatter = ISO8601DateFormatter()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current
dateFormatter.timeStyle = .short
if let date = isoDateFormatter.date(from: lastUpdate) {
return dateFormatter.string(from: date)
}
return nil
}
}
class WidgetData {
static let WidgetDataStoreKey = "WidgetDataStoreKey"
static let WidgetCachedDataStoreKey = "WidgetCachedDataStoreKey"
static func savePriceRateAndLastUpdate(rate: String, lastUpdate: String) {
guard let userDefaults = UserDefaults(suiteName: UserDefaultsGroupKey.GroupName.rawValue) else { return }
userDefaults.setValue(["rate": rate, "lastUpdate": lastUpdate], forKey: WidgetDataStoreKey)
userDefaults.synchronize()
}
}

View File

@ -35,10 +35,10 @@ struct WalletInformationAndMarketWidgetProvider: TimelineProvider {
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
} else {
let userPreferredCurrency = WidgetAPI.getUserPreferredCurrency()
let userPreferredCurrency = Currency.getUserPreferredCurrency()
let allwalletsBalance = WalletData(balance: UserDefaultsGroup.getAllWalletsBalance(), latestTransactionTime: UserDefaultsGroup.getAllWalletsLatestTransactionTime())
WidgetAPI.fetchMarketData(currency: userPreferredCurrency) { (result, error) in
MarketAPI.fetchMarketData(currency: userPreferredCurrency) { (result, error) in
let entry: WalletInformationAndMarketWidgetEntry
if let result = result {

230
package-lock.json generated
View File

@ -11,7 +11,7 @@
"license": "MIT",
"dependencies": {
"@babel/preset-env": "^7.20.0",
"@bugsnag/react-native": "7.22.6",
"@bugsnag/react-native": "7.22.7",
"@bugsnag/source-maps": "2.3.2",
"@keystonehq/bc-ur-registry": "0.6.4",
"@ngraveio/bc-ur": "1.1.12",
@ -97,10 +97,10 @@
"react-native-tcp-socket": "6.0.6",
"react-native-vector-icons": "10.0.3",
"react-native-watch-connectivity": "1.1.0",
"react-native-webview": "13.8.4",
"react-native-webview": "13.8.6",
"react-native-widget-center": "https://github.com/BlueWallet/react-native-widget-center#a128c38",
"readable-stream": "3.6.2",
"realm": "12.6.2",
"realm": "12.7.0",
"rn-ldk": "github:BlueWallet/rn-ldk#v0.8.4",
"rn-nodeify": "10.3.0",
"scryptsy": "2.1.0",
@ -2140,9 +2140,9 @@
"dev": true
},
"node_modules/@bugsnag/core": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@bugsnag/core/-/core-7.19.0.tgz",
"integrity": "sha512-2KGwdaLD9PhR7Wk7xPi3jGuGsKTatc/28U4TOZIDU3CgC2QhGjubwiXSECel5gwxhZ3jACKcMKSV2ovHhv1NrA==",
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/core/-/core-7.22.7.tgz",
"integrity": "sha512-9DPWBkkBjhFJc5dCFy/wVC3HE0Aw3ZiLJKjyAxgywSKbILgtpD+qT1Xe8sacWyxU92znamlZ8H8ziQOe7jhhbA==",
"dependencies": {
"@bugsnag/cuid": "^3.0.0",
"@bugsnag/safe-json-stringify": "^6.0.0",
@ -2152,38 +2152,38 @@
}
},
"node_modules/@bugsnag/cuid": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@bugsnag/cuid/-/cuid-3.0.2.tgz",
"integrity": "sha512-cIwzC93r3PQ/INeuwtZwkZIG2K8WWN0rRLZQhu+mr48Ay+i6sEki4GYfTsflse7hZ1BeDWrNb/Q9vgY3B31xHQ=="
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@bugsnag/cuid/-/cuid-3.1.0.tgz",
"integrity": "sha512-U6vPVhFPLWLXQIGnmk/uPbyidB5xLVFfEdZDmmLbv41Be67re4mNsjMukHdUv669RXOWTDQNJ+bMODhuWpshkw=="
},
"node_modules/@bugsnag/delivery-react-native": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@bugsnag/delivery-react-native/-/delivery-react-native-7.22.3.tgz",
"integrity": "sha512-6lHcberdmzVDlMumM3Ff6A/Ycmo4nIWN28d+Q1i13HS1AYnbmry9wVRzgC/dYfad7Nk5zKwOR9yhwQz7xUaBzw==",
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/delivery-react-native/-/delivery-react-native-7.22.7.tgz",
"integrity": "sha512-PfJ8kcGzoW2KTC8CGSVjdmnIMUDGym3YyYNWr2K397NHs2oW0f/wprbQUx+WAotogK6BSSGzD0dnEX45cepYdA==",
"peerDependencies": {
"@bugsnag/core": "^7.0.0"
}
},
"node_modules/@bugsnag/plugin-console-breadcrumbs": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-console-breadcrumbs/-/plugin-console-breadcrumbs-7.22.3.tgz",
"integrity": "sha512-SFfyFlvjkeQ9cJK64QLDdhNjSttpS/fbTxi4UnB1CTjj9G1spD4InUUnXDFHtSis4Dnx2GL4lWUbXXhE0cXIfw==",
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-console-breadcrumbs/-/plugin-console-breadcrumbs-7.22.7.tgz",
"integrity": "sha512-f+nEy0+pLepPhPtEe94Yqm1qJH8ayZmLIjuTYvtcyC2+2kF6g1DMctPFn0EBKv7wvT2P7Vls6PgTLaohjECHOg==",
"peerDependencies": {
"@bugsnag/core": "^7.0.0"
}
},
"node_modules/@bugsnag/plugin-network-breadcrumbs": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-network-breadcrumbs/-/plugin-network-breadcrumbs-7.22.3.tgz",
"integrity": "sha512-C1hpB05pvXBSI9rv9N3AxJBRJ3hY7BLIgkNRKYq+E3l8HNFipDrDGKt0i7R6nUSmolSFJkheQqr12BNmdOPosw==",
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-network-breadcrumbs/-/plugin-network-breadcrumbs-7.22.7.tgz",
"integrity": "sha512-+zAq9D0G58+b/IJ/LGE2eNOIYrgIgQltJPMWdAyQFZcJWTqulN61ggt9yD2dO5PfSpAE/7KhNsR9TVbRPMb/qA==",
"peerDependencies": {
"@bugsnag/core": "^7.0.0"
}
},
"node_modules/@bugsnag/plugin-react": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react/-/plugin-react-7.19.0.tgz",
"integrity": "sha512-owC4QXYJWGllMoOPcH5P7sbDIDuFLMCbjGAU6FwH5mBMObSQo+1ViSKImlTJQUFXATM8ySISTBVt7w3C6FFHng==",
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react/-/plugin-react-7.22.7.tgz",
"integrity": "sha512-CDyCHK5+KMkpf/2vmVC7xqqP4ys25Yuj9M8xVrmP7LC02nFUPt/UzEIt43MSO4Jfw254ZimbsQrhIweabQNyxQ==",
"peerDependencies": {
"@bugsnag/core": "^7.0.0"
},
@ -2194,69 +2194,69 @@
}
},
"node_modules/@bugsnag/plugin-react-native-client-sync": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-client-sync/-/plugin-react-native-client-sync-7.22.3.tgz",
"integrity": "sha512-Ji5k8JRjFVyZYwGdlPRHTGbJNQI9n/rd/bqZn8697wQtl49M8WStVuLYlYoqMvjRpYaAmL7XQlNXRb/1Y8AkRQ==",
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-client-sync/-/plugin-react-native-client-sync-7.22.7.tgz",
"integrity": "sha512-tFPQyRLa5dwJh7wkiYliQviLcD4zxaxucZcCn3moTGAcL5Oc/BpmACpOnYXuBeF9w7gFZMcHTsxvrhld9X1QQQ==",
"peerDependencies": {
"@bugsnag/core": "^7.0.0"
}
},
"node_modules/@bugsnag/plugin-react-native-event-sync": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-event-sync/-/plugin-react-native-event-sync-7.22.3.tgz",
"integrity": "sha512-xqvVcRjpAtOcS9e3jdW8h+SiZQwjd2yVmbKVLo2NESAbjeMVQcdTLogdUrdG92JnI5UnVWFW1bqoQqtug6shxA==",
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-event-sync/-/plugin-react-native-event-sync-7.22.7.tgz",
"integrity": "sha512-wmjOyhMcxh0GeC1xk/XgWYutRCeaSiK89mkL3A+e3sJHv3Cmc6UNY9b0v38jMBr8L7ef5KnDy/9Yh0D0pR7DxQ==",
"peerDependencies": {
"@bugsnag/core": "^7.0.0"
}
},
"node_modules/@bugsnag/plugin-react-native-global-error-handler": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-global-error-handler/-/plugin-react-native-global-error-handler-7.22.3.tgz",
"integrity": "sha512-nJwwpg3EPbTbCoi+FuxFwU4svJnqbIyHqBw1IddXiGUmI6irYnI700HYDmeFIEZXEmaX19lrOPhdhecGLhOolg==",
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-global-error-handler/-/plugin-react-native-global-error-handler-7.22.7.tgz",
"integrity": "sha512-+zreCSjUkGviU76LacuNnTUK+oj5Fs4ZT4xdh7PZhWb0z4CZol/d6GR20OPNHpyLEuuLMjXMZsqu9sXnr+VKdw==",
"peerDependencies": {
"@bugsnag/core": "^7.0.0"
}
},
"node_modules/@bugsnag/plugin-react-native-hermes": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-hermes/-/plugin-react-native-hermes-7.19.0.tgz",
"integrity": "sha512-6SGTSR6NMS2t8j02ZQ6FlA+K/nKkZqvGA+8A7WS/0M8HAShzyoMpZH10kGrU2dcCaiEtmD2T6OGBSbpF+385Dg==",
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-hermes/-/plugin-react-native-hermes-7.22.7.tgz",
"integrity": "sha512-6Oqwi4KghjlPKkr+LDKBxJeorCnCeUahQuVFHXq/45gThH50CDQ4GduraZX9iqtThKX1T8PPIODubWyH6PF2Vg==",
"peerDependencies": {
"@bugsnag/core": "^7.0.0"
}
},
"node_modules/@bugsnag/plugin-react-native-session": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-session/-/plugin-react-native-session-7.22.3.tgz",
"integrity": "sha512-lreErWPZgjnoiGHD282+a9tCsFN4D34XEjbYLRnLRLjlugJeuTnkvDAYTFtmYpMDyEg/WBhWMaNqAdpJPj/nkQ==",
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-session/-/plugin-react-native-session-7.22.7.tgz",
"integrity": "sha512-p3C7m6GXh9ICnGt+m1FwWpBCiGNGdQvoTzzN0LAxT6YQdB3t2nmhqE3QIpHmXpJK1PveTCIOO2DbeSerWtUEsg==",
"peerDependencies": {
"@bugsnag/core": "^7.0.0"
}
},
"node_modules/@bugsnag/plugin-react-native-unhandled-rejection": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-unhandled-rejection/-/plugin-react-native-unhandled-rejection-7.19.0.tgz",
"integrity": "sha512-+XDk0OoeM6MZhBh7kEalbRwFuhCZST6Y1jOostfz0fhrmT4FdgQYi1FWcPNsUTcjqv7M48pOFZNx8yWI0lGaYg==",
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-unhandled-rejection/-/plugin-react-native-unhandled-rejection-7.22.7.tgz",
"integrity": "sha512-xmFpUPYrQxwsr9RJ1HTu9lfNUbAHM+hIyUEshg+/Wfj/1Zvnkr0AnkqRWbQFqkOBklzYI4s7maJvm4S2go/KOQ==",
"peerDependencies": {
"@bugsnag/core": "^7.0.0"
}
},
"node_modules/@bugsnag/react-native": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@bugsnag/react-native/-/react-native-7.22.6.tgz",
"integrity": "sha512-0TVul4cN0VVNzKkiiZTmmSNtFoOC5SGCW2ncikdF1sijc5IsavXDRemVMfLGg1xm0w3BWNsUjujmrmGCvkLxLA==",
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/react-native/-/react-native-7.22.7.tgz",
"integrity": "sha512-vHmynQj7rzPW+1v8aK41G9T5HSaXipgFkkCmczOiFG9YYNzVKeaPcbwcS6Z6+tLZ55ZQeJdupfezcmj4rnAZVw==",
"dependencies": {
"@bugsnag/core": "^7.19.0",
"@bugsnag/delivery-react-native": "^7.22.3",
"@bugsnag/plugin-console-breadcrumbs": "^7.22.3",
"@bugsnag/plugin-network-breadcrumbs": "^7.22.3",
"@bugsnag/plugin-react": "^7.19.0",
"@bugsnag/plugin-react-native-client-sync": "^7.22.3",
"@bugsnag/plugin-react-native-event-sync": "^7.22.3",
"@bugsnag/plugin-react-native-global-error-handler": "^7.22.3",
"@bugsnag/plugin-react-native-hermes": "^7.19.0",
"@bugsnag/plugin-react-native-session": "^7.22.3",
"@bugsnag/plugin-react-native-unhandled-rejection": "^7.19.0",
"@bugsnag/core": "^7.22.7",
"@bugsnag/delivery-react-native": "^7.22.7",
"@bugsnag/plugin-console-breadcrumbs": "^7.22.7",
"@bugsnag/plugin-network-breadcrumbs": "^7.22.7",
"@bugsnag/plugin-react": "^7.22.7",
"@bugsnag/plugin-react-native-client-sync": "^7.22.7",
"@bugsnag/plugin-react-native-event-sync": "^7.22.7",
"@bugsnag/plugin-react-native-global-error-handler": "^7.22.7",
"@bugsnag/plugin-react-native-hermes": "^7.22.7",
"@bugsnag/plugin-react-native-session": "^7.22.7",
"@bugsnag/plugin-react-native-unhandled-rejection": "^7.22.7",
"iserror": "^0.0.2"
}
},
@ -19828,9 +19828,9 @@
}
},
"node_modules/react-native-webview": {
"version": "13.8.4",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.8.4.tgz",
"integrity": "sha512-dFoM9EfkAb++ZzycZyKRnjZtNUn85cf6bWp1iBlkgyNml7ULzR1gfaPT3qESoA3K1RfTmf5Xhw0M2In2A3a3wg==",
"version": "13.8.6",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.8.6.tgz",
"integrity": "sha512-jtZ9OgB2AN6rhDwto6dNL3PtOtl/SI4VN93pZEPbMLvRjqHfxiUrilGllL5fKAXq5Ry5FJyfUi82A4Ii8olZ7A==",
"dependencies": {
"escape-string-regexp": "2.0.0",
"invariant": "2.2.4"
@ -20090,9 +20090,9 @@
"integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg=="
},
"node_modules/realm": {
"version": "12.6.2",
"resolved": "https://registry.npmjs.org/realm/-/realm-12.6.2.tgz",
"integrity": "sha512-6ICUaKHNeiEAwVIKC3AkCDTCVEtpkFAVeWvmUVdmVIUjcY/+2cMLe/tgFpLcY7pEB/n1EUg3pVyUBcVuMvwdqg==",
"version": "12.7.0",
"resolved": "https://registry.npmjs.org/realm/-/realm-12.7.0.tgz",
"integrity": "sha512-8epWL4lLd1ZoA7gWfZdqlKDt9JqfoeEAcS66P8afXlFKATMhdUnUqYVFUfYhgV4KtC/JQCwipd0ysxVAY8nKuQ==",
"hasInstallScript": true,
"dependencies": {
"@realm/fetch": "^0.1.1",
@ -23911,9 +23911,9 @@
"dev": true
},
"@bugsnag/core": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@bugsnag/core/-/core-7.19.0.tgz",
"integrity": "sha512-2KGwdaLD9PhR7Wk7xPi3jGuGsKTatc/28U4TOZIDU3CgC2QhGjubwiXSECel5gwxhZ3jACKcMKSV2ovHhv1NrA==",
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/core/-/core-7.22.7.tgz",
"integrity": "sha512-9DPWBkkBjhFJc5dCFy/wVC3HE0Aw3ZiLJKjyAxgywSKbILgtpD+qT1Xe8sacWyxU92znamlZ8H8ziQOe7jhhbA==",
"requires": {
"@bugsnag/cuid": "^3.0.0",
"@bugsnag/safe-json-stringify": "^6.0.0",
@ -23923,76 +23923,76 @@
}
},
"@bugsnag/cuid": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@bugsnag/cuid/-/cuid-3.0.2.tgz",
"integrity": "sha512-cIwzC93r3PQ/INeuwtZwkZIG2K8WWN0rRLZQhu+mr48Ay+i6sEki4GYfTsflse7hZ1BeDWrNb/Q9vgY3B31xHQ=="
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@bugsnag/cuid/-/cuid-3.1.0.tgz",
"integrity": "sha512-U6vPVhFPLWLXQIGnmk/uPbyidB5xLVFfEdZDmmLbv41Be67re4mNsjMukHdUv669RXOWTDQNJ+bMODhuWpshkw=="
},
"@bugsnag/delivery-react-native": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@bugsnag/delivery-react-native/-/delivery-react-native-7.22.3.tgz",
"integrity": "sha512-6lHcberdmzVDlMumM3Ff6A/Ycmo4nIWN28d+Q1i13HS1AYnbmry9wVRzgC/dYfad7Nk5zKwOR9yhwQz7xUaBzw=="
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/delivery-react-native/-/delivery-react-native-7.22.7.tgz",
"integrity": "sha512-PfJ8kcGzoW2KTC8CGSVjdmnIMUDGym3YyYNWr2K397NHs2oW0f/wprbQUx+WAotogK6BSSGzD0dnEX45cepYdA=="
},
"@bugsnag/plugin-console-breadcrumbs": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-console-breadcrumbs/-/plugin-console-breadcrumbs-7.22.3.tgz",
"integrity": "sha512-SFfyFlvjkeQ9cJK64QLDdhNjSttpS/fbTxi4UnB1CTjj9G1spD4InUUnXDFHtSis4Dnx2GL4lWUbXXhE0cXIfw=="
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-console-breadcrumbs/-/plugin-console-breadcrumbs-7.22.7.tgz",
"integrity": "sha512-f+nEy0+pLepPhPtEe94Yqm1qJH8ayZmLIjuTYvtcyC2+2kF6g1DMctPFn0EBKv7wvT2P7Vls6PgTLaohjECHOg=="
},
"@bugsnag/plugin-network-breadcrumbs": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-network-breadcrumbs/-/plugin-network-breadcrumbs-7.22.3.tgz",
"integrity": "sha512-C1hpB05pvXBSI9rv9N3AxJBRJ3hY7BLIgkNRKYq+E3l8HNFipDrDGKt0i7R6nUSmolSFJkheQqr12BNmdOPosw=="
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-network-breadcrumbs/-/plugin-network-breadcrumbs-7.22.7.tgz",
"integrity": "sha512-+zAq9D0G58+b/IJ/LGE2eNOIYrgIgQltJPMWdAyQFZcJWTqulN61ggt9yD2dO5PfSpAE/7KhNsR9TVbRPMb/qA=="
},
"@bugsnag/plugin-react": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react/-/plugin-react-7.19.0.tgz",
"integrity": "sha512-owC4QXYJWGllMoOPcH5P7sbDIDuFLMCbjGAU6FwH5mBMObSQo+1ViSKImlTJQUFXATM8ySISTBVt7w3C6FFHng=="
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react/-/plugin-react-7.22.7.tgz",
"integrity": "sha512-CDyCHK5+KMkpf/2vmVC7xqqP4ys25Yuj9M8xVrmP7LC02nFUPt/UzEIt43MSO4Jfw254ZimbsQrhIweabQNyxQ=="
},
"@bugsnag/plugin-react-native-client-sync": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-client-sync/-/plugin-react-native-client-sync-7.22.3.tgz",
"integrity": "sha512-Ji5k8JRjFVyZYwGdlPRHTGbJNQI9n/rd/bqZn8697wQtl49M8WStVuLYlYoqMvjRpYaAmL7XQlNXRb/1Y8AkRQ=="
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-client-sync/-/plugin-react-native-client-sync-7.22.7.tgz",
"integrity": "sha512-tFPQyRLa5dwJh7wkiYliQviLcD4zxaxucZcCn3moTGAcL5Oc/BpmACpOnYXuBeF9w7gFZMcHTsxvrhld9X1QQQ=="
},
"@bugsnag/plugin-react-native-event-sync": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-event-sync/-/plugin-react-native-event-sync-7.22.3.tgz",
"integrity": "sha512-xqvVcRjpAtOcS9e3jdW8h+SiZQwjd2yVmbKVLo2NESAbjeMVQcdTLogdUrdG92JnI5UnVWFW1bqoQqtug6shxA=="
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-event-sync/-/plugin-react-native-event-sync-7.22.7.tgz",
"integrity": "sha512-wmjOyhMcxh0GeC1xk/XgWYutRCeaSiK89mkL3A+e3sJHv3Cmc6UNY9b0v38jMBr8L7ef5KnDy/9Yh0D0pR7DxQ=="
},
"@bugsnag/plugin-react-native-global-error-handler": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-global-error-handler/-/plugin-react-native-global-error-handler-7.22.3.tgz",
"integrity": "sha512-nJwwpg3EPbTbCoi+FuxFwU4svJnqbIyHqBw1IddXiGUmI6irYnI700HYDmeFIEZXEmaX19lrOPhdhecGLhOolg=="
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-global-error-handler/-/plugin-react-native-global-error-handler-7.22.7.tgz",
"integrity": "sha512-+zreCSjUkGviU76LacuNnTUK+oj5Fs4ZT4xdh7PZhWb0z4CZol/d6GR20OPNHpyLEuuLMjXMZsqu9sXnr+VKdw=="
},
"@bugsnag/plugin-react-native-hermes": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-hermes/-/plugin-react-native-hermes-7.19.0.tgz",
"integrity": "sha512-6SGTSR6NMS2t8j02ZQ6FlA+K/nKkZqvGA+8A7WS/0M8HAShzyoMpZH10kGrU2dcCaiEtmD2T6OGBSbpF+385Dg=="
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-hermes/-/plugin-react-native-hermes-7.22.7.tgz",
"integrity": "sha512-6Oqwi4KghjlPKkr+LDKBxJeorCnCeUahQuVFHXq/45gThH50CDQ4GduraZX9iqtThKX1T8PPIODubWyH6PF2Vg=="
},
"@bugsnag/plugin-react-native-session": {
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-session/-/plugin-react-native-session-7.22.3.tgz",
"integrity": "sha512-lreErWPZgjnoiGHD282+a9tCsFN4D34XEjbYLRnLRLjlugJeuTnkvDAYTFtmYpMDyEg/WBhWMaNqAdpJPj/nkQ=="
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-session/-/plugin-react-native-session-7.22.7.tgz",
"integrity": "sha512-p3C7m6GXh9ICnGt+m1FwWpBCiGNGdQvoTzzN0LAxT6YQdB3t2nmhqE3QIpHmXpJK1PveTCIOO2DbeSerWtUEsg=="
},
"@bugsnag/plugin-react-native-unhandled-rejection": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-unhandled-rejection/-/plugin-react-native-unhandled-rejection-7.19.0.tgz",
"integrity": "sha512-+XDk0OoeM6MZhBh7kEalbRwFuhCZST6Y1jOostfz0fhrmT4FdgQYi1FWcPNsUTcjqv7M48pOFZNx8yWI0lGaYg=="
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/plugin-react-native-unhandled-rejection/-/plugin-react-native-unhandled-rejection-7.22.7.tgz",
"integrity": "sha512-xmFpUPYrQxwsr9RJ1HTu9lfNUbAHM+hIyUEshg+/Wfj/1Zvnkr0AnkqRWbQFqkOBklzYI4s7maJvm4S2go/KOQ=="
},
"@bugsnag/react-native": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@bugsnag/react-native/-/react-native-7.22.6.tgz",
"integrity": "sha512-0TVul4cN0VVNzKkiiZTmmSNtFoOC5SGCW2ncikdF1sijc5IsavXDRemVMfLGg1xm0w3BWNsUjujmrmGCvkLxLA==",
"version": "7.22.7",
"resolved": "https://registry.npmjs.org/@bugsnag/react-native/-/react-native-7.22.7.tgz",
"integrity": "sha512-vHmynQj7rzPW+1v8aK41G9T5HSaXipgFkkCmczOiFG9YYNzVKeaPcbwcS6Z6+tLZ55ZQeJdupfezcmj4rnAZVw==",
"requires": {
"@bugsnag/core": "^7.19.0",
"@bugsnag/delivery-react-native": "^7.22.3",
"@bugsnag/plugin-console-breadcrumbs": "^7.22.3",
"@bugsnag/plugin-network-breadcrumbs": "^7.22.3",
"@bugsnag/plugin-react": "^7.19.0",
"@bugsnag/plugin-react-native-client-sync": "^7.22.3",
"@bugsnag/plugin-react-native-event-sync": "^7.22.3",
"@bugsnag/plugin-react-native-global-error-handler": "^7.22.3",
"@bugsnag/plugin-react-native-hermes": "^7.19.0",
"@bugsnag/plugin-react-native-session": "^7.22.3",
"@bugsnag/plugin-react-native-unhandled-rejection": "^7.19.0",
"@bugsnag/core": "^7.22.7",
"@bugsnag/delivery-react-native": "^7.22.7",
"@bugsnag/plugin-console-breadcrumbs": "^7.22.7",
"@bugsnag/plugin-network-breadcrumbs": "^7.22.7",
"@bugsnag/plugin-react": "^7.22.7",
"@bugsnag/plugin-react-native-client-sync": "^7.22.7",
"@bugsnag/plugin-react-native-event-sync": "^7.22.7",
"@bugsnag/plugin-react-native-global-error-handler": "^7.22.7",
"@bugsnag/plugin-react-native-hermes": "^7.22.7",
"@bugsnag/plugin-react-native-session": "^7.22.7",
"@bugsnag/plugin-react-native-unhandled-rejection": "^7.22.7",
"iserror": "^0.0.2"
}
},
@ -37369,9 +37369,9 @@
}
},
"react-native-webview": {
"version": "13.8.4",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.8.4.tgz",
"integrity": "sha512-dFoM9EfkAb++ZzycZyKRnjZtNUn85cf6bWp1iBlkgyNml7ULzR1gfaPT3qESoA3K1RfTmf5Xhw0M2In2A3a3wg==",
"version": "13.8.6",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.8.6.tgz",
"integrity": "sha512-jtZ9OgB2AN6rhDwto6dNL3PtOtl/SI4VN93pZEPbMLvRjqHfxiUrilGllL5fKAXq5Ry5FJyfUi82A4Ii8olZ7A==",
"requires": {
"escape-string-regexp": "2.0.0",
"invariant": "2.2.4"
@ -37481,9 +37481,9 @@
"integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg=="
},
"realm": {
"version": "12.6.2",
"resolved": "https://registry.npmjs.org/realm/-/realm-12.6.2.tgz",
"integrity": "sha512-6ICUaKHNeiEAwVIKC3AkCDTCVEtpkFAVeWvmUVdmVIUjcY/+2cMLe/tgFpLcY7pEB/n1EUg3pVyUBcVuMvwdqg==",
"version": "12.7.0",
"resolved": "https://registry.npmjs.org/realm/-/realm-12.7.0.tgz",
"integrity": "sha512-8epWL4lLd1ZoA7gWfZdqlKDt9JqfoeEAcS66P8afXlFKATMhdUnUqYVFUfYhgV4KtC/JQCwipd0ysxVAY8nKuQ==",
"requires": {
"@realm/fetch": "^0.1.1",
"bson": "^4.7.2",

View File

@ -98,7 +98,7 @@
},
"dependencies": {
"@babel/preset-env": "^7.20.0",
"@bugsnag/react-native": "7.22.6",
"@bugsnag/react-native": "7.22.7",
"@bugsnag/source-maps": "2.3.2",
"@keystonehq/bc-ur-registry": "0.6.4",
"@ngraveio/bc-ur": "1.1.12",
@ -184,10 +184,10 @@
"react-native-tcp-socket": "6.0.6",
"react-native-vector-icons": "10.0.3",
"react-native-watch-connectivity": "1.1.0",
"react-native-webview": "13.8.4",
"react-native-webview": "13.8.6",
"react-native-widget-center": "https://github.com/BlueWallet/react-native-widget-center#a128c38",
"readable-stream": "3.6.2",
"realm": "12.6.2",
"realm": "12.7.0",
"rn-ldk": "github:BlueWallet/rn-ldk#v0.8.4",
"rn-nodeify": "10.3.0",
"scryptsy": "2.1.0",

View File

@ -1,12 +1,9 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { ActivityIndicator, KeyboardAvoidingView, Linking, StyleSheet, Platform, TextInput, View, Keyboard } from 'react-native';
import { useRoute, useNavigation } from '@react-navigation/native';
import * as bitcoin from 'bitcoinjs-lib';
import loc from '../../loc';
import { HDSegwitBech32Wallet } from '../../class';
import navigationStyle from '../../components/navigationStyle';
import {
BlueBigCheckmark,
BlueButtonLink,
@ -24,6 +21,7 @@ import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/h
import SafeArea from '../../components/SafeArea';
import presentAlert from '../../components/Alert';
import { scanQrHelper } from '../../helpers/scan-qr';
import { isTablet } from 'react-native-device-info';
const BROADCAST_RESULT = Object.freeze({
none: 'Input transaction hex',
@ -32,13 +30,17 @@ const BROADCAST_RESULT = Object.freeze({
error: 'error',
});
const Broadcast = () => {
interface SuccessScreenProps {
tx: string;
}
const Broadcast: React.FC = () => {
const { name } = useRoute();
const { navigate } = useNavigation();
const [tx, setTx] = useState();
const [txHex, setTxHex] = useState();
const [tx, setTx] = useState<string | undefined>();
const [txHex, setTxHex] = useState<string | undefined>();
const { colors } = useTheme();
const [broadcastResult, setBroadcastResult] = useState(BROADCAST_RESULT.none);
const [broadcastResult, setBroadcastResult] = useState<string>(BROADCAST_RESULT.none);
const stylesHooks = StyleSheet.create({
input: {
@ -48,7 +50,7 @@ const Broadcast = () => {
},
});
const handleUpdateTxHex = nextValue => setTxHex(nextValue.trim());
const handleUpdateTxHex = (nextValue: string) => setTxHex(nextValue.trim());
const handleBroadcast = async () => {
Keyboard.dismiss();
@ -57,19 +59,22 @@ const Broadcast = () => {
await BlueElectrum.ping();
await BlueElectrum.waitTillConnected();
const walletObj = new HDSegwitBech32Wallet();
const result = await walletObj.broadcastTx(txHex);
if (result) {
const newTx = bitcoin.Transaction.fromHex(txHex);
const txid = newTx.getId();
setTx(txid);
if (txHex) {
const result = await walletObj.broadcastTx(txHex);
if (result) {
const newTx = bitcoin.Transaction.fromHex(txHex);
const txid = newTx.getId();
setTx(txid);
setBroadcastResult(BROADCAST_RESULT.success);
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
Notifications.majorTomToGroundControl([], [], [txid]);
} else {
setBroadcastResult(BROADCAST_RESULT.error);
setBroadcastResult(BROADCAST_RESULT.success);
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
// @ts-ignore: fix later
Notifications.majorTomToGroundControl([], [], [txid]);
} else {
setBroadcastResult(BROADCAST_RESULT.error);
}
}
} catch (error) {
} catch (error: any) {
presentAlert({ title: loc.errors.error, message: error.message });
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
setBroadcastResult(BROADCAST_RESULT.error);
@ -86,7 +91,7 @@ const Broadcast = () => {
}
try {
// sould be base64 encoded PSBT
// should be base64 encoded PSBT
const validTx = bitcoin.Psbt.fromBase64(scannedData).extractTransaction();
return handleUpdateTxHex(validTx.toHex());
} catch (e) {}
@ -112,11 +117,7 @@ const Broadcast = () => {
return (
<SafeArea>
<KeyboardAvoidingView
enabled={!Platform.isPad}
behavior={Platform.OS === 'ios' ? 'position' : null}
keyboardShouldPersistTaps="handled"
>
<KeyboardAvoidingView enabled={!isTablet} behavior={Platform.OS === 'ios' ? 'position' : undefined}>
<View style={styles.wrapper} testID="BroadcastView">
{BROADCAST_RESULT.success !== broadcastResult && (
<BlueCard style={styles.mainCard}>
@ -128,10 +129,6 @@ const Broadcast = () => {
<View style={[styles.input, stylesHooks.input]}>
<TextInput
style={styles.text}
maxHeight={100}
minHeight={100}
maxWidth="100%"
minWidth="100%"
multiline
editable
placeholderTextColor="#81868e"
@ -155,15 +152,34 @@ const Broadcast = () => {
<BlueSpacing20 />
</BlueCard>
)}
{BROADCAST_RESULT.success === broadcastResult && <SuccessScreen tx={tx} />}
{BROADCAST_RESULT.success === broadcastResult && tx && <SuccessScreen tx={tx} />}
</View>
</KeyboardAvoidingView>
</SafeArea>
);
};
const SuccessScreen: React.FC<SuccessScreenProps> = ({ tx }) => {
if (!tx) {
return null;
}
return (
<View style={styles.wrapper}>
<BlueCard>
<View style={styles.broadcastResultWrapper}>
<BlueBigCheckmark />
<BlueSpacing20 />
<BlueTextCentered>{loc.settings.success_transaction_broadcasted}</BlueTextCentered>
<BlueSpacing10 />
<BlueButtonLink title={loc.settings.open_link_in_explorer} onPress={() => Linking.openURL(`https://mempool.space/tx/${tx}`)} />
</View>
</BlueCard>
</View>
);
};
export default Broadcast;
Broadcast.navigationOptions = navigationStyle({}, opts => ({ ...opts, title: loc.send.create_broadcast }));
const styles = StyleSheet.create({
wrapper: {
@ -203,31 +219,10 @@ const styles = StyleSheet.create({
},
text: {
padding: 8,
minHeight: 33,
color: '#81868e',
maxHeight: 100,
minHeight: 100,
maxWidth: '100%',
minWidth: '100%',
},
});
const SuccessScreen = ({ tx }) => {
if (!tx) {
return null;
}
return (
<View style={styles.wrapper}>
<BlueCard>
<View style={styles.broadcastResultWrapper}>
<BlueBigCheckmark />
<BlueSpacing20 />
<BlueTextCentered>{loc.settings.success_transaction_broadcasted}</BlueTextCentered>
<BlueSpacing10 />
<BlueButtonLink title={loc.settings.open_link_in_explorer} onPress={() => Linking.openURL(`https://mempool.space/tx/${tx}`)} />
</View>
</BlueCard>
</View>
);
};
SuccessScreen.propTypes = {
tx: PropTypes.string.isRequired,
};

View File

@ -12,7 +12,7 @@ import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
import presentAlert from '../../components/Alert';
import { requestCameraAuthorization } from '../../helpers/scan-qr';
import { Button } from '../../components/Button';
import { AppStorage } from '../../BlueApp';
import { BlueApp } from '../../class';
const styles = StyleSheet.create({
uri: {
@ -62,7 +62,7 @@ const LightningSettings: React.FC & { navigationOptions: NavigationOptionsGetter
});
useEffect(() => {
AsyncStorage.getItem(AppStorage.LNDHUB)
AsyncStorage.getItem(BlueApp.LNDHUB)
.then(value => setURI(value ?? undefined))
.then(() => setIsLoading(false))
.catch(() => setIsLoading(false));
@ -101,9 +101,9 @@ const LightningSettings: React.FC & { navigationOptions: NavigationOptionsGetter
// validating only if its not empty. empty means use default
}
if (URI) {
await AsyncStorage.setItem(AppStorage.LNDHUB, URI);
await AsyncStorage.setItem(BlueApp.LNDHUB, URI);
} else {
await AsyncStorage.removeItem(AppStorage.LNDHUB);
await AsyncStorage.removeItem(BlueApp.LNDHUB);
}
presentAlert({ message: loc.settings.lightning_saved });
} catch (error) {

View File

@ -18,7 +18,14 @@ import {
import { BlueButtonLink, BlueFormLabel, BlueSpacing20, BlueSpacing40, BlueText } from '../../BlueComponents';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import { HDSegwitBech32Wallet, HDSegwitP2SHWallet, LightningCustodianWallet, LightningLdkWallet, SegwitP2SHWallet } from '../../class';
import {
BlueApp,
HDSegwitBech32Wallet,
HDSegwitP2SHWallet,
LightningCustodianWallet,
LightningLdkWallet,
SegwitP2SHWallet,
} from '../../class';
import presentAlert from '../../components/Alert';
import Button from '../../components/Button';
import { LdkButton } from '../../components/LdkButton';
@ -27,7 +34,6 @@ import { useTheme } from '../../components/themes';
import useAsyncPromise from '../../hooks/useAsyncPromise';
import loc from '../../loc';
import { Chain } from '../../models/bitcoinUnits';
import { AppStorage } from '../../BlueApp';
import WalletButton from '../../components/WalletButton';
import A from '../../blue_modules/analytics';
@ -143,7 +149,7 @@ const WalletsAdd: React.FC = () => {
};
useEffect(() => {
AsyncStorage.getItem(AppStorage.LNDHUB)
AsyncStorage.getItem(BlueApp.LNDHUB)
.then(url => (url ? setWalletBaseURI(url) : setWalletBaseURI('')))
.catch(() => setWalletBaseURI(''))
.finally(() => setIsLoading(false));

View File

@ -3,7 +3,7 @@ const path = require('path');
const mainLocFile = './loc/en.json';
const dirsToInterate = ['components', 'screen', 'blue_modules', 'class', 'hooks', 'helpers'];
const addFiles = ['BlueComponents.js', 'App.js', 'BlueApp.ts', 'Navigation.tsx'];
const addFiles = ['BlueComponents.js', 'App.js', 'Navigation.tsx'];
const allowedLocPrefixes = ['loc.lnurl_auth', 'loc.units'];
const allLocKeysHashmap = {}; // loc key -> used or not

View File

@ -1,7 +1,6 @@
import assert from 'assert';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { SegwitP2SHWallet } from '../../class';
import { AppStorage } from '../../BlueApp';
import { SegwitP2SHWallet, BlueApp } from '../../class';
jest.mock('../../blue_modules/BlueElectrum', () => {
return {
@ -10,8 +9,8 @@ jest.mock('../../blue_modules/BlueElectrum', () => {
});
it('Appstorage - loadFromDisk works', async () => {
/** @type {AppStorage} */
const Storage = new AppStorage();
/** @type {BlueApp} */
const Storage = new BlueApp();
const w = new SegwitP2SHWallet();
w.setLabel('testlabel');
await w.generate();
@ -20,7 +19,7 @@ it('Appstorage - loadFromDisk works', async () => {
// saved, now trying to load
const Storage2 = new AppStorage();
const Storage2 = new BlueApp();
await Storage2.loadFromDisk();
assert.strictEqual(Storage2.wallets.length, 1);
assert.strictEqual(Storage2.wallets[0].getLabel(), 'testlabel');
@ -30,15 +29,15 @@ it('Appstorage - loadFromDisk works', async () => {
// emulating encrypted storage (and testing flag)
await AsyncStorage.setItem('data', false);
await AsyncStorage.setItem(AppStorage.FLAG_ENCRYPTED, '1');
const Storage3 = new AppStorage();
await AsyncStorage.setItem(BlueApp.FLAG_ENCRYPTED, '1');
const Storage3 = new BlueApp();
isEncrypted = await Storage3.storageIsEncrypted();
assert.ok(isEncrypted);
});
it('Appstorage - encryptStorage & load encrypted storage works', async () => {
/** @type {AppStorage} */
const Storage = new AppStorage();
/** @type {BlueApp} */
const Storage = new BlueApp();
let w = new SegwitP2SHWallet();
w.setLabel('testlabel');
await w.generate();
@ -53,7 +52,7 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => {
// saved, now trying to load, using good password
let Storage2 = new AppStorage();
let Storage2 = new BlueApp();
isEncrypted = await Storage2.storageIsEncrypted();
assert.ok(isEncrypted);
let loadResult = await Storage2.loadFromDisk('password');
@ -63,7 +62,7 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => {
// now trying to load, using bad password
Storage2 = new AppStorage();
Storage2 = new BlueApp();
isEncrypted = await Storage2.storageIsEncrypted();
assert.ok(isEncrypted);
loadResult = await Storage2.loadFromDisk('passwordBAD');
@ -73,7 +72,7 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => {
// now, trying case with adding data after decrypt.
// saveToDisk should be handled correctly
Storage2 = new AppStorage();
Storage2 = new BlueApp();
isEncrypted = await Storage2.storageIsEncrypted();
assert.ok(isEncrypted);
loadResult = await Storage2.loadFromDisk('password');
@ -88,7 +87,7 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => {
assert.strictEqual(Storage2.wallets[1].getLabel(), 'testlabel2');
await Storage2.saveToDisk();
// saved to encrypted storage after load. next load should be successfull
Storage2 = new AppStorage();
Storage2 = new BlueApp();
isEncrypted = await Storage2.storageIsEncrypted();
assert.ok(isEncrypted);
loadResult = await Storage2.loadFromDisk('password');
@ -109,13 +108,13 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => {
await Storage2.saveToDisk();
// now, will try to load & decrypt with real password and with fake password
// real:
let Storage3 = new AppStorage();
let Storage3 = new BlueApp();
loadResult = await Storage3.loadFromDisk('password');
assert.ok(loadResult);
assert.strictEqual(Storage3.wallets.length, 2);
assert.strictEqual(Storage3.wallets[0].getLabel(), 'testlabel');
// fake:
Storage3 = new AppStorage();
Storage3 = new BlueApp();
loadResult = await Storage3.loadFromDisk('fakePassword');
assert.ok(loadResult);
assert.strictEqual(Storage3.wallets.length, 1);
@ -123,8 +122,8 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => {
});
it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load storage works', async () => {
/** @type {AppStorage} */
const Storage = new AppStorage();
/** @type {BlueApp} */
const Storage = new BlueApp();
let w = new SegwitP2SHWallet();
w.setLabel('testlabel');
await w.generate();
@ -139,7 +138,7 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s
// saved, now trying to load, using good password
let Storage2 = new AppStorage();
let Storage2 = new BlueApp();
isEncrypted = await Storage2.storageIsEncrypted();
assert.ok(isEncrypted);
let loadResult = await Storage2.loadFromDisk('password');
@ -149,7 +148,7 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s
// now trying to load, using bad password
Storage2 = new AppStorage();
Storage2 = new BlueApp();
isEncrypted = await Storage2.storageIsEncrypted();
assert.ok(isEncrypted);
loadResult = await Storage2.loadFromDisk('passwordBAD');
@ -159,7 +158,7 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s
// now, trying case with adding data after decrypt.
// saveToDisk should be handled correctly
Storage2 = new AppStorage();
Storage2 = new BlueApp();
isEncrypted = await Storage2.storageIsEncrypted();
assert.ok(isEncrypted);
loadResult = await Storage2.loadFromDisk('password');
@ -174,7 +173,7 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s
assert.strictEqual(Storage2.wallets[1].getLabel(), 'testlabel2');
await Storage2.saveToDisk();
// saved to encrypted storage after load. next load should be successfull
Storage2 = new AppStorage();
Storage2 = new BlueApp();
isEncrypted = await Storage2.storageIsEncrypted();
assert.ok(isEncrypted);
loadResult = await Storage2.loadFromDisk('password');
@ -195,13 +194,13 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s
await Storage2.saveToDisk();
// now, will try to load & decrypt with real password and with fake password
// real:
let Storage3 = new AppStorage();
let Storage3 = new BlueApp();
loadResult = await Storage3.loadFromDisk('password');
assert.ok(loadResult);
assert.strictEqual(Storage3.wallets.length, 2);
assert.strictEqual(Storage3.wallets[0].getLabel(), 'testlabel');
// fake:
Storage3 = new AppStorage();
Storage3 = new BlueApp();
loadResult = await Storage3.loadFromDisk('fakePassword');
assert.ok(loadResult);
assert.strictEqual(Storage3.wallets.length, 1);
@ -209,7 +208,7 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s
// now will decrypt storage. label of wallet should be testlabel
const Storage4 = new AppStorage();
const Storage4 = new BlueApp();
isEncrypted = await Storage4.storageIsEncrypted();
assert.ok(isEncrypted);
loadResult = await Storage4.loadFromDisk('password');
@ -217,7 +216,7 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s
const decryptStorageResult = await Storage4.decryptStorage('password');
assert.ok(decryptStorageResult);
const Storage5 = new AppStorage();
const Storage5 = new BlueApp();
isEncrypted = await Storage5.storageIsEncrypted();
assert.strictEqual(isEncrypted, false);
const storage5loadResult = await Storage5.loadFromDisk();
@ -228,8 +227,8 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s
});
it('can decrypt storage that is second in a list of buckets; and isPasswordInUse() works', async () => {
/** @type {AppStorage} */
const Storage = new AppStorage();
/** @type {BlueApp} */
const Storage = new BlueApp();
let w = new SegwitP2SHWallet();
w.setLabel('testlabel');
await w.generate();
@ -256,7 +255,7 @@ it('can decrypt storage that is second in a list of buckets; and isPasswordInUse
// now will decrypt storage. will try to decrypt FAKE storage (second in the list) while
// currently decrypted is the MAIN (non-fake) storage. this should throw an exception
const Storage4 = new AppStorage();
const Storage4 = new BlueApp();
isEncrypted = await Storage4.storageIsEncrypted();
assert.ok(isEncrypted);
let loadResult = await Storage4.loadFromDisk('password');
@ -275,7 +274,7 @@ it('can decrypt storage that is second in a list of buckets; and isPasswordInUse
// storage, purging other buckets. this should be possible since if user wants to shoot himsel in the foot
// he should be able to do it.
const Storage5 = new AppStorage();
const Storage5 = new BlueApp();
isEncrypted = await Storage5.storageIsEncrypted();
assert.ok(isEncrypted);
loadResult = await Storage5.loadFromDisk('fakePassword');
@ -288,7 +287,7 @@ it('can decrypt storage that is second in a list of buckets; and isPasswordInUse
// now we will decrypt storage. label of wallet should be testlabel
const Storage6 = new AppStorage();
const Storage6 = new BlueApp();
isEncrypted = await Storage6.storageIsEncrypted();
assert.ok(isEncrypted);
loadResult = await Storage6.loadFromDisk('fakePassword');
@ -296,7 +295,7 @@ it('can decrypt storage that is second in a list of buckets; and isPasswordInUse
const decryptStorageResult = await Storage6.decryptStorage('fakePassword');
assert.ok(decryptStorageResult);
const Storage7 = new AppStorage();
const Storage7 = new BlueApp();
isEncrypted = await Storage7.storageIsEncrypted();
assert.strictEqual(isEncrypted, false);
const storage5loadResult = await Storage7.loadFromDisk();
@ -305,6 +304,6 @@ it('can decrypt storage that is second in a list of buckets; and isPasswordInUse
});
it('Appstorage - hashIt() works', async () => {
const storage = new AppStorage();
const storage = new BlueApp();
assert.strictEqual(storage.hashIt('hello'), '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824');
});