From 58b9492dbd1ae8e090dabbaaa68d1e86ecf55336 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 19:45:59 +0000 Subject: [PATCH 1/3] Initial plan From 4ec6affe9f7ddc513838ffb197c22221b22104a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 19:50:09 +0000 Subject: [PATCH 2/3] Apply code quality improvements to transaction explorer - Rename currentWallet to userWallet for clarity - Add NormalizedVinSource interface for type safety - Add NormalizedVoutSource interface for type safety - Add SATOSHIS_PER_BTC constant to replace magic number - Add ExtendedTransactionDetails interface to replace type assertions Co-authored-by: jamespepper81 <84083764+jamespepper81@users.noreply.github.com> --- app/transaction-explorer.tsx | 81 +++++++++++++++++++++++++----------- package-lock.json | 9 ++-- 2 files changed, 63 insertions(+), 27 deletions(-) diff --git a/app/transaction-explorer.tsx b/app/transaction-explorer.tsx index be64718..7723ee4 100644 --- a/app/transaction-explorer.tsx +++ b/app/transaction-explorer.tsx @@ -50,6 +50,38 @@ interface TransactionExplorerData { }[]; } +const SATOSHIS_PER_BTC = 1e8; + +interface NormalizedVoutSource { + value?: number; + amount?: number; + address?: string; + scriptpubkey_address?: string; +} + +interface NormalizedVinSource { + prevout?: { + value?: number; + scriptpubkey_address?: string; + }; + value?: number; + address?: string; +} + +// 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 { + net_amount?: number; + vsize?: number; + time?: number; + confirmations?: number; + size?: number; + weight?: number; + version?: number; + locktime?: number; + rbf?: boolean; +} + export default function TransactionExplorerScreen() { const { txid } = useLocalSearchParams<{ txid: string }>(); const { theme, transactions, bitcoinPrice, currentWallet, formatCurrency } = useWallet(); @@ -646,32 +678,32 @@ const styles = StyleSheet.create({ const buildExplorerData = ( txDetails: Transaction, bitcoinPrice: { usd?: number } | null, - currentWallet: Wallet | null, + userWallet: 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 normalizeVin = vinList.map((vin: any) => ({ + const normalizeVin = vinList.map((vin: NormalizedVinSource) => ({ prevout: vin.prevout || { - value: vin.value ? Math.round(vin.value * 1e8) : 0, + value: vin.value ? Math.round(vin.value * SATOSHIS_PER_BTC) : 0, scriptpubkey_address: vin.address, }, })); - const normalizeVout = voutList.map((vout: any) => ({ - value: typeof vout.value === 'number' ? vout.value : Math.round((vout.amount ?? 0) * 1e8), + const normalizeVout = voutList.map((vout: NormalizedVoutSource) => ({ + value: typeof vout.value === 'number' ? vout.value : Math.round((vout.amount ?? 0) * SATOSHIS_PER_BTC), 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 feeSats = typeof txDetails.fee === 'number' ? txDetails.fee : (txDetails.fee ?? 0); - const feeBtc = feeSats / 1e8; + const feeBtc = feeSats / SATOSHIS_PER_BTC; - const addressSet = new Set(currentWallet?.addresses ?? []); + const addressSet = new Set(userWallet?.addresses ?? []); const fallbackNetAmountSats = (() => { if (typeof txDetails.amount === 'number') { - return Math.round(txDetails.amount * 1e8); + return Math.round(txDetails.amount * SATOSHIS_PER_BTC); } if (addressSet.size > 0) { @@ -693,39 +725,40 @@ const buildExplorerData = ( return 0; })(); - const netAmountBtc = typeof (txDetails as any).net_amount === 'number' - ? (txDetails as any).net_amount / 1e8 - : fallbackNetAmountSats / 1e8; + const extendedTxDetails = txDetails as ExtendedTransactionDetails; + const netAmountBtc = typeof extendedTxDetails.net_amount === 'number' + ? extendedTxDetails.net_amount / SATOSHIS_PER_BTC + : fallbackNetAmountSats / SATOSHIS_PER_BTC; const feeUsd = bitcoinPrice?.usd ? feeBtc * bitcoinPrice.usd : 0; - const feePerVB = (txDetails as any).vsize ? feeSats / (txDetails as any).vsize : 0; + const feePerVB = extendedTxDetails.vsize ? feeSats / extendedTxDetails.vsize : 0; return { txid: txDetails.txid, - timestamp: ((statusInfo.block_time ?? (txDetails as any).time ?? Math.floor(Date.now() / 1000)) * 1000), + timestamp: ((statusInfo.block_time ?? extendedTxDetails.time ?? Math.floor(Date.now() / 1000)) * 1000), netAmount: netAmountBtc, fee: feeBtc, feeUSD: feeUsd, - confirmations: typeof (txDetails as any).confirmations === 'number' - ? (txDetails as any).confirmations + confirmations: typeof extendedTxDetails.confirmations === 'number' + ? extendedTxDetails.confirmations : (statusInfo.confirmed ? 1 : 0), blockHeight: statusInfo.block_height ?? 0, status: statusInfo.confirmed ? 'confirmed' : 'pending', - inputValue: inputValueSats / 1e8, - outputValue: outputValueSats / 1e8, + inputValue: inputValueSats / SATOSHIS_PER_BTC, + outputValue: outputValueSats / SATOSHIS_PER_BTC, feePerVB, - size: (txDetails as any).size ?? 0, - weight: (txDetails as any).weight ?? 0, - version: (txDetails as any).version ?? 0, - locktime: (txDetails as any).locktime ?? 0, - rbf: (txDetails as any).rbf ?? false, + size: extendedTxDetails.size ?? 0, + weight: extendedTxDetails.weight ?? 0, + version: extendedTxDetails.version ?? 0, + locktime: extendedTxDetails.locktime ?? 0, + rbf: extendedTxDetails.rbf ?? false, inputs: normalizeVin.map(vin => ({ address: vin.prevout?.scriptpubkey_address ?? 'Unknown', - value: (vin.prevout?.value ?? 0) / 1e8, + value: (vin.prevout?.value ?? 0) / SATOSHIS_PER_BTC, })), outputs: normalizeVout.map(vout => ({ address: vout.scriptpubkey_address ?? 'Unknown', - value: (vout.value ?? 0) / 1e8, + value: (vout.value ?? 0) / SATOSHIS_PER_BTC, })), }; }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b5686a2..332337d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3508,8 +3508,9 @@ "version": "1.24.0", "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz", "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==", - "extraneous": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "merge-options": "^3.0.4" }, @@ -11738,8 +11739,9 @@ "version": "1.24.0", "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz", "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==", - "extraneous": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "merge-options": "^3.0.4" }, @@ -19342,8 +19344,9 @@ "version": "2.8.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "extraneous": true, "license": "ISC", + "optional": true, + "peer": true, "bin": { "yaml": "bin.mjs" }, From 24e76f7fdb14f4ad87cee2178cb629ab6a5a24b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 19:51:29 +0000 Subject: [PATCH 3/3] Add clarifying comment for vin normalization logic Co-authored-by: jamespepper81 <84083764+jamespepper81@users.noreply.github.com> --- app/transaction-explorer.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/transaction-explorer.tsx b/app/transaction-explorer.tsx index 7723ee4..b59f888 100644 --- a/app/transaction-explorer.tsx +++ b/app/transaction-explorer.tsx @@ -684,6 +684,8 @@ const buildExplorerData = ( const vinList = Array.isArray(txDetails.inputs) ? txDetails.inputs : txDetails.vin || []; const voutList = Array.isArray(txDetails.outputs) ? txDetails.outputs : txDetails.vout || []; + // Normalize vin objects to ensure consistent structure. + // Some sources provide prevout directly, others provide value/address at the top level. const normalizeVin = vinList.map((vin: NormalizedVinSource) => ({ prevout: vin.prevout || { value: vin.value ? Math.round(vin.value * SATOSHIS_PER_BTC) : 0,