mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 01:40:12 +01:00
ADD: rename counterparty paymentcode
This commit is contained in:
parent
8007c73094
commit
8ad6af025e
@ -1,7 +1,7 @@
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { startAndDecrypt } from './start-and-decrypt';
|
||||
import Notifications from '../blue_modules/notifications';
|
||||
import { LegacyWallet, TTXMetadata, WatchOnlyWallet, BlueApp as BlueAppClass } from '../class';
|
||||
import { LegacyWallet, TTXMetadata, WatchOnlyWallet, BlueApp as BlueAppClass, TCounterpartyMetadata } from '../class';
|
||||
import type { TWallet } from '../class/wallets/types';
|
||||
import presentAlert from '../components/Alert';
|
||||
import loc from '../loc';
|
||||
@ -19,6 +19,7 @@ interface BlueStorageContextType {
|
||||
wallets: TWallet[];
|
||||
setWalletsWithNewOrder: (wallets: TWallet[]) => void;
|
||||
txMetadata: TTXMetadata;
|
||||
counterpartyMetadata: TCounterpartyMetadata;
|
||||
saveToDisk: (force?: boolean) => Promise<void>;
|
||||
selectedWalletID: string | undefined;
|
||||
setSelectedWalletID: (walletID: string | undefined) => void;
|
||||
@ -87,9 +88,11 @@ export const BlueStorageProvider = ({ children }: { children: React.ReactNode })
|
||||
return;
|
||||
}
|
||||
BlueApp.tx_metadata = txMetadata;
|
||||
BlueApp.counterparty_metadata = counterpartyMetadata;
|
||||
await BlueApp.saveToDisk();
|
||||
setWallets([...BlueApp.getWallets()]);
|
||||
txMetadata = BlueApp.tx_metadata;
|
||||
counterpartyMetadata = BlueApp.counterparty_metadata;
|
||||
});
|
||||
};
|
||||
|
||||
@ -195,6 +198,7 @@ export const BlueStorageProvider = ({ children }: { children: React.ReactNode })
|
||||
};
|
||||
|
||||
let txMetadata = BlueApp.tx_metadata;
|
||||
let counterpartyMetadata = BlueApp.counterparty_metadata || {}; // init
|
||||
const getTransactions = BlueApp.getTransactions;
|
||||
const fetchWalletBalances = BlueApp.fetchWalletBalances;
|
||||
const fetchWalletTransactions = BlueApp.fetchWalletTransactions;
|
||||
@ -214,6 +218,7 @@ export const BlueStorageProvider = ({ children }: { children: React.ReactNode })
|
||||
wallets,
|
||||
setWalletsWithNewOrder,
|
||||
txMetadata,
|
||||
counterpartyMetadata,
|
||||
saveToDisk,
|
||||
getTransactions,
|
||||
selectedWalletID,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Transaction, TWallet } from './wallets/types';
|
||||
import { ExtendedTransaction, Transaction, TWallet } from './wallets/types';
|
||||
import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import * as encryption from '../blue_modules/encryption';
|
||||
@ -31,7 +31,18 @@ let savingInProgress = 0; // its both a flag and a counter of attempts to write
|
||||
export type TTXMetadata = {
|
||||
[txid: string]: {
|
||||
memo?: string;
|
||||
txhex?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TCounterpartyMetadata = {
|
||||
/**
|
||||
* our contact identifier, such as bip47 payment code
|
||||
*/
|
||||
[counterparty: string]: {
|
||||
/**
|
||||
* custom human-readable name we assign ourselves
|
||||
*/
|
||||
label: string;
|
||||
};
|
||||
};
|
||||
|
||||
@ -44,6 +55,7 @@ type TRealmTransaction = {
|
||||
type TBucketStorage = {
|
||||
wallets: string[]; // array of serialized wallets, not actual wallet objects
|
||||
tx_metadata: TTXMetadata;
|
||||
counterparty_metadata: TCounterpartyMetadata;
|
||||
};
|
||||
|
||||
const isReactNative = typeof navigator !== 'undefined' && navigator?.product === 'ReactNative';
|
||||
@ -61,11 +73,13 @@ export class BlueApp {
|
||||
|
||||
public cachedPassword?: false | string;
|
||||
public tx_metadata: TTXMetadata;
|
||||
public counterparty_metadata: TCounterpartyMetadata;
|
||||
public wallets: TWallet[];
|
||||
|
||||
constructor() {
|
||||
this.wallets = [];
|
||||
this.tx_metadata = {};
|
||||
this.counterparty_metadata = {};
|
||||
this.cachedPassword = false;
|
||||
}
|
||||
|
||||
@ -187,6 +201,7 @@ export class BlueApp {
|
||||
await this.saveToDisk();
|
||||
this.wallets = [];
|
||||
this.tx_metadata = {};
|
||||
this.counterparty_metadata = {};
|
||||
return this.loadFromDisk();
|
||||
} else {
|
||||
throw new Error('Incorrect password. Please, try again.');
|
||||
@ -216,10 +231,12 @@ export class BlueApp {
|
||||
usedBucketNum = false; // resetting currently used bucket so we wont overwrite it
|
||||
this.wallets = [];
|
||||
this.tx_metadata = {};
|
||||
this.counterparty_metadata = {};
|
||||
|
||||
const data: TBucketStorage = {
|
||||
wallets: [],
|
||||
tx_metadata: {},
|
||||
counterparty_metadata: {},
|
||||
};
|
||||
|
||||
let buckets = await this.getItem('data');
|
||||
@ -454,6 +471,7 @@ export class BlueApp {
|
||||
if (!this.wallets.some(wallet => wallet.getID() === ID)) {
|
||||
this.wallets.push(unserializedWallet);
|
||||
this.tx_metadata = data.tx_metadata;
|
||||
this.counterparty_metadata = data.counterparty_metadata;
|
||||
}
|
||||
}
|
||||
if (realm) realm.close();
|
||||
@ -653,6 +671,7 @@ export class BlueApp {
|
||||
let data: TBucketStorage | string[] /* either a bucket, or an array of encrypted buckets */ = {
|
||||
wallets: walletsToSave,
|
||||
tx_metadata: this.tx_metadata,
|
||||
counterparty_metadata: this.counterparty_metadata,
|
||||
};
|
||||
|
||||
if (this.cachedPassword) {
|
||||
@ -797,35 +816,49 @@ export class BlueApp {
|
||||
* Getter for all transactions in all wallets.
|
||||
* But if index is provided - only for wallet with corresponding index
|
||||
*
|
||||
* @param index {Integer|null} Wallet index in this.wallets. Empty (or null) for all wallets.
|
||||
* @param limit {Integer} How many txs return, starting from the earliest. Default: all of them.
|
||||
* @param includeWalletsWithHideTransactionsEnabled {Boolean} Wallets' _hideTransactionsInWalletsList property determines wether the user wants this wallet's txs hidden from the main list view.
|
||||
* @param index {number|undefined} Wallet index in this.wallets. Empty (or undef) for all wallets.
|
||||
* @param limit {number} How many txs return, starting from the earliest. Default: all of them.
|
||||
* @param includeWalletsWithHideTransactionsEnabled {boolean} Wallets' _hideTransactionsInWalletsList property determines wether the user wants this wallet's txs hidden from the main list view.
|
||||
*/
|
||||
getTransactions = (
|
||||
index?: number,
|
||||
limit: number = Infinity,
|
||||
includeWalletsWithHideTransactionsEnabled: boolean = false,
|
||||
): Transaction[] => {
|
||||
): ExtendedTransaction[] => {
|
||||
if (index || index === 0) {
|
||||
let txs: Transaction[] = [];
|
||||
let c = 0;
|
||||
for (const wallet of this.wallets) {
|
||||
if (c++ === index) {
|
||||
txs = txs.concat(wallet.getTransactions());
|
||||
|
||||
const txsRet: ExtendedTransaction[] = [];
|
||||
const walletID = wallet.getID();
|
||||
const walletPreferredBalanceUnit = wallet.getPreferredBalanceUnit();
|
||||
txs.map(tx =>
|
||||
txsRet.push({
|
||||
...tx,
|
||||
walletID,
|
||||
walletPreferredBalanceUnit,
|
||||
}),
|
||||
);
|
||||
return txsRet;
|
||||
}
|
||||
}
|
||||
return txs;
|
||||
}
|
||||
|
||||
let txs: Transaction[] = [];
|
||||
const txs: ExtendedTransaction[] = [];
|
||||
for (const wallet of this.wallets.filter(w => includeWalletsWithHideTransactionsEnabled || !w.getHideTransactionsInWalletsList())) {
|
||||
const walletTransactions = wallet.getTransactions();
|
||||
const walletTransactions: Transaction[] = wallet.getTransactions();
|
||||
const walletID = wallet.getID();
|
||||
const walletPreferredBalanceUnit = wallet.getPreferredBalanceUnit();
|
||||
for (const t of walletTransactions) {
|
||||
t.walletPreferredBalanceUnit = wallet.getPreferredBalanceUnit();
|
||||
t.walletID = walletID;
|
||||
txs.push({
|
||||
...t,
|
||||
walletID,
|
||||
walletPreferredBalanceUnit,
|
||||
});
|
||||
}
|
||||
txs = txs.concat(walletTransactions);
|
||||
}
|
||||
|
||||
return txs
|
||||
|
@ -15,6 +15,7 @@ import { SegwitBech32Wallet } from './segwit-bech32-wallet';
|
||||
import { SegwitP2SHWallet } from './segwit-p2sh-wallet';
|
||||
import { SLIP39LegacyP2PKHWallet, SLIP39SegwitBech32Wallet, SLIP39SegwitP2SHWallet } from './slip39-wallets';
|
||||
import { WatchOnlyWallet } from './watch-only-wallet';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
|
||||
export type Utxo = {
|
||||
// Returned by BlueElectrum
|
||||
@ -112,6 +113,15 @@ export type Transaction = {
|
||||
counterparty?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* in some cases we add additional data to each tx object so the code that works with that transaction can find the
|
||||
* wallet that owns it etc
|
||||
*/
|
||||
export type ExtendedTransaction = Transaction & {
|
||||
walletID: string;
|
||||
walletPreferredBalanceUnit: BitcoinUnit;
|
||||
};
|
||||
|
||||
export type TWallet =
|
||||
| HDAezeedWallet
|
||||
| HDLegacyBreadwalletWallet
|
||||
|
@ -32,7 +32,7 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
||||
const { colors } = useTheme();
|
||||
const { navigate } = useNavigation();
|
||||
const menuRef = useRef();
|
||||
const { txMetadata, wallets } = useContext(BlueStorageContext);
|
||||
const { txMetadata, counterpartyMetadata, wallets } = useContext(BlueStorageContext);
|
||||
const { preferredFiatCurrency, language } = useSettings();
|
||||
const containerStyle = useMemo(
|
||||
() => ({
|
||||
@ -45,8 +45,9 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
||||
[colors.lightBorder],
|
||||
);
|
||||
|
||||
const shortenContactName = (addr: string): string => {
|
||||
return addr.substr(0, 5) + '...' + addr.substr(addr.length - 4, 4);
|
||||
const shortenContactName = (name: string): string => {
|
||||
if (name.length < 16) return name;
|
||||
return name.substr(0, 8) + '...' + name.substr(name.length - 8, 8);
|
||||
};
|
||||
|
||||
const title = useMemo(() => {
|
||||
@ -58,7 +59,11 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [item.confirmations, item.received, language]);
|
||||
|
||||
const txMemo = (item.counterparty ? `[${shortenContactName(item.counterparty)}] ` : '') + (txMetadata[item.hash]?.memo ?? '');
|
||||
let counterparty;
|
||||
if (item.counterparty) {
|
||||
counterparty = counterpartyMetadata?.[item.counterparty]?.label ?? item.counterparty;
|
||||
}
|
||||
const txMemo = (counterparty ? `[${shortenContactName(counterparty)}] ` : '') + (txMetadata[item.hash]?.memo ?? '');
|
||||
const subtitle = useMemo(() => {
|
||||
let sub = Number(item.confirmations) < 7 ? loc.formatString(loc.transactions.list_conf, { number: item.confirmations }) : '';
|
||||
if (sub !== '') sub += ' ';
|
||||
|
@ -167,6 +167,7 @@
|
||||
"details_next": "Next",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "Note to Self",
|
||||
"counterparty_label_placeholder": "Edit contact name",
|
||||
"details_scan": "Scan",
|
||||
"details_scan_hint": "Double tap to scan or import a destination",
|
||||
"details_total_exceeds_balance": "The sending amount exceeds the available balance.",
|
||||
@ -341,7 +342,6 @@
|
||||
"cpfp_title": "Bump Fee (CPFP)",
|
||||
"details_balance_hide": "Hide Balance",
|
||||
"details_balance_show": "Show Balance",
|
||||
"details_block": "Block Height",
|
||||
"details_copy": "Copy",
|
||||
"details_copy_amount": "Copy Amount",
|
||||
"details_copy_block_explorer_link": "Copy Block Explorer Link",
|
||||
@ -352,7 +352,7 @@
|
||||
"details_outputs": "Outputs",
|
||||
"date": "Date",
|
||||
"details_received": "Received",
|
||||
"transaction_note_saved": "Transaction note has been successfully saved.",
|
||||
"transaction_saved": "Saved",
|
||||
"details_show_in_block_explorer": "View in Block Explorer",
|
||||
"details_title": "Transaction",
|
||||
"details_to": "Output",
|
||||
|
@ -26,7 +26,7 @@ enum ButtonStatus {
|
||||
}
|
||||
|
||||
interface TransactionStatusProps {
|
||||
route: RouteProp<{ params: { hash?: string; walletID?: string } }, 'params'>;
|
||||
route: RouteProp<{ params: { hash: string; walletID: string } }, 'params'>;
|
||||
navigation: NativeStackNavigationProp<any>;
|
||||
}
|
||||
|
||||
@ -84,10 +84,10 @@ const reducer = (state: State, action: { type: ActionType; payload?: any }): Sta
|
||||
}
|
||||
};
|
||||
|
||||
const TransactionsStatus = () => {
|
||||
const TransactionStatus = () => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const { isCPFPPossible, isRBFBumpFeePossible, isRBFCancelPossible, tx, isLoading, eta, intervalMs } = state;
|
||||
const { setSelectedWalletID, wallets, txMetadata, fetchAndSaveWalletTransactions } = useStorage();
|
||||
const { setSelectedWalletID, wallets, txMetadata, counterpartyMetadata, fetchAndSaveWalletTransactions } = useStorage();
|
||||
const { hash, walletID } = useRoute<TransactionStatusProps['route']>().params;
|
||||
const { navigate, setOptions, goBack } = useNavigation<TransactionStatusProps['navigation']>();
|
||||
const { colors } = useTheme();
|
||||
@ -366,7 +366,7 @@ const TransactionsStatus = () => {
|
||||
});
|
||||
};
|
||||
const navigateToTransactionDetials = () => {
|
||||
navigate('TransactionDetails', { hash: tx.hash });
|
||||
navigate('TransactionDetails', { hash: tx.hash, walletID });
|
||||
};
|
||||
|
||||
const renderCPFP = () => {
|
||||
@ -432,8 +432,6 @@ const TransactionsStatus = () => {
|
||||
};
|
||||
|
||||
const renderTXMetadata = () => {
|
||||
const counterparty = tx.counterparty ? shortenCounterpartyName(tx.counterparty) : false;
|
||||
|
||||
if (txMetadata[tx.hash]) {
|
||||
if (txMetadata[tx.hash].memo) {
|
||||
return (
|
||||
@ -441,27 +439,35 @@ const TransactionsStatus = () => {
|
||||
<Text selectable style={styles.memoText}>
|
||||
{txMetadata[tx.hash].memo}
|
||||
</Text>
|
||||
{counterparty ? (
|
||||
<View>
|
||||
<BlueSpacing10 />
|
||||
<Text selectable style={styles.memoText}>
|
||||
{tx.value < 0
|
||||
? loc.formatString(loc.transactions.to, {
|
||||
counterparty,
|
||||
})
|
||||
: loc.formatString(loc.transactions.from, {
|
||||
counterparty,
|
||||
})}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
<BlueSpacing20 />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const renderTXCounterparty = () => {
|
||||
if (!tx.counterparty) return; // no BIP47 counterparty for this tx, return early
|
||||
|
||||
// theres a counterparty. lets lookup if theres an alias for him
|
||||
let counterparty = counterpartyMetadata?.[tx.counterparty]?.label ?? tx.counterparty;
|
||||
counterparty = shortenCounterpartyName(counterparty);
|
||||
|
||||
return (
|
||||
<View style={styles.memo}>
|
||||
<Text selectable style={styles.memoText}>
|
||||
{tx.value < 0
|
||||
? loc.formatString(loc.transactions.to, {
|
||||
counterparty,
|
||||
})
|
||||
: loc.formatString(loc.transactions.from, {
|
||||
counterparty,
|
||||
})}
|
||||
</Text>
|
||||
<BlueSpacing20 />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
if (isLoading || !tx || wallet.current === undefined) {
|
||||
return (
|
||||
<SafeArea>
|
||||
@ -489,6 +495,7 @@ const TransactionsStatus = () => {
|
||||
</View>
|
||||
|
||||
{renderTXMetadata()}
|
||||
{renderTXCounterparty()}
|
||||
|
||||
<View style={[styles.iconRoot, stylesHook.iconRoot]}>
|
||||
<View>
|
||||
@ -553,7 +560,7 @@ const TransactionsStatus = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default TransactionsStatus;
|
||||
export default TransactionStatus;
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { View, ScrollView, TouchableOpacity, Text, TextInput, Linking, StyleSheet, Keyboard, InteractionManager } from 'react-native';
|
||||
import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { InteractionManager, Keyboard, Linking, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
import { RouteProp, useFocusEffect, useNavigation, useRoute } from '@react-navigation/native';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import dayjs from 'dayjs';
|
||||
import { BlueCard, BlueLoading, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
@ -13,13 +13,20 @@ import presentAlert from '../../components/Alert';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import CopyToClipboardButton from '../../components/CopyToClipboardButton';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { Transaction, TWallet } from '../../class/wallets/types';
|
||||
import assert from 'assert';
|
||||
|
||||
function onlyUnique(value, index, self) {
|
||||
interface TransactionDetailsProps {
|
||||
route: RouteProp<{ params: { hash: string; walletID: string } }, 'params'>;
|
||||
navigation: NativeStackNavigationProp<any>;
|
||||
}
|
||||
|
||||
function onlyUnique(value: any, index: number, self: any[]) {
|
||||
return self.indexOf(value) === index;
|
||||
}
|
||||
|
||||
function arrDiff(a1, a2) {
|
||||
function arrDiff(a1: any[], a2: any[]) {
|
||||
const ret = [];
|
||||
for (const v of a2) {
|
||||
if (a1.indexOf(v) === -1) {
|
||||
@ -29,15 +36,18 @@ function arrDiff(a1, a2) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
const TransactionsDetails = () => {
|
||||
const TransactionDetails = () => {
|
||||
const { setOptions, navigate } = useNavigation();
|
||||
const { hash } = useRoute().params;
|
||||
const { saveToDisk, txMetadata, wallets, getTransactions } = useContext(BlueStorageContext);
|
||||
const [from, setFrom] = useState();
|
||||
const [to, setTo] = useState();
|
||||
const { hash, walletID } = useRoute<TransactionDetailsProps['route']>().params;
|
||||
const { saveToDisk, txMetadata, counterpartyMetadata, wallets, getTransactions } = useContext(BlueStorageContext);
|
||||
const [from, setFrom] = useState<string[]>([]);
|
||||
const [to, setTo] = useState<string[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [tx, setTX] = useState();
|
||||
const [memo, setMemo] = useState();
|
||||
const [tx, setTX] = useState<Transaction>();
|
||||
const [memo, setMemo] = useState<string>('');
|
||||
const [counterpartyLabel, setCounterpartyLabel] = useState<string>('');
|
||||
const [paymentCode, setPaymentCode] = useState<string>('');
|
||||
const [isCounterpartyLabelVisible, setIsCounterpartyLabelVisible] = useState<boolean>(false);
|
||||
const { colors } = useTheme();
|
||||
const stylesHooks = StyleSheet.create({
|
||||
memoTextInput: {
|
||||
@ -61,12 +71,16 @@ const TransactionsDetails = () => {
|
||||
|
||||
const handleOnSaveButtonTapped = useCallback(() => {
|
||||
Keyboard.dismiss();
|
||||
if (!tx) return;
|
||||
txMetadata[tx.hash] = { memo };
|
||||
if (counterpartyLabel && paymentCode) {
|
||||
counterpartyMetadata[paymentCode] = { label: counterpartyLabel };
|
||||
}
|
||||
saveToDisk().then(_success => {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.Success);
|
||||
presentAlert({ message: loc.transactions.transaction_note_saved });
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
|
||||
presentAlert({ message: loc.transactions.transaction_saved });
|
||||
});
|
||||
}, [tx, memo, saveToDisk, txMetadata]);
|
||||
}, [tx, txMetadata, memo, counterpartyLabel, paymentCode, saveToDisk, counterpartyMetadata]);
|
||||
|
||||
const HeaderRightButton = useMemo(() => {
|
||||
return (
|
||||
@ -89,35 +103,39 @@ const TransactionsDetails = () => {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const task = InteractionManager.runAfterInteractions(() => {
|
||||
let foundTx = {};
|
||||
let newFrom = [];
|
||||
let newTo = [];
|
||||
for (const transaction of getTransactions(null, Infinity, true)) {
|
||||
let foundTx: Transaction | false = false;
|
||||
let newFrom: string[] = [];
|
||||
let newTo: string[] = [];
|
||||
for (const transaction of getTransactions(undefined, Infinity, true)) {
|
||||
if (transaction.hash === hash) {
|
||||
foundTx = transaction;
|
||||
for (const input of foundTx.inputs) {
|
||||
newFrom = newFrom.concat(input.addresses);
|
||||
newFrom = newFrom.concat(input?.addresses ?? []);
|
||||
}
|
||||
for (const output of foundTx.outputs) {
|
||||
if (output.addresses) newTo = newTo.concat(output.addresses);
|
||||
if (output.scriptPubKey && output.scriptPubKey.addresses) newTo = newTo.concat(output.scriptPubKey.addresses);
|
||||
if (output?.scriptPubKey?.addresses) newTo = newTo.concat(output.scriptPubKey.addresses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const w of wallets) {
|
||||
for (const t of w.getTransactions()) {
|
||||
if (t.hash === hash) {
|
||||
console.log('tx', hash, 'belongs to', w.getLabel());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (txMetadata[foundTx.hash]) {
|
||||
if (txMetadata[foundTx.hash].memo) {
|
||||
setMemo(txMetadata[foundTx.hash].memo);
|
||||
assert(foundTx, 'Internal error: could not find transaction');
|
||||
|
||||
const wallet = wallets.find(w => w.getID() === walletID);
|
||||
assert(wallet, 'Internal error: could not find wallet');
|
||||
|
||||
if (wallet.allowBIP47() && wallet.isBIP47Enabled() && 'getBip47CounterpartyByTxid' in wallet) {
|
||||
const foundPaymentCode = wallet.getBip47CounterpartyByTxid(hash);
|
||||
if (foundPaymentCode) {
|
||||
// okay, this txid _was_ with someone using payment codes, so we show the label edit dialog
|
||||
// and load user-defined alias for the pc if any
|
||||
|
||||
setCounterpartyLabel(counterpartyMetadata ? counterpartyMetadata[foundPaymentCode]?.label ?? '' : '');
|
||||
setIsCounterpartyLabelVisible(true);
|
||||
setPaymentCode(foundPaymentCode);
|
||||
}
|
||||
}
|
||||
|
||||
setMemo(txMetadata[foundTx.hash]?.memo ?? '');
|
||||
setTX(foundTx);
|
||||
setFrom(newFrom);
|
||||
setTo(newTo);
|
||||
@ -131,7 +149,7 @@ const TransactionsDetails = () => {
|
||||
);
|
||||
|
||||
const handleOnOpenTransactionOnBlockExplorerTapped = () => {
|
||||
const url = `https://mempool.space/tx/${tx.hash}`;
|
||||
const url = `https://mempool.space/tx/${tx?.hash}`;
|
||||
Linking.canOpenURL(url)
|
||||
.then(supported => {
|
||||
if (supported) {
|
||||
@ -155,9 +173,9 @@ const TransactionsDetails = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleCopyPress = stringToCopy => {
|
||||
const handleCopyPress = (stringToCopy: string) => {
|
||||
Clipboard.setString(
|
||||
stringToCopy !== TransactionsDetails.actionKeys.CopyToClipboard ? stringToCopy : `https://mempool.space/tx/${tx.hash}`,
|
||||
stringToCopy !== TransactionDetails.actionKeys.CopyToClipboard ? stringToCopy : `https://mempool.space/tx/${tx?.hash}`,
|
||||
);
|
||||
};
|
||||
|
||||
@ -165,7 +183,7 @@ const TransactionsDetails = () => {
|
||||
return <BlueLoading />;
|
||||
}
|
||||
|
||||
const weOwnAddress = address => {
|
||||
const weOwnAddress = (address: string): TWallet | null => {
|
||||
for (const w of wallets) {
|
||||
if (w.weOwnAddress(address)) {
|
||||
return w;
|
||||
@ -174,30 +192,30 @@ const TransactionsDetails = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const navigateToWallet = wallet => {
|
||||
const walletID = wallet.getID();
|
||||
const navigateToWallet = (wallet: TWallet) => {
|
||||
// @ts-ignore idk how to fix it
|
||||
navigate('WalletTransactions', {
|
||||
walletID,
|
||||
walletID: wallet.getID(),
|
||||
walletType: wallet.type,
|
||||
});
|
||||
};
|
||||
|
||||
const renderSection = array => {
|
||||
const renderSection = (array: any[]) => {
|
||||
const fromArray = [];
|
||||
|
||||
for (const [index, address] of array.entries()) {
|
||||
const actions = [];
|
||||
actions.push({
|
||||
id: TransactionsDetails.actionKeys.CopyToClipboard,
|
||||
id: TransactionDetails.actionKeys.CopyToClipboard,
|
||||
text: loc.transactions.details_copy,
|
||||
icon: TransactionsDetails.actionIcons.Clipboard,
|
||||
icon: TransactionDetails.actionIcons.Clipboard,
|
||||
});
|
||||
const isWeOwnAddress = weOwnAddress(address);
|
||||
if (isWeOwnAddress) {
|
||||
actions.push({
|
||||
id: TransactionsDetails.actionKeys.GoToWallet,
|
||||
id: TransactionDetails.actionKeys.GoToWallet,
|
||||
text: loc.formatString(loc.transactions.view_wallet, { walletLabel: isWeOwnAddress.getLabel() }),
|
||||
icon: TransactionsDetails.actionIcons.GoToWallet,
|
||||
icon: TransactionDetails.actionIcons.GoToWallet,
|
||||
});
|
||||
}
|
||||
|
||||
@ -208,11 +226,11 @@ const TransactionsDetails = () => {
|
||||
title={address}
|
||||
isMenuPrimaryAction
|
||||
actions={actions}
|
||||
onPressMenuItem={id => {
|
||||
if (id === TransactionsDetails.actionKeys.CopyToClipboard) {
|
||||
onPressMenuItem={(id: string) => {
|
||||
if (id === TransactionDetails.actionKeys.CopyToClipboard) {
|
||||
handleCopyPress(address);
|
||||
} else if (id === TransactionsDetails.actionKeys.GoToWallet) {
|
||||
navigateToWallet(isWeOwnAddress);
|
||||
} else if (id === TransactionDetails.actionKeys.GoToWallet) {
|
||||
isWeOwnAddress && navigateToWallet(isWeOwnAddress);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@ -243,7 +261,19 @@ const TransactionsDetails = () => {
|
||||
style={[styles.memoTextInput, stylesHooks.memoTextInput]}
|
||||
onChangeText={setMemo}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
{isCounterpartyLabelVisible ? (
|
||||
<View>
|
||||
<BlueSpacing20 />
|
||||
<TextInput
|
||||
placeholder={loc.send.counterparty_label_placeholder}
|
||||
value={counterpartyLabel}
|
||||
placeholderTextColor="#81868e"
|
||||
style={[styles.memoTextInput, stylesHooks.memoTextInput]}
|
||||
onChangeText={setCounterpartyLabel}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{from && (
|
||||
@ -268,14 +298,6 @@ const TransactionsDetails = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{tx.fee && (
|
||||
<>
|
||||
<BlueText style={styles.rowCaption}>{loc.send.create_fee}</BlueText>
|
||||
<BlueText style={styles.rowValue}>{tx.fee + ` ${BitcoinUnit.SATS}`}</BlueText>
|
||||
<View style={styles.marginBottom18} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{tx.hash && (
|
||||
<>
|
||||
<View style={styles.rowHeader}>
|
||||
@ -295,14 +317,6 @@ const TransactionsDetails = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{tx.block_height > 0 && (
|
||||
<>
|
||||
<BlueText style={styles.rowCaption}>{loc.transactions.details_block}</BlueText>
|
||||
<BlueText style={styles.rowValue}>{tx.block_height}</BlueText>
|
||||
<View style={styles.marginBottom18} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{tx.inputs && (
|
||||
<>
|
||||
<BlueText style={styles.rowCaption}>{loc.transactions.details_inputs}</BlueText>
|
||||
@ -322,9 +336,9 @@ const TransactionsDetails = () => {
|
||||
isButton
|
||||
actions={[
|
||||
{
|
||||
id: TransactionsDetails.actionKeys.CopyToClipboard,
|
||||
id: TransactionDetails.actionKeys.CopyToClipboard,
|
||||
text: loc.transactions.copy_link,
|
||||
icon: TransactionsDetails.actionIcons.Clipboard,
|
||||
icon: TransactionDetails.actionIcons.Clipboard,
|
||||
},
|
||||
]}
|
||||
onPressMenuItem={handleCopyPress}
|
||||
@ -338,12 +352,12 @@ const TransactionsDetails = () => {
|
||||
);
|
||||
};
|
||||
|
||||
TransactionsDetails.actionKeys = {
|
||||
TransactionDetails.actionKeys = {
|
||||
CopyToClipboard: 'copyToClipboard',
|
||||
GoToWallet: 'goToWallet',
|
||||
};
|
||||
|
||||
TransactionsDetails.actionIcons = {
|
||||
TransactionDetails.actionIcons = {
|
||||
Clipboard: {
|
||||
iconType: 'SYSTEM',
|
||||
iconValue: 'doc.on.doc',
|
||||
@ -422,9 +436,9 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
export default TransactionsDetails;
|
||||
export default TransactionDetails;
|
||||
|
||||
TransactionsDetails.navigationOptions = navigationStyle({ headerTitle: loc.transactions.details_title }, (options, { theme }) => {
|
||||
TransactionDetails.navigationOptions = navigationStyle({ headerTitle: loc.transactions.details_title }, (options, { theme }) => {
|
||||
return {
|
||||
...options,
|
||||
statusBarStyle: 'auto',
|
@ -30,7 +30,7 @@ import presentAlert from '../../components/Alert';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import A from '../../blue_modules/analytics';
|
||||
import * as fs from '../../blue_modules/fs';
|
||||
import { TWallet, Transaction } from '../../class/wallets/types';
|
||||
import { TWallet, Transaction, ExtendedTransaction } from '../../class/wallets/types';
|
||||
import { useIsLargeScreen } from '../../hooks/useIsLargeScreen';
|
||||
import { Header } from '../../components/Header';
|
||||
|
||||
@ -263,11 +263,10 @@ const WalletsList: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const renderTransactionListsRow = (data: { item: Transaction }) => {
|
||||
const renderTransactionListsRow = (item: ExtendedTransaction) => {
|
||||
return (
|
||||
<View style={styles.transaction}>
|
||||
{/** @ts-ignore: Fix later **/}
|
||||
<TransactionListItem item={data.item} itemPriceUnit={data.item.walletPreferredBalanceUnit} walletID={data.item.walletID} />
|
||||
<TransactionListItem item={item} itemPriceUnit={item.walletPreferredBalanceUnit} walletID={item.walletID} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@ -289,13 +288,12 @@ const WalletsList: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderSectionItem = (item: { section?: any; item?: Transaction }) => {
|
||||
const renderSectionItem = (item: { section: any; item: ExtendedTransaction }) => {
|
||||
switch (item.section.key) {
|
||||
case WalletsListSections.CAROUSEL:
|
||||
return isLargeScreen ? null : renderWalletsCarousel();
|
||||
case WalletsListSections.TRANSACTIONS:
|
||||
/* @ts-ignore: fix later */
|
||||
return renderTransactionListsRow(item);
|
||||
return renderTransactionListsRow(item.item);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
InteractionManager,
|
||||
Keyboard,
|
||||
KeyboardAvoidingView,
|
||||
Linking,
|
||||
Platform,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
@ -18,11 +17,7 @@ import {
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import RNFS from 'react-native-fs';
|
||||
import { PERMISSIONS, RESULTS, request } from 'react-native-permissions';
|
||||
import Share from 'react-native-share';
|
||||
import { BlueCard, BlueLoading, BlueSpacing10, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import { isDesktop } from '../../blue_modules/environment';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import Notifications from '../../blue_modules/notifications';
|
||||
import { BlueStorageContext } from '../../blue_modules/storage-context';
|
||||
@ -51,6 +46,7 @@ import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
|
||||
import SaveFileButton from '../../components/SaveFileButton';
|
||||
import { useSettings } from '../../components/Context/SettingsContext';
|
||||
import { HeaderRightButton } from '../../components/HeaderRightButton';
|
||||
import { writeFileAndExport } from '../../blue_modules/fs';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scrollViewContent: {
|
||||
@ -332,46 +328,8 @@ const WalletDetails = () => {
|
||||
null,
|
||||
2,
|
||||
);
|
||||
if (Platform.OS === 'ios') {
|
||||
const filePath = RNFS.TemporaryDirectoryPath + `/${fileName}`;
|
||||
await RNFS.writeFile(filePath, contents);
|
||||
Share.open({
|
||||
url: 'file://' + filePath,
|
||||
saveToFiles: isDesktop,
|
||||
failOnCancel: false,
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
RNFS.unlink(filePath);
|
||||
});
|
||||
} else if (Platform.OS === 'android') {
|
||||
const granted = await request(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE);
|
||||
if (granted === RESULTS.GRANTED) {
|
||||
console.log('Storage Permission: Granted');
|
||||
const filePath = RNFS.DownloadDirectoryPath + `/${fileName}`;
|
||||
try {
|
||||
await RNFS.writeFile(filePath, contents);
|
||||
presentAlert({ message: loc.formatString(loc.send.txSaved, { filePath: fileName }) });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
presentAlert({ message: e.message });
|
||||
}
|
||||
} else {
|
||||
console.log('Storage Permission: Denied');
|
||||
Alert.alert(loc.send.permission_storage_title, loc.send.permission_storage_denied_message, [
|
||||
{
|
||||
text: loc.send.open_settings,
|
||||
onPress: () => {
|
||||
Linking.openSettings();
|
||||
},
|
||||
style: 'default',
|
||||
},
|
||||
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
await writeFileAndExport(fileName, contents, false);
|
||||
};
|
||||
|
||||
const purgeTransactions = async () => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import assert from 'assert';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { SegwitP2SHWallet, BlueApp } from '../../class';
|
||||
import { SegwitP2SHWallet, BlueApp, HDSegwitBech32Wallet } from '../../class';
|
||||
|
||||
jest.mock('../../blue_modules/BlueElectrum', () => {
|
||||
return {
|
||||
@ -15,6 +15,16 @@ it('Appstorage - loadFromDisk works', async () => {
|
||||
w.setLabel('testlabel');
|
||||
await w.generate();
|
||||
Storage.wallets.push(w);
|
||||
Storage.tx_metadata = {
|
||||
txid: {
|
||||
memo: 'tx label',
|
||||
},
|
||||
};
|
||||
Storage.counterparty_metadata = {
|
||||
'payment code': {
|
||||
label: 'yegor letov',
|
||||
},
|
||||
};
|
||||
await Storage.saveToDisk();
|
||||
|
||||
// saved, now trying to load
|
||||
@ -23,6 +33,8 @@ it('Appstorage - loadFromDisk works', async () => {
|
||||
await Storage2.loadFromDisk();
|
||||
assert.strictEqual(Storage2.wallets.length, 1);
|
||||
assert.strictEqual(Storage2.wallets[0].getLabel(), 'testlabel');
|
||||
assert.strictEqual(Storage2.tx_metadata.txid.memo, 'tx label');
|
||||
assert.strictEqual(Storage2.counterparty_metadata['payment code'].label, 'yegor letov');
|
||||
let isEncrypted = await Storage2.storageIsEncrypted();
|
||||
assert.ok(!isEncrypted);
|
||||
|
||||
@ -35,6 +47,156 @@ it('Appstorage - loadFromDisk works', async () => {
|
||||
assert.ok(isEncrypted);
|
||||
});
|
||||
|
||||
it('AppStorage - getTransactions() work', async () => {
|
||||
const Storage = new BlueApp();
|
||||
const w = new HDSegwitBech32Wallet();
|
||||
w.setLabel('testlabel');
|
||||
await w.generate();
|
||||
w._txs_by_internal_index = {
|
||||
0: [
|
||||
{
|
||||
blockhash: '000000000000000000054fae1935a8e5c3ac29ce04a45cca25d7329af5e5db2e',
|
||||
blocktime: 1678137003,
|
||||
confirmations: 61788,
|
||||
hash: '73a2ac70858c5b306b101a861d582f40c456a692096a4e4805aa739258c4400d',
|
||||
locktime: 0,
|
||||
size: 192,
|
||||
time: 1678137003,
|
||||
txid: '73a2ac70858c5b306b101a861d582f40c456a692096a4e4805aa739258c4400d',
|
||||
version: 1,
|
||||
vsize: 110,
|
||||
weight: 438,
|
||||
inputs: [
|
||||
{
|
||||
scriptSig: {
|
||||
asm: '',
|
||||
hex: '',
|
||||
},
|
||||
sequence: 4294967295,
|
||||
txid: '06b4c14587182fd0474f265a77b156519b4778769a99c21623863a8194d0fa4f',
|
||||
txinwitness: [
|
||||
'3045022100f2dfd9679719a5b10695c5142cb2998c0dde9d84fb3a0f6e2f82c972846da2b10220059c34862231eda0b8b4059859ae55e2fca5739c664f3ff45be71fbcf438a68d01',
|
||||
'034f150e09d0489a047b1299131180ce174769b28c03ca6a96054555211fdd7fd6',
|
||||
],
|
||||
vout: 3,
|
||||
addresses: ['bc1qtnsyvl8zkteg7ap57j6w8hc7gk5nxk8vj5vrmz'],
|
||||
value: 0.00077308,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
n: 0,
|
||||
scriptPubKey: {
|
||||
address: 'bc1qaxxc4gwx6rd6rymq08qwpxhesd4jqu93lvjsyt',
|
||||
asm: '0 e98d8aa1c6d0dba1936079c0e09af9836b2070b1',
|
||||
desc: 'addr(bc1qaxxc4gwx6rd6rymq08qwpxhesd4jqu93lvjsyt)#pl83f4nc',
|
||||
hex: '0014e98d8aa1c6d0dba1936079c0e09af9836b2070b1',
|
||||
type: 'witness_v0_keyhash',
|
||||
addresses: ['bc1qaxxc4gwx6rd6rymq08qwpxhesd4jqu93lvjsyt'],
|
||||
},
|
||||
value: 0.00074822,
|
||||
},
|
||||
],
|
||||
received: 1678137003000,
|
||||
value: -77308,
|
||||
sort_ts: 1678137003000,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const w2 = new HDSegwitBech32Wallet();
|
||||
w2.setLabel('testlabel');
|
||||
await w2.generate();
|
||||
w2._txs_by_internal_index = {
|
||||
0: [
|
||||
{
|
||||
blockhash: '000000000000000000054fae1935a8e5c3ac29ce04a45cca25d7329af5e5db2e',
|
||||
blocktime: 1678137003,
|
||||
confirmations: 61788,
|
||||
hash: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||
locktime: 0,
|
||||
size: 192,
|
||||
time: 1678137003,
|
||||
txid: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
|
||||
version: 1,
|
||||
vsize: 110,
|
||||
weight: 438,
|
||||
inputs: [
|
||||
{
|
||||
scriptSig: {
|
||||
asm: '',
|
||||
hex: '',
|
||||
},
|
||||
sequence: 4294967295,
|
||||
txid: '06b4c14587182fd0474f265a77b156519b4778769a99c21623863a8194d0fa4f',
|
||||
txinwitness: [
|
||||
'3045022100f2dfd9679719a5b10695c5142cb2998c0dde9d84fb3a0f6e2f82c972846da2b10220059c34862231eda0b8b4059859ae55e2fca5739c664f3ff45be71fbcf438a68d01',
|
||||
'034f150e09d0489a047b1299131180ce174769b28c03ca6a96054555211fdd7fd6',
|
||||
],
|
||||
vout: 3,
|
||||
addresses: ['bc1qtnsyvl8zkteg7ap57j6w8hc7gk5nxk8vj5vrmz'],
|
||||
value: 0.00077308,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
n: 0,
|
||||
scriptPubKey: {
|
||||
address: 'bc1qaxxc4gwx6rd6rymq08qwpxhesd4jqu93lvjsyt',
|
||||
asm: '0 e98d8aa1c6d0dba1936079c0e09af9836b2070b1',
|
||||
desc: 'addr(bc1qaxxc4gwx6rd6rymq08qwpxhesd4jqu93lvjsyt)#pl83f4nc',
|
||||
hex: '0014e98d8aa1c6d0dba1936079c0e09af9836b2070b1',
|
||||
type: 'witness_v0_keyhash',
|
||||
addresses: ['bc1qaxxc4gwx6rd6rymq08qwpxhesd4jqu93lvjsyt'],
|
||||
},
|
||||
value: 0.00074822,
|
||||
},
|
||||
],
|
||||
received: 1678137003000,
|
||||
value: -77308,
|
||||
sort_ts: 1678137003000,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
Storage.wallets.push(w);
|
||||
Storage.wallets.push(w2);
|
||||
|
||||
// setup complete. now we have a storage with 2 wallets, each wallet has
|
||||
// exactly one transaction
|
||||
|
||||
let txs = Storage.getTransactions();
|
||||
assert.strictEqual(txs.length, 2); // getter for _all_ txs works
|
||||
|
||||
for (const tx of txs) {
|
||||
assert.ok([w.getID(), w2.getID()].includes(tx.walletID));
|
||||
assert.strictEqual(tx.walletPreferredBalanceUnit, w.getPreferredBalanceUnit());
|
||||
assert.strictEqual(tx.walletPreferredBalanceUnit, 'BTC');
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
txs = Storage.getTransactions(0, 666, true);
|
||||
assert.strictEqual(txs.length, 1); // getter for a specific wallet works
|
||||
|
||||
for (const tx of txs) {
|
||||
assert.ok([w.getID()].includes(tx.walletID));
|
||||
assert.strictEqual(tx.walletPreferredBalanceUnit, w.getPreferredBalanceUnit());
|
||||
assert.strictEqual(tx.walletPreferredBalanceUnit, 'BTC');
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
txs = Storage.getTransactions(1, 666, true);
|
||||
assert.strictEqual(txs.length, 1); // getter for a specific wallet works
|
||||
|
||||
for (const tx of txs) {
|
||||
assert.ok([w2.getID()].includes(tx.walletID));
|
||||
assert.strictEqual(tx.walletPreferredBalanceUnit, w.getPreferredBalanceUnit());
|
||||
assert.strictEqual(tx.walletPreferredBalanceUnit, 'BTC');
|
||||
}
|
||||
});
|
||||
|
||||
it('Appstorage - encryptStorage & load encrypted storage works', async () => {
|
||||
/** @type {BlueApp} */
|
||||
const Storage = new BlueApp();
|
||||
|
Loading…
Reference in New Issue
Block a user