Merge branch 'master' into renovate/react-native-permissions-5.x

This commit is contained in:
Overtorment 2026-06-15 16:57:12 +01:00 committed by GitHub
commit a50b50496f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 122 additions and 174 deletions

View File

@ -194,9 +194,6 @@ jobs:
mkdir -p ios/build/Build/Products/Release-iphonesimulator
tar -xzf BlueWallet.app.tar.gz -C ios/build/Build/Products/Release-iphonesimulator
- name: Disable simulator animations
run: defaults write com.apple.iphonesimulator SlowMotionAnimation -bool NO
# Pre-boot simulator so first detox launchApp lands warm.
- name: Pre-boot iOS simulator
run: |
@ -210,6 +207,13 @@ jobs:
xcrun simctl bootstatus "$UDID" -b
xcrun simctl launch "$UDID" com.apple.springboard >/dev/null 2>&1 || true
# Cut animations so detox sync stays steady on slow CI VMs; Reduce Motion makes reanimated skip to final value.
- name: Disable simulator animations
run: |
defaults write com.apple.iphonesimulator SlowMotionAnimation -bool NO
xcrun simctl spawn booted defaults write com.apple.Accessibility ReduceMotionEnabled -bool true
xcrun simctl spawn booted notifyutil -p com.apple.Accessibility.ReduceMotionStatusDidChange
- name: Run detox tests
timeout-minutes: 360
run: |

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

@ -1,5 +1,5 @@
PODS:
- BugsnagReactNative (8.8.1):
- BugsnagReactNative (8.9.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@ -29,7 +29,7 @@ PODS:
- hermes-engine/Pre-built (= 250829098.0.10)
- hermes-engine/Pre-built (250829098.0.10)
- lottie-ios (4.6.0)
- lottie-react-native (7.3.7):
- lottie-react-native (7.3.8):
- hermes-engine
- lottie-ios (= 4.6.0)
- RCTRequired
@ -2018,6 +2018,8 @@ PODS:
- ReactNativeDependencies (0.85.3)
- RealmJS (20.2.0):
- React
- RNBackgroundFetch (4.2.9):
- React-Core
- RNCAsyncStorage (2.2.0):
- hermes-engine
- RCTRequired
@ -2543,6 +2545,7 @@ DEPENDENCIES:
- ReactNativeCameraKit (from `../node_modules/react-native-camera-kit-no-google`)
- ReactNativeDependencies (from `../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`)
- RealmJS (from `../node_modules/realm`)
- RNBackgroundFetch (from `../node_modules/react-native-background-fetch`)
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
- RNDefaultPreference (from `../node_modules/react-native-default-preference`)
@ -2762,6 +2765,8 @@ EXTERNAL SOURCES:
:podspec: "../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec"
RealmJS:
:path: "../node_modules/realm"
RNBackgroundFetch:
:path: "../node_modules/react-native-background-fetch"
RNCAsyncStorage:
:path: "../node_modules/@react-native-async-storage/async-storage"
RNCClipboard:
@ -2802,13 +2807,13 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
BugsnagReactNative: bee770e3f497a8571feb1579bdc083a070bee1f3
BugsnagReactNative: 73ce58aac04585e7cba3081c0abba06d848d62fc
BVLinearGradient: cb006ba232a1f3e4f341bb62c42d1098c284da70
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
FBLazyVector: 24e62c765683b8d89006a88a2c8f5cf019f0074d
hermes-engine: 86cdbf283775c54dc008895c3eacd24a1f2a40b4
hermes-engine: 4ed74710a31e8e31f20356c641eab1d8f7d54595
lottie-ios: 8f959969761e9c45d70353667d00af0e5b9cadb3
lottie-react-native: 26b365c3d5615e87f4db048dcb151de3eb9a8e76
lottie-react-native: ee142214581f3bb68fbda7efcf07b835a189eeda
RCTDeprecation: a4c521821fab57cbb125b36effe84d897d0dfa12
RCTRequired: 9f3a7e5645d4bc3f551593de7550bb66ab6e42bc
RCTSwiftUI: 239ed2eb9e73de5a6f518810630f0c95e01c8702
@ -2817,7 +2822,7 @@ SPEC CHECKSUMS:
React: e2dc35338068bbd299c66f043ae0d7f25de8499e
React-callinvoker: 28b25d21b124c26cebaea713ba7d801b9351dc48
React-Core: 02ed7d2ffb70437bdf2aba074a13078a7b0b9ff0
React-Core-prebuilt: 9e875134f667c471ab68bf9edf1661fa11b86540
React-Core-prebuilt: 3445f1028d9b206cd45c8bbb7e2427ee891f810e
React-CoreModules: b3a5a42dadcde3b5d47b325bd912eb2ced89e146
React-cxxreact: fe8f88dda044e5905e99a00f41b7a874c3908716
React-debug: 92944dc4d89f56d640e75498266cbde557a48189
@ -2898,8 +2903,9 @@ SPEC CHECKSUMS:
ReactCodegen: 1bd7f2174582b0e142f8671735b5c906c08b72ea
ReactCommon: 7dfc3250793bf36cf221096ff59e1179e13eef7f
ReactNativeCameraKit: 5974256fc608631c1c812710cd98abe95dae0f88
ReactNativeDependencies: 0a5c93845772e4b1c5ad065c59a859518b13a6b7
ReactNativeDependencies: 75299c281f422106c723e79dc1f6ce7ef03241be
RealmJS: 1c37c6bdfe060f4caa0f9175aa0eedb962622ee1
RNBackgroundFetch: 64b1215fbb8ec58afba877ca0ce177e009ce12b7
RNCAsyncStorage: 2ad919e88b8bc2cd80e8697ce66d04d006743283
RNCClipboard: 715fa7c6c8366f17d00f05a439ee7488f390fa5f
RNDefaultPreference: 8a089ee8ce829a66c5453e3c5434f0785499d1c3

28
package-lock.json generated
View File

@ -10,8 +10,8 @@
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@arkade-os/boltz-swap": "0.3.38",
"@arkade-os/sdk": "0.4.33",
"@arkade-os/boltz-swap": "0.3.40",
"@arkade-os/sdk": "0.4.35",
"@babel/preset-env": "7.29.5",
"@bugsnag/react-native": "8.9.0",
"@bugsnag/source-maps": "2.3.3",
@ -25,7 +25,7 @@
"@react-native-community/cli-platform-android": "20.1.3",
"@react-native-community/cli-platform-ios": "20.1.3",
"@react-native-documents/picker": "12.0.1",
"@react-native-vector-icons/entypo": "13.1.1",
"@react-native-vector-icons/entypo": "13.1.2",
"@react-native-vector-icons/fontawesome": "13.1.2",
"@react-native-vector-icons/fontawesome6": "13.1.2",
"@react-native-vector-icons/ionicons": "13.1.2",
@ -179,12 +179,12 @@
}
},
"node_modules/@arkade-os/boltz-swap": {
"version": "0.3.38",
"resolved": "https://registry.npmjs.org/@arkade-os/boltz-swap/-/boltz-swap-0.3.38.tgz",
"integrity": "sha512-BVbyw9Fj+1eQn771t0ZO9uW7E1BgViAPLFddb4pnW9p3rM9fCIdWEs2ZrjPnq70leDdhrUxRy++cJuK7zFThuA==",
"version": "0.3.40",
"resolved": "https://registry.npmjs.org/@arkade-os/boltz-swap/-/boltz-swap-0.3.40.tgz",
"integrity": "sha512-Q1myKKXC5c44wzAD6eb4lrq3rro0qwyJqNqf0powjfbhSTzHfk5Do6DfZYrciueEK4agilynLNurWCYsoE8yEw==",
"license": "MIT",
"dependencies": {
"@arkade-os/sdk": "0.4.33",
"@arkade-os/sdk": "0.4.35",
"@noble/curves": "2.0.1",
"@noble/hashes": "2.0.1",
"@scure/base": "2.0.0",
@ -218,9 +218,9 @@
}
},
"node_modules/@arkade-os/sdk": {
"version": "0.4.33",
"resolved": "https://registry.npmjs.org/@arkade-os/sdk/-/sdk-0.4.33.tgz",
"integrity": "sha512-EvfmDhSyAiZ7DW89o5D1N4woDEFMfZLHXi/zh9C1xKlPHB2PCezEkHpVe51lNF0Vx3rgkf6bx54QXoGOvg1p9A==",
"version": "0.4.35",
"resolved": "https://registry.npmjs.org/@arkade-os/sdk/-/sdk-0.4.35.tgz",
"integrity": "sha512-gMARWDEgy5YL15vE4hBoUf4IGBi94tDRymtVwIehL+2MQylFm6cO1Qt50/aA6dwle5Ae+XMfF99Wf6k/Gc257A==",
"license": "MIT",
"dependencies": {
"@bitcoinerlab/descriptors-scure": "3.1.7",
@ -3953,12 +3953,12 @@
}
},
"node_modules/@react-native-vector-icons/entypo": {
"version": "13.1.1",
"resolved": "https://registry.npmjs.org/@react-native-vector-icons/entypo/-/entypo-13.1.1.tgz",
"integrity": "sha512-K3uZ/S0Nr0a/vuXw81tZDhKJaUfaGeTG+50vPHO60Ucl/L9b3O4KUtzMJa7zd0c400CO0vl5Lr97Wk266eXwLQ==",
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/@react-native-vector-icons/entypo/-/entypo-13.1.2.tgz",
"integrity": "sha512-oxfKPz8amwmI/IiYadwgKlGBo4y68bwYVhx5N4dTffaIR4n73Lk6AUlNUcYzSoMzSAYZVfySGPq7YV8whrc8dw==",
"license": "MIT",
"dependencies": {
"@react-native-vector-icons/common": "^13.0.0"
"@react-native-vector-icons/common": "^13.0.1"
},
"engines": {
"node": ">= 18.0.0"

View File

@ -93,8 +93,8 @@
"unit": "jest -b tests/unit/*"
},
"dependencies": {
"@arkade-os/boltz-swap": "0.3.38",
"@arkade-os/sdk": "0.4.33",
"@arkade-os/boltz-swap": "0.3.40",
"@arkade-os/sdk": "0.4.35",
"@babel/preset-env": "7.29.5",
"@bugsnag/react-native": "8.9.0",
"@bugsnag/source-maps": "2.3.3",
@ -108,7 +108,7 @@
"@react-native-community/cli-platform-android": "20.1.3",
"@react-native-community/cli-platform-ios": "20.1.3",
"@react-native-documents/picker": "12.0.1",
"@react-native-vector-icons/entypo": "13.1.1",
"@react-native-vector-icons/entypo": "13.1.2",
"@react-native-vector-icons/fontawesome": "13.1.2",
"@react-native-vector-icons/fontawesome6": "13.1.2",
"@react-native-vector-icons/ionicons": "13.1.2",

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();

View File

@ -173,7 +173,7 @@ export async function helperDeleteWallet(label, remainingBalanceSat = false) {
await waitForId('WalletDetails');
await element(by.id('WalletDetails')).tap();
await element(by.id('WalletDetailsScroll')).swipe('up', 'fast', 1);
await sleep(200);
await sleep(1000);
await element(by.id('DeleteWallet')).tap();
await waitForText('Yes, delete');
await element(by.text('Yes, delete')).tap();
@ -368,19 +368,37 @@ export async function goBack() {
// Try each back/close affordance in order; retry the full set up to 10 times.
const candidates = [by.id('BackButton'), by.id('NavigationCloseButton'), by.label('Back'), by.text('Close')];
// A matcher can hit several elements across stacked screens: each nav back
// button exists twice (_UIButtonBarButton wrapper + UIAccessibilityBackButtonElement),
// and when a modal covers a stack that also has a back button, the covered
// one can precede the visible one in match order (seen with Reduce Motion on).
// Probe attributes and only tap an element detox reports as visible & hittable.
let lastErr;
for (let attempt = 0; attempt < 10; attempt++) {
for (const matcher of candidates) {
try {
await element(matcher).atIndex(0).tap();
return;
} catch (_) {
/* try next */
for (let idx = 0; idx < 6; idx++) {
let attrs;
try {
attrs = await element(matcher).atIndex(idx).getAttributes();
} catch (err) {
lastErr = err;
break; // no element at this index — try next candidate
}
if (!attrs.visible || attrs.hittable === false) continue;
try {
await element(matcher).atIndex(idx).tap();
return;
} catch (err) {
lastErr = err;
}
}
}
await sleep(500);
}
rethrowWithCallsite(new Error('goBack: no back/close affordance tappable after 10 attempts.'), callsite);
const wrapped = new Error('goBack: no back/close affordance tappable after 10 attempts.');
if (lastErr) wrapped.cause = lastErr;
rethrowWithCallsite(wrapped, callsite);
}
export async function typeTextIntoAlertInput(text) {
@ -405,7 +423,7 @@ export async function scrollUpOnHomeScreen() {
// if no wallets there will be just one scroll
await element(by.type('RCTEnhancedScrollView')).swipe('down', 'slow', 0.5);
}
await sleep(200); // bounce animation
await sleep(1000); // bounce animation
}
// We really only need this function when running tests locally.