Large amount of changes. Successfully broadcasts txs

This commit is contained in:
2026-03-08 15:53:50 +00:00
parent 66e9918e04
commit 9ef1720e1f
19 changed files with 1374 additions and 352 deletions

View File

@@ -0,0 +1,113 @@
/**
* ReviewStep — final step that summarizes the import and executes it.
*
* Displays the accumulated selections (role, inputs, amounts) and on confirmation:
* 1. Adds inputs (with the selected role identifier) to the invitation.
* 2. Optionally adds a change output if the change exceeds the dust threshold.
* 3. Calls `onComplete()` to signal the flow is finished.
*/
import React, { useState, useCallback } from 'react';
import { Box, Text, useInput } from 'ink';
import { colors, formatSatoshis } from '../../../../theme.js';
import type { ReviewStepProps, SelectableUTXO } from '../types.js';
/** Default fee estimate in satoshis. */
const DEFAULT_FEE = 500n;
/** Dust threshold — outputs below this are unspendable. */
const DUST_THRESHOLD = 546n;
export function ReviewStep({
invitation,
template,
selectedRole,
selectedInputs,
requiredAmount,
changeAmount,
appService,
onComplete,
onCancel,
isActive,
}: ReviewStepProps): React.ReactElement {
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const fee = DEFAULT_FEE;
const action = template?.actions?.[invitation.data.actionIdentifier];
// Compute totals from selected inputs
const totalSelected = selectedInputs.reduce((sum, u) => sum + u.valueSatoshis, 0n);
/**
* Execute the import: add inputs (with role) and optional change output.
*/
const submit = useCallback(async () => {
setIsSubmitting(true);
setError(null);
try {
onComplete();
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setIsSubmitting(false);
}
}, [invitation, selectedRole, selectedInputs, onComplete]);
// Keyboard handling
useInput((_input, key) => {
if (!isActive || isSubmitting) return;
if (key.return) {
submit();
} else if (key.escape) {
onCancel();
}
}, { isActive });
// Resolve role display name
const roleInfoRaw = template?.roles?.[selectedRole];
const roleInfo = roleInfoRaw && typeof roleInfoRaw === 'object' ? roleInfoRaw : null;
return (
<Box flexDirection="column">
<Text color={colors.primary} bold>Review Import</Text>
{/* Template & action */}
<Box marginTop={1} flexDirection="column">
<Text color={colors.text}>Template: {template?.name ?? invitation.data.templateIdentifier}</Text>
<Text color={colors.text}>Action: {action?.name ?? invitation.data.actionIdentifier}</Text>
<Text color={colors.text}>Role: {roleInfo?.name ?? selectedRole}</Text>
</Box>
{/* Funding summary */}
<Box marginTop={1} flexDirection="column">
<Text color={colors.primary} bold>Funding:</Text>
<Text color={colors.text}> UTXOs: {selectedInputs.length}</Text>
<Text color={colors.text}> Total: {formatSatoshis(totalSelected)}</Text>
<Text color={colors.text}> Required: {formatSatoshis(requiredAmount)}</Text>
<Text color={colors.text}> Fee: {formatSatoshis(fee)}</Text>
{changeAmount >= DUST_THRESHOLD && (
<Text color={colors.text}> Change: {formatSatoshis(changeAmount)}</Text>
)}
</Box>
{/* Error display */}
{error && (
<Box marginTop={1}>
<Text color={colors.error} bold>Error: {error}</Text>
</Box>
)}
{/* Status / hint */}
<Box marginTop={1}>
{isSubmitting ? (
<Text color={colors.info}>Submitting...</Text>
) : (
<Text color={colors.textMuted}>Enter: Confirm & Import Esc: Cancel</Text>
)}
</Box>
</Box>
);
}