@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
133 lines • 5.24 kB
JavaScript
import { getMainAccount } from "@ledgerhq/live-common/account/index";
import { applyMemoToTransaction } from "@ledgerhq/live-common/bridge/descriptor/send/memo";
import { getAccountBridge } from "@ledgerhq/live-common/bridge/index";
import { useCallback, useMemo, useRef, useState } from "react";
const DEBOUNCE_DELAY = 300;
/**
* Hook to validate recipient address using the bridge transaction status.
* This hook leverages the existing bridge infrastructure to get
* recipient and sender validation errors/warnings.
*/
export function useBridgeRecipientValidation({ recipient, account, parentAccount, memo, enabled = true, }) {
const [validationState, setValidationState] = useState({
errors: {},
warnings: {},
isLoading: false,
status: null,
});
const lastRecipientRef = useRef("");
const validationTriggeredRef = useRef(false);
const debounceTimeoutRef = useRef(null);
const abortControllerRef = useRef(null);
// Cleanup function to clear timeout and abort pending validations
const cleanup = useCallback(() => {
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
debounceTimeoutRef.current = null;
}
if (abortControllerRef.current) {
abortControllerRef.current.abort();
abortControllerRef.current = null;
}
}, []);
const validateRecipient = useCallback(async () => {
// Clear timeout reference when callback executes
debounceTimeoutRef.current = null;
if (!account || !recipient || !enabled) {
setValidationState({
errors: {},
warnings: {},
isLoading: false,
status: null,
});
return;
}
// Cancel any pending validation
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
abortControllerRef.current = new AbortController();
const signal = abortControllerRef.current.signal;
setValidationState(prev => ({ ...prev, isLoading: true }));
try {
const mainAccount = getMainAccount(account, parentAccount);
const bridge = getAccountBridge(account, parentAccount);
let transaction = bridge.createTransaction(mainAccount);
transaction = bridge.updateTransaction(transaction, { recipient });
if (memo) {
const memoUpdates = applyMemoToTransaction(transaction.family, memo.value, memo.type, transaction);
transaction = bridge.updateTransaction(transaction, memoUpdates);
}
const preparedTransaction = await bridge.prepareTransaction(mainAccount, transaction);
if (signal.aborted)
return;
const status = await bridge.getTransactionStatus(mainAccount, preparedTransaction);
if (signal.aborted)
return;
const errors = {};
const warnings = {};
if (status.errors.recipient) {
errors.recipient = status.errors.recipient;
}
if (status.errors.sender) {
errors.sender = status.errors.sender;
}
Object.entries(status.warnings).forEach(([key, value]) => {
if (value) {
warnings[key] = value;
}
});
setValidationState({
errors,
warnings,
isLoading: false,
status,
});
}
catch (error) {
if (signal.aborted)
return;
console.error("Bridge recipient validation failed:", error);
setValidationState({
errors: {},
warnings: {},
isLoading: false,
status: null,
});
}
}, [account, recipient, enabled, parentAccount, memo]);
if (recipient !== lastRecipientRef.current) {
lastRecipientRef.current = recipient;
validationTriggeredRef.current = false;
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
debounceTimeoutRef.current = null;
}
if (!recipient) {
setValidationState({
errors: {},
warnings: {},
isLoading: false,
status: null,
});
}
}
if (recipient && !validationTriggeredRef.current && enabled) {
validationTriggeredRef.current = true;
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
}
setValidationState(prev => ({ ...prev, isLoading: true }));
debounceTimeoutRef.current = setTimeout(() => {
validateRecipient();
}, DEBOUNCE_DELAY);
}
return useMemo(() => ({
errors: validationState.errors,
warnings: validationState.warnings,
isLoading: validationState.isLoading,
status: validationState.status,
cleanup,
}), [validationState, cleanup]);
}
//# sourceMappingURL=useBridgeRecipientValidation.js.map