Massive changes. I dont know what happens. Rewrote the action wizard again. Fixed several bugs related to the utxo selection. QR codes were added for address. Add support for data results. Experiment with other methods of role extraction
This commit is contained in:
117
src/tui/screens/action-wizard/hooks/useUtxoSelection.ts
Normal file
117
src/tui/screens/action-wizard/hooks/useUtxoSelection.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
import type { SelectableUTXO, VariableInput } from '../types.js';
|
||||
import type { Invitation } from '../../../../services/invitation.js';
|
||||
import { formatSatoshis } from '../../../theme.js';
|
||||
import {
|
||||
autoSelectGreedyUtxos,
|
||||
mapUnspentOutputsToSelectable,
|
||||
} from '../../../../utils/invitation-flow.js';
|
||||
|
||||
/**
|
||||
* Manages UTXO selection state for the wizard's inputs step.
|
||||
*
|
||||
* Only active for transaction flows that require the creator
|
||||
* to provide funding inputs.
|
||||
*/
|
||||
export function useUtxoSelection() {
|
||||
const [availableUtxos, setAvailableUtxos] = useState<SelectableUTXO[]>([]);
|
||||
const [selectedUtxoIndex, setSelectedUtxoIndex] = useState(0);
|
||||
const [requiredAmount, setRequiredAmount] = useState<bigint>(0n);
|
||||
const [fee, setFee] = useState<bigint>(500n);
|
||||
|
||||
const selectedAmount = useMemo(
|
||||
() => availableUtxos.filter((u) => u.selected).reduce((sum, u) => sum + u.valueSatoshis, 0n),
|
||||
[availableUtxos],
|
||||
);
|
||||
|
||||
const changeAmount = useMemo(
|
||||
() => selectedAmount - requiredAmount - fee,
|
||||
[selectedAmount, requiredAmount, fee],
|
||||
);
|
||||
|
||||
/** Toggle the selected state of a single UTXO. */
|
||||
const toggleSelection = useCallback((index: number) => {
|
||||
setAvailableUtxos((prev) => {
|
||||
const updated = [...prev];
|
||||
const utxo = updated[index];
|
||||
if (utxo) {
|
||||
updated[index] = { ...utxo, selected: !utxo.selected };
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
}, []);
|
||||
|
||||
/** Select all available UTXOs. */
|
||||
const selectAll = useCallback(() => {
|
||||
setAvailableUtxos((prev) => prev.map((u) => ({ ...u, selected: true })));
|
||||
}, []);
|
||||
|
||||
/** Deselect all UTXOs. */
|
||||
const deselectAll = useCallback(() => {
|
||||
setAvailableUtxos((prev) => prev.map((u) => ({ ...u, selected: false })));
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Query the invitation instance for suitable UTXOs and auto-select
|
||||
* greedily to meet the required amount.
|
||||
*/
|
||||
const loadUtxos = useCallback(async (
|
||||
invitationInstance: Invitation,
|
||||
templateIdentifier: string,
|
||||
variables: VariableInput[],
|
||||
setStatus: (msg: string) => void,
|
||||
): Promise<void> => {
|
||||
setStatus('Finding suitable UTXOs...');
|
||||
|
||||
// Derive required amount from variables that look like satoshi/amount fields.
|
||||
const requestedVar = variables.find(
|
||||
(v) =>
|
||||
v.id.toLowerCase().includes('satoshi') ||
|
||||
v.id.toLowerCase().includes('amount'),
|
||||
);
|
||||
const requested = requestedVar ? BigInt(requestedVar.value || '0') : 0n;
|
||||
setRequiredAmount(requested);
|
||||
|
||||
const unspentOutputs = await invitationInstance.findSuitableResources({
|
||||
templateIdentifier,
|
||||
});
|
||||
|
||||
const mapped = mapUnspentOutputsToSelectable(unspentOutputs);
|
||||
const autoSelected = autoSelectGreedyUtxos(mapped, requested + fee);
|
||||
setAvailableUtxos(autoSelected as SelectableUTXO[]);
|
||||
setStatus('Ready');
|
||||
}, [fee]);
|
||||
|
||||
/** Validate that the selection meets the required amounts. */
|
||||
const validate = useCallback((): string | null => {
|
||||
const selected = availableUtxos.filter((u) => u.selected);
|
||||
if (selected.length === 0) {
|
||||
return 'Please select at least one UTXO';
|
||||
}
|
||||
if (selectedAmount < requiredAmount + fee) {
|
||||
return `Insufficient funds. Need ${formatSatoshis(requiredAmount + fee)}, selected ${formatSatoshis(selectedAmount)}`;
|
||||
}
|
||||
if (changeAmount < 546n) {
|
||||
return `Change amount (${changeAmount}) is below dust threshold (546 sats)`;
|
||||
}
|
||||
return null;
|
||||
}, [availableUtxos, selectedAmount, requiredAmount, fee, changeAmount]);
|
||||
|
||||
return {
|
||||
availableUtxos,
|
||||
setAvailableUtxos,
|
||||
selectedUtxoIndex,
|
||||
setSelectedUtxoIndex,
|
||||
requiredAmount,
|
||||
fee,
|
||||
selectedAmount,
|
||||
changeAmount,
|
||||
toggleSelection,
|
||||
selectAll,
|
||||
deselectAll,
|
||||
loadUtxos,
|
||||
validate,
|
||||
} as const;
|
||||
}
|
||||
|
||||
export type UtxoSelectionState = ReturnType<typeof useUtxoSelection>;
|
||||
Reference in New Issue
Block a user