Avail Nexus SDK Tutorial | Part 2: Cross-Chain Transfers

In this tutorial we'll enable crosschain transfers using the Nexus SDK. This is a follow on from Tutorial 1, where we set up the Nexus SDK and viewed unified balances.

By Christine Perry 22 min read
Avail Nexus SDK Tutorial | Part 2: Cross-Chain Transfers

Welcome to Part 2 of the Nexus SDK tutorial! In this part, you'll add seamless cross-chain transfer functionality to your existing portfolio viewer, allowing users to send assets directly to any address on any supported blockchain network. All this can be done just from inside your dApp, no need to use custom third party bridges or manage multiple networks or network switching, it's all handled by the Nexus SDK!

This is a follow on from part 1, if you haven't done that yet, we recommend implementing it first.

What You'll Build

By the end of this tutorial, your app will have:

  • Integrated transfer interface on the same page as your portfolio
  • Smart chain selection with source and destination chain pickers
  • Real-time transfer simulation with cost estimation
  • Transaction history with status tracking and explorer links
  • Seamless user experience with tab navigation between portfolio and transfer views

Prerequisites

Before we begin, ensure you have:

  • Node.js (v16 or higher) - Download here
  • A package manager (npm, yarn, or pnpm)
  • Basic knowledge of React/Next.js - hooks, components, state management
  • A wallet extension like MetaMask installed and set up
  • Some testnet tokens for testing (we'll show you how to get them)

Just want the code?

You can skip the tutorial and run the complete application here!


Step 1: Update the NexusProvider

Replace your components/NexusProvider.tsx with the updated version:

'use client';

import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
import { useAccount } from 'wagmi';

// Global window extension for wallet provider
declare global {
  interface Window {
    ethereum?: any;
  }
}

interface TransferParams {
  token: string;
  amount: string;
  chainId: number;
  recipient: string;
}

interface NexusContextType {
  sdk: any; // Replace with actual SDK type when available
  isInitialized: boolean;
  balances: any[];
  isLoading: boolean;
  error: string | null;
  refreshBalances: () => Promise<void>;
  transfer: (params: TransferParams) => Promise<any>;
  simulateTransfer: (params: TransferParams) => Promise<any>;
}

const NexusContext = createContext<NexusContextType | undefined>(undefined);

interface NexusProviderProps {
  children: ReactNode;
}

export function NexusProvider({ children }: NexusProviderProps) {
  const { isConnected, address } = useAccount();
  const [sdk, setSdk] = useState<any>(null);
  const [isInitialized, setIsInitialized] = useState(false);
  const [balances, setBalances] = useState<any[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // Initialize SDK when wallet connects
  useEffect(() => {
    if (isConnected && window.ethereum && !isInitialized && !isLoading) {
      initializeSDK();
    }
  }, [isConnected, isInitialized, isLoading]);

  const initializeSDK = async () => {
    try {
      setIsLoading(true);
      setError(null);
      
      // Dynamic import with updated package name
      const { NexusSDK } = await import('@avail-project/nexus');
      const nexusSDK = new NexusSDK({ network: 'testnet' });
      
      // Initialize with the wallet provider
      await nexusSDK.initialize(window.ethereum);
      
      // Set up allowance hook for token approvals
      nexusSDK.setOnAllowanceHook(async ({ allow, deny, sources }) => {
        console.log('Allowance required for sources:', sources);
        
        // For tutorial, we'll auto-approve with minimum allowances
        // In production, show proper approval modals
        const allowances = sources.map(() => 'min');
        allow(allowances);
      });
      
      // Set up intent hook for transaction previews
      nexusSDK.setOnIntentHook(({ intent, allow, deny, refresh }) => {
        console.log('Transaction intent:', intent);
        
        // For tutorial, we'll auto-approve
        // In production, show transaction preview modals
        allow();
      });
      
      setSdk(nexusSDK);
      setIsInitialized(true);
      
      // Fetch initial balances
      await fetchBalances(nexusSDK);
      
    } catch (error) {
      console.error('Failed to initialize Nexus SDK:', error);
      setError(error instanceof Error ? error.message : 'Failed to initialize SDK');
    } finally {
      setIsLoading(false);
    }
  };

  const fetchBalances = async (sdkInstance = sdk) => {
    if (!sdkInstance || !isInitialized) return;
    
    try {
      setIsLoading(true);
      setError(null);
      
      const unifiedBalances = await sdkInstance.getUnifiedBalances();
      setBalances(unifiedBalances);
      console.log('Unified balances fetched:', unifiedBalances);
      
    } catch (error) {
      console.error('Failed to fetch balances:', error);
      setError(error instanceof Error ? error.message : 'Failed to fetch balances');
    } finally {
      setIsLoading(false);
    }
  };

  const refreshBalances = async () => {
    await fetchBalances();
  };

  // Transfer function for Part 2
  const transfer = async (params: TransferParams) => {
    if (!sdk) {
      throw new Error('SDK not initialized');
    }
    
    try {
      console.log('Starting transfer transaction:', params);
      
      const result = await sdk.transfer(params);
      console.log('Transfer transaction result:', result);
      
      // Save transaction to history
      const transaction = {
        id: Date.now().toString(),
        type: 'transfer' as const,
        token: params.token,
        amount: params.amount,
        toChain: params.chainId,
        recipient: params.recipient,
        status: 'pending' as const,
        timestamp: new Date(),
        hash: result.hash || result.transactionHash || undefined
      };
      
      // Store in localStorage
      const existingHistory = localStorage.getItem('nexus-transfer-transactions');
      const history = existingHistory ? JSON.parse(existingHistory) : [];
      history.unshift(transaction);
      
      // Keep only last 50 transactions
      const trimmedHistory = history.slice(0, 50);
      localStorage.setItem('nexus-transfer-transactions', JSON.stringify(trimmedHistory));
      
      // Refresh balances after successful transfer
      setTimeout(() => {
        refreshBalances();
      }, 5000);
      
      return result;
    } catch (error) {
      console.error('Transfer transaction failed:', error);
      throw error;
    }
  };

  // Simulate transfer transaction for Part 2
  const simulateTransfer = async (params: TransferParams) => {
    if (!sdk) {
      throw new Error('SDK not initialized');
    }
    
    try {
      console.log('Simulating transfer transaction:', params);
      
      const simulation = await sdk.simulateTransfer(params);
      console.log('Transfer simulation result:', simulation);
      
      return simulation;
    } catch (error) {
      console.error('Transfer simulation failed:', error);
      throw error;
    }
  };

  // Reset state when wallet disconnects
  useEffect(() => {
    if (!isConnected) {
      setSdk(null);
      setIsInitialized(false);
      setBalances([]);
      setError(null);
    }
  }, [isConnected]);

  return (
    <NexusContext.Provider 
      value={{ 
        sdk, 
        isInitialized, 
        balances, 
        isLoading, 
        error,
        refreshBalances,
        transfer,
        simulateTransfer
      }}
    >
      {children}
    </NexusContext.Provider>
  );
}

export function useNexus() {
  const context = useContext(NexusContext);
  if (context === undefined) {
    throw new Error('useNexus must be used within a NexusProvider');
  }
  return context;
}

What's New in the Provider

  • Transfer Function: Handles transfer transactions and saves history to localStorage
  • Simulate Transfer: Previews transfer costs and feasibility before execution
  • Transaction History: Automatically tracks all transfer operations
  • Auto-refresh: Updates balances after successful transfer operations

Step 2: Create the Transfer History Component

Create components/TransferHistory.tsx:

'use client';

import { useState, useEffect } from 'react';
import { Clock, CheckCircle, XCircle, ExternalLink, History, Send } from 'lucide-react';

interface TransferTransaction {
  id: string;
  type: 'transfer';
  token: string;
  amount: string;
  toChain: number;
  recipient: string;
  status: 'pending' | 'completed' | 'failed';
  timestamp: Date;
  hash?: string;
}

export function TransferHistory() {
  const [transactions, setTransactions] = useState<TransferTransaction[]>([]);
  const [showHistory, setShowHistory] = useState(false);

  const chains = {
    11155111: 'Sepolia',
    84532: 'Base Sepolia',
    80002: 'Polygon Amoy',
    421614: 'Arbitrum Sepolia',
    11155420: 'Optimism Sepolia'
  };

  useEffect(() => {
    loadTransactionHistory();
  }, []);

  const loadTransactionHistory = () => {
    try {
      const savedTransactions = localStorage.getItem('nexus-transfer-transactions');
      if (savedTransactions) {
        const parsed = JSON.parse(savedTransactions);
        setTransactions(parsed.map((tx: any) => ({
          ...tx,
          timestamp: new Date(tx.timestamp)
        })));
      }
    } catch (error) {
      console.error('Error loading transfer history:', error);
    }
  };

  const getStatusIcon = (status: TransferTransaction['status']) => {
    switch (status) {
      case 'completed':
        return <CheckCircle className="w-4 h-4 text-green-500" />;
      case 'failed':
        return <XCircle className="w-4 h-4 text-red-500" />;
      case 'pending':
        return <Clock className="w-4 h-4 text-yellow-500" />;
    }
  };

  const getChainName = (chainId: number) => {
    return chains[chainId as keyof typeof chains] || `Chain ${chainId}`;
  };

  const getExplorerUrl = (hash: string, chainId: number) => {
    const explorers = {
      11155111: 'https://sepolia.etherscan.io',
      84532: 'https://sepolia.basescan.org',
      80002: 'https://amoy.polygonscan.com',
      421614: 'https://sepolia.arbiscan.io',
      11155420: 'https://sepolia-optimism.etherscan.io'
    };
    
    const explorer = explorers[chainId as keyof typeof explorers];
    return explorer ? `${explorer}/tx/${hash}` : `https://etherscan.io/tx/${hash}`;
  };

  const truncateAddress = (address: string) => {
    return `${address.slice(0, 6)}...${address.slice(-4)}`;
  };

  if (transactions.length === 0) {
    return null;
  }

  return (
    <div className="space-y-4">
      <div className="flex items-center justify-between">
        <div className="flex items-center space-x-2">
          <History className="w-5 h-5 text-slate-600" />
          <h3 className="text-lg font-semibold text-slate-900">Transfer History</h3>
        </div>
        <button
          onClick={() => setShowHistory(!showHistory)}
          className="text-purple-600 hover:text-purple-700 text-sm font-medium"
        >
          {showHistory ? 'Hide' : 'Show'} History ({transactions.length})
        </button>
      </div>

      {showHistory && (
        <div className="bg-white rounded-xl border border-slate-200 p-4">
          <div className="space-y-3">
            {transactions.slice(0, 10).map((tx) => (
              <div key={tx.id} className="flex items-center justify-between p-3 bg-slate-50 rounded-lg">
                <div className="flex items-center space-x-3">
                  {getStatusIcon(tx.status)}
                  <div>
                    <p className="font-medium text-slate-900">
                      {tx.amount} {tx.token}
                    </p>
                    <p className="text-sm text-slate-500">
                      To: {truncateAddress(tx.recipient)} on {getChainName(tx.toChain)}
                    </p>
                    <p className="text-xs text-slate-400">
                      {tx.timestamp.toLocaleString()}
                    </p>
                  </div>
                </div>
                <div className="flex items-center space-x-2">
                  <span className={`
                    px-2 py-1 rounded-full text-xs font-medium
                    ${tx.status === 'completed' ? 'bg-green-100 text-green-700' : ''}
                    ${tx.status === 'failed' ? 'bg-red-100 text-red-700' : ''}
                    ${tx.status === 'pending' ? 'bg-yellow-100 text-yellow-700' : ''}
                  `}>
                    {tx.status}
                  </span>
                  {tx.hash && (
                    <a
                      href={getExplorerUrl(tx.hash, tx.toChain)}
                      target="_blank"
                      rel="noopener noreferrer"
                      className="text-slate-400 hover:text-slate-600 transition-colors"
                      title="View on Explorer"
                    >
                      <ExternalLink className="w-4 h-4" />
                    </a>
                  )}
                </div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

Transfer History Features

  • Transaction Tracking: Stores and displays all transfer operations
  • Status Indicators: Visual icons for pending/completed/failed states
  • Explorer Links: Direct links to view transactions on blockchain explorers
  • Recipient Display: Shows truncated recipient addresses
  • Collapsible UI: Toggle show/hide with transaction count display

Step 3: Create the Transfer Form Component

Create components/TransferForm.tsx:

'use client';

import { useState, useEffect } from 'react';
import { useNexus } from './NexusProvider';
import { Send, RefreshCw, ArrowRight, AlertCircle, Info, ChevronDown } from 'lucide-react';

export function TransferForm() {
  const { sdk, isInitialized, balances, refreshBalances } = useNexus();
  const [selectedToken, setSelectedToken] = useState('');
  const [targetChain, setTargetChain] = useState('');
  const [transferAmount, setTransferAmount] = useState('');
  const [recipient, setRecipient] = useState('');
  const [isTransferring, setIsTransferring] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [isSimulating, setIsSimulating] = useState(false);
  const [simulation, setSimulation] = useState<any>(null);

  // Chain mapping for testnet
  const chains = {
    11155111: { name: 'Sepolia', shortName: 'SEP', icon: 'πŸ”·' },
    84532: { name: 'Base Sepolia', shortName: 'BASE-SEP', icon: 'πŸ”΅' },
    80002: { name: 'Polygon Amoy', shortName: 'AMOY', icon: '🟣' },
    421614: { name: 'Arbitrum Sepolia', shortName: 'ARB-SEP', icon: 'πŸ”΅' },
    11155420: { name: 'Optimism Sepolia', shortName: 'OP-SEP', icon: 'πŸ”΄' }
  };

  // Get available tokens with non-zero balances
  const availableTokens = balances.filter(token => 
    token.breakdown && token.breakdown.some((item: any) => parseFloat(item.balance) > 0)
  );

  const selectedTokenData = availableTokens.find(token => token.symbol === selectedToken);

  // Get total balance for selected token
  const totalBalance = selectedTokenData ? parseFloat(selectedTokenData.balance) : 0;

  // Get all possible target chains
  const availableTargetChains = Object.entries(chains);

  const canSubmit = selectedToken && 
    targetChain && 
    transferAmount && 
    recipient && 
    parseFloat(transferAmount) > 0 && 
    parseFloat(transferAmount) <= totalBalance &&
    /^0x[a-fA-F0-9]{40}$/.test(recipient);

  // Reset dependent fields when selections change
  useEffect(() => {
    if (selectedToken) {
      setTransferAmount('');
    }
  }, [selectedToken]);

  useEffect(() => {
    if (targetChain) {
      setTransferAmount('');
    }
  }, [targetChain]);

  // Simulate transfer transaction
  useEffect(() => {
    if (canSubmit && sdk) {
      simulateTransfer();
    } else {
      setSimulation(null);
    }
  }, [selectedToken, targetChain, transferAmount, recipient, canSubmit]);

  const simulateTransfer = async () => {
    if (!sdk || !canSubmit) return;
    
    try {
      setIsSimulating(true);
      
      const simulationResult = await sdk.simulateTransfer({
        token: selectedToken,
        amount: transferAmount,
        chainId: parseInt(targetChain),
        recipient: recipient
      });
      
      setSimulation(simulationResult);
    } catch (error) {
      console.error('Simulation failed:', error);
      setSimulation(null);
    } finally {
      setIsSimulating(false);
    }
  };

  const handleTransfer = async () => {
    if (!canSubmit || !sdk) return;
    
    try {
      setIsTransferring(true);
      setError(null);
      
      const result = await sdk.transfer({
        token: selectedToken,
        amount: transferAmount,
        chainId: parseInt(targetChain),
        recipient: recipient
      });
      
      console.log('Transfer transaction result:', result);
      
      // Reset form on success
      setSelectedToken('');
      setTargetChain('');
      setTransferAmount('');
      setRecipient('');
      setSimulation(null);
      
      // Refresh balances after a delay
      setTimeout(() => {
        refreshBalances();
      }, 3000);
      
    } catch (error) {
      console.error('Transfer failed:', error);
      setError(error instanceof Error ? error.message : 'Transfer transaction failed');
    } finally {
      setIsTransferring(false);
    }
  };

  const setMaxAmount = () => {
    if (selectedTokenData) {
      setTransferAmount(selectedTokenData.balance);
    }
  };

  const isValidAddress = (address: string) => {
    return /^0x[a-fA-F0-9]{40}$/.test(address);
  };

  if (!isInitialized) {
    return (
      <div className="text-center py-8">
        <p className="text-slate-500">Connect your wallet to start transferring</p>
      </div>
    );
  }

  if (availableTokens.length === 0) {
    return (
      <div className="text-center py-8">
        <AlertCircle className="w-8 h-8 text-yellow-500 mx-auto mb-3" />
        <p className="text-slate-600 mb-2">No tokens available for transfer</p>
        <p className="text-sm text-slate-500">Make sure you have tokens on supported networks</p>
      </div>
    );
  }

  return (
    <div className="space-y-6">
      {/* Header */}
      <div className="flex justify-between items-center">
        <div>
          <h2 className="text-2xl font-bold text-slate-900">Transfer Assets</h2>
          <p className="text-slate-600">Send tokens to any address across chains</p>
        </div>
        <button
          onClick={refreshBalances}
          className="flex items-center space-x-2 bg-slate-100 hover:bg-slate-200 text-slate-700 px-3 py-2 rounded-lg transition-colors"
        >
          <RefreshCw className="w-4 h-4" />
          <span>Refresh</span>
        </button>
      </div>

      <div className="bg-white rounded-xl border border-slate-200 p-6 space-y-6">
        {/* Token Selection */}
        <div>
          <label className="block text-sm font-medium text-slate-700 mb-2">
            Select Token to Transfer
          </label>
          <div className="relative">
            <select
              value={selectedToken}
              onChange={(e) => setSelectedToken(e.target.value)}
              className="w-full p-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
            >
              <option value="">Choose a token...</option>
              {availableTokens.map((token, index) => (
                <option key={index} value={token.symbol}>
                  {token.symbol} - {token.balance} total across chains
                </option>
              ))}
            </select>
            <ChevronDown className="absolute right-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-slate-400 pointer-events-none" />
          </div>
        </div>

        {/* Target Chain Selection */}
        {selectedToken && (
          <div>
            <label className="block text-sm font-medium text-slate-700 mb-2">
              Transfer To (Destination Chain)
            </label>
            <div className="relative">
              <select
                value={targetChain}
                onChange={(e) => setTargetChain(e.target.value)}
                className="w-full p-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
              >
                <option value="">Choose destination chain...</option>
                {availableTargetChains.map(([chainId, chain]) => (
                  <option key={chainId} value={chainId}>
                    {chain.icon} {chain.name}
                  </option>
                ))}
              </select>
              <ChevronDown className="absolute right-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-slate-400 pointer-events-none" />
            </div>
          </div>
        )}

        {/* Recipient Address */}
        {selectedToken && targetChain && (
          <div>
            <label className="block text-sm font-medium text-slate-700 mb-2">
              Recipient Address
            </label>
            <input
              type="text"
              placeholder="0x..."
              value={recipient}
              onChange={(e) => setRecipient(e.target.value)}
              className={`w-full p-3 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent ${
                recipient && !isValidAddress(recipient) ? 'border-red-300' : 'border-slate-300'
              }`}
            />
            {recipient && !isValidAddress(recipient) && (
              <p className="text-red-500 text-sm mt-1">Invalid Ethereum address</p>
            )}
          </div>
        )}

        {/* Amount Input */}
        {selectedToken && targetChain && recipient && (
          <div>
            <div className="flex justify-between items-center mb-2">
              <label className="text-sm font-medium text-slate-700">
                Amount to Transfer
              </label>
              {selectedTokenData && (
                <button
                  onClick={setMaxAmount}
                  className="text-sm text-purple-600 hover:text-purple-700 font-medium"
                >
                  Max: {parseFloat(selectedTokenData.balance).toFixed(4)} {selectedToken}
                </button>
              )}
            </div>
            <input
              type="number"
              step="any"
              placeholder="0.0"
              value={transferAmount}
              onChange={(e) => setTransferAmount(e.target.value)}
              className="w-full p-3 border border-slate-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
            />
          </div>
        )}

        {/* Transfer Preview */}
        {selectedToken && targetChain && recipient && transferAmount && (
          <div className="bg-gradient-to-r from-purple-50 to-pink-50 rounded-lg p-4 border border-purple-200">
            <div className="flex items-center justify-between mb-3">
              <h4 className="font-medium text-slate-900">Transfer Summary</h4>
              <ArrowRight className="w-5 h-5 text-purple-500" />
            </div>
            <div className="space-y-2 text-sm">
              <div className="flex justify-between">
                <span className="text-slate-600">Amount:</span>
                <span className="font-medium">{transferAmount} {selectedToken}</span>
              </div>
              <div className="flex justify-between">
                <span className="text-slate-600">To:</span>
                <span className="font-medium">
                  {chains[parseInt(targetChain) as keyof typeof chains]?.icon} {chains[parseInt(targetChain) as keyof typeof chains]?.name}
                </span>
              </div>
              <div className="flex justify-between">
                <span className="text-slate-600">Recipient:</span>
                <span className="font-mono text-xs">{recipient.slice(0, 6)}...{recipient.slice(-4)}</span>
              </div>
            </div>
          </div>
        )}

        {/* Simulation Results */}
        {isSimulating && (
          <div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
            <div className="flex items-center space-x-2">
              <div className="w-4 h-4 border-2 border-yellow-400 border-t-transparent rounded-full animate-spin"></div>
              <span className="text-yellow-800">Simulating transaction...</span>
            </div>
          </div>
        )}

        {simulation && (
          <div className="bg-green-50 border border-green-200 rounded-lg p-4">
            <div className="flex items-start space-x-2">
              <Info className="w-5 h-5 text-green-600 mt-0.5" />
              <div>
                <p className="text-green-800 font-medium">Transfer Ready</p>
                <p className="text-green-700 text-sm">
                  Transaction simulated successfully. Ready to send your tokens!
                </p>
              </div>
            </div>
          </div>
        )}

        {/* Error Display */}
        {error && (
          <div className="bg-red-50 border border-red-200 rounded-lg p-4">
            <div className="flex items-start space-x-2">
              <AlertCircle className="w-5 h-5 text-red-600 mt-0.5" />
              <div>
                <p className="text-red-800 font-medium">Transfer Failed</p>
                <p className="text-red-700 text-sm">{error}</p>
              </div>
            </div>
          </div>
        )}

        {/* Submit Button */}
        <button
          onClick={handleTransfer}
          disabled={!canSubmit || isTransferring || isSimulating}
          className={`
            w-full py-3 px-4 rounded-lg font-medium transition-all duration-200
            ${canSubmit && !isTransferring && !isSimulating
              ? 'bg-purple-600 hover:bg-purple-700 text-white shadow-lg hover:shadow-xl'
              : 'bg-slate-300 text-slate-500 cursor-not-allowed'
            }
          `}
        >
          {isTransferring ? (
            <div className="flex items-center justify-center space-x-2">
              <div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
              <span>Sending Transfer...</span>
            </div>
          ) : (
            <div className="flex items-center justify-center space-x-2">
              <Send className="w-4 h-4" />
              <span>Send Transfer</span>
            </div>
          )}
        </button>
      </div>
    </div>
  );
}

Transfer Form Features

  • Smart Validation: Progressive form validation with real-time feedback
  • Chain Selection: Destination chain picker with chain icons
  • Address Validation: Real-time validation of recipient addresses
  • Real-time Simulation: Preview transfer costs before execution
  • Max Amount Button: One-click to use maximum available balance
  • Visual Feedback: Clear transfer summary with chain icons

Step 4: Update the Main Page

Replace your main page (app/page.tsx) with:

'use client';

import { WalletConnection } from '@/components/WalletConnection';
import { UnifiedBalances } from '@/components/UnifiedBalances';
import { TransferForm } from '@/components/TransferForm';
import { TransferHistory } from '@/components/TransferHistory';
import { useAccount } from 'wagmi';
import { useNexus } from '@/components/NexusProvider';
import { Globe, Zap, Shield, ArrowRight, Send } from 'lucide-react';
import { useState } from 'react';

export default function Home() {
  const { isConnected } = useAccount();
  const { isInitialized, isLoading } = useNexus();
  const [activeTab, setActiveTab] = useState<'portfolio' | 'transfer'>('portfolio');

  return (
    <main className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50">
      <div className="container mx-auto px-4 py-8">
        
        {/* Header */}
        <div className="text-center mb-12">
          <div className="inline-flex items-center space-x-2 mb-4">
            <Globe className="w-8 h-8 text-blue-600" />
            <h1 className="text-4xl font-bold text-slate-900">
              Nexus SDK Tutorial
            </h1>
          </div>
          <div className="mb-4 flex justify-center gap-2">
            <span className="bg-blue-100 text-blue-800 text-sm font-medium px-3 py-1 rounded-full">
              Part 1: Portfolio View
            </span>
            <span className="bg-purple-100 text-purple-800 text-sm font-medium px-3 py-1 rounded-full">
              Part 2: Cross-Chain Transfers
            </span>
          </div>
          <p className="text-xl text-slate-600 max-w-2xl mx-auto">
            Experience unified Web3 interactions with portfolio management and
            seamless cross-chain transfers in one interface
          </p>
        </div>

        {/* Features */}
        <div className="grid md:grid-cols-3 gap-6 mb-12">
          <div className="bg-white/60 backdrop-blur-sm rounded-xl p-6 border border-white/20">
            <Zap className="w-8 h-8 text-yellow-500 mb-3" />
            <h3 className="font-semibold text-slate-900 mb-2">Lightning Fast</h3>
            <p className="text-slate-600 text-sm">
              Instant balance updates and transfer simulations across all supported chains
            </p>
          </div>
          
          <div className="bg-white/60 backdrop-blur-sm rounded-xl p-6 border border-white/20">
            <Shield className="w-8 h-8 text-green-500 mb-3" />
            <h3 className="font-semibold text-slate-900 mb-2">Secure by Design</h3>
            <p className="text-slate-600 text-sm">
              Built-in security with smart allowance management and transaction previews
            </p>
          </div>
          
          <div className="bg-white/60 backdrop-blur-sm rounded-xl p-6 border border-white/20">
            <Globe className="w-8 h-8 text-blue-500 mb-3" />
            <h3 className="font-semibold text-slate-900 mb-2">Testnet Ready</h3>
            <p className="text-slate-600 text-sm">
              Safe development environment using Sepolia, Base Sepolia, and more
            </p>
          </div>
        </div>

        {/* Wallet Connection */}
        <div className="flex justify-center mb-8">
          <WalletConnection />
        </div>

        {/* Main Content */}
        {isConnected && isInitialized ? (
          <div className="max-w-6xl mx-auto">
            {/* Tab Navigation */}
            <div className="flex justify-center mb-8">
              <div className="bg-white rounded-lg p-1 shadow-sm border border-slate-200">
                <button
                  onClick={() => setActiveTab('portfolio')}
                  className={`px-6 py-2 rounded-md font-medium transition-all duration-200 ${
                    activeTab === 'portfolio'
                      ? 'bg-blue-500 text-white shadow-sm'
                      : 'text-slate-600 hover:text-slate-900'
                  }`}
                >
                  <div className="flex items-center space-x-2">
                    <Globe className="w-4 h-4" />
                    <span>Portfolio View</span>
                  </div>
                </button>
                <button
                  onClick={() => setActiveTab('transfer')}
                  className={`px-6 py-2 rounded-md font-medium transition-all duration-200 ${
                    activeTab === 'transfer'
                      ? 'bg-purple-500 text-white shadow-sm'
                      : 'text-slate-600 hover:text-slate-900'
                  }`}
                >
                  <div className="flex items-center space-x-2">
                    <Send className="w-4 h-4" />
                    <span>Cross-Chain Transfer</span>
                  </div>
                </button>
              </div>
            </div>

            {/* Content Based on Active Tab */}
            {activeTab === 'portfolio' ? (
              <div className="space-y-8">
                {/* Unified Balances */}
                <UnifiedBalances />
                
                {/* Quick Transfer CTA */}
                <div className="bg-gradient-to-r from-purple-50 to-pink-50 rounded-xl p-6 border border-purple-200">
                  <div className="flex items-center justify-between">
                    <div>
                      <h3 className="font-semibold text-slate-900 mb-1">
                        Ready to Send Tokens?
                      </h3>
                      <p className="text-sm text-slate-600">
                        Send your assets directly to any address on any chain with one click
                      </p>
                    </div>
                    <button
                      onClick={() => setActiveTab('transfer')}
                      className="flex items-center space-x-2 bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg transition-colors"
                    >
                      <Send className="w-4 h-4" />
                      <span>Send Tokens</span>
                      <ArrowRight className="w-4 h-4" />
                    </button>
                  </div>
                </div>
              </div>
            ) : (
              <div className="space-y-8">
                {/* Transfer History */}
                <TransferHistory />
                
                {/* Transfer Form */}
                <TransferForm />
                
                {/* Current Balances Reference */}
                <div className="bg-blue-50 rounded-xl p-6 border border-blue-200">
                  <div className="flex items-center justify-between">
                    <div>
                      <h3 className="font-semibold text-slate-900 mb-1">
                        Need to Check Your Balances?
                      </h3>
                      <p className="text-sm text-slate-600">
                        View your complete portfolio across all chains
                      </p>
                    </div>
                    <button
                      onClick={() => setActiveTab('portfolio')}
                      className="flex items-center space-x-2 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-colors"
                    >
                      <Globe className="w-4 h-4" />
                      <span>View Portfolio</span>
                    </button>
                  </div>
                </div>
              </div>
            )}
          </div>
        ) : isConnected ? (
          <div className="text-center py-12">
            <div className="max-w-md mx-auto">
              <h2 className="text-2xl font-bold text-slate-900 mb-4">
                Setting Up Your Multi-Chain Experience
              </h2>
              <p className="text-slate-600 mb-8">
                <strong>Testnet Tutorial:</strong> This demo safely uses testnet tokens
              </p>
              <ul className="text-sm text-slate-500 list-disc list-inside space-y-1">
                <li>Sepolia (Ethereum testnet)</li>
                <li>Base Sepolia (Base testnet)</li>
                <li>Polygon Amoy, Arbitrum Sepolia, Optimism Sepolia</li>
              </ul>
            </div>
          </div>
        ) : (
          <div className="text-center py-12">
            <div className="max-w-md mx-auto">
              <h2 className="text-2xl font-bold text-slate-900 mb-4">
                Ready to Experience Chain Abstraction?
              </h2>
              <p className="text-slate-600 mb-8">
                Connect your wallet to see how the Nexus SDK unifies your Web3 experience
              </p>
              <div className="bg-white/60 backdrop-blur-sm rounded-xl p-6 border border-white/20">
                <p className="text-sm text-slate-500 mb-2">
                  <strong>Testnet Tutorial:</strong> This demo safely uses testnet tokens
                </p>
                <ul className="text-sm text-slate-500 list-disc list-inside space-y-1">
                  <li>Sepolia (Ethereum testnet)</li>
                  <li>Base Sepolia (Base testnet)</li>
                  <li>Polygon Amoy, Arbitrum Sepolia, Optimism Sepolia</li>
                </ul>
              </div>
            </div>
          </div>
        )}

        {/* Loading State */}
        {isConnected && isLoading && !isInitialized && (
          <div className="fixed inset-0 bg-black/20 backdrop-blur-sm flex items-center justify-center z-50">
            <div className="bg-white rounded-xl p-6 shadow-xl">
              <div className="flex items-center space-x-3">
                <div className="animate-spin rounded-full h-6 w-6 border-2 border-purple-600 border-t-transparent"></div>
                <span className="text-slate-700">Initializing Nexus SDK...</span>
              </div>
            </div>
          </div>
        )}

        {/* Tutorial Series Navigation */}
        <div className="max-w-4xl mx-auto mt-16">
          <div className="bg-white/80 backdrop-blur-sm rounded-xl p-8 border border-white/20">
            <h3 className="text-xl font-bold text-slate-900 mb-4">What's Next?</h3>
            <div className="grid md:grid-cols-2 gap-4">
              <div className="p-4 bg-green-50 rounded-lg border border-green-200">
                <div className="flex items-center space-x-2 mb-2">
                  <span className="bg-green-500 text-white text-xs font-bold px-2 py-1 rounded">PART 3</span>
                  <span className="text-sm font-medium text-slate-900">Direct Transfers</span>
                </div>
                <p className="text-sm text-slate-600">
                  Send tokens directly across chains to any address with advanced routing
                </p>
              </div>
              
              <div className="p-4 bg-orange-50 rounded-lg border border-orange-200">
                <div className="flex items-center space-x-2 mb-2">
                  <span className="bg-orange-500 text-white text-xs font-bold px-2 py-1 rounded">PART 4</span>
                  <span className="text-sm font-medium text-slate-900">Production Ready</span>
                </div>
                <p className="text-sm text-slate-600">
                  Deploy to mainnet with advanced monitoring, error handling, and optimization
                </p>
              </div>
            </div>
          </div>
        </div>
      </div>
    </main>
  );
}

Step 5: Test Your Transfer Implementation

Now let's test everything to make sure it works perfectly.

1. Start the Development Server

npm run dev

2. Connect your wallet

Ensure you have testnet tokens or get testnet ETH from faucets for testing

3. Testing Checklist

Visit http://localhost:3000 and verify:

βœ… Test the Portfolio Tab:

  • [ ] Verify balances display correctly
  • [ ] Check chain breakdown shows per-chain amounts
  • [ ] Click "Send Tokens" CTA to switch tabs

βœ… Test the Transfer Tab:

  • [ ] Select a token with available balance
  • [ ] Choose destination chain
  • [ ] Enter a valid recipient address
  • [ ] Enter amount and verify Max button works
  • [ ] Check transfer preview shows correct details
  • [ ] Watch for simulation success/failure

βœ… Execute a Test Transfer:

  • [ ] Start with a small amount (0.01 tokens)
  • [ ] Confirm transaction in MetaMask
  • [ ] Monitor transaction history
  • [ ] Check explorer link works
  • [ ] Verify balances update after completion

Common Issues & Solutions

Transfer Simulation Fails:

  • Check you have sufficient balance
  • Ensure transfer amount is greater than 0
  • Verify recipient address is valid Ethereum address

Transaction History Not Showing:

  • Check browser console for localStorage errors
  • Try clearing localStorage and testing again

Balances Not Updating:

  • Wait 30-60 seconds for blockchain confirmation
  • Click the Refresh button manually
  • Check if transaction was actually confirmed

Understanding the Transfer Flow

Step-by-Step Transfer Process

  1. Token Selection β†’ User picks from available tokens with balance
  2. Destination Chain β†’ All supported chains available
  3. Recipient Address β†’ Real-time validation for Ethereum addresses
  4. Amount Input β†’ With max amount helper and validation
  5. Simulation β†’ Real-time preview of transfer cost and feasibility
  6. Execution β†’ SDK handles transfer transaction automatically
  7. History Tracking β†’ Transaction saved to localStorage
  8. Balance Update β†’ Automatic refresh after successful transfer

How the Nexus SDK Simplifies Transfers

Traditional Cross-Chain Transfers:

  • Find compatible bridge protocol
  • Navigate to bridge website
  • Connect wallet on both chains
  • Bridge tokens to destination chain
  • Send tokens to recipient
  • Wait for multiple confirmations

With Nexus SDK:

  • Select token and destination chain in one interface
  • Automatic route optimization
  • Single transaction for entire transfer
  • Built-in confirmation tracking
  • Unified balance updates

Security & Best Practices

Production Considerations

Approval Management:

// In production, implement proper approval modals
nexusSDK.setOnAllowanceHook(async ({ allow, deny, sources }) => {
  // Show approval modal to user
  const userApproval = await showApprovalModal(sources);
  if (userApproval) {
    allow(userApproval.allowances);
  } else {
    deny();
  }
});

Transaction Previews:

// Show detailed transaction preview before execution
nexusSDK.setOnIntentHook(({ intent, allow, deny }) => {
  // Display transaction details, costs, risks
  const userConfirmed = await showTransactionPreview(intent);
  if (userConfirmed) {
    allow();
  } else {
    deny();
  }
});

Error Handling:

  • Always validate user inputs before simulation
  • Handle network-specific errors gracefully
  • Provide clear error messages for failed transactions
  • Implement retry mechanisms for failed operations

What You've Accomplished

Congratulations! You've successfully built a complete cross-chain transfer interface.

Your app now includes:

  • βœ… Unified Portfolio View - See all your assets across multiple chains
  • βœ… Cross-Chain Transfers - Send assets directly to any address on any chain
  • βœ… Real-time Simulation - Preview transfer costs before execution
  • βœ… Transaction History - Track all transfer operations with status
  • βœ… Explorer Integration - Direct links to blockchain explorers
  • βœ… Seamless UX - Tab navigation and cross-references between features

Key Technical Achievements

  • Chain Abstraction: Users don't need to understand complex bridge protocols
  • Unified Interface: Portfolio and transfer functionality in one clean interface
  • Smart Validation: Progressive form validation with real-time feedback
  • Transaction Tracking: Persistent history with status monitoring
  • Testnet Safety: Safe development environment with testnet tokens

Next Steps: Part 3 Preview

In Part 3: Advanced Transfer Features, you'll add:

  • Batch Transfers - Send to multiple addresses in one transaction
  • Address Book - Save and manage frequently used addresses
  • Gas Optimization - Advanced routing for optimal transfer costs
  • Scheduled Transfers - Set up future transfers with time delays

Stay tuned for Part 3 where we'll build the ultimate cross-chain transfer experience!

Advanced Customization Tips

Custom Transfer Themes

// Customize transfer UI colors
const transferTheme = {
  primary: '#8b5cf6', // Purple
  secondary: '#ec4899', // Pink
  success: '#10b981', // Green
  warning: '#f59e0b', // Yellow
  error: '#ef4444' // Red
};

Add Transfer Analytics

// Track transfer usage for analytics
const trackTransferEvent = (eventName: string, properties: any) => {
  console.log('Transfer Event:', eventName, properties);
  // Add your analytics service here
};

// In transfer function:
trackTransferEvent('transfer_initiated', {
  token: params.token,
  amount: params.amount,
  toChain: params.chainId,
  recipient: params.recipient
});

Custom Chain Support

// Add new testnet chains
const customChains = {
  999999: { 
    name: 'My Custom Testnet', 
    shortName: 'CUSTOM', 
    icon: '⚑',
    explorer: 'https://custom-explorer.com'
  }
};

Additional Resources

Troubleshooting Guide

Common Error Messages

"SDK not initialized"
Solution: Ensure wallet is connected before attempting transfer operations

"Insufficient balance"
Solution: Check you have enough tokens, including gas fees

"Invalid recipient address"
Solution: Verify recipient address is valid Ethereum format (0x...)

"Simulation failed"
Solution: Check transfer route exists for the selected token and chains

"Transaction reverted"
Solution: Check gas fees, token allowances, and network connectivity

Performance Optimization

Slow Balance Updates:

// Implement balance caching
const cachedBalances = useMemo(() => {
  return balances.filter(balance => parseFloat(balance.balance) > 0);
}, [balances]);

Optimize Simulations:

// Debounce simulation calls
const debouncedSimulate = useMemo(
  () => debounce(simulateTransfer, 500),
  [simulateTransfer]
);

Debug Mode

Enable detailed logging for development:

// Add to NexusProvider
const [debugMode, setDebugMode] = useState(process.env.NODE_ENV === 'development');

if (debugMode) {
  console.log('Transfer params:', params);
  console.log('Simulation result:', simulation);
  console.log('Transaction history:', transactions);
}

Learning Objectives Completed

By finishing Part 2, you've mastered:

Technical Skills

  • βœ… Cross-chain transfer integration with React
  • βœ… Real-time transaction simulation
  • βœ… Persistent storage with localStorage
  • βœ… Complex form validation and UX flows
  • βœ… Error handling for blockchain operations

Web3 Concepts

  • βœ… Chain abstraction principles
  • βœ… Transfer protocols and routing
  • βœ… Transaction lifecycle management
  • βœ… Multi-chain asset management
  • βœ… Gas optimization strategies

User Experience Design

  • βœ… Progressive disclosure in forms
  • βœ… Tab-based navigation patterns
  • βœ… Loading states and error feedback
  • βœ… Transaction status communication
  • βœ… Cross-component interaction design

🌟 Bonus Challenges

Ready for more? Try these advanced features:

  1. Transfer Fee Calculator: Show estimated fees before transferring
  2. Slippage Protection: Add slippage tolerance settings
  3. Batch Transfers: Send to multiple addresses in one operation
  4. Transfer Scheduling: Schedule transfers for optimal gas prices
  5. Cross-Chain Swaps: Combine transfers with token swaps

πŸŽ‰ Congratulations! You've mastered cross-chain transfers with the Nexus SDK. Your users can now send assets seamlessly between blockchains with a professional, secure interface.

Ready for Part 3? We'll add advanced transfer features, letting users batch transfers, manage address books, and optimize gas costs across multiple chains!