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:
2026-03-22 13:20:46 +00:00
parent be52f73e64
commit a28d43a68b
35 changed files with 2226 additions and 1169 deletions

View 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>;