(null);
-
- // Render token icon (swap-only crossfade from source to destination)
- const renderTokenIcon = useCallback(() => {
- const progress = processing?.animationProgress ?? 0;
-
- // Non-swap: keep existing single token icon
- if (transactionType !== 'swap') {
- return (
-
-
-
- );
- }
-
- // Swap: crossfade from source token to destination token around 50%
- const swapResult = simulationResult as SwapSimulationResult;
- const destSymbol = swapResult?.intent?.destination?.token?.symbol?.toUpperCase();
- const destIcon = destSymbol ? TOKEN_METADATA[destSymbol]?.icon : undefined;
- const sourceIcon = tokenMeta?.icon;
-
- return (
-
- {/* Source token */}
-
-
-
- {/* Destination token */}
- = 50 ? 1 : 0, scale: progress >= 50 ? 1 : 1.05 }}
- transition={{ duration: 0.25 }}
- >
-
-
-
- );
- }, [
- processing?.animationProgress,
- tokenMeta?.icon,
- tokenMeta?.symbol,
- transactionType,
- simulationResult,
- ]);
-
- const sourceAmount = useCallback(() => {
- if (transactionType === 'bridge' || transactionType === 'transfer') {
- return formatCost((simulationResult as SimulationResult)?.intent?.sourcesTotal);
- } else if (transactionType === 'swap') {
- const swapResult = simulationResult as SwapSimulationResult;
- return formatCost(swapResult?.intent?.sources?.[0]?.amount ?? '0');
- } else {
- const bridgeExecuteResult = simulationResult as BridgeAndExecuteSimulationResult;
-
- // If bridge was skipped, use input amount from metadata
- if (bridgeExecuteResult?.metadata?.bridgeSkipped) {
- return formatCost(bridgeExecuteResult.metadata.inputAmount ?? '');
- }
-
- return formatCost(bridgeExecuteResult?.bridgeSimulation?.intent?.sourcesTotal ?? '');
- }
- }, [transactionType, simulationResult]);
-
- const destinationAmount = useCallback(() => {
- if (transactionType === 'bridge' || transactionType === 'transfer') {
- return formatCost((simulationResult as SimulationResult)?.intent?.destination?.amount);
- } else if (transactionType === 'swap') {
- const swapResult = simulationResult as SwapSimulationResult;
- return formatCost(swapResult?.intent?.destination?.amount ?? '0');
- } else {
- const bridgeExecuteResult = simulationResult as BridgeAndExecuteSimulationResult;
-
- // If bridge was skipped, use input amount from metadata (same as source since no bridge)
- if (bridgeExecuteResult?.metadata?.bridgeSkipped) {
- return formatCost(bridgeExecuteResult.metadata.inputAmount ?? '');
- }
-
- return formatCost(bridgeExecuteResult?.bridgeSimulation?.intent?.destination?.amount ?? '');
- }
- }, [transactionType, simulationResult]);
-
- // Avoid nested ternaries by isolating explorer links rendering
- const renderExplorerLinks = useCallback((): React.ReactNode => {
- if (transactionType === 'bridgeAndExecute') {
- return (
-
- {/* Only show bridge transaction link if bridge wasn't skipped */}
- {explorerURL && !(executionResult as BridgeAndExecuteResult)?.bridgeSkipped && (
- window.open(explorerURL, '_blank')}
- >
- View Bridge Transaction{' '}
-
-
- )}
- {(executionResult as BridgeAndExecuteResult)?.executeExplorerUrl && (
-
- window.open(
- (executionResult as BridgeAndExecuteResult)?.executeExplorerUrl,
- '_blank',
- )
- }
- >
- {(executionResult as BridgeAndExecuteResult)?.bridgeSkipped
- ? 'View Transaction'
- : 'View Execute Transaction'}{' '}
-
-
- )}
-
- );
- }
-
- if (transactionType === 'swap') {
- return (
-
- {explorerURLs?.source && (
- window.open(explorerURLs.source, '_blank')}
- >
- View Source Transaction{' '}
-
-
- )}
- {explorerURLs?.destination && (
- window.open(explorerURLs.destination, '_blank')}
- >
- View Destination Transaction{' '}
-
-
- )}
-
- );
- }
-
- if (explorerURL) {
- return (
- window.open(explorerURL, '_blank')}
- >
- View on Explorer
-
- );
- }
-
- return null;
- }, [transactionType, explorerURL, explorerURLs, executionResult]);
-
- return (
- <>
-
- {
- lottieRef.current?.resize();
- }}
- className="absolute top-16 left-1/2 -translate-x-1/2 pointer-events-none"
- >
-
- {
- lottieRef.current = instance;
- }}
- />
-
-
- {
- if (status === 'error' || status === 'success') {
- cancelTransaction();
- } else {
- disableCollapse ? cancelTransaction() : toggleTransactionCollapse();
- }
- }}
- >
- {status === 'error' || status === 'success' || disableCollapse ? (
-
- ) : (
-
- )}
-
-
-
- {/* Chains Row */}
-
- {/* Sources */}
-
-
- {Array.isArray(sourceChainMeta) &&
- sourceChainMeta
- .slice(0, 3)
- .map((chain, index) => (
-
0 ? '-ml-5' : '',
- chain?.id !== SUPPORTED_CHAINS.BASE &&
- chain?.id !== SUPPORTED_CHAINS.BASE_SEPOLIA
- ? 'rounded-nexus-full'
- : '',
- )}
- style={{ zIndex: (sourceChainMeta?.length || 0) - index }}
- />
- ))}
-
-
-
- {sourceAmount()}
-
-
- From {sourceChainMeta?.length ?? 0} chain
- {(sourceChainMeta?.length ?? 0) > 1 ? 's' : ''}
-
-
-
- {/* Progress */}
-
-
-
- {/* Destination */}
-
- {destChainMeta ? (
- <>
-
-
-
-
-
- {destinationAmount()}
-
-
- To {destChainMeta?.name ?? ''}
-
-
- >
- ) : (
-
- )}
-
-
- {/* Text & timer */}
-
- {error ? (
-
- ) : (
- <>
-
-
- {Math.floor(timer)}
-
-
- .
-
-
- {String(Math.floor((timer % 1) * 1000)).padStart(3, '0')}s
-
-
-
-
-
-
- {description}
-
- >
- )}
- {/* Explorer links */}
- {renderExplorerLinks()}
-
-
-
- {/* Footer */}
-
-
- {status === 'success' && (
-
-
- Close
-
-
- )}
-
- Powered By
-
-
-
- >
- );
-};
diff --git a/packages/widgets/src/components/processing/processor-mini-card.tsx b/packages/widgets/src/components/processing/processor-mini-card.tsx
deleted file mode 100644
index 61676d48..00000000
--- a/packages/widgets/src/components/processing/processor-mini-card.tsx
+++ /dev/null
@@ -1,268 +0,0 @@
-import React, { useCallback } from 'react';
-import { motion } from 'motion/react';
-import SuccessRipple from '../motion/success-ripple';
-import { Maximize, ExternalLink } from '../icons';
-import { type BridgeAndExecuteResult, SUPPORTED_CHAINS, TOKEN_METADATA } from '@nexus/commons';
-import { WordsPullUp } from '../motion/pull-up-words';
-import { cn } from '../../utils/utils';
-import { ThreeStageProgress } from '../motion/three-stage-progress';
-import { Button } from '../motion/button-motion';
-import { EnhancedInfoMessage } from '../shared/enhanced-info-message';
-import { ProcessorCardProps, SwapSimulationResult } from '../../types';
-import { TokenIcon } from '../shared/icons';
-
-export const ProcessorMiniCard: React.FC = ({
- status,
- toggleTransactionCollapse,
- sourceChainMeta,
- destChainMeta,
- tokenMeta,
- transactionType,
- simulationResult,
- processing,
- explorerURL,
- explorerURLs,
- description,
- error,
- executionResult,
-}: ProcessorCardProps) => {
- const renderTokenIcon = useCallback(() => {
- const progress = processing?.animationProgress ?? 0;
-
- if (transactionType !== 'swap') {
- return (
-
- );
- }
-
- const swapResult = simulationResult as SwapSimulationResult;
- const destSymbol = swapResult?.intent?.destination?.token?.symbol?.toUpperCase();
- const destIcon = destSymbol ? TOKEN_METADATA[destSymbol]?.icon : undefined;
- const sourceIcon = tokenMeta?.icon;
-
- return (
-
-
-
-
- = 50 ? 1 : 0, scale: progress >= 50 ? 1 : 1.05 }}
- transition={{ duration: 0.25 }}
- >
-
-
-
- );
- }, [
- processing?.animationProgress,
- tokenMeta?.icon,
- tokenMeta?.symbol,
- transactionType,
- simulationResult,
- ]);
-
- return (
-
- {/* Header */}
-
-
- {/* Sources */}
-
- {Array.isArray(sourceChainMeta) &&
- sourceChainMeta
- .slice(0, 3)
- .map((chain, index) => (
-
0 ? '-ml-3' : '',
- chain?.id !== SUPPORTED_CHAINS.BASE &&
- chain?.id !== SUPPORTED_CHAINS.BASE_SEPOLIA
- ? 'rounded-nexus-full'
- : '',
- )}
- style={{ zIndex: (sourceChainMeta?.length || 0) - index }}
- />
- ))}
-
-
- {/* Progress */}
-
-
-
-
- {/* Destination */}
- {destChainMeta ? (
-
-
-
- ) : (
-
- )}
-
-
{
- e.stopPropagation();
- }}
- onPointerDown={(e) => {
- e.stopPropagation();
- }}
- onMouseDownCapture={(e) => {
- e.stopPropagation();
- }}
- onMouseDown={(e) => {
- e.stopPropagation();
- }}
- onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- toggleTransactionCollapse();
- }}
- className="p-1 hover:bg-gray-100 rounded-nexus-md transition-colors text-nexus-foreground"
- variant="link"
- >
-
-
-
-
- {/* Body */}
- {status === 'error' ? (
-
-
-
- ) : (
-
-
-
-
- {status === 'success' &&
- (() => {
- if (transactionType === 'swap') {
- if (explorerURLs?.destination) {
- return (
-
window.open(explorerURLs.destination as string, '_blank')}
- >
- View Transaction{' '}
-
-
- );
- }
- if (explorerURLs?.source) {
- return (
-
window.open(explorerURLs.source as string, '_blank')}
- >
- View Transaction{' '}
-
-
- );
- }
- return null;
- }
- if (transactionType !== 'bridgeAndExecute') {
- if (!explorerURL) return null;
- return (
-
window.open(explorerURL, '_blank')}
- >
- View on Explorer{' '}
-
-
- );
- }
- const executeUrl = (executionResult as BridgeAndExecuteResult)?.executeExplorerUrl;
- if (executeUrl) {
- return (
-
window.open(executeUrl, '_blank')}
- >
- View Transaction{' '}
-
-
- );
- }
- return null;
- })()}
- {status !== 'success' && (
-
- {description}
-
- )}
-
- )}
-
- );
-};
diff --git a/packages/widgets/src/components/processing/transaction-processor-shell.tsx b/packages/widgets/src/components/processing/transaction-processor-shell.tsx
deleted file mode 100644
index 13ffa8fd..00000000
--- a/packages/widgets/src/components/processing/transaction-processor-shell.tsx
+++ /dev/null
@@ -1,234 +0,0 @@
-import { useEffect, useMemo, useState, useRef, memo } from 'react';
-import { motion, AnimatePresence } from 'motion/react';
-import { useInternalNexus } from '../../providers/InternalNexusProvider';
-import { ProcessorMiniCard } from './processor-mini-card';
-import { ProcessorFullCard } from './processor-full-card';
-import {
- type BridgeAndExecuteSimulationResult,
- type SimulationResult,
- CHAIN_METADATA,
- logger,
- TOKEN_METADATA,
-} from '@nexus/commons';
-import { getOperationText, getTokenFromInputData } from '../../utils/utils';
-import { useDragConstraints } from '../motion/drag-constraints';
-import { TransactionType, SwapSimulationResult } from '../../types';
-
-const COLLAPSED = { width: 400, height: 120, radius: 16 } as const;
-const EXPANDED = { width: 480, height: 500, radius: 16 } as const;
-
-const TransactionProcessorShell = ({ disableCollapse = false }: { disableCollapse?: boolean }) => {
- const lastLoggedProcessingState = useRef('');
- const {
- activeTransaction,
- processing,
- explorerURL,
- explorerURLs,
- timer,
- toggleTransactionCollapse,
- isTransactionCollapsed,
- cancelTransaction,
- } = useInternalNexus();
-
- const { type: transactionType, simulationResult } = activeTransaction;
-
- const sources = useMemo(() => {
- if (!simulationResult) return [] as number[];
-
- if (transactionType === 'bridge' || transactionType === 'transfer') {
- return (simulationResult as SimulationResult)?.intent?.sources?.map((s) => s.chainID) || [];
- }
-
- if (transactionType === 'swap') {
- const swapResult = simulationResult as SwapSimulationResult;
- // For swap, extract chain IDs from sources
- return swapResult?.intent?.sources?.map((source) => source?.chain?.id) || [];
- }
-
- const bridgeExecuteResult = simulationResult as BridgeAndExecuteSimulationResult;
-
- // If bridge was skipped, use the target chain as the source since we're executing directly
- if (bridgeExecuteResult?.metadata?.bridgeSkipped) {
- return [bridgeExecuteResult.metadata.targetChain];
- }
-
- return bridgeExecuteResult.bridgeSimulation?.intent?.sources?.map((s) => s.chainID) || [];
- }, [simulationResult, transactionType]);
-
- const destination = useMemo(() => {
- if (!simulationResult) return 0;
-
- if (transactionType === 'bridge' || transactionType === 'transfer') {
- return (simulationResult as SimulationResult)?.intent?.destination?.chainID || 0;
- }
-
- if (transactionType === 'swap') {
- const swapResult = simulationResult as SwapSimulationResult;
- // For swap, extract destination chain ID
- return swapResult?.intent?.destination?.chain?.id ?? 0;
- }
-
- const bridgeExecuteResult = simulationResult as BridgeAndExecuteSimulationResult;
-
- // If bridge was skipped, use the target chain as the destination
- if (bridgeExecuteResult?.metadata?.bridgeSkipped) {
- return bridgeExecuteResult.metadata.targetChain;
- }
-
- return bridgeExecuteResult.bridgeSimulation?.intent?.destination?.chainID || 0;
- }, [simulationResult, transactionType]);
-
- const token = getTokenFromInputData(activeTransaction.inputData) || '';
- const sourceChainMeta = sources
- .filter((s): s is number => s != null && !isNaN(s))
- .map((s) => CHAIN_METADATA[s])
- .filter(Boolean);
-
- const destChainMeta = destination ? CHAIN_METADATA[destination] : null;
- const tokenMeta = token ? TOKEN_METADATA[token] : null;
-
- const getDescription = () => {
- if (activeTransaction?.type === 'swap') {
- if (processing?.statusText === 'Swap is completed') {
- return 'Transaction Completed Successfully';
- }
- const destinationToken = (activeTransaction?.simulationResult as SwapSimulationResult)?.intent
- ?.destination?.token;
- const destinationTokenSymbol = destinationToken
- ? destinationToken.symbol.toUpperCase()
- : 'token';
-
- return `${getOperationText(transactionType as TransactionType)} ${tokenMeta?.symbol || 'token'} to ${destinationTokenSymbol} on ${destChainMeta?.name || 'destination chain'}`;
- }
- if (activeTransaction?.executionResult?.success) return 'Transaction Completed Successfully';
- return `${getOperationText(transactionType as TransactionType)} ${tokenMeta?.symbol || 'token'} from ${sourceChainMeta.length > 1 ? 'multiple chains' : sourceChainMeta[0]?.name} to ${destChainMeta?.name || 'destination chain'}`;
- };
-
- const shellActive = ['processing', 'success', 'error'].includes(activeTransaction.status);
-
- const dragConstraints = useDragConstraints();
-
- const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });
-
- useEffect(() => {
- const update = () => setWindowSize({ width: window.innerWidth, height: window.innerHeight });
- update();
- window.addEventListener('resize', update);
- return () => window.removeEventListener('resize', update);
- }, []);
-
- const collapsedPos = {
- x: Math.max(16, windowSize.width - COLLAPSED.width - 16),
- y: 16,
- };
-
- const expandedPos = {
- x: Math.max(0, (windowSize.width - EXPANDED.width) / 2),
- y: Math.max(0, (windowSize.height - EXPANDED.height) / 2),
- };
-
- if (!shellActive || !transactionType || !simulationResult) {
- return null;
- }
-
- // Only log processing changes when state actually changes to reduce noise
- const processingStateKey = `${processing?.currentStep}-${processing?.totalSteps}-${processing?.statusText}-${processing?.animationProgress}`;
- if (lastLoggedProcessingState.current !== processingStateKey && processing) {
- logger.info('processing from hook', processing);
- lastLoggedProcessingState.current = processingStateKey;
- }
-
- return (
-
- <>
- {/* Backdrop */}
- {!isTransactionCollapsed && (
-
- )}
-
- {/* Processor Card */}
-
- {isTransactionCollapsed ? (
-
- ) : (
-
- )}
-
- >
-
- );
-};
-TransactionProcessorShell.displayName = 'TransactionProcessorShell';
-
-export default memo(TransactionProcessorShell);
diff --git a/packages/widgets/src/components/processing/transaction-simulation.tsx b/packages/widgets/src/components/processing/transaction-simulation.tsx
deleted file mode 100644
index 1351ff36..00000000
--- a/packages/widgets/src/components/processing/transaction-simulation.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { type SimulationResult, type BridgeAndExecuteSimulationResult } from '@nexus/commons';
-import { InfoMessage } from '../shared/info-message';
-import { TransactionDetailsDrawer } from '../shared/transaction-details-drawer';
-import TextLoader from '../motion/text-loader';
-import {
- OrchestratorStatus,
- ReviewStatus,
- TransactionType,
- SwapSimulationResult,
-} from '../../types';
-
-interface TransactionSimulationProps {
- isLoading: boolean;
- simulationResult?: (
- | SimulationResult
- | BridgeAndExecuteSimulationResult
- | SwapSimulationResult
- ) & {
- allowance?: { needsApproval: boolean };
- };
- inputData?: {
- token?: string;
- amount?: string | number;
- chainId?: number;
- toChainId?: number;
- };
- type?: TransactionType;
- callback: () => void;
- status: OrchestratorStatus;
- reviewStatus: ReviewStatus;
-}
-
-export function TransactionSimulation({
- isLoading,
- simulationResult,
- inputData,
- type,
- callback,
- status,
- reviewStatus,
-}: Readonly) {
- if (isLoading) {
- return (
-
-
-
- );
- }
-
- if (!simulationResult) {
- return null;
- }
-
- return (
-
- {simulationResult?.allowance?.needsApproval && (
-
-
- You need to set allowance in your wallet first to continue.
-
-
- )}
-
-
-
-
-
- );
-}
diff --git a/packages/widgets/src/components/shared/action-buttons.tsx b/packages/widgets/src/components/shared/action-buttons.tsx
deleted file mode 100644
index 03998cc3..00000000
--- a/packages/widgets/src/components/shared/action-buttons.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { Button } from '../motion/button-motion';
-import { cn } from '../../utils/utils';
-import { SmallAvailLogo } from '../icons/SmallAvailLogo';
-import LoadingDots from '../motion/loading-dots';
-
-interface ActionButtonsProps {
- onCancel: () => void;
- onPrimary: () => void;
- primaryText?: string;
- primaryLoading?: boolean;
- primaryDisabled?: boolean;
- className?: string;
-}
-
-export function ActionButtons({
- onCancel,
- onPrimary,
- primaryText = 'Continue',
- primaryLoading = false,
- primaryDisabled = false,
- className,
-}: Readonly) {
- return (
-
-
-
- Cancel
-
-
-
- {primaryLoading ? : primaryText}
-
-
-
- Powered By
-
-
-
- );
-}
diff --git a/packages/widgets/src/components/shared/address-field.tsx b/packages/widgets/src/components/shared/address-field.tsx
deleted file mode 100644
index e5217b88..00000000
--- a/packages/widgets/src/components/shared/address-field.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { Input } from '../motion/input';
-import { cn } from '../../utils/utils';
-
-interface AddressFieldProps {
- value?: string;
- onChange?: (value: string) => void;
- disabled?: boolean;
- placeholder?: string;
- className?: string;
- hasValidationError?: boolean;
-}
-
-export function AddressField({
- value,
- onChange,
- disabled = false,
- placeholder = '0x...',
- className,
- hasValidationError = false,
-}: Readonly) {
- return (
-
-
- onChange?.(e.target.value)}
- disabled={disabled}
- className={cn(
- 'px-0 placeholder:font-nexus-primary text-nexus-black font-semibold text-base',
- hasValidationError ? 'border-red-500 focus:border-red-500' : '',
- )}
- />
-
-
- );
-}
diff --git a/packages/widgets/src/components/shared/allowance-form.tsx b/packages/widgets/src/components/shared/allowance-form.tsx
deleted file mode 100644
index ed456bc5..00000000
--- a/packages/widgets/src/components/shared/allowance-form.tsx
+++ /dev/null
@@ -1,265 +0,0 @@
-import { Fragment, useCallback, useEffect, useRef, useState } from 'react';
-import { cn, formatCost } from '../../utils/utils';
-import { EnhancedInfoMessage } from './enhanced-info-message';
-import { useInternalNexus } from '../../providers/InternalNexusProvider';
-import { CHAIN_METADATA, SUPPORTED_CHAINS, TOKEN_METADATA, formatUnits } from '@nexus/commons';
-import { FormField } from '../motion/form-field';
-import { Input } from '../motion/input';
-
-export interface AllowanceFormProps {
- token: string;
- minimumAmount: string;
- inputAmount: string;
- sourceChains: { chainId: number; amount: string; needsApproval?: boolean }[];
- onApprove: (amount: string, isMinimum: boolean) => void;
- onCancel: () => void;
- isLoading?: boolean;
- error?: string | null;
- // Expose form state for external button handling
- onFormStateChange?: (isValid: boolean, approveHandler: () => void) => void;
-}
-
-export function AllowanceForm({
- token,
- minimumAmount,
- inputAmount,
- sourceChains,
- onApprove,
- onCancel: _onCancel,
- isLoading = false,
- error = null,
- onFormStateChange,
-}: Readonly) {
- const [currentAllowance, setCurrentAllowance] = useState(null);
- const [selectedType, setSelectedType] = useState<'minimum' | 'custom'>('minimum');
- const [customAmount, setCustomAmount] = useState('');
- const { sdk } = useInternalNexus();
-
- // Keep latest form values in refs to avoid stale closures when parent stores handler
- const latestValuesRef = useRef({ selectedType, customAmount, minimumAmount });
- latestValuesRef.current.selectedType = selectedType;
- latestValuesRef.current.customAmount = customAmount;
- latestValuesRef.current.minimumAmount = minimumAmount;
-
- const tokenMetadata = TOKEN_METADATA[token];
-
- // Stable handler that reads latest values from refs, so parent always has a fresh handler
- const stableApproveHandler = useCallback(() => {
- const { selectedType, customAmount, minimumAmount } = latestValuesRef.current;
- if (selectedType === 'minimum') {
- onApprove(minimumAmount, true);
- } else {
- onApprove(customAmount, false);
- }
- }, [onApprove]);
-
- const validateCustomAmount = (amount: string): boolean => {
- if (!amount) return false;
- const numAmount = parseFloat(amount);
- const numInputAmount = parseFloat(inputAmount);
- return !isNaN(numAmount) && numAmount > 0 && numAmount >= numInputAmount;
- };
-
- const getCurrentAllowance = async () => {
- // Find the first chain that actually needs allowance
- const chainThatNeedsAllowance = sourceChains.find((chain) => chain.needsApproval === true);
-
- if (!chainThatNeedsAllowance) {
- // If no chain needs approval, show allowance from first chain or 0
- const firstChain = sourceChains[0];
- if (firstChain) {
- const allowance = await sdk.getAllowance(firstChain.chainId, [token]);
- const decimals = Number(TOKEN_METADATA[token].decimals);
- const formattedAllowance = formatUnits(allowance[0]?.allowance ?? 0n, decimals);
- setCurrentAllowance(formattedAllowance);
- } else {
- setCurrentAllowance('0');
- }
- return;
- }
-
- // Get allowance from the chain that needs approval
- const allowance = await sdk.getAllowance(chainThatNeedsAllowance.chainId, [token]);
- const decimals = Number(TOKEN_METADATA[token].decimals);
- const formattedAllowance = formatUnits(allowance[0]?.allowance ?? 0n, decimals);
- setCurrentAllowance(formattedAllowance);
- };
-
- useEffect(() => {
- if (!currentAllowance) {
- getCurrentAllowance();
- }
- }, [sourceChains, token]);
-
- const isCustomValid = selectedType === 'custom' ? validateCustomAmount(customAmount) : true;
- const isFormValid = selectedType === 'minimum' || isCustomValid;
-
- // Notify parent of form state changes; avoid depending on onFormStateChange to prevent loops
- useEffect(() => {
- if (onFormStateChange) {
- onFormStateChange(isFormValid, stableApproveHandler);
- }
- }, [isFormValid]);
-
- return (
-
-
- {/* Header */}
-
-
- Allow access to {formatCost(minimumAmount)} {token} to complete your transaction.
-
-
-
- {/* Token Information */}
-
-
-
Token
-
- {tokenMetadata?.icon && (
-
- )}
-
- {token} on
-
-
- {sourceChains
- .filter((chain) => chain.needsApproval !== false) // Show chains that need approval or are undefined
- .map((source, index, filteredChains) => {
- const chainMeta = CHAIN_METADATA[source?.chainId];
- return (
-
- 0 ? '-ml-5' : '',
- chainMeta?.id !== SUPPORTED_CHAINS.BASE &&
- chainMeta?.id !== SUPPORTED_CHAINS.BASE_SEPOLIA
- ? 'rounded-nexus-full '
- : '',
- )}
- style={{ zIndex: filteredChains.length - index }}
- title={chainMeta?.name}
- />
-
- );
- })}
- {sourceChains.filter((chain) => chain.needsApproval !== false).length > 1 && (
-
- +{sourceChains.filter((chain) => chain.needsApproval !== false).length} chains
-
- )}
-
-
-
- {currentAllowance && (
-
-
-
- Current Allowance
-
-
- {currentAllowance}
-
-
-
- )}
-
-
- {error ? (
-
- ) : (
-
-
- {/* Minimum Option */}
-
setSelectedType('minimum')}
- >
-
-
-
setSelectedType('minimum')}
- className="text-blue-600"
- />
-
- Min:
-
- {formatCost(minimumAmount)}
-
-
-
-
- RECOMMENDED
-
-
-
-
- {/* Custom Option */}
-
-
setSelectedType('custom')}
- >
- setSelectedType('custom')}
- className="text-blue-600"
- />
-
- Custom
-
-
-
-
- {selectedType === 'custom' && (
-
-
- setCustomAmount(e.target.value)}
- className={cn(
- 'text-nexus-black text-base font-semibold font-nexus-primary leading-normal px-4 py-2 border border-nexus-input rounded-nexus-md',
- customAmount && !isCustomValid ? 'border-red-500 focus:border-red-500' : '',
- )}
- />
-
-
- )}
-
- )}
-
-
- );
-}
diff --git a/packages/widgets/src/components/shared/amount-input.tsx b/packages/widgets/src/components/shared/amount-input.tsx
deleted file mode 100644
index 757fc1de..00000000
--- a/packages/widgets/src/components/shared/amount-input.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import * as React from 'react';
-import { cn } from '../../utils/utils';
-import { Input } from '../motion/input';
-import { useInternalNexus } from '../../providers/InternalNexusProvider';
-import { getFiatValue } from '../../utils/balance-utils';
-
-interface AmountInputProps {
- value?: string;
- disabled?: boolean;
- onChange?: (value: string) => void;
- className?: string;
- placeholder?: string;
- debounceMs?: number;
- token?: string;
-}
-
-export function AmountInput({
- value,
- disabled = false,
- onChange,
- className,
- placeholder = '0.0',
- debounceMs = 500,
- token,
-}: Readonly) {
- const [localValue, setLocalValue] = React.useState(value || '');
- const timeoutRef = React.useRef(undefined);
- const { exchangeRates } = useInternalNexus();
-
- React.useEffect(() => {
- setLocalValue(value || '');
- }, [value]);
-
- const validateNumberInput = (input: string): string => {
- if (input === '') return '';
- if (input === '.') return '0.';
- // Remove non-numeric characters except dots
- let cleaned = input.replace(/[^0-9.]/g, '');
-
- // Handle case where input starts with decimal point
- if (cleaned.startsWith('.')) {
- cleaned = '0' + cleaned;
- }
-
- // Handle multiple decimal points - keep only the first one
- const decimalCount = (cleaned.match(/\./g) || []).length;
- if (decimalCount > 1) {
- const firstDecimalIndex = cleaned.indexOf('.');
- cleaned =
- cleaned.substring(0, firstDecimalIndex + 1) +
- cleaned.substring(firstDecimalIndex + 1).replace(/\./g, '');
- }
-
- if (cleaned.length > 1 && cleaned.startsWith('0')) {
- const decimalIndex = cleaned.indexOf('.');
- if (decimalIndex === -1 || decimalIndex > 1) {
- cleaned = cleaned.replace(/^0+/, '');
- if (cleaned === '' || cleaned.startsWith('.')) {
- cleaned = '0' + cleaned;
- }
- } else if (decimalIndex === 1) {
- if (cleaned.length > 2 && cleaned.substring(0, 2) === '00') {
- cleaned = cleaned.replace(/^0+/, '0');
- }
- }
- }
-
- const decimalIndex = cleaned.indexOf('.');
- if (decimalIndex !== -1 && cleaned.length - decimalIndex > 19) {
- cleaned = cleaned.substring(0, decimalIndex + 19);
- }
-
- return cleaned;
- };
-
- const handleInputChange = (e: React.ChangeEvent) => {
- const rawValue = e.target.value;
- const validatedValue = validateNumberInput(rawValue);
-
- setLocalValue(validatedValue);
-
- if (!onChange) return;
-
- if (timeoutRef.current) {
- clearTimeout(timeoutRef.current);
- }
-
- timeoutRef.current = setTimeout(() => {
- if (validatedValue !== value) {
- onChange(validatedValue);
- }
- }, debounceMs);
- };
-
- React.useEffect(() => {
- return () => {
- if (timeoutRef.current) {
- clearTimeout(timeoutRef.current);
- }
- };
- }, []);
-
- return (
-
-
- {onChange ? (
-
- ) : (
-
- {value ?? 0}
-
- )}
-
- {token && value && (
-
- {getFiatValue(value, token, exchangeRates)}
-
- )}
-
- );
-}
diff --git a/packages/widgets/src/components/shared/chain-select.tsx b/packages/widgets/src/components/shared/chain-select.tsx
deleted file mode 100644
index d790510a..00000000
--- a/packages/widgets/src/components/shared/chain-select.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-import { useMemo } from 'react';
-import { ChainSelectProps } from '../../types';
-import { CHAIN_METADATA, DESTINATION_SWAP_TOKENS } from '@nexus/commons';
-import { ChainIcon } from './icons';
-import { cn } from '../../utils/utils';
-import { Button } from '../motion/button-motion';
-import { DrawerAutoClose } from '../motion/drawer';
-import { getFilteredChainsForToken } from '../../utils/token-utils';
-import { useInternalNexus } from '../../providers/InternalNexusProvider';
-import type { TransactionType } from '../../utils/balance-utils';
-
-interface ChainSelectOption {
- value: string;
- label: string;
- chainId: number;
- logo: string;
-}
-
-export function ChainSelect({
- value,
- onValueChange,
- disabled = false,
- network = 'mainnet',
- className,
- hasValues,
- isSource,
- selectedToken,
- transactionType,
-}: ChainSelectProps & {
- network?: 'mainnet' | 'testnet';
- selectedToken?: string;
- transactionType?: TransactionType;
-}) {
- const { sdk } = useInternalNexus();
- const availableChainIds = useMemo(() => {
- if (!sdk) return [] as number[];
- let ids: number[] = [];
- if (network === 'testnet' && transactionType !== 'swap') {
- ids = sdk?.utils?.getSupportedChains(0)?.map((chain) => chain?.id) ?? [];
- } else if (transactionType === 'swap' && network !== 'testnet') {
- ids = isSource
- ? (sdk?.utils?.getSwapSupportedChainsAndTokens()?.map((chain) => chain?.id) ?? [])
- : Array.from(DESTINATION_SWAP_TOKENS.keys());
- } else {
- ids = sdk?.utils?.getSupportedChains()?.map((chain) => chain?.id) ?? [];
- }
- // Exclude Fuel (9889) and any chains without known metadata to avoid runtime errors
- return ids.filter((id) => id !== 9889 && !!CHAIN_METADATA[id]);
- }, [sdk, network, transactionType, isSource]);
-
- const filteredChainIds = useMemo(() => {
- if (!availableChainIds?.length) return [] as number[];
- if (selectedToken && transactionType) {
- return getFilteredChainsForToken(
- selectedToken,
- availableChainIds,
- transactionType,
- sdk,
- !isSource,
- );
- }
- return availableChainIds;
- }, [availableChainIds, selectedToken, transactionType, sdk, isSource]);
-
- const chainOptions: ChainSelectOption[] = filteredChainIds
- .filter((chainId) => !!CHAIN_METADATA[chainId])
- .map((chainId) => {
- const metadata = CHAIN_METADATA[chainId];
- return {
- value: chainId.toString(),
- label: metadata?.name ?? `Chain ${chainId}`,
- chainId,
- logo: metadata?.logo ?? '',
- };
- });
- const selectedOption = useMemo(
- () => chainOptions.find((opt) => opt.value === (value ?? '')),
- [value, chainOptions],
- );
-
- const handleSelect = (chainId: string) => {
- if (disabled) return;
- onValueChange(chainId);
- };
-
- // Check if current selection is still valid after filtering
- const isCurrentSelectionValid = useMemo(() => {
- if (!value) return true;
- return chainOptions.some((option) => option.value === value);
- }, [value, chainOptions]);
-
- if (network === 'testnet' && transactionType === 'swap') {
- throw new Error('Swap not supported on testnet');
- }
-
- return (
-
-
-
- {isSource ? 'Source' : 'Destination'} Chain
-
- {selectedToken && transactionType && !isCurrentSelectionValid && (
-
- Current chain doesn't support {selectedToken}
-
- )}
-
-
- {chainOptions.map((chain, index) => (
-
- handleSelect(chain?.chainId.toString())}
- className={cn(
- 'p-3 flex items-center justify-start gap-x-2 rounded-nexus-md border border-nexus-border w-full hover:bg-nexus-accent-green/10',
- disabled &&
- 'pointer-events-none cursor-not-allowed opacity-50 text-nexus-foreground ',
- selectedOption?.chainId === chain?.chainId ? 'bg-nexus-accent-green/10' : '',
- index === chainOptions.length - 1 ? 'mb-20' : '',
- )}
- >
-
-
- {chain?.label}
-
-
-
- ))}
-
- {/* Empty state */}
- {chainOptions.length === 0 && (
-
-
No chains available for selected token
-
- )}
-
-
- );
-}
diff --git a/packages/widgets/src/components/shared/destination-drawer.tsx b/packages/widgets/src/components/shared/destination-drawer.tsx
deleted file mode 100644
index 7cb51734..00000000
--- a/packages/widgets/src/components/shared/destination-drawer.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import { ChainSelect } from './chain-select';
-import { TokenSelect } from './token-select';
-import {
- Drawer,
- DrawerClose,
- DrawerContent,
- DrawerHeader,
- DrawerTitle,
- DrawerTrigger,
-} from '../motion/drawer';
-import { ChevronDownIcon, CircleX } from '../icons';
-import { FormField } from '../motion/form-field';
-import { CHAIN_METADATA, SUPPORTED_CHAINS } from '@nexus/commons';
-import { cn } from '../../utils/utils';
-import { TokenIcon } from './icons';
-import type { TransactionType as BalanceTransactionType } from '../../utils/balance-utils';
-
-interface DestinationDrawerProps {
- chainValue?: string;
- tokenValue?: string;
- isChainSelectDisabled?: boolean;
- isTokenSelectDisabled?: boolean;
- network?: 'mainnet' | 'testnet';
- onChainValueChange: (chain: string) => void;
- onTokenValueChange: (token: string, iconUrl?: string) => void;
- fieldLabel?: string;
- drawerTitle?: string;
- type?: BalanceTransactionType;
- isDestination?: boolean;
- isSourceChain?: boolean;
-}
-
-const DestinationTrigger = ({
- chainValue,
- tokenValue,
- fieldLabel = 'Destination',
-}: {
- chainValue?: string;
- tokenValue?: string;
- fieldLabel?: string;
-}) => {
- const chainId = chainValue ? parseInt(chainValue) : undefined;
-
- return (
-
-
-
-
- {tokenValue ? (
-
- ) : (
-
- )}
- {chainId ? (
-
- ) : (
-
- )}
-
-
-
- {tokenValue ?? 'Token'}
-
-
- {chainId ? CHAIN_METADATA[chainId]?.name : 'Chain'}
-
-
-
-
-
-
- );
-};
-
-const DestinationDrawer = ({
- chainValue,
- tokenValue,
- isChainSelectDisabled,
- isTokenSelectDisabled,
- network,
- onChainValueChange,
- onTokenValueChange,
- fieldLabel,
- drawerTitle = 'Select Destination Chain & Token',
- type,
- isDestination = false,
- isSourceChain = false,
-}: DestinationDrawerProps) => {
- return (
-
-
-
-
-
-
-
-
- {drawerTitle}
-
-
-
-
-
-
-
-
-
- onTokenValueChange(token, iconUrl)}
- disabled={isTokenSelectDisabled}
- network={network}
- className="w-full"
- hasValues={!!chainValue}
- type={type}
- chainId={chainValue ? parseInt(chainValue) : undefined}
- isDestination={isDestination}
- />
-
-
-
- );
-};
-
-export default DestinationDrawer;
diff --git a/packages/widgets/src/components/shared/enhanced-info-message.tsx b/packages/widgets/src/components/shared/enhanced-info-message.tsx
deleted file mode 100644
index 77a9f189..00000000
--- a/packages/widgets/src/components/shared/enhanced-info-message.tsx
+++ /dev/null
@@ -1,143 +0,0 @@
-import { useState } from 'react';
-import { InfoMessage } from './info-message';
-import { Button } from '../motion/button-motion';
-import {
- isChainError,
- extractChainIdFromError,
- addChainToWallet,
- formatErrorForUI,
- cn,
-} from '../../utils/utils';
-import { Plus } from '../icons';
-import { useInternalNexus } from '../../providers/InternalNexusProvider';
-import LoadingDots from '../motion/loading-dots';
-import { CHAIN_METADATA, SUPPORTED_CHAINS, logger } from '@nexus/commons';
-
-interface EnhancedInfoMessageProps {
- error: unknown;
- context?: string;
- className?: string;
-}
-
-export function EnhancedInfoMessage({
- error,
- context,
- className,
-}: Readonly) {
- const [isAddingChain, setIsAddingChain] = useState(false);
- const [chainAdded, setChainAdded] = useState(false);
- const { sdk } = useInternalNexus();
-
- const isChainRelatedError = isChainError(error);
- const chainId = isChainRelatedError ? extractChainIdFromError(error) : null;
- const chainMetadata = chainId ? CHAIN_METADATA[chainId] : null;
-
- const handleAddChain = async () => {
- if (!chainId) return;
-
- setIsAddingChain(true);
- try {
- const provider = sdk.getEVMProviderWithCA();
- const success = await addChainToWallet(chainId, provider);
- if (success) {
- setChainAdded(true);
- }
- } catch (err) {
- logger.error('Failed to add chain:', err as Error);
- } finally {
- setIsAddingChain(false);
- }
- };
-
- const formattedError = formatErrorForUI(error, context);
-
- if (isChainRelatedError && chainMetadata && !chainAdded) {
- return (
-
-
-
{formattedError}
-
-
-
-
-
- {chainMetadata.name}
-
-
- Chain ID: {chainId}
-
-
-
- {isAddingChain ? (
- <>
-
- Adding...
- >
- ) : (
- <>
-
- Add Chain
- >
- )}
-
-
-
-
- This will add {chainMetadata.name} network to your wallet so you can use it for
- transactions.
-
-
-
- );
- }
-
- if (chainAdded) {
- return (
-
-
-
-
-
- {chainMetadata
- ? `${chainMetadata.name} network added successfully!`
- : 'Network added successfully!'}
-
-
- You can now retry your transaction.
-
-
-
-
- );
- }
-
- // Fallback to regular formatted error message
- return (
-
- {formattedError}
-
- );
-}
diff --git a/packages/widgets/src/components/shared/icons.tsx b/packages/widgets/src/components/shared/icons.tsx
deleted file mode 100644
index b0233697..00000000
--- a/packages/widgets/src/components/shared/icons.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import {
- CHAIN_METADATA,
- SUPPORTED_CHAINS,
- TOKEN_METADATA,
- DESTINATION_SWAP_TOKENS,
- type ChainMetadata,
-} from '@nexus/commons';
-import { cn } from '../../utils/utils';
-
-// Additional token logos that might not be in TOKEN_METADATA
-const ADDITIONAL_TOKEN_LOGOS: Record = {
- WETH: 'https://assets.coingecko.com/coins/images/279/large/ethereum.png?1595348880',
- USDS: 'https://assets.coingecko.com/coins/images/39926/standard/usds.webp?1726666683',
- SOPH: 'https://assets.coingecko.com/coins/images/38680/large/sophon_logo_200.png',
- KAIA: 'https://assets.coingecko.com/asset_platforms/images/9672/large/kaia.png',
- BNB: 'https://assets.coingecko.com/coins/images/825/large/bnb-icon2_2x.png',
- // Add ETH as fallback for any ETH-related tokens
- ETH: 'https://coin-images.coingecko.com/coins/images/279/large/ethereum.png?1696501628',
- // Add common token fallbacks
- POL: 'https://coin-images.coingecko.com/coins/images/32440/standard/polygon.png',
- AVAX: 'https://assets.coingecko.com/coins/images/12559/standard/Avalanche_Circle_RedWhite_Trans.png',
- FUEL: 'https://coin-images.coingecko.com/coins/images/279/large/ethereum.png',
- HYPE: 'https://assets.coingecko.com/asset_platforms/images/243/large/hyperliquid.png',
- // Popular swap tokens
- DAI: 'https://coin-images.coingecko.com/coins/images/9956/large/Badge_Dai.png?1696509996',
- UNI: 'https://coin-images.coingecko.com/coins/images/12504/large/uni.jpg?1696512319',
- AAVE: 'https://coin-images.coingecko.com/coins/images/12645/large/AAVE.png?1696512452',
- LDO: 'https://coin-images.coingecko.com/coins/images/13573/large/Lido_DAO.png?1696513326',
- PEPE: 'https://coin-images.coingecko.com/coins/images/29850/large/pepe-token.jpeg?1696528776',
- OP: 'https://coin-images.coingecko.com/coins/images/25244/large/Optimism.png?1696524385',
- ZRO: 'https://coin-images.coingecko.com/coins/images/28206/large/ftxG9_TJ_400x400.jpeg?1696527208',
- OM: 'https://assets.coingecko.com/coins/images/12151/standard/OM_Token.png?1696511991',
- KAITO: 'https://assets.coingecko.com/coins/images/54411/standard/Qm4DW488_400x400.jpg',
-};
-
-export const ChainIcon = ({ chainId }: { chainId: string }) => {
- const chain = Object.values(CHAIN_METADATA).find(
- (c: ChainMetadata) => c.id.toString() === chainId,
- );
- const iconUrl = chain?.logo;
-
- if (!iconUrl) {
- return
;
- }
-
- return (
-
- );
-};
-
-export const TokenIcon = ({
- tokenSymbol,
- iconUrl,
- className = 'w-6 h-6 rounded-nexus-full',
-}: {
- tokenSymbol: string;
- iconUrl?: string;
- className?: string;
-}) => {
- let finalIconUrl = iconUrl;
-
- // Comprehensive icon resolution logic
- if (!finalIconUrl) {
- // 1. First check additional token logos (prioritize over TOKEN_METADATA for better icons)
- finalIconUrl = ADDITIONAL_TOKEN_LOGOS[tokenSymbol];
-
- // 2. Then check standard TOKEN_METADATA
- if (!finalIconUrl) {
- const standardToken = TOKEN_METADATA[tokenSymbol];
- finalIconUrl = standardToken?.icon;
- }
-
- // 3. Check destination swap tokens
- if (!finalIconUrl) {
- const allDestinationTokens = Array.from(DESTINATION_SWAP_TOKENS.values()).flat();
- const destinationToken = allDestinationTokens.find((token) => token.symbol === tokenSymbol);
- finalIconUrl = destinationToken?.logo;
- }
-
- // 4. Special handling for wrapped tokens
- if (!finalIconUrl && tokenSymbol.startsWith('W') && tokenSymbol.length > 1) {
- const baseSymbol = tokenSymbol.substring(1); // Remove 'W' prefix
- finalIconUrl = ADDITIONAL_TOKEN_LOGOS[baseSymbol];
- }
-
- // 5. ETH fallback for any ethereum-related tokens
- if (!finalIconUrl && (tokenSymbol.includes('ETH') || tokenSymbol === 'WETH')) {
- finalIconUrl = ADDITIONAL_TOKEN_LOGOS['ETH'];
- }
- }
-
- // Fallback placeholder with first letter of token symbol
- if (!finalIconUrl) {
- return (
-
- {tokenSymbol.charAt(0).toUpperCase()}
-
- );
- }
-
- return ;
-};
diff --git a/packages/widgets/src/components/shared/info-message.tsx b/packages/widgets/src/components/shared/info-message.tsx
deleted file mode 100644
index 9c86796a..00000000
--- a/packages/widgets/src/components/shared/info-message.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import * as React from 'react';
-import { cn } from '../../utils/utils';
-import { cva, type VariantProps } from 'class-variance-authority';
-
-const infoMessageVariants = cva(
- 'px-2 py-3 rounded-nexus-md overflow-hidden font-nexus-primary font-semibold text-sm leading-[18px] backdrop-blur-[48px] border border-nexus-black/80',
- {
- variants: {
- variant: {
- success: 'bg-gradient-to-r from-[#86DF00]/16 to-[#73BF01]/16 text-nexus-black',
- info: 'bg-blue-50 text-nexus-black',
- warning: 'bg-gradient-to-r from-[#DFC200]/16 to-[#DFC200]/16 text-nexus-black',
- error: 'bg-[#C03C541A] text-[#C03C54] border border-[#C03C541A]',
- },
- },
- defaultVariants: {
- variant: 'success',
- },
- },
-);
-
-interface InfoMessageProps extends VariantProps {
- children: React.ReactNode;
- className?: string;
-}
-
-export function InfoMessage({ variant, children, className }: Readonly) {
- return (
-
- );
-}
diff --git a/packages/widgets/src/components/shared/prefilled-inputs.tsx b/packages/widgets/src/components/shared/prefilled-inputs.tsx
deleted file mode 100644
index 56539327..00000000
--- a/packages/widgets/src/components/shared/prefilled-inputs.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import { CHAIN_METADATA, SUPPORTED_CHAINS, TOKEN_METADATA } from '@nexus/commons';
-import { cn, formatCost, truncateAddress } from '../../utils/utils';
-import { useInternalNexus } from '../../providers/InternalNexusProvider';
-import { getFiatValue } from '../../utils/balance-utils';
-
-interface PrefilledInputsProps {
- inputData: {
- chainId?: number;
- toChainId?: number;
- token?: string;
- amount?: string | number;
- recipient?: string;
- };
- className?: string;
-}
-
-const PrefilledInputs = ({ inputData, className = '' }: PrefilledInputsProps) => {
- const { exchangeRates } = useInternalNexus();
- const destinationChain =
- CHAIN_METADATA[inputData?.chainId ?? inputData?.toChainId ?? SUPPORTED_CHAINS.ETHEREUM];
- const destinationToken = TOKEN_METADATA[inputData?.token ?? 'ETH'];
- return (
-
-
-
Sending
-
-
-
-
-
- {formatCost(inputData?.amount as string)}
-
-
- {inputData?.token}
-
-
-
-
-
To
-
-
- {destinationChain?.name}
-
-
-
-
- {inputData?.amount && inputData?.token && (
-
- {getFiatValue(inputData?.amount, inputData?.token, exchangeRates)}
-
- )}
- {inputData?.recipient && (
-
-
To
-
- {truncateAddress(inputData?.recipient, 4, 4)}
-
-
- )}
-
- );
-};
-
-export default PrefilledInputs;
diff --git a/packages/widgets/src/components/shared/swap-prefilled-inputs.tsx b/packages/widgets/src/components/shared/swap-prefilled-inputs.tsx
deleted file mode 100644
index 40eb7b6d..00000000
--- a/packages/widgets/src/components/shared/swap-prefilled-inputs.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import { CHAIN_METADATA, SUPPORTED_CHAINS, formatBalance } from '@nexus/commons';
-import { cn } from '../../utils/utils';
-import { useInternalNexus } from '../../providers/InternalNexusProvider';
-import { getFiatValue } from '../../utils/balance-utils';
-import { SwapInputData, SwapSimulationResult } from '../../types';
-import { TokenIcon } from './icons';
-
-interface SwapPrefilledInputsProps {
- inputData: Omit;
- className?: string;
-}
-
-const SwapPrefilledInputs = ({ inputData, className = '' }: SwapPrefilledInputsProps) => {
- const { exchangeRates, activeTransaction } = useInternalNexus();
- const sourceChain = CHAIN_METADATA[inputData?.fromChainID ?? SUPPORTED_CHAINS.ETHEREUM];
- const destinationChain = CHAIN_METADATA[inputData?.toChainID ?? SUPPORTED_CHAINS.ETHEREUM];
- const transactionIntent = (activeTransaction?.simulationResult as SwapSimulationResult)?.intent;
-
- return (
-
-
-
-
-
-
-
- {inputData?.fromAmount} {inputData?.fromTokenAddress}
-
-
-
-
-
-
-
- {transactionIntent
- ? formatBalance(
- transactionIntent?.destination?.amount,
- transactionIntent?.destination?.token?.decimals,
- 6,
- )
- : '...'}{' '}
- {inputData?.toTokenAddress}
-
-
-
-
-
- {inputData?.fromAmount && inputData?.fromTokenAddress && (
-
- {getFiatValue(inputData?.fromAmount, inputData?.fromTokenAddress, exchangeRates)}
-
- )}
-
- );
-};
-
-export default SwapPrefilledInputs;
diff --git a/packages/widgets/src/components/shared/token-select.tsx b/packages/widgets/src/components/shared/token-select.tsx
deleted file mode 100644
index 8d444aa6..00000000
--- a/packages/widgets/src/components/shared/token-select.tsx
+++ /dev/null
@@ -1,139 +0,0 @@
-import { useMemo } from 'react';
-import { TOKEN_METADATA, TESTNET_TOKEN_METADATA } from '@nexus/commons';
-import { TokenIcon } from './icons';
-import { cn } from '../../utils/utils';
-import { Button } from '../motion/button-motion';
-import { useInternalNexus } from '../../providers/InternalNexusProvider';
-import { DrawerAutoClose } from '../motion/drawer';
-import type { SwapInputData, TokenSelectProps } from '../../types';
-import { useAvailableTokens, type TokenSelectOption } from '../../utils/token-utils';
-
-export function TokenSelect({
- value,
- onValueChange,
- disabled = false,
- network = 'mainnet',
- className,
- hasValues,
- type,
- chainId,
- isDestination = false,
-}: TokenSelectProps & {
- network?: 'mainnet' | 'testnet';
- chainId?: number;
- isDestination?: boolean;
-}) {
- const { unifiedBalance, sdk, isSdkInitialized, activeTransaction } = useInternalNexus();
-
- const tokenOptions = useAvailableTokens({
- chainId,
- type: type ?? 'bridge',
- network,
- isDestination,
- sdk: isSdkInitialized ? sdk : undefined,
- });
-
- // Fallback to legacy logic if no type provided (backward compatibility)
- const legacyTokenOptions: TokenSelectOption[] = useMemo(() => {
- if (type) return []; // Use enhanced logic when type is available
-
- const tokenMetadata = network === 'testnet' ? TESTNET_TOKEN_METADATA : TOKEN_METADATA;
- return Object.values(tokenMetadata).map((token) => ({
- value: token.symbol,
- label: token.symbol,
- icon: token.icon,
- metadata: {
- ...token,
- contractAddress: undefined,
- },
- }));
- }, [network, type]);
-
- const finalTokenOptions = useMemo(() => {
- const tokens = type ? tokenOptions : legacyTokenOptions;
- const inputData = activeTransaction?.inputData as SwapInputData;
- if (inputData && inputData?.fromTokenAddress) {
- return tokens.filter((token) => token?.value !== inputData?.fromTokenAddress);
- }
- return tokens;
- }, [type, tokenOptions, legacyTokenOptions]);
-
- const tokenBalanceBreakdown = useMemo(() => {
- let breakdown: Record = {};
- unifiedBalance?.map((balance) => {
- const key = balance?.symbol;
- breakdown[key] = {
- bal: parseFloat(balance?.balance) > 0 ? balance?.balance : '00',
- chains: `${balance?.breakdown?.length > 1 ? balance?.breakdown?.length + ' chains' : balance?.breakdown?.length > 0 ? balance?.breakdown?.length + ' chain' : '-'}`,
- };
- });
- return breakdown;
- }, [unifiedBalance]);
-
- const selectedOption = useMemo(
- () => finalTokenOptions.find((opt) => opt.value === (value ?? '')),
- [finalTokenOptions, value],
- );
-
- const handleSelect = (token: string) => {
- if (disabled) return;
- onValueChange(token);
- };
-
- return (
-
-
- {type !== 'swap'
- ? 'Destination Token'
- : isDestination
- ? 'Destination Token'
- : 'Source Token'}
-
-
- {finalTokenOptions.map((token, index) => (
-
- handleSelect(token?.value)}
- className={cn(
- 'w-full px-3 py-0.5 rounded-nexus-md hover:bg-nexus-accent-green/10',
- disabled &&
- 'pointer-events-none cursor-not-allowed opacity-50 text-nexus-foreground',
- selectedOption?.value === token?.value ? 'bg-nexus-accent-green/10' : '',
- index === finalTokenOptions.length - 1 && isDestination ? 'mb-20' : '',
- )}
- >
-
-
- {tokenBalanceBreakdown[token?.value]?.bal &&
- tokenBalanceBreakdown[token?.value]?.chains && (
-
-
- {parseFloat(tokenBalanceBreakdown[token?.value]?.bal).toFixed(6)}
-
-
- {tokenBalanceBreakdown[token?.value]?.chains}
-
-
- )}
-
-
-
- ))}
-
- {/* Empty state */}
- {finalTokenOptions.length === 0 && (
-
- )}
-
-
- );
-}
diff --git a/packages/widgets/src/components/shared/transaction-details-drawer.tsx b/packages/widgets/src/components/shared/transaction-details-drawer.tsx
deleted file mode 100644
index 160a76cf..00000000
--- a/packages/widgets/src/components/shared/transaction-details-drawer.tsx
+++ /dev/null
@@ -1,390 +0,0 @@
-import {
- type SimulationResult,
- type BridgeAndExecuteSimulationResult,
- type ReadableIntent as Intent,
- CHAIN_METADATA,
- SUPPORTED_CHAINS,
-} from '@nexus/commons';
-import { SwapSimulationResult } from '../../types';
-import { cn, formatCost, getPrimaryButtonText, truncateAddress } from '../../utils/utils';
-import {
- Drawer,
- DrawerTrigger,
- DrawerContent,
- DrawerHeader,
- DrawerTitle,
- DrawerClose,
- DrawerFooter,
-} from '../motion/drawer';
-import { CircleX } from '../icons';
-import Clock from '../icons/Clock';
-import TwoCircles from '../icons/TwoCircles';
-import MoneyCircles from '../icons/MoneyCircles';
-import { Button } from '../motion/button-motion';
-import { useInternalNexus } from '../../providers/InternalNexusProvider';
-import { getFiatValue } from '../../utils/balance-utils';
-import type { OrchestratorStatus, ReviewStatus, TransactionType } from '../../types';
-
-interface TransactionDetailsDrawerProps {
- simulationResult?: (
- | SimulationResult
- | BridgeAndExecuteSimulationResult
- | SwapSimulationResult
- ) & {
- allowance?: { needsApproval: boolean };
- };
- inputData?: {
- token?: string;
- amount?: string | number;
- chainId?: number;
- toChainId?: number;
- };
- callback: () => void;
- triggerClassname?: string;
- type?: TransactionType;
- status: OrchestratorStatus;
- reviewStatus: ReviewStatus;
-}
-
-interface ChainInfo {
- amount: string;
- chainID: number;
- chainLogo?: string;
- chainName: string;
- contractAddress?: string;
-}
-
-interface FeesInfo {
- caGas: string;
- gasSupplied: string;
- protocol: string;
- solver: string;
- total: string;
-}
-
-interface TokenInfo {
- decimals: number;
- logo?: string;
- name: string;
- symbol: string;
-}
-
-interface SimulationData {
- contractAddress?: string;
- functionName?: string;
- destination: ChainInfo;
- sources: ChainInfo[];
- fees: FeesInfo;
- token: TokenInfo;
- sourcesTotal: string;
-}
-
-export function TransactionDetailsDrawer({
- simulationResult,
- inputData,
- callback,
- triggerClassname = '',
- type,
- status,
- reviewStatus,
-}: Readonly) {
- const { exchangeRates } = useInternalNexus();
- const getSimulationData = (): SimulationData | null => {
- if (!simulationResult) return null;
- console.log('Original simulationResult', simulationResult);
-
- // Handle swap simulation result
- if ('swapMetadata' in simulationResult) {
- const swapSim = simulationResult as SwapSimulationResult;
- const intent = swapSim.intent;
- if (!intent) return null;
- const destinationChain = CHAIN_METADATA[intent?.destination?.chain.id];
- const sources = intent.sources.map((source) => {
- const sourceChain = CHAIN_METADATA[source.chain.id];
- return {
- chainID: sourceChain?.id,
- chainName: sourceChain?.name || 'Unknown',
- chainLogo: sourceChain?.logo,
- amount: source.amount,
- } as ChainInfo;
- });
-
- return {
- destination: {
- chainID: destinationChain?.id,
- chainName: destinationChain?.name || 'Unknown',
- chainLogo: destinationChain?.logo,
- amount: swapSim?.intent?.destination?.amount,
- } as ChainInfo,
- sources,
- fees: {
- total: '0', // Swap fees are typically handled differently
- caGas: '0',
- gasSupplied: '0',
- protocol: '0',
- solver: '0',
- } as FeesInfo,
- token: {
- symbol: intent?.sources?.[0]?.token?.symbol || 'Unknown',
- name: intent?.sources?.[0]?.token?.symbol,
- decimals: intent?.sources?.[0]?.token?.decimals,
- } as TokenInfo,
- sourcesTotal: intent.sources?.[0]?.amount || '0',
- };
- }
-
- // Check if bridge was skipped in bridge & execute flow
- if (
- 'metadata' in simulationResult &&
- (simulationResult as BridgeAndExecuteSimulationResult)?.metadata?.bridgeSkipped
- ) {
- const simulation = simulationResult as BridgeAndExecuteSimulationResult;
- const metadata = simulation?.metadata;
-
- if (!metadata) return null;
-
- return {
- contractAddress: metadata?.contractAddress ?? '',
- functionName: metadata?.functionName ?? '',
- destination: {
- chainID: metadata?.targetChain,
- chainName: CHAIN_METADATA[metadata?.targetChain]?.name || 'Unknown',
- chainLogo: CHAIN_METADATA[metadata?.targetChain]?.logo,
- amount: metadata?.inputAmount,
- } as ChainInfo,
- sources: [
- {
- chainName: CHAIN_METADATA[metadata?.targetChain]?.name,
- chainID: metadata?.targetChain,
- chainLogo: CHAIN_METADATA[metadata?.targetChain]?.logo,
- amount: metadata?.inputAmount,
- },
- ],
- fees: {
- total: simulation?.executeSimulation?.gasUsed ?? '0',
- bridge: '0',
- caGas: '0',
- gasSupplied: '0',
- protocol: '0',
- solver: '0',
- } as FeesInfo,
- token: { name: simulationResult?.metadata?.token || 'Unknown' } as TokenInfo,
- sourcesTotal: metadata?.inputAmount || '0',
- };
- }
-
- // Handle bridge & execute result where intent is nested
- let intent: Intent | undefined = undefined;
- if ('intent' in simulationResult) {
- intent = (simulationResult as SimulationResult)?.intent;
- } else if ('bridgeSimulation' in simulationResult && simulationResult?.bridgeSimulation) {
- const simulation = simulationResult as BridgeAndExecuteSimulationResult;
- intent = simulation?.bridgeSimulation?.intent;
- const fees = {
- total: simulation?.totalEstimatedCost?.total ?? '0',
- ...simulation?.bridgeSimulation?.intent?.fees,
- } as FeesInfo;
-
- return {
- contractAddress: simulation?.executeSimulation?.contractAddress ?? '',
- functionName: simulation?.executeSimulation?.functionName ?? '',
- destination: intent?.destination as ChainInfo,
- sources: (intent?.sources || []) as ChainInfo[],
- fees: fees,
- token: intent?.token as TokenInfo,
- sourcesTotal: intent?.sourcesTotal as string,
- };
- }
-
- if (!intent) return null;
-
- return {
- destination: intent?.destination as ChainInfo,
- sources: (intent?.sources || []) as ChainInfo[],
- fees: intent?.fees as FeesInfo,
- token: intent?.token as TokenInfo,
- sourcesTotal: intent?.sourcesTotal ?? '0',
- };
- };
-
- const data = getSimulationData();
-
- console.log('Simulation data', data);
-
- const getDestinationChain = () => {
- if (inputData?.toChainId) return inputData.toChainId;
- if (inputData?.chainId) return inputData.chainId;
- return data?.destination?.chainID;
- };
-
- const destinationChainId = getDestinationChain();
- const destinationChain = destinationChainId ? CHAIN_METADATA[destinationChainId] : null;
-
- if (!data) return null;
-
- return (
-
-
- View Full Transaction Details
-
-
-
-
- Transaction Details
-
-
-
-
-
-
- {/* Estimated Time */}
-
-
-
-
- Estimated Transaction time
-
-
-
- ~{type === 'bridgeAndExecute' ? '1.5 mins' : '30 seconds'}
-
-
-
- {/* Total Fees */}
-
-
-
-
- {formatCost(data.fees.total)} {inputData?.token || data.token.symbol}
-
- {inputData?.token && (
-
- {getFiatValue(data.fees.total, inputData?.token, exchangeRates)}
-
- )}
-
-
-
- {/* Contract Address */}
- {data?.contractAddress && data?.functionName && (
-
-
-
-
- {data?.functionName} to
-
-
-
- {truncateAddress(data?.contractAddress, 4, 4)}
-
-
- )}
-
- {/* Sending */}
-
-
-
-
-
- {inputData?.amount || data.sourcesTotal} {inputData?.token || data.token.symbol}
-
- {inputData?.token && (
-
- {getFiatValue(data.sourcesTotal, inputData?.token, exchangeRates)}
-
- )}
-
- {destinationChain && (
- <>
-
on
-
-
- {destinationChain.name}
-
- >
- )}
-
-
-
- {/* From Section */}
- {Array.isArray(data.sources) && data.sources.length > 0 && (
-
-
From
-
- {data.sources.map((source) => {
- const chainMeta = CHAIN_METADATA[source.chainID];
- return (
-
-
-
-
-
- {inputData?.token || data.token.symbol}
-
-
- on {chainMeta?.name || 'Unknown Chain'}
-
-
-
-
-
- {source.amount}
-
-
- {inputData?.token &&
- getFiatValue(source.amount, inputData?.token, exchangeRates)}
-
-
-
- );
- })}
-
-
- )}
-
-
-
-
- {getPrimaryButtonText(status, reviewStatus)}
-
-
-
-
-
- );
-}
diff --git a/packages/widgets/src/components/shared/unified-balance.tsx b/packages/widgets/src/components/shared/unified-balance.tsx
deleted file mode 100644
index bcd83406..00000000
--- a/packages/widgets/src/components/shared/unified-balance.tsx
+++ /dev/null
@@ -1,287 +0,0 @@
-import { useMemo } from 'react';
-import {
- Drawer,
- DrawerContent,
- DrawerTrigger,
- DrawerHeader,
- DrawerTitle,
- DrawerClose,
-} from '../motion/drawer';
-import { useInternalNexus } from '../../providers/InternalNexusProvider';
-import {
- type SUPPORTED_TOKENS,
- type UserAsset,
- CHAIN_METADATA,
- SUPPORTED_CHAINS,
-} from '@nexus/commons';
-import SolarWallet from '../icons/SolarWallet';
-import { ChevronDownIcon, CircleX } from '../icons';
-import { cn, getTokenFromInputData } from '../../utils/utils';
-import { TokenIcon } from './icons';
-
-const BalanceTrigger = ({ balance, token }: { balance?: UserAsset; token?: SUPPORTED_TOKENS }) => {
- return (
-
-
-
- {balance && token ? (
-
-
Total {token}
-
- accross {balance?.breakdown?.length} chains
-
-
- ) : (
-
Select token to view cross chain balance
- )}
-
- {balance && token && (
-
-
-
- {parseFloat(balance?.balance).toFixed(6)} {token}
-
-
- β ${balance?.balanceInFiat}
-
-
-
-
- )}
-
- );
-};
-
-const AllBalancesTrigger = ({ balances }: { balances: UserAsset[] }) => {
- const totalFiat = useMemo(() => {
- return balances?.reduce((sum, asset) => sum + (asset?.balanceInFiat || 0), 0) || 0;
- }, [balances]);
-
- const { tokenCount, uniqueChainCount } = useMemo(() => {
- const chains = new Set();
- balances?.forEach((asset) => {
- asset?.breakdown?.forEach((b) => {
- if (b?.chain?.id != null) chains.add(b.chain.id);
- });
- });
- return { tokenCount: balances?.length || 0, uniqueChainCount: chains.size };
- }, [balances]);
-
- return (
-
-
-
-
-
Unified Balance
-
- across {tokenCount} tokens β’ {uniqueChainCount} chains
-
-
-
-
-
-
- β ${totalFiat.toFixed(2)}
-
-
-
-
-
- );
-};
-
-const ChainBalance = ({
- balance,
- symbol,
-}: {
- balance: {
- balance: string;
- balanceInFiat: number;
- chain: {
- id: number;
- logo: string;
- name: string;
- };
- contractAddress: `0x${string}`;
- decimals: number;
- isNative?: boolean;
- };
- symbol: string;
-}) => {
- return (
-
-
-
-
-
- {symbol}
-
-
- on {balance.chain?.name || `Chain ${balance.chain?.id}`}
-
-
-
-
-
- {parseFloat(balance.balance).toFixed(2)}
-
-
- ${balance.balanceInFiat}
-
-
-
- );
-};
-
-const UnifiedBalance = () => {
- const { unifiedBalance, activeTransaction } = useInternalNexus();
- const { inputData } = activeTransaction;
- const tokenSymbol = getTokenFromInputData(inputData);
-
- const relevantBalance = useMemo(() => {
- if (!unifiedBalance || !tokenSymbol) return [] as UserAsset[];
- return unifiedBalance.filter((balance) => balance?.symbol === tokenSymbol);
- }, [tokenSymbol, unifiedBalance]);
-
- const tokenBalance = relevantBalance[0];
-
- if (!unifiedBalance) return null;
-
- if (!tokenSymbol)
- return (
-
-
-
-
-
-
-
- Balances Across Tokens
-
-
-
-
-
-
-
- {unifiedBalance.map((asset) => {
- return (
-
-
-
- {asset.symbol ? (
-
- ) : null}
-
- Total {asset.symbol}
-
-
-
-
- {parseFloat(asset.balance) > 0
- ? parseFloat(asset.balance).toFixed(2)
- : '0.00'}{' '}
- {asset.symbol}
-
-
- β ${asset.balanceInFiat}
-
-
-
-
- {parseFloat(asset.balance) > 0 && (
-
- {asset.breakdown?.map((breakdownBalance, index: number) => (
-
- ))}
-
- )}
-
- );
- })}
-
-
-
- );
-
- if (!tokenBalance) return null;
-
- return (
-
-
-
-
-
-
-
- Balance Across Chains
-
-
-
-
-
-
-
- {/* Total Balance */}
-
-
-
-
- Total {tokenSymbol}
-
-
-
-
- {parseFloat(tokenBalance.balance).toFixed(6)} {tokenSymbol}
-
-
- β ${tokenBalance.balanceInFiat}
-
-
-
-
- {/* Individual Chain Balances */}
- {parseFloat(tokenBalance.balance) > 0 && (
-
- {tokenBalance.breakdown?.map((breakdownBalance, index: number) => (
-
- ))}
-
- )}
-
-
-
- );
-};
-
-export default UnifiedBalance;
diff --git a/packages/widgets/src/components/shared/unified-transaction-form.tsx b/packages/widgets/src/components/shared/unified-transaction-form.tsx
deleted file mode 100644
index abc68b54..00000000
--- a/packages/widgets/src/components/shared/unified-transaction-form.tsx
+++ /dev/null
@@ -1,378 +0,0 @@
-import { AmountInput } from './amount-input';
-import { AddressField } from './address-field';
-import { cn } from '../../utils/utils';
-import { useInternalNexus } from '../../providers/InternalNexusProvider';
-import { type TransactionType } from '../../utils/balance-utils';
-import { useMemo, useEffect } from 'react';
-import { CHAIN_METADATA, NexusNetwork } from '@nexus/commons';
-import { FormField } from '../motion/form-field';
-import DestinationDrawer from './destination-drawer';
-import { isAddress } from 'viem';
-import { SwapSimulationResult, SwapInputData } from 'src/types';
-import { isTokenChainCombinationValid } from '../../utils/token-utils';
-
-export interface UnifiedInputData {
- chainId?: number;
- toChainId?: number;
- token?: string;
- inputToken?: string;
- outputToken?: string;
- amount?: string | number;
- recipient?: string;
-}
-
-interface UnifiedTransactionFormProps {
- type: TransactionType;
- inputData: UnifiedInputData;
- onUpdate: (data: UnifiedInputData) => void;
- disabled?: boolean;
- className?: string;
- prefillFields?: {
- chainId?: boolean;
- toChainId?: boolean;
- token?: boolean;
- inputToken?: boolean;
- outputToken?: boolean;
- amount?: boolean;
- recipient?: boolean;
- };
-}
-
-interface SwapTransactionFormProps {
- inputData: SwapInputData;
- onUpdate: (data: SwapInputData) => void;
- disabled?: boolean;
- className?: string;
- prefillFields?: {
- fromChainID?: boolean;
- toChainID?: boolean;
- fromTokenAddress?: boolean;
- toTokenAddress?: boolean;
- fromAmount?: boolean;
- toAmount?: boolean;
- };
-}
-
-interface SwapFormProps {
- title: string;
- inputData: SwapInputData;
- isAmountDisabled?: boolean;
- handleUpdate: (data: Partial) => void;
- isChainSelectDisabled?: boolean;
- isTokenSelectDisabled?: boolean;
- isOutputTokenSelectDisabled?: boolean;
- network?: NexusNetwork;
- destinationAmount?: string;
-}
-
-const FORM_CONFIG = {
- bridge: {
- chainLabel: 'Destination Network',
- tokenLabel: 'Token to be transferred',
- chainField: 'chainId',
- showRecipient: false,
- showOutputToken: false,
- showDestinationAmount: false,
- },
- bridgeAndExecute: {
- chainLabel: 'Destination Network',
- tokenLabel: 'Token to be deposited',
- chainField: 'toChainId',
- showRecipient: false,
- showOutputToken: false,
- showDestinationAmount: false,
- },
- transfer: {
- chainLabel: 'Source Network',
- tokenLabel: 'Token to transfer',
- chainField: 'chainId',
- showRecipient: true,
- showOutputToken: false,
- showDestinationAmount: false,
- },
- swap: {
- chainLabel: 'Source Network',
- tokenLabel: 'Input Token',
- outputTokenLabel: 'Output Token',
- chainField: 'fromChainID',
- toChainField: 'toChainID',
- showRecipient: false,
- showDestinationAmount: true,
- showOutputToken: true,
- showDestinationChain: true,
- },
-} as const;
-
-const SwapForm = ({
- title,
- inputData,
- isAmountDisabled,
- handleUpdate,
- isChainSelectDisabled,
- isTokenSelectDisabled,
- isOutputTokenSelectDisabled,
- network = 'mainnet',
- destinationAmount,
-}: SwapFormProps) => {
- return (
-
-
-
- handleUpdate({ fromAmount: value, toAmount: value })
- }
- token={inputData?.fromTokenAddress}
- debounceMs={1000}
- />
-
-
-
{
- if (isChainSelectDisabled) return;
- handleUpdate({ fromChainID: parseInt(chainId, 10) as any });
- }}
- onTokenValueChange={(token) => {
- if (!isTokenSelectDisabled) {
- handleUpdate({ fromTokenAddress: token as any });
- }
- }}
- isTokenSelectDisabled={isTokenSelectDisabled}
- isChainSelectDisabled={isChainSelectDisabled}
- network={network}
- drawerTitle="Select Source Chain & Token"
- fieldLabel="Source"
- type="swap"
- isSourceChain={true}
- />
-
-
-
-
-
-
-
{
- if (isChainSelectDisabled) return;
- handleUpdate({ toChainID: parseInt(chainId, 10) as any });
- }}
- onTokenValueChange={(token) => {
- if (!isOutputTokenSelectDisabled) {
- handleUpdate({ toTokenAddress: token as any });
- }
- }}
- isTokenSelectDisabled={isOutputTokenSelectDisabled}
- isChainSelectDisabled={isChainSelectDisabled}
- network={network}
- drawerTitle="Select Destination Chain & Token"
- fieldLabel="Destination"
- type="swap"
- isDestination={true}
- isSourceChain={false}
- />
-
-
- );
-};
-
-export function SwapTransactionForm({
- inputData,
- onUpdate,
- disabled = false,
- className,
- prefillFields = {},
-}: Readonly) {
- const { config, isSimulating, activeTransaction } = useInternalNexus();
-
- const isInputDisabled = disabled || isSimulating;
- const isChainSelectDisabled = isInputDisabled || prefillFields.fromChainID;
- const isTokenSelectDisabled = isInputDisabled || prefillFields.fromTokenAddress;
- const isOutputTokenSelectDisabled = isInputDisabled || prefillFields.toTokenAddress;
- const isAmountDisabled = isInputDisabled || prefillFields.fromAmount;
-
- const title = useMemo(() => {
- const fromToken = inputData?.fromTokenAddress;
- const toToken = inputData?.toTokenAddress;
- if (fromToken && toToken) {
- return `Swapping (${fromToken} β ${toToken})`;
- }
- return 'Swap';
- }, [inputData?.fromTokenAddress, inputData?.toTokenAddress]);
-
- const handleUpdate = (data: Partial) => {
- onUpdate({ ...inputData, ...data });
- };
-
- // Reset token when chain changes to invalid combination (disabled for swaps to prevent aggressive resets)
- useEffect(() => {
- // For swaps, we allow users to make selections and validate at execution time
- // This prevents tokens from being reset when switching between valid chains
- if (inputData.fromChainID && inputData.fromTokenAddress) {
- // Skip validation for swaps to maintain user selections
- const shouldReset = false;
- if (shouldReset) {
- handleUpdate({ fromTokenAddress: undefined });
- }
- }
- }, [inputData.fromChainID]);
-
- useEffect(() => {
- // For swaps, we allow users to make selections and validate at execution time
- if (inputData.toChainID && inputData.toTokenAddress) {
- // Skip validation for swaps to maintain user selections
- const shouldReset = false;
- if (shouldReset) {
- handleUpdate({ toTokenAddress: undefined });
- }
- }
- }, [inputData.toChainID]);
-
- const destinationAmount = useMemo(() => {
- const intent = (activeTransaction?.simulationResult as SwapSimulationResult)?.intent;
- if (intent?.destination?.amount) {
- return parseFloat(intent.destination.amount).toFixed(6);
- }
- return '0';
- }, [activeTransaction?.simulationResult]);
-
- return (
-
-
-
- );
-}
-
-export function UnifiedTransactionForm({
- type,
- inputData,
- onUpdate,
- disabled = false,
- className,
- prefillFields = {},
-}: Readonly) {
- const { config, isSimulating } = useInternalNexus();
-
- const formConfig = FORM_CONFIG[type];
- const isInputDisabled = disabled || isSimulating;
- const isChainSelectDisabled =
- isInputDisabled || prefillFields[formConfig.chainField as keyof typeof prefillFields];
- const isTokenSelectDisabled = isInputDisabled || prefillFields.token || prefillFields.inputToken;
- const isAmountDisabled = isInputDisabled || prefillFields.amount;
- const isReceipientDisabled = isInputDisabled || prefillFields.recipient;
-
- const title = useMemo(() => {
- const chainId = inputData?.chainId || inputData?.toChainId;
- const token = inputData?.token || inputData?.inputToken;
-
- if (chainId && token) {
- return `Sending (${token} to ${CHAIN_METADATA[chainId]?.name})`;
- }
- return 'Sending';
- }, [inputData, type]);
-
- const hasValidationError = useMemo(
- () => inputData?.recipient && !isAddress(inputData?.recipient ?? ''),
- [inputData?.recipient],
- );
-
- const handleUpdate = (data: UnifiedInputData) => {
- onUpdate(data);
- };
-
- // Reset token when chain changes to invalid combination for bridge/bridgeAndExecute
- useEffect(() => {
- if (type === 'bridge' || type === 'bridgeAndExecute') {
- const chainId = type === 'bridgeAndExecute' ? inputData.toChainId : inputData.chainId;
- if (chainId && inputData.token) {
- if (!isTokenChainCombinationValid(inputData.token, chainId, type)) {
- handleUpdate({ token: undefined });
- }
- }
- }
- }, [inputData.chainId, inputData.toChainId, type]);
-
- return (
-
-
-
-
- handleUpdate({ amount: value })}
- token={inputData?.token || inputData?.inputToken}
- debounceMs={1000}
- />
-
-
-
{
- if (isChainSelectDisabled) return;
- const fieldName = formConfig.chainField;
- handleUpdate({ [fieldName]: parseInt(chainId, 10) });
- }}
- onTokenValueChange={(token) => {
- if (!isTokenSelectDisabled) {
- handleUpdate({ token });
- }
- }}
- isTokenSelectDisabled={isTokenSelectDisabled}
- isChainSelectDisabled={isChainSelectDisabled}
- network={config?.network ?? 'mainnet'}
- />
-
-
- {formConfig.showRecipient && (
-
- {
- if (!isReceipientDisabled) {
- handleUpdate({ recipient: value });
- }
- }}
- disabled={isReceipientDisabled}
- />
-
- )}
-
-
- );
-}
diff --git a/packages/widgets/src/components/shared/unified-transaction-modal.tsx b/packages/widgets/src/components/shared/unified-transaction-modal.tsx
deleted file mode 100644
index 00a0b2cf..00000000
--- a/packages/widgets/src/components/shared/unified-transaction-modal.tsx
+++ /dev/null
@@ -1,324 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { BaseModal } from '../motion/base-modal';
-import { useInternalNexus } from '../../providers/InternalNexusProvider';
-import {
- cn,
- getContentKey,
- getModalTitle,
- getPrimaryButtonText,
- getTokenFromInputData,
- getAmountFromInputData,
-} from '../../utils/utils';
-import { type TransactionType } from '../../utils/balance-utils';
-import { TransactionSimulation } from '../processing/transaction-simulation';
-import { AvailLogo } from '../icons/AvailLogo';
-import UnifiedBalance from './unified-balance';
-import { InfoMessage } from './info-message';
-import { AllowanceForm } from './allowance-form';
-import { DialogFooter, DialogHeader, DialogTitle } from '../motion/dialog-motion';
-import { SlideTransition } from '../motion/slide-transition';
-import { EnhancedInfoMessage } from './enhanced-info-message';
-import { ActionButtons } from './action-buttons';
-import type { UnifiedInputData, SwapInputData } from '../../types';
-
-interface UnifiedTransactionModalProps {
- transactionType: TransactionType;
- modalTitle: string;
- FormComponent: React.ComponentType<{
- inputData: UnifiedInputData | SwapInputData;
- onUpdate: (data: UnifiedInputData | SwapInputData) => void;
- disabled: boolean;
- prefillFields?: any;
- }>;
- getSimulationError?: (simulationResult: any) => boolean;
- getMinimumAmount?: (simulationResult: any) => string;
- getSourceChains?: (
- simulationResult: any,
- ) => { chainId: number; amount: string; needsApproval?: boolean }[];
- transformInputData?: (inputData: any) => any;
-}
-
-export function UnifiedTransactionModal({
- transactionType,
- modalTitle,
- FormComponent,
- getSimulationError,
- getMinimumAmount,
- getSourceChains,
- transformInputData,
-}: Readonly) {
- const {
- activeTransaction,
- activeController,
- updateInput,
- confirmAndProceed,
- cancelTransaction,
- initializeSdk,
- triggerSimulation,
- retrySimulation,
- isSdkInitialized,
- isSimulating,
- insufficientBalance,
- allowanceError,
- isSettingAllowance,
- approveAllowance,
- denyAllowance,
- startAllowanceFlow,
- initiateSwap,
- proceedWithSwap,
- } = useInternalNexus();
-
- const { status, reviewStatus, inputData, simulationResult, type, prefillFields } =
- activeTransaction;
- const [isInitializing, setIsInitializing] = useState(false);
- const [allowanceFormValid, setAllowanceFormValid] = useState(false);
- const [allowanceApproveHandler, setAllowanceApproveHandler] = useState<(() => void) | null>(null);
-
- const handleAllowanceFormStateChange = useCallback(
- (isValid: boolean, handler: () => void) => {
- setAllowanceFormValid(isValid);
- setAllowanceApproveHandler(() => handler);
- },
- [setAllowanceFormValid, setAllowanceApproveHandler],
- );
-
- // Helper function to check sufficient input for both regular transactions and swaps
- const checkHasSufficientInput = useCallback(
- (inputData: any) => {
- if (!inputData) return false;
-
- if (transactionType === 'swap') {
- // For swaps, check input directly (since activeController is null)
- const data = inputData as Partial;
- return !!(
- data.fromChainID &&
- data.toChainID &&
- data.fromTokenAddress &&
- data.toTokenAddress &&
- data.fromAmount &&
- parseFloat(data.fromAmount?.toString() || '0') > 0
- );
- } else {
- // For regular transactions, use activeController
- return activeController?.hasSufficientInput(inputData || {}) || false;
- }
- },
- [transactionType, activeController],
- );
-
- // Type guard - return null if wrong transaction type
- if (type !== transactionType) {
- return null;
- }
-
- const isOpen =
- status !== 'idle' && status !== 'processing' && status !== 'success' && status !== 'error';
- const isBusy = status === 'processing' || reviewStatus === 'simulating';
-
- const handleInitialize = async () => {
- try {
- setIsInitializing(true);
- await initializeSdk();
- } finally {
- setIsInitializing(false);
- }
- };
-
- const handleReviewGatheringInput = async () => {
- if (!checkHasSufficientInput(inputData)) return;
- if (transactionType === 'swap') {
- await initiateSwap(inputData as SwapInputData);
- return;
- }
- triggerSimulation();
- };
-
- const handleReviewReady = () => {
- if (transactionType === 'swap') {
- proceedWithSwap();
- return;
- }
- confirmAndProceed();
- };
-
- const handleSetAllowance = () => {
- if (allowanceApproveHandler) allowanceApproveHandler();
- };
-
- const handleButtonClick = async () => {
- // Early-return guards to keep complexity low
- if (status === 'initializing') return handleInitialize();
- if (status === 'simulation_error') return retrySimulation();
-
- if (status === 'review') {
- if (reviewStatus === 'gathering_input') return handleReviewGatheringInput();
- if (reviewStatus === 'ready') return handleReviewReady();
- if (reviewStatus === 'needs_allowance') return startAllowanceFlow();
- }
-
- if (status === 'set_allowance') return handleSetAllowance();
-
- return confirmAndProceed();
- };
-
- const debouncedClick = () => {
- setTimeout(handleButtonClick, 500);
- };
-
- const hasSufficientInput = checkHasSufficientInput(inputData);
- const shouldShowSimulation = isSdkInitialized && hasSufficientInput;
- const transformedInputData = transformInputData ? transformInputData(inputData) : inputData;
-
- const renderAllowanceContent = () => {
- if (!simulationResult || !inputData) return null;
-
- // Get minimum amount and source chains using provided functions or defaults
- const minimumAmount = getMinimumAmount ? getMinimumAmount(simulationResult) : '0';
- const sourceChains = getSourceChains ? getSourceChains(simulationResult) : [];
-
- return (
-
- );
- };
-
- const showFooterButtons = status !== 'processing' && status !== 'success' && status !== 'error';
-
- const preventClose = status === 'processing' || reviewStatus === 'simulating';
-
- const showHeader =
- activeTransaction?.status !== 'processing' &&
- activeTransaction?.status !== 'success' &&
- activeTransaction?.status !== 'error';
-
- const isPrimaryLoading =
- isBusy || isInitializing || (status === 'set_allowance' && isSettingAllowance);
-
- return (
- {} : cancelTransaction}
- hideCloseButton={true}
- >
- {/* Header - Fixed at top */}
- {showHeader && (
-
-
-
- {getModalTitle(status, modalTitle)}
-
-
- )}
-
- {/* Content - Flexible middle area */}
-
-
- {(status === 'initializing' || status === 'review' || status === 'simulation_error') && (
- <>
-
-
-
-
- {!isSdkInitialized && (
-
- Sign a quick message to turn on cross-chain transfers. Don't worry
- it's gasless & no funds will move yet.
-
- )}
-
- {isSdkInitialized && insufficientBalance && (
-
-
-
- Insufficient {getTokenFromInputData(inputData)} balance
-
-
- You don't have enough {getTokenFromInputData(inputData)} to complete this
- transaction.
- {transactionType === 'bridgeAndExecute'
- ? ' Consider using a smaller amount or add more funds to your wallet.'
- : ' Please add more funds to your wallet or reduce the transaction amount.'}
-
-
-
- )}
- {(activeTransaction?.error && status === 'simulation_error') ||
- (simulationResult && getSimulationError && getSimulationError(simulationResult)) ? (
-
- ) : (
- shouldShowSimulation &&
- !insufficientBalance &&
- status !== 'simulation_error' &&
- type !== 'swap' && (
-
- )
- )}
-
- >
- )}
- {status === 'set_allowance' && <>{renderAllowanceContent()}>}
-
-
-
- {/* Footer - Fixed at bottom */}
- {showFooterButtons && (
-
-
-
- )}
-
- );
-}
diff --git a/packages/widgets/src/components/swap/swap-button.tsx b/packages/widgets/src/components/swap/swap-button.tsx
deleted file mode 100644
index d68e92d6..00000000
--- a/packages/widgets/src/components/swap/swap-button.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-'use client';
-import { FC } from 'react';
-import { useInternalNexus } from '../../providers/InternalNexusProvider';
-import { SwapButtonProps } from '../../types';
-import SwapModal from './swap-modal';
-
-export const SwapButton: FC = ({ prefill, children, className, title }) => {
- const { startTransaction, activeTransaction, config } = useInternalNexus();
-
- if (config?.network === 'testnet') {
- throw new Error('Testnet is not supported');
- }
-
- const isLoading =
- activeTransaction.status === 'processing' || activeTransaction.reviewStatus === 'simulating';
-
- const handleClick = () => {
- startTransaction('swap', prefill);
- };
-
- return (
- <>
- {children({ onClick: handleClick, isLoading })}
-
- >
- );
-};
diff --git a/packages/widgets/src/components/swap/swap-modal.tsx b/packages/widgets/src/components/swap/swap-modal.tsx
deleted file mode 100644
index 27403568..00000000
--- a/packages/widgets/src/components/swap/swap-modal.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { UnifiedTransactionModal } from '../shared/unified-transaction-modal';
-import { SwapTransactionForm, UnifiedInputData } from '../shared/unified-transaction-form';
-import { SwapSimulationResult, SwapInputData } from '../../types';
-import SwapPrefilledInputs from '../shared/swap-prefilled-inputs';
-
-interface SwapFormSectionProps {
- inputData: SwapInputData | UnifiedInputData;
- onUpdate: (data: SwapInputData | UnifiedInputData) => void;
- disabled: boolean;
- prefillFields?: any;
-}
-
-function SwapFormSection({
- inputData,
- onUpdate,
- disabled = false,
- prefillFields = {},
-}: Readonly) {
- const swapInputData = inputData as SwapInputData;
- const requiredFields = [
- 'fromChainID',
- 'toChainID',
- 'fromTokenAddress',
- 'toTokenAddress',
- 'fromAmount',
- ];
- // Check if fields are actually prefilled (boolean values in prefillFields indicate prefilled fields)
- const hasPrefilledInputs = requiredFields.every((field) => prefillFields[field] === true);
-
- if (hasPrefilledInputs) {
- return ;
- }
-
- return (
- void}
- disabled={disabled}
- prefillFields={prefillFields}
- />
- );
-}
-
-export default function SwapModal({ title = 'Nexus Widget' }: Readonly<{ title?: string }>) {
- const getSimulationError = (simulationResult: SwapSimulationResult): boolean => {
- if (!simulationResult) return true;
- return (
- simulationResult.success === false ||
- Boolean(simulationResult.error) ||
- !simulationResult.intent
- );
- };
- const transformInputData = (inputData: SwapInputData | null | undefined) => {
- if (!inputData) return {};
- return inputData;
- };
-
- return (
-
- );
-}
diff --git a/packages/widgets/src/components/transfer/transfer-button.tsx b/packages/widgets/src/components/transfer/transfer-button.tsx
deleted file mode 100644
index 6841a458..00000000
--- a/packages/widgets/src/components/transfer/transfer-button.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-'use client';
-import type { TransferButtonProps } from '../../types';
-import { useInternalNexus } from '../../providers/InternalNexusProvider';
-import TransferModal from './transfer-modal';
-
-export function TransferButton({
- prefill,
- children,
- className,
- title,
-}: Readonly) {
- const { startTransaction, activeTransaction } = useInternalNexus();
- const isLoading =
- activeTransaction.status === 'processing' || activeTransaction.reviewStatus === 'simulating';
-
- const handleClick = () => {
- startTransaction('transfer', prefill);
- };
-
- return (
- <>
- {children({ onClick: handleClick, isLoading })}
-
- >
- );
-}
diff --git a/packages/widgets/src/components/transfer/transfer-modal.tsx b/packages/widgets/src/components/transfer/transfer-modal.tsx
deleted file mode 100644
index 5f66bc1d..00000000
--- a/packages/widgets/src/components/transfer/transfer-modal.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import { UnifiedTransactionModal } from '../shared/unified-transaction-modal';
-import { SimulationResult } from '@nexus/commons';
-import { UnifiedInputData, UnifiedTransactionForm } from '../shared/unified-transaction-form';
-import { SwapInputData } from '../../types';
-import PrefilledInputs from '../shared/prefilled-inputs';
-import { useInternalNexus } from '../../providers/InternalNexusProvider';
-
-interface TransferFormSectionProps {
- inputData: UnifiedInputData | SwapInputData;
- onUpdate: (data: UnifiedInputData | SwapInputData) => void;
- disabled: boolean;
- prefillFields?: any;
-}
-
-type InputData = {
- chainId?: number;
- token?: string;
- amount?: string | number;
- recipient?: string;
-};
-
-function TransferFormSection({
- inputData,
- onUpdate,
- disabled = false,
- prefillFields = {},
-}: Readonly) {
- const { activeController } = useInternalNexus();
-
- if (!activeController) return null;
-
- // Cast to UnifiedInputData since transfer operations only use this type
- const transferInputData = inputData as UnifiedInputData;
-
- const requiredFields: (keyof InputData)[] = ['chainId', 'token', 'amount', 'recipient'];
- const hasEnoughInputs = requiredFields.every((field) => prefillFields[field] === true);
-
- if (hasEnoughInputs) {
- return ;
- }
-
- return (
- void}
- disabled={disabled}
- prefillFields={prefillFields}
- />
- );
-}
-
-export default function TransferModal({ title = 'Nexus Widget' }: Readonly<{ title?: string }>) {
- const getSimulationError = (simulationResult: SimulationResult) => {
- return simulationResult && !simulationResult.intent;
- };
-
- const getMinimumAmount = (simulationResult: SimulationResult) => {
- return simulationResult?.intent?.sourcesTotal || '0';
- };
-
- const getSourceChains = (
- simulationResult: SimulationResult & {
- allowance?: {
- chainDetails?: Array<{ chainId: number; amount: string; needsApproval: boolean }>;
- };
- },
- ) => {
- // Use chainDetails from allowance if available (provides needsApproval info)
- if (simulationResult?.allowance?.chainDetails) {
- return simulationResult.allowance.chainDetails;
- }
-
- // Fallback to original sources mapping
- return (
- simulationResult?.intent?.sources?.map((source) => ({
- chainId: source.chainID,
- amount: source.amount,
- })) || []
- );
- };
-
- return (
-
- );
-}
diff --git a/packages/widgets/src/controllers/BridgeAndExecuteController.tsx b/packages/widgets/src/controllers/BridgeAndExecuteController.tsx
deleted file mode 100644
index 42f9ac4e..00000000
--- a/packages/widgets/src/controllers/BridgeAndExecuteController.tsx
+++ /dev/null
@@ -1,198 +0,0 @@
-import React from 'react';
-import type { ITransactionController, ActiveTransaction } from '../types';
-import { NexusSDK } from '@avail-project/nexus-core';
-import {
- UnifiedTransactionForm,
- UnifiedInputData,
-} from '../components/shared/unified-transaction-form';
-
-import {
- type DynamicParamBuilder,
- type ExecuteParams,
- type SUPPORTED_TOKENS,
- type SUPPORTED_CHAINS_IDS,
- type BridgeAndExecuteParams,
- type BridgeAndExecuteResult,
- type BridgeAndExecuteSimulationResult,
- logger,
-} from '@nexus/commons';
-import { Abi } from 'viem';
-
-export interface BridgeAndExecuteConfig extends Partial {}
-
-const BridgeAndExecuteInputForm: React.FC<{
- prefill: Partial;
- onUpdate: (data: Partial) => void;
- isBusy: boolean;
- prefillFields?: {
- toChainId?: boolean;
- token?: boolean;
- amount?: boolean;
- };
-}> = ({ prefill, onUpdate, isBusy, prefillFields = {} }) => {
- // Transform BridgeAndExecuteConfig to UnifiedInputData
- const unifiedInputData: UnifiedInputData = {
- toChainId: prefill?.toChainId,
- token: prefill?.token,
- amount: prefill?.amount,
- };
-
- // Transform UnifiedInputData back to BridgeAndExecuteConfig
- const handleUpdate = (data: UnifiedInputData) => {
- // Only include defined values to avoid overwriting existing data
- const transformedData: any = {};
- if (data.toChainId !== undefined) transformedData.toChainId = data.toChainId;
- if (data.token !== undefined) transformedData.token = data.token;
- if (data.amount !== undefined) transformedData.amount = data.amount;
-
- onUpdate(transformedData);
- };
-
- return (
-
- );
-};
-
-export class BridgeAndExecuteController implements ITransactionController {
- InputForm = BridgeAndExecuteInputForm;
-
- hasSufficientInput(inputData: Partial): boolean {
- const {
- token,
- amount,
- toChainId,
- contractAddress,
- contractAbi,
- functionName,
- buildFunctionParams,
- } = inputData as any;
-
- if (!token || !amount || !toChainId) return false;
- if (!contractAddress || !contractAbi || !functionName || !buildFunctionParams) return false;
-
- const amt = parseFloat(amount.toString());
- return !isNaN(amt) && amt > 0;
- }
-
- private buildExecute(inputData: {
- token: SUPPORTED_TOKENS;
- amount: string | number;
- toChainId: SUPPORTED_CHAINS_IDS;
- contractAddress: `0x${string}`;
- contractAbi: Abi;
- functionName: string;
- buildFunctionParams: DynamicParamBuilder;
- }): Omit {
- // Return new callback-based execute params directly
- return {
- contractAddress: inputData.contractAddress,
- contractAbi: inputData.contractAbi,
- functionName: inputData.functionName,
- buildFunctionParams: inputData.buildFunctionParams,
- tokenApproval:
- inputData.token !== 'ETH'
- ? {
- token: inputData.token,
- amount: inputData.amount.toString(),
- }
- : undefined,
- };
- }
-
- async runReview(
- sdk: NexusSDK,
- inputData: Partial,
- ): Promise {
- let params: BridgeAndExecuteParams = inputData as BridgeAndExecuteParams;
- if (!params.execute) {
- const execute = this.buildExecute(inputData as any);
- params = { ...inputData, execute } as BridgeAndExecuteParams;
- }
- const simulationResult = await sdk.simulateBridgeAndExecute(params);
- logger.info('bridgeAndExecute simulationResult', simulationResult);
-
- let needsApproval = false;
- const chainDetails: Array<{
- chainId: number;
- amount: string;
- needsApproval: boolean;
- }> = [];
-
- // Check if bridge part needs allowance (when bridge is NOT skipped)
- if (simulationResult?.bridgeSimulation?.intent?.sources && inputData.token !== 'ETH') {
- const sourcesData = simulationResult.bridgeSimulation.intent.sources;
-
- for (const source of sourcesData) {
- const requiredAmount = sdk.utils.parseUnits(
- source.amount,
- sdk.utils.getTokenMetadata(inputData.token!)?.decimals ?? 18,
- );
-
- const allowances = await sdk.getAllowance(source.chainID, [inputData.token!]);
- logger.info(`bridgeAndExecute bridge allowances for chain ${source.chainID}:`, allowances);
-
- const currentAllowance = allowances[0]?.allowance ?? 0n;
- const chainNeedsApproval = currentAllowance < requiredAmount;
-
- if (chainNeedsApproval) {
- needsApproval = true;
- logger.info(
- `BridgeAndExecute bridge allowance needed on chain ${source.chainID}: required=${requiredAmount.toString()}, current=${currentAllowance.toString()}`,
- );
- }
-
- chainDetails.push({
- chainId: source.chainID,
- amount: requiredAmount.toString(),
- needsApproval: chainNeedsApproval,
- });
- }
- }
-
- // Also check if contract execution needs approval (when bridge is skipped)
- // This is handled by the execute service internally, but we can inform the UI
- const contractApprovalNeeded = !!simulationResult?.metadata?.approvalRequired;
- if (contractApprovalNeeded) {
- needsApproval = true;
- }
-
- return {
- ...simulationResult,
- allowance: {
- needsApproval,
- chainDetails: chainDetails.length > 0 ? chainDetails : undefined,
- },
- } as BridgeAndExecuteSimulationResult & {
- allowance: {
- needsApproval: boolean;
- chainDetails?: Array<{
- chainId: number;
- amount: string;
- needsApproval: boolean;
- }>;
- };
- };
- }
-
- async confirmAndProceed(
- sdk: NexusSDK,
- inputData: Partial,
- _simulationResult?: ActiveTransaction['simulationResult'],
- ): Promise {
- let params: BridgeAndExecuteParams = inputData as BridgeAndExecuteParams;
-
- if (!params.execute) {
- const execute = this.buildExecute(inputData as any);
- params = { ...inputData, execute } as BridgeAndExecuteParams;
- }
-
- const result = await sdk.bridgeAndExecute(params);
- return result;
- }
-}
diff --git a/packages/widgets/src/controllers/BridgeController.tsx b/packages/widgets/src/controllers/BridgeController.tsx
deleted file mode 100644
index 6b9e4e22..00000000
--- a/packages/widgets/src/controllers/BridgeController.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-import React from 'react';
-import type { ITransactionController, BridgeConfig, ActiveTransaction } from '../types';
-import { NexusSDK } from '@avail-project/nexus-core';
-import { type BridgeParams, type BridgeResult, logger } from '@nexus/commons';
-import {
- UnifiedTransactionForm,
- UnifiedInputData,
-} from '../components/shared/unified-transaction-form';
-
-const BridgeInputForm: React.FC<{
- prefill: Partial;
- onUpdate: (data: Partial) => void;
- isBusy: boolean;
- prefillFields?: {
- chainId?: boolean;
- toChainId?: boolean;
- token?: boolean;
- amount?: boolean;
- recipient?: boolean;
- };
-}> = ({ prefill, onUpdate, isBusy, prefillFields = {} }) => {
- // Transform BridgeConfig to UnifiedInputData
- const unifiedInputData: UnifiedInputData = {
- chainId: prefill?.chainId,
- toChainId: prefill?.chainId, // Bridge uses same source chain
- token: prefill?.token,
- amount: prefill?.amount,
- };
-
- // Transform UnifiedInputData back to BridgeConfig
- const handleUpdate = (data: UnifiedInputData) => {
- onUpdate({
- chainId: data.chainId as any,
- token: data.token as any,
- amount: data.amount,
- });
- };
-
- return (
-
- );
-};
-
-export class BridgeController implements ITransactionController {
- InputForm = BridgeInputForm;
-
- hasSufficientInput(inputData: Partial): boolean {
- if (!inputData.amount || !inputData.chainId || !inputData.token) {
- return false;
- }
-
- const amount = parseFloat(inputData.amount.toString());
- return !isNaN(amount) && amount > 0;
- }
-
- async runReview(
- sdk: NexusSDK,
- inputData: BridgeParams,
- ): Promise {
- const simulationResult = await sdk.simulateBridge(inputData);
- logger.info('bridge simulationResult', simulationResult);
-
- const sourcesData = simulationResult?.intent?.sources || [];
- let needsApproval = false;
- const chainDetails: Array<{
- chainId: number;
- amount: string;
- needsApproval: boolean;
- }> = [];
-
- for (const source of sourcesData) {
- if (inputData?.token === 'ETH') {
- chainDetails.push({
- chainId: source.chainID,
- amount: source.amount,
- needsApproval: false,
- });
- continue;
- }
-
- const requiredAmount = sdk.utils.parseUnits(
- source.amount,
- sdk.utils.getTokenMetadata(inputData.token)?.decimals ?? 18,
- );
-
- const allowances = await sdk.getAllowance(source.chainID, [inputData.token]);
- logger.info(`allowances for chain ${source.chainID}:`, allowances);
-
- const currentAllowance = allowances[0]?.allowance ?? 0n;
- const chainNeedsApproval = currentAllowance < requiredAmount;
-
- if (chainNeedsApproval) {
- needsApproval = true;
- logger.info(
- `Allowance needed on chain ${source.chainID}: required=${requiredAmount}, current=${currentAllowance}`,
- );
- }
-
- chainDetails.push({
- chainId: source.chainID,
- amount: requiredAmount.toString(),
- needsApproval: chainNeedsApproval,
- });
- }
-
- return {
- ...simulationResult,
- allowance: {
- needsApproval,
- chainDetails,
- },
- };
- }
-
- async confirmAndProceed(sdk: NexusSDK, inputData: BridgeParams): Promise {
- const result = await sdk.bridge(inputData);
- return result;
- }
-}
diff --git a/packages/widgets/src/controllers/TransferController.tsx b/packages/widgets/src/controllers/TransferController.tsx
deleted file mode 100644
index 28b73aaf..00000000
--- a/packages/widgets/src/controllers/TransferController.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import React from 'react';
-import type { ITransactionController, ActiveTransaction } from '../types';
-import { NexusSDK } from '@avail-project/nexus-core';
-import { type TransferParams, type TransferResult, logger } from '@nexus/commons';
-import {
- UnifiedTransactionForm,
- UnifiedInputData,
-} from '../components/shared/unified-transaction-form';
-
-export interface TransferConfig extends Partial {}
-
-const TransferInputForm: React.FC<{
- prefill: Partial;
- onUpdate: (data: Partial) => void;
- isBusy: boolean;
- prefillFields?: {
- chainId?: boolean;
- toChainId?: boolean;
- token?: boolean;
- amount?: boolean;
- recipient?: boolean;
- };
-}> = ({ prefill, onUpdate, isBusy, prefillFields = {} }) => {
- // Transform TransferConfig to UnifiedInputData
- const unifiedInputData: UnifiedInputData = {
- chainId: prefill?.chainId,
- toChainId: prefill?.chainId, // Transfer uses same chain
- token: prefill?.token,
- amount: prefill?.amount,
- recipient: prefill?.recipient,
- };
-
- // Transform UnifiedInputData back to TransferConfig
- const handleUpdate = (data: UnifiedInputData) => {
- onUpdate({
- chainId: data.chainId as any,
- token: data.token as any,
- amount: data.amount,
- recipient: data.recipient as any, // Cast to proper hex string type
- });
- };
-
- return (
-
- );
-};
-
-export class TransferController implements ITransactionController {
- InputForm = TransferInputForm;
-
- hasSufficientInput(inputData: Partial): boolean {
- if (!inputData.amount || !inputData.chainId || !inputData.token || !inputData.recipient) {
- return false;
- }
-
- const amount = parseFloat(inputData.amount.toString());
- if (isNaN(amount) || amount <= 0) {
- return false;
- }
-
- if (!/^0x[a-fA-F0-9]{40}$/.test(inputData.recipient)) {
- return false;
- }
-
- return true;
- }
-
- async runReview(
- sdk: NexusSDK,
- inputData: TransferParams,
- ): Promise {
- const simulationResult = await sdk.simulateTransfer(inputData);
- logger.info('transfer simulationResult', simulationResult);
- const sourcesData = simulationResult?.intent?.sources || [];
- let needsApproval = false;
- const chainDetails: Array<{
- chainId: number;
- amount: string;
- needsApproval: boolean;
- }> = [];
-
- for (const source of sourcesData) {
- if (inputData?.token === 'ETH') {
- chainDetails.push({
- chainId: source.chainID,
- amount: source.amount,
- needsApproval: false,
- });
- continue;
- }
-
- const requiredAmount = sdk.utils.parseUnits(
- source.amount,
- sdk.utils.getTokenMetadata(inputData.token)?.decimals ?? 18,
- );
- const allowances = await sdk.getAllowance(source.chainID, [inputData.token]);
- logger.info(`transfer allowances for chain ${source.chainID}:`, allowances);
-
- const currentAllowance = allowances[0]?.allowance ?? 0n;
- const chainNeedsApproval = currentAllowance < requiredAmount;
-
- if (chainNeedsApproval) {
- needsApproval = true;
- logger.info(
- `Transfer allowance needed on chain ${source.chainID}: required=${requiredAmount.toString()}, current=${currentAllowance.toString()}`,
- );
- }
-
- chainDetails.push({
- chainId: source.chainID,
- amount: requiredAmount.toString(),
- needsApproval: chainNeedsApproval,
- });
- }
-
- return {
- ...simulationResult,
- allowance: {
- needsApproval,
- chainDetails,
- },
- };
- }
-
- async confirmAndProceed(sdk: NexusSDK, inputData: TransferParams): Promise {
- const result = await sdk.transfer(inputData);
- return result;
- }
-}
diff --git a/packages/widgets/src/hooks/useListenTransaction.tsx b/packages/widgets/src/hooks/useListenTransaction.tsx
deleted file mode 100644
index 67c943e9..00000000
--- a/packages/widgets/src/hooks/useListenTransaction.tsx
+++ /dev/null
@@ -1,363 +0,0 @@
-import { useEffect, useState, useCallback } from 'react';
-import { NEXUS_EVENTS } from '@nexus/commons';
-import { getStatusText } from '../utils/utils';
-import { NexusSDK } from '@avail-project/nexus-core';
-import { ActiveTransaction } from '../types';
-import { ProgressStep, ProgressSteps, SwapStep } from '@avail-project/nexus-core';
-
-// Swap-specific step handling
-export const getTextFromSwapStep = (step: SwapStep): string => {
- switch (step.type) {
- case 'CREATE_PERMIT_EOA_TO_EPHEMERAL':
- return `Creating permit for eoa to ephemeral for ${step.symbol} on ${step.chain?.name || 'chain'}`;
- case 'CREATE_PERMIT_FOR_SOURCE_SWAP':
- return `Creating permit for source swap for ${step.symbol} on ${step.chain?.name || 'chain'}`;
- case 'DESTINATION_SWAP_BATCH_TX':
- return `Creating destination swap transaction`;
- case 'DESTINATION_SWAP_HASH':
- return `Hash for destination swap on ${step.chain?.name || 'chain'}`;
- case 'DETERMINING_SWAP':
- return `Generating routes for XCS`;
- case 'RFF_ID':
- return `Chain abstracted intent`;
- case 'SOURCE_SWAP_BATCH_TX':
- return 'Creating source swap batch transactions';
- case 'SOURCE_SWAP_HASH':
- return `Hash for source swap on ${step.chain?.name || 'chain'}`;
- case 'SWAP_COMPLETE':
- return `Swap is completed`;
- case 'SWAP_START':
- return 'Swap starting';
- default:
- return 'Processing swap';
- }
-};
-
-const swapSteps = [
- { id: 0, type: 'SWAP_START', typeID: 'SWAP_START', name: 'Starting Swap' },
- { id: 1, type: 'DETERMINING_SWAP', typeID: 'DETERMINING_SWAP', name: 'Finding Best Route' },
- {
- id: 2,
- type: 'SOURCE_SWAP_BATCH_TX',
- typeID: 'SOURCE_SWAP_BATCH_TX',
- name: 'Source Transaction',
- },
- { id: 3, type: 'SOURCE_SWAP_HASH', typeID: 'SOURCE_SWAP_HASH', name: 'Source Transaction hash' },
- { id: 4, type: 'RFF_ID', typeID: 'RFF_ID', name: 'Source Transaction hash' },
- {
- id: 5,
- type: 'DESTINATION_SWAP_BATCH_TX',
- typeID: 'DESTINATION_SWAP_BATCH_TX',
- name: 'Destination Transaction',
- },
- {
- id: 6,
- type: 'DESTINATION_SWAP_HASH',
- typeID: 'DESTINATION_SWAP_HASH',
- name: 'Destination Transaction hash',
- },
- {
- id: 7,
- type: 'CREATE_PERMIT_FOR_SOURCE_SWAP',
- typeID: 'CREATE_PERMIT_FOR_SOURCE_SWAP',
- name: 'Permit',
- },
-
- {
- id: 8,
- type: 'CREATE_PERMIT_EOA_TO_EPHEMERAL',
- typeID: 'CREATE_PERMIT_EOA_TO_EPHEMERAL',
- name: 'Permit Ephemeral',
- },
- { id: 9, type: 'SWAP_COMPLETE', typeID: 'SWAP_COMPLETE', name: 'Swap Complete' },
-];
-
-interface ProcessingStep {
- id: number;
- completed: boolean;
- progress: number; // 0-100
- stepData?: ProgressStep | ProgressSteps | SwapStep;
-}
-
-interface ProcessingState {
- currentStep: number;
- totalSteps: number;
- steps: ProcessingStep[];
- statusText: string;
- animationProgress: number;
-}
-
-const useListenTransaction = ({
- sdk,
- activeTransaction,
-}: {
- sdk: NexusSDK;
- activeTransaction: ActiveTransaction;
-}) => {
- const { type } = activeTransaction;
- const DEFAULT_INITIAL_STEPS = 10;
-
- const [processing, setProcessing] = useState(() => ({
- currentStep: 0,
- totalSteps: DEFAULT_INITIAL_STEPS,
- steps: Array.from({ length: DEFAULT_INITIAL_STEPS }, (_, i) => ({
- id: i,
- completed: false,
- progress: 0,
- })),
- statusText: 'Verifying Request',
- animationProgress: 0,
- }));
- const [explorerURL, setExplorerURL] = useState(null);
- const [explorerURLs, setExplorerURLs] = useState<{ source?: string; destination?: string }>({});
-
- const resetProcessingState = useCallback(() => {
- setProcessing({
- currentStep: 0,
- totalSteps: DEFAULT_INITIAL_STEPS,
- steps: Array.from({ length: DEFAULT_INITIAL_STEPS }, (_, i) => ({
- id: i,
- completed: false,
- progress: 0,
- })),
- statusText: 'Verifying Request',
- animationProgress: 0,
- });
- setExplorerURL(null);
- setExplorerURLs({});
- }, []);
-
- useEffect(() => {
- if (!sdk) return;
-
- // Special handling for swap transactions
- if (type === 'swap') {
- // For swap, we create our own progress steps since no expected_steps are emitted
-
- const initialSteps = swapSteps.map((step, index) => ({
- id: index,
- completed: false,
- progress: 0,
- stepData: step as any, // Step structure for swap mock data
- }));
-
- setProcessing({
- currentStep: 0,
- totalSteps: swapSteps.length,
- steps: initialSteps,
- statusText: 'Preparing Swap',
- animationProgress: 0,
- });
-
- const handleSwapStepComplete = (stepData: SwapStep) => {
- setProcessing((prev) => {
- // Find matching step by type
- const stepIndex = swapSteps.findIndex((s) => s.typeID === stepData.type);
-
- if (stepIndex === -1) {
- // Unknown step, just advance progress
- const nextStep = Math.min(prev.currentStep + 1, prev.totalSteps);
- return {
- ...prev,
- currentStep: nextStep,
- animationProgress: (nextStep / prev.totalSteps) * 100,
- statusText: getTextFromSwapStep(stepData),
- };
- }
-
- const newSteps = [...prev.steps];
-
- // Mark all steps up to and including current as completed
- for (let i = 0; i <= stepIndex && i < newSteps.length; i++) {
- newSteps[i] = {
- ...newSteps[i],
- completed: true,
- progress: 100,
- stepData: i === stepIndex ? stepData : newSteps[i].stepData,
- };
- }
-
- const nextStep = Math.min(stepIndex + 1, prev.totalSteps);
- const animationProgress = ((stepIndex + 1) / prev.totalSteps) * 100;
-
- return {
- ...prev,
- currentStep: nextStep,
- steps: newSteps,
- animationProgress: Math.min(animationProgress, 100),
- statusText: getTextFromSwapStep(stepData),
- };
- });
-
- // Handle explorer URL extraction for swap
- if (stepData.type === 'SOURCE_SWAP_HASH' && 'explorerURL' in stepData) {
- setExplorerURLs((prev) => ({ ...prev, source: stepData.explorerURL }));
- setExplorerURL(stepData.explorerURL); // Keep for backward compatibility
- } else if (stepData.type === 'DESTINATION_SWAP_HASH' && 'explorerURL' in stepData) {
- setExplorerURLs((prev) => ({ ...prev, destination: stepData.explorerURL }));
- setExplorerURL(stepData.explorerURL); // Update to show latest
- }
- };
-
- sdk?.nexusEvents?.on(NEXUS_EVENTS.SWAP_STEPS, handleSwapStepComplete);
-
- return () => {
- sdk.nexusEvents?.off(NEXUS_EVENTS.SWAP_STEPS, handleSwapStepComplete);
- };
- }
-
- // Regular handling for non-swap transactions
- // Flag to know when we have received the complete expected-steps list
- let expectedReceived = false;
- // Queue to store stepComplete events that arrive before expected steps
- const pendingSteps: ProgressStep[] = [];
- const expectedEventType =
- type === 'bridgeAndExecute'
- ? NEXUS_EVENTS.BRIDGE_EXECUTE_EXPECTED_STEPS
- : NEXUS_EVENTS.EXPECTED_STEPS;
-
- const completedEventType =
- type === 'bridgeAndExecute'
- ? NEXUS_EVENTS.BRIDGE_EXECUTE_COMPLETED_STEPS
- : NEXUS_EVENTS.STEP_COMPLETE;
-
- const handleExpectedSteps = (expectedSteps: ProgressSteps[]) => {
- expectedReceived = true;
- const stepCount = Array.isArray(expectedSteps) ? expectedSteps.length : expectedSteps;
- const steps = Array.isArray(expectedSteps) ? expectedSteps : [];
-
- // Build initial step objects from expected steps array
- const initialSteps = Array.from({ length: stepCount }, (_, i) => ({
- id: i,
- completed: false,
- progress: 0,
- stepData: steps[i] || null,
- }));
-
- // Preserve any steps that were already completed before this event arrived
- setProcessing((prev: ProcessingState) => {
- const completedTypeIDs = prev.steps
- .filter((s) => s.completed)
- .map((s) => (s.stepData as ProgressStep)?.typeID) as string[];
-
- const mergedSteps = initialSteps.map((step) => {
- const typeID = (step.stepData as any)?.typeID as string | undefined;
- if (typeID && completedTypeIDs.includes(typeID)) {
- return { ...step, completed: true, progress: 100 };
- }
- return step;
- });
-
- const completedCount = mergedSteps.filter((s) => s.completed).length;
-
- let newState: ProcessingState = {
- ...prev,
- totalSteps: stepCount,
- steps: mergedSteps,
- currentStep: completedCount,
- animationProgress: (completedCount / stepCount) * 100,
- statusText: 'Verifying Request',
- };
-
- // Now process any queued steps that arrived before expected steps
- if (pendingSteps.length > 0) {
- pendingSteps.forEach((queuedStep) => {
- newState = processStep(newState, queuedStep);
- });
- pendingSteps.length = 0; // clear queue
- }
-
- return newState;
- });
- };
-
- // Helper to process a single step and return updated state (pure function)
- const processStep = (prev: ProcessingState, stepData: ProgressStep): ProcessingState => {
- const { type: stepType, typeID, data } = stepData;
-
- let stepIndex = prev.steps.findIndex((s) => {
- const id = (s.stepData as any)?.typeID as string | undefined;
- return id === typeID;
- });
-
- if (stepIndex === -1) {
- stepIndex = Math.min(prev.currentStep, prev.totalSteps - 1);
- }
-
- const newSteps = [...prev.steps];
-
- for (let i = 0; i <= stepIndex && i < newSteps.length; i++) {
- newSteps[i] = {
- ...newSteps[i],
- completed: true,
- progress: 100,
- stepData: i === stepIndex ? stepData : newSteps[i].stepData,
- };
- }
-
- const nextStep = Math.min(stepIndex + 1, prev.totalSteps);
- const animationProgress = ((stepIndex + 1) / prev.totalSteps) * 100;
-
- let description = getStatusText(stepData, type || 'bridge');
- if (stepType === 'INTENT_COLLECTION' && data) {
- description = 'Collecting Confirmations';
- }
-
- return {
- ...prev,
- currentStep: nextStep,
- steps: newSteps,
- animationProgress: Math.min(animationProgress, 100),
- statusText: description,
- };
- };
-
- const handleStepComplete = (stepData: ProgressStep) => {
- const { typeID, data } = stepData;
-
- // Always advance progress for better UX
- setProcessing((prev) => processStep(prev, stepData));
-
- // Queue until we have real mapping
- if (!expectedReceived) {
- pendingSteps.push(stepData);
- }
-
- if (typeID === 'IS' && data && 'explorerURL' in data) {
- setExplorerURL((data as any)?.explorerURL as string);
- }
- };
-
- sdk?.nexusEvents?.on(expectedEventType, handleExpectedSteps);
- sdk?.nexusEvents?.on(completedEventType, handleStepComplete);
-
- return () => {
- sdk.nexusEvents?.off(expectedEventType, handleExpectedSteps);
- sdk.nexusEvents?.off(completedEventType, handleStepComplete);
- };
- }, [sdk, type]);
-
- useEffect(() => {
- if (!sdk) return;
- const handleBeforeUnload = (e: BeforeUnloadEvent) => {
- if (
- activeTransaction.status === 'processing' ||
- activeTransaction.status === 'set_allowance'
- ) {
- e.preventDefault();
- e.returnValue = 'A transaction is currently in progress. Are you sure you want to leave?';
- }
- return 'A transaction is currently in progress. Are you sure you want to leave?';
- };
-
- window.addEventListener('beforeunload', handleBeforeUnload);
-
- return () => {
- window.removeEventListener('beforeunload', handleBeforeUnload);
- };
- }, [activeTransaction.status]);
-
- return { processing, explorerURL, explorerURLs, resetProcessingState };
-};
-
-export default useListenTransaction;
diff --git a/packages/widgets/src/hooks/useNexus.tsx b/packages/widgets/src/hooks/useNexus.tsx
deleted file mode 100644
index d7ddb48d..00000000
--- a/packages/widgets/src/hooks/useNexus.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { useInternalNexus } from '../providers/InternalNexusProvider';
-
-const useNexus = () => {
- const { setProvider, sdk, isSdkInitialized, provider, initializeSdk, deinitializeSdk } =
- useInternalNexus();
- return {
- setProvider,
- sdk,
- isSdkInitialized,
- provider,
- initializeSdk,
- deinitializeSdk,
- };
-};
-
-export default useNexus;
diff --git a/packages/widgets/src/hooks/useOutsideClick.tsx b/packages/widgets/src/hooks/useOutsideClick.tsx
deleted file mode 100644
index 0fe08606..00000000
--- a/packages/widgets/src/hooks/useOutsideClick.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React, { useEffect } from 'react';
-
-const useOutsideClick = (
- ref: React.RefObject,
- callback: (event: MouseEvent | TouchEvent) => void,
-) => {
- useEffect(() => {
- const listener = (event: MouseEvent | TouchEvent) => {
- if (!ref.current || !event.target || ref.current.contains(event.target as Node)) {
- return;
- }
- callback(event);
- };
-
- document.addEventListener('mousedown', listener);
- document.addEventListener('touchstart', listener);
-
- return () => {
- document.removeEventListener('mousedown', listener);
- document.removeEventListener('touchstart', listener);
- };
- }, [ref]);
-};
-
-export default useOutsideClick;
diff --git a/packages/widgets/src/index.ts b/packages/widgets/src/index.ts
deleted file mode 100644
index 0f7553ee..00000000
--- a/packages/widgets/src/index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-// UI SDK entry point - React components and providers
-import './styles/globals.css';
-
-export { default as NexusProvider } from './providers/NexusProvider';
-export { default as useNexus } from './hooks/useNexus';
-
-// Button components (named exports)
-export { BridgeButton } from './components/bridge/bridge-button';
-export { TransferButton } from './components/transfer/transfer-button';
-export { BridgeAndExecuteButton } from './components/bridge-execute/bridge-execute-button';
-export { SwapButton } from './components/swap/swap-button';
-
-export * from '@nexus/commons';
diff --git a/packages/widgets/src/providers/InternalNexusProvider.tsx b/packages/widgets/src/providers/InternalNexusProvider.tsx
deleted file mode 100644
index db24aa52..00000000
--- a/packages/widgets/src/providers/InternalNexusProvider.tsx
+++ /dev/null
@@ -1,1073 +0,0 @@
-'use client';
-import {
- createContext,
- useContext,
- useState,
- ReactNode,
- useCallback,
- useMemo,
- useEffect,
- useRef,
-} from 'react';
-import {
- NexusSDK,
- EthereumProvider,
- UserAsset,
- BridgeParams,
- TransferParams,
- BridgeAndExecuteParams,
- SimulationResult,
- NexusNetwork,
- BridgeAndExecuteSimulationResult,
-} from '@avail-project/nexus-core';
-import type {
- ActiveTransaction,
- BridgeConfig,
- NexusContextValue,
- TransactionType,
- ITransactionController,
- SwapInputData,
-} from '../types';
-import type { TransferConfig } from '../controllers/TransferController';
-import { BridgeController } from '../controllers/BridgeController';
-import { TransferController } from '../controllers/TransferController';
-import { BridgeAndExecuteController } from '../controllers/BridgeAndExecuteController';
-import TransactionProcessorShell from '../components/processing/transaction-processor-shell';
-import { LayoutGroup } from 'motion/react';
-import useListenTransaction from '../hooks/useListenTransaction';
-import {
- logger,
- SwapIntentHook,
- parseUnits,
- TOKEN_METADATA,
- ExactInSwapInput,
-} from '@nexus/commons';
-import { DragConstraintsProvider } from '../components/motion/drag-constraints';
-import { getTokenFromInputData, getAmountFromInputData, formatSwapError } from '../utils/utils';
-import { getTokenAddress } from '../utils/token-utils';
-
-const controllers: Record, ITransactionController> = {
- bridge: new BridgeController(),
- transfer: new TransferController(),
- bridgeAndExecute: new BridgeAndExecuteController(),
-};
-
-// Type guards
-
-const NexusContext = createContext(null);
-
-const initialState: ActiveTransaction = {
- type: null,
- status: 'idle',
- reviewStatus: 'gathering_input',
- inputData: null,
- prefillFields: {},
- simulationResult: null,
- executionResult: null,
- error: null,
-};
-
-// Utility: extract chain identifier regardless of transaction type
-function getInputChainId(
- data:
- | Partial
- | Partial
- | Partial
- | Partial
- | null
- | undefined,
-): number | undefined {
- if (!data) return undefined;
- if ('chainId' in data && data.chainId !== undefined) return data.chainId as number;
- if ('toChainId' in data && data.toChainId !== undefined) return data.toChainId;
- return undefined;
-}
-
-export function InternalNexusProvider({
- config,
- children,
- disableCollapse,
-}: Readonly<{
- config?: { network?: NexusNetwork; debug?: boolean };
- children: ReactNode;
- disableCollapse?: boolean;
-}>) {
- const [sdk] = useState(
- () => new NexusSDK({ network: config?.network ?? 'mainnet', debug: config?.debug ?? false }),
- );
-
- const [provider, setProvider] = useState(undefined);
- const [isSdkInitialized, setIsSdkInitialized] = useState(false);
- const [activeTransaction, setActiveTransaction] = useState(initialState);
- const [unifiedBalance, setUnifiedBalance] = useState([]);
- const [exchangeRates, setExchangeRates] = useState>({});
- const [isSimulating, setIsSimulating] = useState(false);
- const [insufficientBalance, setInsufficientBalance] = useState(false);
- const [isTransactionCollapsed, setIsTransactionCollapsed] = useState(false);
- const [timer, setTimer] = useState(0);
- const [allowanceError, setAllowanceError] = useState(null);
- const [isSettingAllowance, setIsSettingAllowance] = useState(false);
-
- // Swap-specific state
- const swapAllowCallbackRef = useRef<(() => void) | null>(null);
- const [isSwapExecuting, setIsSwapExecuting] = useState(false);
-
- const timerRef = useRef(null);
- const debounceTimeoutRef = useRef(null);
-
- // Keep a live ref of SDK initialized state to avoid stale closures in callbacks
- const isSdkInitializedRef = useRef(false);
- useEffect(() => {
- isSdkInitializedRef.current = isSdkInitialized;
- }, [isSdkInitialized]);
-
- const activeController = useMemo(() => {
- if (!activeTransaction.type) return null;
- if (activeTransaction.type === 'swap') return null; // Swaps handled directly in provider
- return controllers[activeTransaction.type];
- }, [activeTransaction.type]);
-
- const { processing, explorerURL, explorerURLs, resetProcessingState } = useListenTransaction({
- sdk,
- activeTransaction,
- });
-
- const fetchExchangeRates = useCallback(async () => {
- try {
- const response = await fetch('https://api.coinbase.com/v2/exchange-rates?currency=USD');
- const bnbExchangeRate = await fetch(
- 'https://api.coingecko.com/api/v3/simple/price?ids=binancecoin&vs_currencies=usd',
- );
- const bnbData = await bnbExchangeRate.json();
- const data = await response.json();
- const rates = (data?.data?.rates ?? {}) as Record;
- logger.info('all rates', rates);
- // Convert from "units per USD" to "USD per unit" for easier UI multiplication
- const usdPerUnit: Record = { BNB: bnbData.binancecoin.usd };
- for (const [symbol, value] of Object.entries(rates)) {
- const unitsPerUsd = parseFloat(value);
- if (Number.isFinite(unitsPerUsd) && unitsPerUsd > 0) {
- usdPerUnit[symbol] = 1 / unitsPerUsd;
- }
- }
-
- // Ensure common stablecoins have a sane fallback
- ['USD', 'USDC', 'USDT'].forEach((stable) => {
- if (usdPerUnit[stable] === undefined) usdPerUnit[stable] = 1;
- });
- setExchangeRates(usdPerUnit);
- } catch (error) {
- logger.error('Error fetching exchange rates:', error as Error);
- }
- }, []);
-
- const fetchBalances = async () => {
- const unifiedBalance = await sdk.getUnifiedBalances();
- logger.debug('Unified balance', { unifiedBalance });
- setUnifiedBalance(unifiedBalance);
- };
-
- const initializeSdk = async (ethProvider?: EthereumProvider) => {
- if (isSdkInitialized) return true;
- const eipProvider = ethProvider ?? provider;
- if (!eipProvider) {
- setActiveTransaction((prev) => ({
- ...prev,
- status: 'simulation_error',
- error: new Error('Wallet provider not connected.'),
- }));
- return false;
- }
-
- if (!provider && eipProvider) {
- setProvider(ethProvider);
- }
-
- try {
- setActiveTransaction((prev) => ({ ...prev, status: 'initializing' }));
- await sdk.initialize(eipProvider);
- await fetchExchangeRates();
- await fetchBalances();
- setIsSdkInitialized(sdk.isInitialized());
- isSdkInitializedRef.current = sdk.isInitialized();
- setActiveTransaction((prev) => ({ ...prev, status: 'review' }));
- return true;
- } catch (err) {
- logger.error('SDK initialization failed:', err as Error);
- const error = err instanceof Error ? err : new Error('SDK Initialization failed.');
- setActiveTransaction((prev) => ({ ...prev, status: 'simulation_error', error }));
- return false;
- }
- };
-
- const deinitializeSdk = async () => {
- if (!isSdkInitialized) return;
- try {
- await sdk?.deinit();
- reset();
- } catch (e) {
- logger.error('Error deinitializing SDK', e as Error);
- }
- };
-
- const reset = () => {
- setProvider(undefined);
- setIsSdkInitialized(false);
- isSdkInitializedRef.current = false;
- setActiveTransaction(initialState);
- setUnifiedBalance([]);
- setIsSimulating(false);
- setInsufficientBalance(false);
- setIsTransactionCollapsed(true);
- setTimer(0);
- setAllowanceError(null);
- setIsSettingAllowance(false);
- };
-
- const startTransaction = useCallback(
- (
- type: TransactionType,
- prefillData:
- | Partial
- | Partial
- | Partial
- | Partial = {},
- ) => {
- // Track which fields were prefilled
- const prefillFields: {
- chainId?: boolean;
- toChainId?: boolean;
- token?: boolean;
- amount?: boolean;
- recipient?: boolean;
- fromChainID?: boolean;
- toChainID?: boolean;
- fromTokenAddress?: boolean;
- toTokenAddress?: boolean;
- fromAmount?: boolean;
- toAmount?: boolean;
- } = {};
-
- if (prefillData) {
- if ('chainId' in prefillData && prefillData.chainId !== undefined) {
- prefillFields.chainId = true;
- if (type === 'bridgeAndExecute') {
- prefillFields.toChainId = true;
- }
- }
- if (
- type === 'bridgeAndExecute' &&
- 'toChainId' in prefillData &&
- prefillData.toChainId !== undefined
- ) {
- prefillFields.toChainId = true;
- }
- if ('token' in prefillData && prefillData.token !== undefined) {
- prefillFields.token = true;
- }
- if ('amount' in prefillData && prefillData.amount !== undefined) {
- prefillFields.amount = true;
- }
- if ('recipient' in prefillData && prefillData.recipient !== undefined) {
- prefillFields.recipient = true;
- }
- // Handle swap-specific fields
- if ('fromChainID' in prefillData && prefillData.fromChainID !== undefined) {
- prefillFields.fromChainID = true;
- }
- if ('toChainID' in prefillData && prefillData.toChainID !== undefined) {
- prefillFields.toChainID = true;
- }
- if ('fromTokenAddress' in prefillData && prefillData.fromTokenAddress !== undefined) {
- prefillFields.fromTokenAddress = true;
- }
- if ('toTokenAddress' in prefillData && prefillData.toTokenAddress !== undefined) {
- prefillFields.toTokenAddress = true;
- }
- if ('fromAmount' in prefillData && prefillData.fromAmount !== undefined) {
- prefillFields.fromAmount = true;
- }
- if ('toAmount' in prefillData && prefillData.toAmount !== undefined) {
- prefillFields.toAmount = true;
- }
- }
- const normalizedPrefillData =
- type === 'bridgeAndExecute' &&
- 'toChainId' in prefillData &&
- prefillData.toChainId !== undefined
- ? { ...prefillData, chainId: prefillData.toChainId }
- : prefillData;
-
- setActiveTransaction({
- ...initialState,
- type,
- status: isSdkInitializedRef.current ? 'review' : 'initializing',
- inputData: normalizedPrefillData as any,
- prefillFields,
- });
- },
- [isSdkInitialized],
- );
-
- const cancelTransaction = useCallback(async () => {
- setIsSimulating(false);
- setInsufficientBalance(false);
- setIsTransactionCollapsed(true);
- setTimer(0);
- setActiveTransaction(initialState);
- resetProcessingState();
- if (isSdkInitialized && sdk) {
- try {
- const updatedBalance = await sdk.getUnifiedBalances();
- setUnifiedBalance(updatedBalance);
- } catch (err) {
- logger.warn('Failed to refetch unified balance after transaction completion:', err);
- }
- }
- }, [isSdkInitialized, sdk, resetProcessingState]);
-
- const toggleTransactionCollapse = useCallback(() => {
- setIsTransactionCollapsed((prev) => !prev);
- }, []);
-
- const updateInput = useCallback(
- (
- data:
- | Partial
- | Partial
- | Partial
- | Partial,
- ) => {
- setActiveTransaction((prev) => ({
- ...prev,
- inputData: { ...prev.inputData, ...data } as any,
- reviewStatus: 'gathering_input',
- status: prev.status === 'simulation_error' ? 'review' : prev.status,
- error: prev.status === 'simulation_error' ? null : prev.error,
- }));
-
- setIsSimulating(false);
- setInsufficientBalance(false);
- },
- [],
- );
-
- const checkInsufficientBalance = useCallback(
- (inputData: Partial | Partial | Partial) => {
- const token = getTokenFromInputData(inputData);
- const amount = getAmountFromInputData(inputData);
-
- if (!token || !amount || !unifiedBalance.length) {
- return false;
- }
-
- const tokenBalance = unifiedBalance.find((asset) => asset.symbol === token);
- if (!tokenBalance) {
- logger.warn('Token not found in unified balance:', {
- requestedToken: token,
- availableTokens: unifiedBalance.map((asset) => asset.symbol),
- });
- return true; // Consider it insufficient if token not found
- }
-
- const requestedAmount = parseFloat(amount.toString());
- const availableBalance = parseFloat(tokenBalance.balance);
-
- const isInsufficient = requestedAmount > availableBalance;
-
- if (isInsufficient) {
- logger.warn('Insufficient balance detected:', {
- token: token,
- requested: requestedAmount,
- available: availableBalance,
- deficit: requestedAmount - availableBalance,
- });
- }
-
- return isInsufficient;
- },
- [unifiedBalance],
- );
-
- const retrySimulation = useCallback(() => {
- setIsSimulating(false);
- setActiveTransaction((prev) => ({
- ...prev,
- status: 'review',
- error: null,
- reviewStatus: 'gathering_input',
- }));
- }, []);
-
- const triggerSimulation = useCallback(async () => {
- if (debounceTimeoutRef.current) {
- clearTimeout(debounceTimeoutRef.current);
- debounceTimeoutRef.current = null;
- }
-
- const conditions = {
- isSdkInitialized,
- statusOk:
- activeTransaction.status === 'review' || activeTransaction.status === 'simulation_error',
- reviewStatusOk: activeTransaction.reviewStatus === 'gathering_input',
- hasController: !!activeController,
-
- hasSufficientInput: activeTransaction.inputData
- ? (() => {
- if (activeTransaction.type === 'swap') {
- // For swaps, check if we have sufficient input directly
- const data = activeTransaction.inputData as Partial