Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions app/(tabs)/receive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function ReceiveScreenContent({ walletContext }: { walletContext: ReturnType<typ
// This prevents showing used addresses after receiving funds
useFocusEffect(
useCallback(() => {
if (currentWallet?.xpub) {
if (currentWallet?.xpub && walletService.clearAddressCache) {
console.log('🔄 Receive screen focused - clearing address cache for fresh data');
walletService.clearAddressCache(currentWallet.xpub);
}
Expand All @@ -86,7 +86,9 @@ function ReceiveScreenContent({ walletContext }: { walletContext: ReturnType<typ
console.log('🔍 Loading first unused receiving address...');

// Get first unused address within gap limit
const unusedAddress = await walletService.getFirstUnusedReceivingAddress(currentWallet.xpub);
const unusedAddress = walletService.getFirstUnusedReceivingAddress
? await walletService.getFirstUnusedReceivingAddress(currentWallet.xpub)
: null;

if (unusedAddress) {
console.log('✅ Found first unused address:', unusedAddress.substring(0, 20) + '...');
Expand Down Expand Up @@ -186,7 +188,9 @@ function ReceiveScreenContent({ walletContext }: { walletContext: ReturnType<typ

// Reload the first unused address after generating a new one
try {
const unusedAddress = await walletService.getFirstUnusedReceivingAddress(currentWallet.xpub);
const unusedAddress = walletService.getFirstUnusedReceivingAddress
? await walletService.getFirstUnusedReceivingAddress(currentWallet.xpub)
: null;
setCurrentAddress(unusedAddress || newlyGeneratedAddress);
} catch (error) {
console.error('❌ Failed to reload first unused address:', error);
Expand Down
4 changes: 2 additions & 2 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Stack, router } from 'expo-router';
import * as ExpoSplashScreen from 'expo-splash-screen';
import { AlertCircle, ArrowLeft } from 'lucide-react-native';
import React, { Component, ReactNode, useEffect, useState } from 'react';
import { Appearance, Platform, StyleSheet, Text, TouchableOpacity, View, EmitterSubscription, NativeModules } from 'react-native';
import { Appearance, Platform, StyleSheet, Text, TouchableOpacity, View, NativeEventSubscription, NativeModules } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';

Expand Down Expand Up @@ -51,7 +51,7 @@ class ErrorBoundary extends Component<
{ children: ReactNode },
{ hasError: boolean; colorScheme: 'light' | 'dark' | null | undefined }
> {
private appearanceSubscription: EmitterSubscription | undefined;
private appearanceSubscription: NativeEventSubscription | undefined;

constructor(props: { children: ReactNode }) {
super(props);
Expand Down
3 changes: 2 additions & 1 deletion app/address-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ export default function AddressDetailsScreen() {
// Use real exchange rate from wallet store based on selected currency
const btcAmount = satoshis / 100000000;
// Get rate for selected currency from price query, fallback to USD if specific currency not available
const rate = priceQuery?.data?.[selectedCurrency]?.last || priceQuery?.data?.USD?.last || 0;
const priceData = priceQuery?.data as any;
const rate = priceData?.[selectedCurrency]?.last || priceData?.USD?.last || 0;
return rate > 0 ? (btcAmount * rate).toFixed(2) : '0.00';
};

Expand Down
22 changes: 11 additions & 11 deletions app/coin-control.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export default function CoinControlScreen() {
console.log('🔍 Coin control: Starting filtering with', utxos.length, 'UTXOs');
console.log('🔍 Coin control: Filter settings:', { filterBy, hideSmallUtxos });

let filtered = utxos.filter(utxo => {
let filtered = utxos.filter((utxo: UTXO) => {
console.log('🔍 Coin control: Filtering UTXO:', {
txid: utxo.txid?.substring(0, 10) + '...',
value: utxo.value,
Expand Down Expand Up @@ -131,7 +131,7 @@ export default function CoinControlScreen() {

console.log('🔍 Coin control: After filtering:', filtered.length, 'UTXOs remain');

const sorted = filtered.sort((a, b) => {
const sorted = filtered.sort((a: UTXO, b: UTXO) => {
let comparison = 0;

switch (sortBy) {
Expand All @@ -155,7 +155,7 @@ export default function CoinControlScreen() {
});

console.log('🔍 Coin control: Final sorted UTXOs:', sorted.length);
console.log('🔍 Coin control: Final UTXO details:', sorted.map(u => ({
console.log('🔍 Coin control: Final UTXO details:', sorted.map((u: UTXO) => ({
txid: u.txid?.substring(0, 10) + '...',
vout: u.vout,
value: u.value,
Expand All @@ -167,7 +167,7 @@ export default function CoinControlScreen() {
}, [utxos, sortBy, sortAscending, filterBy, hideSmallUtxos]);

const toggleUtxoSelection = (utxoId: string) => {
const utxo = utxos.find(item => `${item.txid}:${item.vout}` === utxoId);
const utxo = utxos.find((item: UTXO) => `${item.txid}:${item.vout}` === utxoId);
if (utxo?.frozen && !selectedUtxos.has(utxoId)) {
return;
}
Expand All @@ -181,7 +181,7 @@ export default function CoinControlScreen() {
};

const toggleUtxoFreeze = (utxoId: string) => {
const target = utxos.find(utxo => `${utxo.txid}:${utxo.vout}` === utxoId);
const target = utxos.find((utxo: UTXO) => `${utxo.txid}:${utxo.vout}` === utxoId);
const wasFrozen = target?.frozen ?? false;
coinControl.toggleFreeze(utxoId);
// Note: No need to update local state - the coinControl store manages frozen status
Expand All @@ -198,8 +198,8 @@ export default function CoinControlScreen() {

const selectAllUtxos = () => {
const allIds = filteredAndSortedUtxos
.filter(utxo => !utxo.frozen)
.map(utxo => `${utxo.txid}:${utxo.vout}`);
.filter((utxo: UTXO) => !utxo.frozen)
.map((utxo: UTXO) => `${utxo.txid}:${utxo.vout}`);
setSelectedUtxos(new Set(allIds));
};

Expand Down Expand Up @@ -266,12 +266,12 @@ export default function CoinControlScreen() {

const totalSelectedValue = useMemo(() => {
return filteredAndSortedUtxos
.filter(utxo => selectedUtxos.has(`${utxo.txid}:${utxo.vout}`))
.reduce((sum, utxo) => sum + utxo.value, 0);
.filter((utxo: UTXO) => selectedUtxos.has(`${utxo.txid}:${utxo.vout}`))
.reduce((sum: number, utxo: UTXO) => sum + utxo.value, 0);
}, [filteredAndSortedUtxos, selectedUtxos]);

const totalValue = useMemo(() => {
return filteredAndSortedUtxos.reduce((sum, utxo) => sum + utxo.value, 0);
return filteredAndSortedUtxos.reduce((sum: number, utxo: UTXO) => sum + utxo.value, 0);
}, [filteredAndSortedUtxos]);

const UtxoItem = ({ utxo }: { utxo: UTXO }) => {
Expand Down Expand Up @@ -602,7 +602,7 @@ export default function CoinControlScreen() {
</View>

{/* UTXO Items */}
{filteredAndSortedUtxos.map((utxo) => (
{filteredAndSortedUtxos.map((utxo: UTXO) => (
<UtxoItem key={`${utxo.txid}:${utxo.vout}`} utxo={utxo} />
))}

Expand Down
5 changes: 4 additions & 1 deletion app/transaction-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default function TransactionDetailsScreen() {

useEffect(() => {
if (txid && transactions) {
const tx = transactions.find(t => t.txid === txid);
const tx = transactions.find((t: Transaction) => t.txid === txid);
if (tx) {
setTransaction(tx);
lastTxRef.current = tx;
Expand Down Expand Up @@ -581,6 +581,9 @@ const styles = StyleSheet.create({
borderRadius: platformStyles.borderRadius.medium,
marginBottom: platformStyles.spacing.sm,
},
disabledActionButton: {
opacity: 0.5,
},
actionButtonText: {
...platformStyles.typography.bodyLarge,
marginLeft: platformStyles.spacing.md,
Expand Down
43 changes: 24 additions & 19 deletions app/transaction-explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,20 @@ interface NormalizedVinSource {

// Some transaction detail responses include extra fields such as `net_amount` and `vsize`.
// Model those explicitly instead of using `as any` to preserve type safety.
interface ExtendedTransactionDetails extends Transaction {
interface ExtendedTransactionDetails extends Omit<Transaction, 'status'> {
net_amount?: number;
vsize?: number;
time?: number;
confirmations?: number;
size?: number;
weight?: number;
version?: number;
locktime?: number;
rbf?: boolean;
vin?: any[];
vout?: any[];
status?: {
confirmed?: boolean;
block_height?: number;
block_hash?: string;
block_time?: number;
} | 'pending' | 'confirmed' | 'failed';
}

export default function TransactionExplorerScreen() {
Expand Down Expand Up @@ -123,7 +127,7 @@ export default function TransactionExplorerScreen() {
setError(null);
}

const localTx = transactions?.find(t => t.txid === txid) || lastDetailsRef.current[txid] || null;
const localTx = transactions?.find((t: Transaction) => t.txid === txid) || lastDetailsRef.current[txid] || null;
if (localTx) {
lastDetailsRef.current[txid] = localTx;
}
Expand All @@ -144,7 +148,7 @@ export default function TransactionExplorerScreen() {
} as Transaction;
lastDetailsRef.current[txid] = summaryTransaction;

const explorerSummary = buildExplorerData(summaryTransaction, bitcoinPrice, currentWallet);
const explorerSummary = buildExplorerData(summaryTransaction, bitcoinPrice || null, currentWallet);

if (isMounted) {
setTransaction(summaryTransaction);
Expand All @@ -155,7 +159,7 @@ export default function TransactionExplorerScreen() {
const cached = lastDetailsRef.current[txid];
if (cached) {
setTransaction(cached);
setExplorerData(buildExplorerData(cached, bitcoinPrice, currentWallet));
setExplorerData(buildExplorerData(cached, bitcoinPrice || null, currentWallet));
} else {
setExplorerData(null);
setError(error.message || 'Failed to fetch transaction details');
Expand Down Expand Up @@ -687,13 +691,14 @@ const styles = StyleSheet.create({
});

const buildExplorerData = (
txDetails: Transaction,
bitcoinPrice: { usd?: number } | null,
txDetails: Transaction | ExtendedTransactionDetails,
bitcoinPrice: { usd?: number } | null | undefined,
currentWallet: Wallet | null,
): TransactionExplorerData => {
const statusInfo = txDetails.status || {};
const vinList = Array.isArray(txDetails.inputs) ? txDetails.inputs : txDetails.vin || [];
const voutList = Array.isArray(txDetails.outputs) ? txDetails.outputs : txDetails.vout || [];
const extendedTx = txDetails as ExtendedTransactionDetails;
const statusInfo = (typeof extendedTx.status === 'object' ? extendedTx.status : {}) as { confirmed?: boolean; block_height?: number; block_hash?: string; block_time?: number };
const vinList = Array.isArray(txDetails.inputs) ? txDetails.inputs : extendedTx.vin || [];
const voutList = Array.isArray(txDetails.outputs) ? txDetails.outputs : extendedTx.vout || [];

// Normalize vin objects to ensure consistent structure.
// Some sources provide prevout directly, others provide value/address at the top level.
Expand All @@ -708,8 +713,8 @@ const buildExplorerData = (
scriptpubkey_address: vout.address ?? vout.scriptpubkey_address,
}));

const inputValueSats = normalizeVin.reduce((sum, vin) => sum + (vin.prevout?.value ?? 0), 0);
const outputValueSats = normalizeVout.reduce((sum, vout) => sum + (vout.value ?? 0), 0);
const inputValueSats = normalizeVin.reduce((sum: number, vin: any) => sum + (vin.prevout?.value ?? 0), 0);
const outputValueSats = normalizeVout.reduce((sum: number, vout: any) => sum + (vout.value ?? 0), 0);
const feeSats = txDetails.fee ?? 0;
const feeBtc = feeSats / SATOSHIS_PER_BTC;

Expand All @@ -720,13 +725,13 @@ const buildExplorerData = (
}

if (addressSet.size > 0) {
const received = normalizeVout.reduce((sum, output) =>
const received = normalizeVout.reduce((sum: number, output: any) =>
output.scriptpubkey_address && addressSet.has(output.scriptpubkey_address)
? sum + (output.value ?? 0)
: sum,
0);

const sent = normalizeVin.reduce((sum, input) =>
const sent = normalizeVin.reduce((sum: number, input: any) =>
input.prevout?.scriptpubkey_address && addressSet.has(input.prevout.scriptpubkey_address)
? sum + (input.prevout?.value ?? 0)
: sum,
Expand Down Expand Up @@ -765,11 +770,11 @@ const buildExplorerData = (
version: extendedTxDetails.version ?? 0,
locktime: extendedTxDetails.locktime ?? 0,
rbf: extendedTxDetails.rbf ?? false,
inputs: normalizeVin.map(vin => ({
inputs: normalizeVin.map((vin: any) => ({
address: vin.prevout?.scriptpubkey_address ?? 'Unknown',
value: (vin.prevout?.value ?? 0) / SATOSHIS_PER_BTC,
})),
outputs: normalizeVout.map(vout => ({
outputs: normalizeVout.map((vout: any) => ({
address: vout.scriptpubkey_address ?? 'Unknown',
value: (vout.value ?? 0) / SATOSHIS_PER_BTC,
})),
Expand Down
3 changes: 2 additions & 1 deletion app/transaction-history.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import TransactionItem from '@/components/TransactionItem';
import { useWallet } from '@/hooks/wallet-store';
import { Transaction } from '@/types/wallet';
import { Stack, router } from 'expo-router';
import { ArrowLeft, Clock } from 'lucide-react-native';
import React from 'react';
Expand Down Expand Up @@ -134,7 +135,7 @@ export default function TransactionHistoryScreen() {
{/* Transaction List */}
{transactions.length > 0 && (
<View style={styles.transactionsList}>
{transactions.map((transaction, index) => (
{transactions.map((transaction: Transaction, index: number) => (
<TransactionItem
key={`${transaction.txid}-${index}`}
transaction={transaction}
Expand Down
2 changes: 1 addition & 1 deletion app/wallet-addresses.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ export default function WalletAddressesScreen() {
</View>

<ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
{addressesQuery.isLoading && (!addressesQuery.data || addressesQuery.data.length === 0) ? (
{addressesQuery.isLoading && addressData.length === 0 ? (
<View style={styles.loadingState}>
<ActivityIndicator size="large" color={theme.colors.primary} />
<Text style={[styles.loadingText, { color: theme.colors.textSecondary }]}>
Expand Down
22 changes: 20 additions & 2 deletions components/BitSleuthButton.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createButtonStyle } from '@/constants/themes';
import { createButtonStyle, lightTheme } from '@/constants/themes';
import { HapticService } from '@/services/haptic-service';
import { LinearGradient } from 'expo-linear-gradient';
import React from 'react';
Expand Down Expand Up @@ -127,8 +127,26 @@ export default function BitSleuthButton({
};

// Button style
// Map variant to theme variant
const themeVariant = (() => {
switch (variant) {
case 'accent':
case 'success':
case 'warning':
case 'error':
case 'ghost':
return 'primary';
case 'secondary':
return 'secondary';
case 'fun':
return 'fun';
default:
return variant as 'primary' | 'secondary' | 'outline' | 'gradient' | 'fun';
}
})();

const buttonStyle = [
createButtonStyle({ colors: {}, shadows: {} }, variant),
createButtonStyle(lightTheme, themeVariant),
currentSize,
style,
animatedStyle,
Expand Down
10 changes: 6 additions & 4 deletions components/BitSleuthCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createCardShadow, createCardStyle } from '@/constants/themes';
import { createCardStyle, lightTheme } from '@/constants/themes';
import { HapticService } from '@/services/haptic-service';
import { LinearGradient } from 'expo-linear-gradient';
import React from 'react';
Expand Down Expand Up @@ -43,7 +43,7 @@ export default function BitSleuthCard({
const shadowOpacity = useSharedValue(0.15);

// Color mapping for fun variants
const funColors = {
const funColors: Record<string, readonly [string, string, ...string[]]> = {
purple: ['#9B59B6', '#8E44AD'],
yellow: ['#F1C40F', '#F39C12'],
pink: ['#E91E63', '#C2185B'],
Expand Down Expand Up @@ -84,9 +84,11 @@ export default function BitSleuthCard({
};

// Card style
// Map variant to theme variant
const themeVariant = variant === 'gradient' ? 'elevated' : variant as 'default' | 'elevated' | 'fun';

const cardStyle = [
createCardStyle({ colors: {}, shadows: {} }, variant),
createCardShadow({ shadows: {} }, shadowElevation),
createCardStyle(lightTheme, themeVariant),
style,
animatedStyle,
];
Expand Down
10 changes: 6 additions & 4 deletions components/ConfettiCelebration.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { HapticService } from '@/services/haptic-service';
import React, { useEffect, useRef } from 'react';
import { StyleSheet, View } from 'react-native';
import { Dimensions, StyleSheet, View } from 'react-native';
import ConfettiCannon from 'react-native-confetti-cannon';

const { height: screenHeight } = Dimensions.get('window');

interface ConfettiCelebrationProps {
isVisible: boolean;
onComplete?: () => void;
Expand Down Expand Up @@ -73,16 +75,16 @@ export default function ConfettiCelebration({
};

// Position configuration
const getPositionStyle = () => {
const getPositionStyle = (): { top?: number; bottom?: number } => {
switch (position) {
case 'top':
return { top: 100 };
case 'center':
return { top: '50%' };
return { top: screenHeight / 2 };
case 'bottom':
return { bottom: 100 };
default:
return { top: '50%' };
return { top: screenHeight / 2 };
}
};

Expand Down
1 change: 1 addition & 0 deletions components/EmojiReaction.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { platformStyles } from '@/constants/themes';
import React, { useCallback, useEffect, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Animated, {
Expand Down
Loading
Loading