From f1ac89ef91e2292ceb7eded1ebe4454eeeff4084 Mon Sep 17 00:00:00 2001 From: Harvey Zuccon Date: Sat, 30 May 2026 21:21:52 +0200 Subject: [PATCH] Remove reference to transaction screen --- src/tui/App.tsx | 3 - src/tui/screens/Transaction.tsx | 413 -------------------------------- 2 files changed, 416 deletions(-) delete mode 100644 src/tui/screens/Transaction.tsx diff --git a/src/tui/App.tsx b/src/tui/App.tsx index 1676398..000c258 100644 --- a/src/tui/App.tsx +++ b/src/tui/App.tsx @@ -17,7 +17,6 @@ import { WalletStateScreen } from './screens/WalletState.js'; import { TemplateListScreen } from './screens/TemplateList.js'; import { ActionWizardScreen } from './screens/action-wizard/ActionWizardScreen.js'; import { InvitationScreen } from './screens/invitations/InvitationScreen.js'; -import { TransactionScreen } from './screens/Transaction.js'; import { MessageDialog } from './components/Dialog.js'; @@ -45,8 +44,6 @@ function Router(): React.ReactElement { return ; case 'invitations': return ; - case 'transaction': - return ; default: return Unknown screen: {screen}; } diff --git a/src/tui/screens/Transaction.tsx b/src/tui/screens/Transaction.tsx deleted file mode 100644 index ec1bbfb..0000000 --- a/src/tui/screens/Transaction.tsx +++ /dev/null @@ -1,413 +0,0 @@ -/** - * Transaction Screen - Reviews and broadcasts transactions. - * - * Provides: - * - Transaction details review - * - Input/output inspection - * - Fee calculation display - * - Broadcast confirmation - */ - -import React, { useState, useEffect, useCallback } from 'react'; -import { Box, Text } from 'ink'; -import { ConfirmDialog } from '../components/Dialog.js'; -import { useNavigation } from '../hooks/useNavigation.js'; -import { useAppContext, useStatus } from '../hooks/useAppContext.js'; -import { useBlockableInput } from '../hooks/useInputLayer.js'; -import { useInvitation } from '../hooks/useInvitations.js'; -import { colors, logoSmall, formatSatoshis, formatHex } from '../theme.js'; -import { copyToClipboard } from '../utils/clipboard.js'; - -/** - * Action menu items. - */ -const actionItems = [ - { label: 'Broadcast Transaction', value: 'broadcast' }, - { label: 'Sign Transaction', value: 'sign' }, - { label: 'Copy Transaction Hex', value: 'copy' }, - { label: 'Back to Invitation', value: 'back' }, -]; - -/** - * Transaction Screen Component. - */ -export function TransactionScreen(): React.ReactElement { - const { navigate, goBack, data: navData } = useNavigation(); - const { showError, showInfo } = useAppContext(); - const { setStatus } = useStatus(); - - // Extract invitation ID from navigation data - const invitationId = navData.invitationId as string | undefined; - - // Use hook to get invitation reactively - const invitationInstance = useInvitation(invitationId ?? null); - - // State - const [focusedPanel, setFocusedPanel] = useState<'inputs' | 'outputs' | 'actions'>('actions'); - const [selectedActionIndex, setSelectedActionIndex] = useState(0); - const [isLoading, setIsLoading] = useState(false); - const [showBroadcastConfirm, setShowBroadcastConfirm] = useState(false); - - // Check if invitation exists - useEffect(() => { - if (!invitationId) { - showError('No invitation ID provided'); - goBack(); - return; - } - - if (invitationId && !invitationInstance) { - showError('Invitation not found'); - goBack(); - } - }, [invitationId, invitationInstance, showError, goBack]); - - const invitation = invitationInstance?.data ?? null; - - /** - * Broadcast transaction. - */ - const broadcastTransaction = useCallback(async () => { - if (!invitationInstance) return; - - setShowBroadcastConfirm(false); - setIsLoading(true); - setStatus('Broadcasting transaction...'); - - try { - await invitationInstance.broadcast(); - showInfo( - `Transaction Broadcast Successful!\n\n` + - `The transaction has been submitted to the network.` - ); - navigate('wallet'); - } catch (error) { - showError(`Failed to broadcast: ${error instanceof Error ? error.message : String(error)}`); - } finally { - setIsLoading(false); - setStatus('Ready'); - } - }, [invitationInstance, showInfo, showError, navigate, setStatus]); - - /** - * Sign transaction. - */ - const signTransaction = useCallback(async () => { - if (!invitationInstance) return; - - setIsLoading(true); - setStatus('Signing transaction...'); - - try { - await invitationInstance.sign(); - showInfo('Transaction signed successfully!'); - } catch (error) { - showError(`Failed to sign: ${error instanceof Error ? error.message : String(error)}`); - } finally { - setIsLoading(false); - setStatus('Ready'); - } - }, [invitationInstance, showInfo, showError, setStatus]); - - /** - * Copy transaction hex. - */ - const copyTransactionHex = useCallback(async () => { - if (!invitation) return; - - try { - await copyToClipboard(invitation.invitationIdentifier); - showInfo( - `Copied Invitation ID!\n\n` + - `ID: ${invitation.invitationIdentifier}\n` + - `Commits: ${invitation.commits.length}` - ); - } catch (error) { - showError(`Failed to copy: ${error instanceof Error ? error.message : String(error)}`); - } - }, [invitation, showInfo, showError]); - - /** - * Handle action selection. - */ - const handleAction = useCallback((action: string) => { - switch (action) { - case 'broadcast': - setShowBroadcastConfirm(true); - break; - case 'sign': - signTransaction(); - break; - case 'copy': - copyTransactionHex(); - break; - case 'back': - goBack(); - break; - } - }, [signTransaction, copyTransactionHex, goBack]); - - // Handle keyboard navigation — automatically blocked when the confirm dialog is open. - useBlockableInput((input, key) => { - // Tab to switch panels - if (key.tab) { - setFocusedPanel(prev => { - if (prev === 'inputs') return 'outputs'; - if (prev === 'outputs') return 'actions'; - return 'inputs'; - }); - return; - } - - // Up/Down in actions - if (focusedPanel === 'actions') { - if (key.upArrow || input === 'k') { - setSelectedActionIndex(prev => Math.max(0, prev - 1)); - } else if (key.downArrow || input === 'j') { - setSelectedActionIndex(prev => Math.min(actionItems.length - 1, prev + 1)); - } - } - - // Enter to select - if (key.return && focusedPanel === 'actions') { - const action = actionItems[selectedActionIndex]; - if (action) { - handleAction(action.value); - } - } - }); - - // Extract transaction data from invitation - const commits = invitation?.commits ?? []; - const inputs: Array<{ txid: string; index: number; value?: bigint; inputIdentifier?: string }> = []; - const outputs: Array<{ value?: bigint; lockingBytecode: string; outputIdentifier?: string; isTemplate: boolean }> = []; - const variables: Array<{ id: string; value: string }> = []; - - // Parse commits for inputs, outputs, and variables - for (const commit of commits) { - // Extract variables (to help understand output values) - if (commit.data?.variables) { - for (const variable of commit.data.variables) { - variables.push({ - id: variable.variableIdentifier, - value: String(variable.value), - }); - } - } - - if (commit.data?.inputs) { - for (const input of commit.data.inputs) { - // Convert Uint8Array to hex string if needed - const txidHex = input.outpointTransactionHash - ? typeof input.outpointTransactionHash === 'string' - ? input.outpointTransactionHash - : Buffer.from(input.outpointTransactionHash).toString('hex') - : undefined; - - // Skip inputs that are just placeholders (no txid) - if (txidHex) { - inputs.push({ - txid: txidHex, - index: input.outpointIndex ?? 0, - value: undefined, // Will be looked up from UTXO data - inputIdentifier: (input as any).inputIdentifier, - }); - } - } - } - if (commit.data?.outputs) { - for (const output of commit.data.outputs) { - // Convert Uint8Array to hex string if needed - const lockingBytecodeHex = output.lockingBytecode - ? typeof output.lockingBytecode === 'string' - ? output.lockingBytecode - : Buffer.from(output.lockingBytecode).toString('hex') - : undefined; - - // Check if this is a template-defined output (has outputIdentifier but no direct value) - const isTemplateOutput = !!(output as any).outputIdentifier && !output.valueSatoshis; - - outputs.push({ - value: output.valueSatoshis, - lockingBytecode: lockingBytecodeHex ?? '(pending)', - outputIdentifier: (output as any).outputIdentifier, - isTemplate: isTemplateOutput, - }); - } - } - } - - // Try to resolve template output values from variables - const resolvedOutputs = outputs.map(output => { - if (output.isTemplate && output.outputIdentifier) { - // Look for a matching variable (e.g., requestSatoshisOutput -> requestedSatoshis) - const satoshiVar = variables.find(v => - v.id.toLowerCase().includes('satoshi') || - v.id.toLowerCase().includes('amount') - ); - if (satoshiVar) { - return { - ...output, - value: BigInt(satoshiVar.value), - resolvedFrom: satoshiVar.id, - }; - } - } - return output; - }); - - // Calculate totals (only for resolved values) - const totalOut = resolvedOutputs.reduce((sum, o) => sum + (o.value ?? 0n), 0n); - // Note: We can't calculate totalIn without UTXO lookup, so fee is unknown - const hasUnresolvedOutputs = resolvedOutputs.some(o => o.value === undefined); - const hasUnresolvedInputs = inputs.length > 0; // Input values are always unknown from commit data - - return ( - - {/* Header */} - - {logoSmall} - Transaction Review - - - {/* Summary box */} - - Transaction Summary - {invitation ? ( - - Inputs: {inputs.length} | Outputs: {resolvedOutputs.length} | Commits: {commits.length} - {hasUnresolvedInputs && ( - Total In: (requires UTXO lookup) - )} - Total Out: {formatSatoshis(totalOut)}{hasUnresolvedOutputs ? ' (partial)' : ''} - {hasUnresolvedInputs ? ( - Fee: (calculated at broadcast) - ) : ( - Fee: {formatSatoshis(0n)} - )} - - ) : ( - Loading... - )} - - - {/* Inputs and Outputs */} - - {/* Inputs */} - - Inputs - - {inputs.length === 0 ? ( - No inputs - ) : ( - inputs.map((input, index) => ( - - - {index + 1}. {formatHex(input.txid, 12)}:{input.index} - - {input.value !== undefined && ( - {formatSatoshis(input.value)} - )} - - )) - )} - - - - {/* Outputs */} - - Outputs - - {resolvedOutputs.length === 0 ? ( - No outputs - ) : ( - resolvedOutputs.map((output, index) => ( - - - {index + 1}. {output.value !== undefined ? formatSatoshis(output.value) : '(pending)'} - {output.outputIdentifier && ( - [{output.outputIdentifier}] - )} - - {output.lockingBytecode !== '(pending)' ? formatHex(output.lockingBytecode, 20) : '(pending)'} - {(output as any).resolvedFrom && ( - (from ${(output as any).resolvedFrom}) - )} - - )) - )} - - - - - {/* Actions */} - - Actions - - {actionItems.map((item, index) => ( - - {index === selectedActionIndex && focusedPanel === 'actions' ? '▸ ' : ' '} - {item.label} - - ))} - - - - {/* Help text */} - - - Tab: Switch focus • Enter: Select • Esc: Back - - - - {/* Broadcast confirmation dialog */} - {showBroadcastConfirm && ( - - setShowBroadcastConfirm(false)} - /> - - )} - - ); -}