Update to latest engine. Add sync-v2. Various fixes.

This commit is contained in:
2026-09-07 03:33:44 +00:00
parent 051fc0c9ac
commit f01f9bc56b
31 changed files with 5977 additions and 1392 deletions
+1 -1
View File
@@ -71,7 +71,7 @@ export function AppProvider({
});
// Start the AppService (loads existing invitations)
service.start();
await service.start();
// Set the service and mark as initialized
setAppService(service);
+1 -1
View File
@@ -359,7 +359,7 @@ export function TemplateListScreen(): React.ReactElement {
try {
setStatus('Deleting template...');
await appService.engine.DANGEROUS_deleteImportedTemplate(
await appService.engine.archiveTemplate(
templateToDelete.templateIdentifier,
);
setIsDeleteDialogOpen(false);
@@ -331,7 +331,6 @@ export function useActionWizard() {
);
const success = await invitationManager.addInputsAndOutputs(
selectedUtxos,
utxoSelection.changeAmount,
);
if (success) focus.resetToContent();
return success;
@@ -493,9 +492,12 @@ export function useActionWizard() {
selectedUtxoIndex: utxoSelection.selectedUtxoIndex,
setSelectedUtxoIndex: utxoSelection.setSelectedUtxoIndex,
requiredAmount: utxoSelection.requiredAmount,
fee: utxoSelection.fee,
fee:
invitationManager.feeAwareChange?.feeSatoshis ?? utxoSelection.fee,
selectedAmount: utxoSelection.selectedAmount,
changeAmount: utxoSelection.changeAmount,
changeAmount:
invitationManager.feeAwareChange?.changeAmountSatoshis ??
utxoSelection.changeAmount,
toggleUtxoSelection: utxoSelection.toggleSelection,
selectAll: utxoSelection.selectAll,
deselectAll: utxoSelection.deselectAll,
@@ -11,6 +11,7 @@ import {
resolveProvidedLockingBytecodeHex,
} from "../../../../utils/invitation-flow.js";
import type { AppService } from "../../../../services/app.js";
import type { FeeAwareChangeResult } from "../../../../services/invitation.js";
interface InvitationManagerDeps {
appService: AppService;
@@ -34,6 +35,8 @@ export function useInvitationManager(deps: InvitationManagerDeps) {
const [requirementsComplete, setRequirementsComplete] = useState(false);
const [hasSignedAndBroadcasted, setHasSignedAndBroadcasted] = useState(false);
const [isProcessing, setIsProcessing] = useState(false);
const [feeAwareChange, setFeeAwareChange] =
useState<FeeAwareChangeResult | null>(null);
/** Re-check whether all invitation requirements are satisfied. */
const refreshRequirements = useCallback(
@@ -195,10 +198,7 @@ export function useInvitationManager(deps: InvitationManagerDeps) {
* @returns true on success, false on failure.
*/
const addInputsAndOutputs = useCallback(
async (
selectedUtxos: SelectableUTXO[],
changeAmount: bigint,
): Promise<boolean> => {
async (selectedUtxos: SelectableUTXO[]): Promise<boolean> => {
if (!invitationId || !appService) return false;
setIsProcessing(true);
@@ -218,7 +218,7 @@ export function useInvitationManager(deps: InvitationManagerDeps) {
}));
await instance.addInputs(inputs);
await instance.addOutputs([{ valueSatoshis: changeAmount }]);
setFeeAwareChange(await instance.addFeeAwareChange());
await refreshRequirements(invitationId);
setStatus("Inputs and outputs added");
return true;
@@ -283,6 +283,7 @@ export function useInvitationManager(deps: InvitationManagerDeps) {
invitationId,
requirementsComplete,
hasSignedAndBroadcasted,
feeAwareChange,
isProcessing,
setIsProcessing,
refreshRequirements,
@@ -29,7 +29,8 @@ import {
formatInvitationListItem,
formatInvitationId,
} from '../../../utils/invitation-utils.js';
import type { ResolvedInvitationVariable } from '@xo-cash/engine';
// import type { ResolvedInvitationVariable } from '@xo-cash/engine';
import type { ResolvedInvitationVariable } from '../../../utils/resolve-invitation-data.js'
import { InvitationImportFlow } from './invitation-import/InvitationImportFlow.js';
import { compileCashAssemblyString } from '@xo-cash/engine';
@@ -375,7 +376,7 @@ export function InvitationScreen(): React.ReactElement {
setIsLoading(false)
setStatus('Ready')
}
})
}, [selectedInvitation, showInfo, showError, setStatus]);
const copyId = useCallback(async () => {
if (!selectedInvitation) {
@@ -480,8 +481,6 @@ export function InvitationScreen(): React.ReactElement {
return;
}
const changeAmount = accumulated - requiredAmount - fee;
setStatus('Adding inputs...');
await selectedInvitation.addInputs(
selectedUtxos.map(u => ({
@@ -490,20 +489,16 @@ export function InvitationScreen(): React.ReactElement {
}))
);
if (changeAmount >= dust) {
setStatus('Adding change output...');
await selectedInvitation.addOutputs([{
valueSatoshis: changeAmount,
}]);
}
setStatus('Calculating miner fee and change...');
const feeAwareChange = await selectedInvitation.addFeeAwareChange();
showInfo(
`Requirements filled!\n\n` +
`• Selected ${selectedUtxos.length} UTXO(s)\n` +
`• Total: ${formatSatoshis(accumulated)}\n` +
`• Required: ${formatSatoshis(requiredAmount)}\n` +
`• Fee: ${formatSatoshis(fee)}\n` +
`• Change: ${formatSatoshis(changeAmount)}\n\n` +
`• Fee: ${formatSatoshis(feeAwareChange.feeSatoshis)}\n` +
`• Change: ${formatSatoshis(feeAwareChange.changeAmountSatoshis)}\n\n` +
`Now use "Sign Transaction" to complete.`
);
setStatus('Ready');
@@ -31,9 +31,6 @@ import { hexToBin } from '@bitauth/libauth';
/** Default fee estimate in satoshis. */
const DEFAULT_FEE = 500n;
/** Dust threshold — outputs below this are unspendable. */
const DUST_THRESHOLD = 546n;
/**
* Resolve the fixed index of a flow step from `IMPORT_STEPS`.
* We centralize this so step transitions do not rely on magic numbers.
@@ -70,6 +67,7 @@ export function InvitationImportFlow({
const [variableInputs, setVariableInputs] = useState<ImportVariableInput[]>([]);
const [selectedInputs, setSelectedInputs] = useState<SelectableUTXO[]>([]);
const [changeAmount, setChangeAmount] = useState(0n);
const [fee, setFee] = useState(DEFAULT_FEE);
const [requiredAmount, setRequiredAmount] = useState(0n);
// ── Cancel handler ───────────────────────────────────────────────────────
@@ -198,33 +196,30 @@ export function InvitationImportFlow({
/** InputsSelectStep completed — user selected UTXOs. */
const handleInputsComplete = useCallback(async (inputs: SelectableUTXO[]) => {
setSelectedInputs(inputs);
if (!invitation) return;
await invitation?.addInputs(inputs.map(input => ({
outpointTransactionHash: hexToBin(input.outpointTransactionHash),
outpointIndex: input.outpointIndex,
})));
try {
setSelectedInputs(inputs);
// Compute totals from selected inputs
const totalSelected = inputs.reduce((sum, u) => sum + u.valueSatoshis, 0n);
await invitation.addInputs(inputs.map(input => ({
outpointTransactionHash: hexToBin(input.outpointTransactionHash),
outpointIndex: input.outpointIndex,
})));
// Determine required amount from invitation variables
const requiredSats = await invitation?.getSatsOut() ?? 0n;
setRequiredAmount(requiredSats);
const requiredSats = await invitation.getSatsOut();
setRequiredAmount(requiredSats);
// Set the change amount for the review step
const changeAmountSats = totalSelected - requiredSats - DEFAULT_FEE;
setChangeAmount(changeAmountSats);
const feeAwareChange = await invitation.addFeeAwareChange();
setChangeAmount(feeAwareChange.changeAmountSatoshis);
setFee(feeAwareChange.feeSatoshis);
// Add the change output if it exceeds the dust threshold
if (changeAmountSats >= DUST_THRESHOLD) {
await invitation?.addOutputs([{
valueSatoshis: changeAmountSats,
}]);
setCurrentStep(REVIEW_STEP_INDEX); // → Review
} catch (error) {
showError(
`Failed to add inputs and calculate change: ${error instanceof Error ? error.message : String(error)}`,
);
}
setCurrentStep(REVIEW_STEP_INDEX); // → Review
}, [invitation]);
}, [invitation, showError]);
/** ReviewStep completed — invitation import is done. */
const handleReviewComplete = useCallback(() => {
@@ -332,6 +327,7 @@ export function InvitationImportFlow({
selectedRole={selectedRole}
selectedInputs={selectedInputs}
changeAmount={changeAmount}
fee={fee}
requiredAmount={requiredAmount}
appService={appService}
onComplete={handleReviewComplete}
@@ -14,9 +14,6 @@ import { useSatoshisConversion } from '../../../../hooks/useSatoshisConversion.j
import { useLayeredInput } from '../../../../hooks/useInputLayer.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;
@@ -27,6 +24,7 @@ export function ReviewStep({
selectedInputs,
requiredAmount,
changeAmount,
fee,
onComplete,
onCancel,
isActive,
@@ -35,7 +33,6 @@ export function ReviewStep({
const [error, setError] = useState<string | null>(null);
const { formatSatoshisToFiat } = useSatoshisConversion();
const fee = DEFAULT_FEE;
const action = template?.actions?.[invitation.data.actionIdentifier];
// Compute totals from selected inputs
@@ -119,6 +119,7 @@ export interface ReviewStepProps {
selectedRole: string;
selectedInputs: SelectableUTXO[];
changeAmount: bigint;
fee: bigint;
requiredAmount: bigint;
appService: AppService;
onComplete: () => void;