Compare commits

...

1 Commits
master ... ts

Author SHA1 Message Date
Ivan Vershigora
fa876bfaba fix: improve typescript coverage 2026-06-11 18:16:25 +01:00
15 changed files with 59 additions and 139 deletions

View File

@ -147,11 +147,10 @@ export class BlueApp {
console.warn('error reading', key, error.message);
console.warn('fallback to realm');
const realmKeyValue = await this.openRealmKeyValue();
const obj = realmKeyValue.objectForPrimaryKey('KeyValue', key); // search for a realm object with a primary key
const obj = realmKeyValue.objectForPrimaryKey<{ key: string; value: string }>('KeyValue', key);
value = obj?.value;
realmKeyValue.close();
if (value) {
// @ts-ignore value.length
console.warn('successfully recovered', value.length, 'bytes from realm for key', key);
return value;
}
@ -547,10 +546,11 @@ export class BlueApp {
(walletToInflate._txs_by_internal_index[tx.index] as Transaction[]).push(transaction);
}
} else {
if (!Array.isArray(walletToInflate._txs_by_external_index)) walletToInflate._txs_by_external_index = [];
walletToInflate._txs_by_external_index = walletToInflate._txs_by_external_index || [];
// Legacy single-address wallets - store under index 0
walletToInflate._txs_by_external_index = walletToInflate._txs_by_external_index || {};
walletToInflate._txs_by_external_index[0] = walletToInflate._txs_by_external_index[0] || [];
const transaction = JSON.parse(tx.tx);
(walletToInflate._txs_by_external_index as Transaction[]).push(transaction);
walletToInflate._txs_by_external_index[0].push(transaction);
}
}
}
@ -559,32 +559,6 @@ export class BlueApp {
const id = wallet.getID();
const walletToSave = ('_hdWalletInstance' in wallet && wallet._hdWalletInstance) || wallet;
if (Array.isArray(walletToSave._txs_by_external_index)) {
// if this var is an array that means its a single-address wallet class, and this var is a flat array
// with transactions
realm.write(() => {
// cleanup all existing transactions for the wallet first
const walletTransactionsToDelete = realm.objects('WalletTransactions').filtered(`walletid = '${id}'`);
realm.delete(walletTransactionsToDelete);
// @ts-ignore walletToSave._txs_by_external_index is array
for (const tx of walletToSave._txs_by_external_index) {
realm.create(
'WalletTransactions',
{
walletid: id,
tx: JSON.stringify(tx),
},
Realm.UpdateMode.Modified,
);
}
});
return;
}
/// ########################################################################################################
if (walletToSave._txs_by_external_index) {
realm.write(() => {
// cleanup all existing transactions for the wallet first
@ -592,16 +566,14 @@ export class BlueApp {
realm.delete(walletTransactionsToDelete);
// insert new ones:
for (const index of Object.keys(walletToSave._txs_by_external_index)) {
// @ts-ignore index is number
const txs = walletToSave._txs_by_external_index[index];
for (const [indexStr, txs] of Object.entries(walletToSave._txs_by_external_index)) {
for (const tx of txs) {
realm.create(
'WalletTransactions',
{
walletid: id,
internal: false,
index: parseInt(index, 10),
index: parseInt(indexStr, 10),
tx: JSON.stringify(tx),
},
Realm.UpdateMode.Modified,
@ -609,16 +581,14 @@ export class BlueApp {
}
}
for (const index of Object.keys(walletToSave._txs_by_internal_index)) {
// @ts-ignore index is number
const txs = walletToSave._txs_by_internal_index[index];
for (const [indexStr, txs] of Object.entries(walletToSave._txs_by_internal_index)) {
for (const tx of txs) {
realm.create(
'WalletTransactions',
{
walletid: id,
internal: true,
index: parseInt(index, 10),
index: parseInt(indexStr, 10),
tx: JSON.stringify(tx),
},
Realm.UpdateMode.Modified,

View File

@ -390,7 +390,7 @@ export class HDSegwitBech32Transaction {
}
}
// @ts-ignore stfu
return { tx, inputs, outputs, fee };
// Non-null assertions are safe here because the while loop always runs at least once (add starts at 0)
return { tx: tx!, inputs: inputs!, outputs: outputs!, fee: fee! };
}
}

View File

@ -11,7 +11,7 @@
* @return {Promise.<Uint8Array>} The random bytes
*/
export async function randomBytes(size: number): Promise<Uint8Array> {
const g: any = globalThis as any;
const g = globalThis as any;
const rnCrypto = g && g.crypto;
if (!rnCrypto || typeof rnCrypto.getRandomValues !== 'function') {
throw new Error('crypto.getRandomValues is not available');

View File

@ -45,9 +45,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
_balances_by_external_index: Record<number, BalanceByIndex>;
_balances_by_internal_index: Record<number, BalanceByIndex>;
// @ts-ignore
_txs_by_external_index: Record<number, Transaction[]>;
// @ts-ignore
_txs_by_internal_index: Record<number, Transaction[]>;
_utxo: any[];
@ -204,70 +202,37 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
return child.toWIF();
}
_getNodeAddressByIndex(node: number, index: number): string {
index = index * 1; // cast to int
_getNodeByIndex(node: 0 | 1, index: number): BIP32Interface {
const cachedNode = node === 0 ? this._node0 : this._node1;
if (cachedNode) {
return cachedNode.derive(index);
}
const xpub = this._zpubToXpub(this.getXpub());
const hdNode = bip32.fromBase58(xpub).derive(node);
if (node === 0) {
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
}
if (node === 1) {
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
}
if (node === 0 && !this._node0) {
const xpub = this._zpubToXpub(this.getXpub());
const hdNode = bip32.fromBase58(xpub);
this._node0 = hdNode.derive(node);
}
if (node === 1 && !this._node1) {
const xpub = this._zpubToXpub(this.getXpub());
const hdNode = bip32.fromBase58(xpub);
this._node1 = hdNode.derive(node);
}
let address: string;
if (node === 0) {
// @ts-ignore
address = this._hdNodeToAddress(this._node0.derive(index));
this._node0 = hdNode;
} else {
// tbh the only possible else is node === 1
// @ts-ignore
address = this._hdNodeToAddress(this._node1.derive(index));
this._node1 = hdNode;
}
if (node === 0) {
return (this.external_addresses_cache[index] = address);
} else {
// tbh the only possible else option is node === 1
return (this.internal_addresses_cache[index] = address);
}
return hdNode.derive(index);
}
_getNodePubkeyByIndex(node: number, index: number) {
index = index * 1; // cast to int
_getNodeAddressByIndex(node: 0 | 1, index: number): string {
const cache = node === 0 ? this.external_addresses_cache : this.internal_addresses_cache;
if (node === 0 && !this._node0) {
const xpub = this._zpubToXpub(this.getXpub());
const hdNode = bip32.fromBase58(xpub);
this._node0 = hdNode.derive(node);
}
if (cache[index]) return cache[index]; // cache hit
if (node === 1 && !this._node1) {
const xpub = this._zpubToXpub(this.getXpub());
const hdNode = bip32.fromBase58(xpub);
this._node1 = hdNode.derive(node);
}
const hdNode = this._getNodeByIndex(node, index);
const address = this._hdNodeToAddress(hdNode);
if (node === 0 && this._node0) {
return this._node0.derive(index).publicKey;
}
return (cache[index] = address);
}
if (node === 1 && this._node1) {
return this._node1.derive(index).publicKey;
}
throw new Error('Internal error: this._node0 or this._node1 is undefined');
_getNodePubkeyByIndex(node: 0 | 1, index: number) {
return this._getNodeByIndex(node, index).publicKey;
}
_getExternalAddressByIndex(index: number): string {
@ -610,8 +575,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
let lastHistoriesWithUsedAddresses = null;
for (let c = 0; c < Math.round(index / this.gap_limit); c++) {
const histories = await BlueElectrum.multiGetHistoryByAddress(gerenateChunkAddresses(c));
// @ts-ignore
if (this.constructor._getTransactionsFromHistories(histories).length > 0) {
if (AbstractHDElectrumWallet._getTransactionsFromHistories(histories).length > 0) {
// in this particular chunk we have used addresses
lastChunkWithUsedAddressesNum = c;
lastHistoriesWithUsedAddresses = histories;
@ -653,8 +617,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
let lastHistoriesWithUsedAddresses = null;
for (let c = 0; c < Math.round(index / this.gap_limit); c++) {
const histories = await BlueElectrum.multiGetHistoryByAddress(gerenateChunkAddresses(c));
// @ts-ignore
if (this.constructor._getTransactionsFromHistories(histories).length > 0) {
if (AbstractHDElectrumWallet._getTransactionsFromHistories(histories).length > 0) {
// in this particular chunk we have used addresses
lastChunkWithUsedAddressesNum = c;
lastHistoriesWithUsedAddresses = histories;
@ -696,8 +659,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
let lastHistoriesWithUsedAddresses = null;
for (let c = 0; c < Math.round(index / this.gap_limit); c++) {
const histories = await BlueElectrum.multiGetHistoryByAddress(generateChunkAddresses(c));
// @ts-ignore
if (this.constructor._getTransactionsFromHistories(histories).length > 0) {
if (AbstractHDElectrumWallet._getTransactionsFromHistories(histories).length > 0) {
// in this particular chunk we have used addresses
lastChunkWithUsedAddressesNum = c;
lastHistoriesWithUsedAddresses = histories;

View File

@ -315,7 +315,7 @@ export class AbstractHDWallet extends LegacyWallet {
throw new Error('Not implemented');
}
_getNodePubkeyByIndex(node: number, index: number): Uint8Array | undefined {
_getNodePubkeyByIndex(node: 0 | 1, index: number): Uint8Array | undefined {
throw new Error('Not implemented');
}

View File

@ -27,8 +27,8 @@ export class LegacyWallet extends AbstractWallet {
// @ts-ignore: override
public readonly typeReadable: string;
_txs_by_external_index: Transaction[] = [];
_txs_by_internal_index: Transaction[] = [];
_txs_by_external_index: Record<number, Transaction[]> = {};
_txs_by_internal_index: Record<number, Transaction[]> = {};
constructor(typeReadable?: string) {
super();
@ -344,14 +344,14 @@ export class LegacyWallet extends AbstractWallet {
}
}
this._txs_by_external_index = _txsByExternalIndex;
this._txs_by_external_index = { 0: _txsByExternalIndex };
this._lastTxFetch = +new Date();
}
getTransactions(): Transaction[] {
// a hacky code reuse from electrum HD wallet:
this._txs_by_external_index = this._txs_by_external_index || [];
this._txs_by_internal_index = [];
this._txs_by_external_index = this._txs_by_external_index || {};
this._txs_by_internal_index = {};
const { HDSegwitBech32Wallet } = require('./hd-segwit-bech32-wallet') as {
HDSegwitBech32Wallet: typeof HDSegwitBech32WalletT;

View File

@ -515,15 +515,7 @@ interface WalletsCarouselProps extends Partial<FlatListProps<any>> {
animateChanges?: boolean;
}
type FlatListRefType = FlatList<any> & {
scrollToEnd(params?: { animated?: boolean | null }): void;
scrollToIndex(params: { animated?: boolean | null; index: number; viewOffset?: number; viewPosition?: number }): void;
scrollToItem(params: { animated?: boolean | null; item: TWallet; viewPosition?: number }): void;
scrollToOffset(params: { animated?: boolean | null; offset: number }): void;
recordInteraction(): void;
flashScrollIndicators(): void;
getNativeScrollRef(): View;
};
export type CarouselListRefType = FlatList<TWallet>;
const styles = StyleSheet.create({
listHeaderSeparator: {
@ -534,7 +526,7 @@ const styles = StyleSheet.create({
const ListHeaderSeparator = () => <View style={styles.listHeaderSeparator} />;
const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props, ref) => {
const WalletsCarousel = forwardRef<CarouselListRefType, WalletsCarouselProps>((props, ref) => {
const {
horizontal = true,
data,
@ -569,7 +561,7 @@ const WalletsCarousel = forwardRef<FlatListRefType, WalletsCarouselProps>((props
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const isInitialMount = useRef(true);
const flatListRef = useRef<FlatList<any>>(null);
const flatListRef = useRef<FlatList<TWallet>>(null);
const walletRefs = useRef<Record<string, React.MutableRefObject<View | null>>>({});
const { sizeClass } = useSizeClass();

View File

@ -46,7 +46,7 @@ const LNDViewInvoice = () => {
const [isFetchingInvoices, setIsFetchingInvoices] = useState<boolean>(true);
const [invoiceStatusChanged, setInvoiceStatusChanged] = useState<boolean>(false);
const [qrCodeSize, setQRCodeSize] = useState<number>(90);
const fetchInvoiceInterval = useRef<any>(null);
const fetchInvoiceInterval = useRef<ReturnType<typeof setInterval> | undefined>(undefined);
const isModal = useNavigationState(state => state.routeNames[0] === LNDCreateInvoice.routeName);
// Per-swap claim/refund lookup, by the `swap-${id}` prefix mapped onto
@ -179,7 +179,6 @@ const LNDViewInvoice = () => {
fetchInvoiceInterval.current = setInterval(async () => {
if (isFetchingInvoices) {
try {
// @ts-ignore - getUserInvoices is not set on TWallet
const userInvoices: LightningTransaction[] = await wallet.getUserInvoices(20);
// fetching only last 20 invoices
// for invoice that was created just now - that should be enough (it is basically the last one, so limit=1 would be sufficient)

View File

@ -1,7 +1,7 @@
import { RouteProp, StackActions, useIsFocused, useRoute } from '@react-navigation/native';
import * as bitcoin from 'bitcoinjs-lib';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { ActivityIndicator, ScrollView, StyleSheet, View } from 'react-native';
import { ActivityIndicator, ScrollView, StyleSheet, View, TouchableOpacity } from 'react-native';
import presentAlert from '../../components/Alert';
import { DynamicQRCode } from '../../components/DynamicQRCode';
import SaveFileButton from '../../components/SaveFileButton';
@ -23,7 +23,7 @@ type RouteParams = RouteProp<SendDetailsStackParamList, 'PsbtMultisigQRCode'>;
const PsbtMultisigQRCode: React.FC = () => {
const navigation = useExtendedNavigation();
const { colors } = useTheme();
const openScannerButton = useRef<any>(null);
const openScannerButton = useRef<React.ElementRef<typeof TouchableOpacity>>(null);
const { params } = useRoute<RouteParams>();
const { psbtBase64, isShowOpenScanner, walletID } = params;
const [isLoading, setIsLoading] = useState<boolean>(false);

View File

@ -91,7 +91,7 @@ const SendDetails = () => {
const payjoinUrl = route.params?.payjoinUrl;
const isTransactionReplaceable = route.params?.isTransactionReplaceable;
const routeParams = route.params;
const scrollView = useRef<FlatList<any>>(null);
const scrollView = useRef<FlatList<IPaymentDestinations>>(null);
const scrollIndex = useRef(0);
/** Used so we only clear coin-selection (utxos) when the user switches wallet, not on first mount (e.g. Send opened from wallet details with pre-selected UTXOs). */
const prevWalletIdForCoinResetRef = useRef<string | null>(null);
@ -221,9 +221,6 @@ const SendDetails = () => {
}
return updatedAddresses;
});
// @ts-ignore: Fix later
setParams(prevParams => ({ ...prevParams, addRecipientParams: undefined }));
} else {
setAddresses([{ address: '', key: String(Math.random()), unit: amountUnit }]); // key is for the FlatList
}

View File

@ -5,7 +5,7 @@ import { StyleSheet, View, ViewStyle, Animated, ScrollView } from 'react-native'
import { TWallet } from '../../class/wallets/types';
import { Header } from '../../components/Header';
import { useTheme } from '../../components/themes';
import WalletsCarousel from '../../components/WalletsCarousel';
import WalletsCarousel, { CarouselListRefType } from '../../components/WalletsCarousel';
import loc from '../../loc';
import { useStorage } from '../../hooks/context/useStorage';
import TotalWalletsBalance from '../../components/TotalWalletsBalance';
@ -94,7 +94,7 @@ const DrawerList: React.FC<DrawerContentComponentProps> = memo((props: DrawerCon
const drawerNavigation = props.navigation;
const [state, dispatch] = useReducer(walletReducer, initialState);
const walletsCarousel = useRef<any>(null);
const walletsCarousel = useRef<CarouselListRefType>(null);
const { wallets, selectedWalletID } = useStorage();
const { colors } = useTheme();
const isFocused = useIsFocused();

View File

@ -78,7 +78,7 @@ const ExportMultisigCoordinationSetup: React.FC = () => {
const { wallets } = useStorage();
const { isPrivacyBlurEnabled } = useSettings();
const wallet: TWallet | undefined = wallets.find(w => w.getID() === walletID);
const dynamicQRCode = useRef<any>(null);
const dynamicQRCode = useRef<DynamicQRCode>(null);
const { colors } = useTheme();
const { enableScreenProtect, disableScreenProtect } = useScreenProtect();

View File

@ -8,7 +8,7 @@ import { useTheme } from '../../components/themes';
import loc from '../../loc';
import { Chain } from '../../models/bitcoinUnits';
import { useStorage } from '../../hooks/context/useStorage';
import WalletsCarousel from '../../components/WalletsCarousel';
import WalletsCarousel, { CarouselListRefType } from '../../components/WalletsCarousel';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import { TWallet } from '../../class/wallets/types';
import { pop } from '../../NavigationService';
@ -35,7 +35,7 @@ const SelectWallet: React.FC = () => {
const { wallets } = useStorage();
const { colors } = useTheme();
const isModal = useNavigationState(state => state.routes.length > 1);
const walletsCarousel = useRef<any>(null);
const walletsCarousel = useRef<CarouselListRefType>(null);
const previousRouteName = useNavigationState(state => state.routes[state.routes.length - 2]?.name);
const [filteredWallets, setFilteredWallets] = useState<TWallet[]>([]);

View File

@ -82,7 +82,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }: { rout
const [displayUnit, setDisplayUnit] = useState(wallet.preferredBalanceUnit);
const [isUnitSwitching, setIsUnitSwitching] = useState(false);
const [isWatchOnlyWarningVisible, setIsWatchOnlyWarningVisible] = useState<boolean>(() => {
return wallet.type === WatchOnlyWallet.type && (wallet as any).isWatchOnlyWarningVisible;
return wallet.type === WatchOnlyWallet.type && (wallet as WatchOnlyWallet).isWatchOnlyWarningVisible;
});
const MAX_FAILURES = 3;
const flatListRef = useRef<FlatList<Transaction>>(null);
@ -172,7 +172,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }: { rout
}, [wallet, walletID]);
useEffect(() => {
setIsWatchOnlyWarningVisible(wallet.type === WatchOnlyWallet.type && (wallet as any).isWatchOnlyWarningVisible);
setIsWatchOnlyWarningVisible(wallet.type === WatchOnlyWallet.type && (wallet as WatchOnlyWallet).isWatchOnlyWarningVisible);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [walletID]);
@ -547,7 +547,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }: { rout
if ('setPreferredBalanceUnit' in wallet) {
wallet.setPreferredBalanceUnit(selectedUnit);
} else {
(wallet as any).preferredBalanceUnit = selectedUnit;
(wallet as TWallet).preferredBalanceUnit = selectedUnit;
}
await saveToDisk();
console.debug('[UnitSwitch] persisted preferred unit', { walletID, unit: selectedUnit });

View File

@ -11,7 +11,7 @@ import presentAlert from '../../components/Alert';
import { FButton, FContainer, FloatButtonsBottomFade } from '../../components/FloatButtons';
import { useTheme } from '../../components/themes';
import { TransactionListItem } from '../../components/TransactionListItem';
import WalletsCarousel, { getWalletCarouselItemWidth } from '../../components/WalletsCarousel';
import WalletsCarousel, { getWalletCarouselItemWidth, CarouselListRefType } from '../../components/WalletsCarousel';
import { useSizeClass, SizeClass } from '../../blue_modules/sizeClass';
import loc from '../../loc';
import ActionSheet from '../ActionSheet';
@ -101,7 +101,7 @@ const WalletsList: React.FC = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const { isLoading } = state;
const { sizeClass, isLarge } = useSizeClass();
const walletsCarousel = useRef<any>(null);
const walletsCarousel = useRef<CarouselListRefType>(null);
const connectionPoll = useContext(ConnectionPollContext);
const currentWalletIndex = useRef<number>(0);
const { registerTransactionsHandler, unregisterTransactionsHandler } = useMenuElements();