@@ -2587,6 +2587,189 @@ <h3 class="font-bold text-orange-800 mb-2 text-sm">🎯 Target Price</h3>
25872587 // Initialize
25882588 updateTargetPriceLabels ( ) ;
25892589 </ script >
2590+ <!--
2591+ =====================================================================
2592+ CUSTOM TOKEN METADATA FETCHING
2593+ Add this script tag RIGHT BEFORE the closing </body> tag
2594+ =====================================================================
2595+ -->
2596+ < script >
2597+ // ERC20 ABI for fetching token metadata
2598+ const TOKEN_METADATA_ABI = [
2599+ "function name() view returns (string)" ,
2600+ "function symbol() view returns (string)" ,
2601+ "function decimals() view returns (uint8)"
2602+ ] ;
2603+
2604+ // Function to validate and fetch token metadata
2605+ async function fetchTokenMetadata ( address ) {
2606+ try {
2607+ // Validate address format
2608+ if ( ! address || ! address . match ( / ^ 0 x [ a - f A - F 0 - 9 ] { 40 } $ / ) ) {
2609+ return { valid : false , error : 'Invalid address format' } ;
2610+ }
2611+
2612+ // Create provider (using ethers.js which is already loaded)
2613+ const provider = new ethers . providers . JsonRpcProvider ( 'https://mainnet.base.org' ) ;
2614+
2615+ // Create contract instance
2616+ const tokenContract = new ethers . Contract ( address , TOKEN_METADATA_ABI , provider ) ;
2617+
2618+ // Fetch name, symbol, and decimals in parallel
2619+ const [ name , symbol , decimals ] = await Promise . all ( [
2620+ tokenContract . name ( ) ,
2621+ tokenContract . symbol ( ) ,
2622+ tokenContract . decimals ( )
2623+ ] ) ;
2624+
2625+ return {
2626+ valid : true ,
2627+ address : address ,
2628+ name : name ,
2629+ symbol : symbol ,
2630+ decimals : decimals
2631+ } ;
2632+ } catch ( error ) {
2633+ console . error ( 'Token metadata fetch error:' , error ) ;
2634+ return {
2635+ valid : false ,
2636+ error : 'Unable to read token contract. Make sure this is a valid ERC20 token on Base.'
2637+ } ;
2638+ }
2639+ }
2640+
2641+ // Function to display token info below the input
2642+ function displayTokenInfo ( inputElement , metadata ) {
2643+ // Remove existing info display if any
2644+ const existingInfo = inputElement . parentElement . querySelector ( '.token-info-display' ) ;
2645+ if ( existingInfo ) {
2646+ existingInfo . remove ( ) ;
2647+ }
2648+
2649+ if ( metadata . valid ) {
2650+ // Create success message with token info
2651+ const infoDiv = document . createElement ( 'div' ) ;
2652+ infoDiv . className = 'token-info-display mt-2 p-2 bg-green-50 border border-green-300 rounded-lg text-xs animate-fadeIn' ;
2653+ infoDiv . innerHTML = `
2654+ <div class="flex items-center gap-2">
2655+ <span class="text-green-600 font-bold">✓ Token Recognized:</span>
2656+ <span class="font-bold text-gray-800">${ metadata . symbol } </span>
2657+ </div>
2658+ <div class="text-gray-600 mt-1">${ metadata . name } </div>
2659+ ` ;
2660+ inputElement . parentElement . appendChild ( infoDiv ) ;
2661+
2662+ // Store metadata for later use
2663+ inputElement . dataset . symbol = metadata . symbol ;
2664+ inputElement . dataset . name = metadata . name ;
2665+ inputElement . dataset . decimals = metadata . decimals ;
2666+ inputElement . dataset . valid = 'true' ;
2667+
2668+ } else {
2669+ // Create error message
2670+ const infoDiv = document . createElement ( 'div' ) ;
2671+ infoDiv . className = 'token-info-display mt-2 p-2 bg-red-50 border border-red-300 rounded-lg text-xs animate-fadeIn' ;
2672+ infoDiv . innerHTML = `
2673+ <div class="flex items-center gap-2">
2674+ <span class="text-red-600 font-bold">✗ Error:</span>
2675+ </div>
2676+ <div class="text-red-700 mt-1">${ metadata . error } </div>
2677+ ` ;
2678+ inputElement . parentElement . appendChild ( infoDiv ) ;
2679+
2680+ // Mark as invalid
2681+ inputElement . dataset . valid = 'false' ;
2682+ }
2683+ }
2684+
2685+ // Add debounce helper to avoid too many API calls
2686+ function debounce ( func , wait ) {
2687+ let timeout ;
2688+ return function executedFunction ( ...args ) {
2689+ const later = ( ) => {
2690+ clearTimeout ( timeout ) ;
2691+ func ( ...args ) ;
2692+ } ;
2693+ clearTimeout ( timeout ) ;
2694+ timeout = setTimeout ( later , wait ) ;
2695+ } ;
2696+ }
2697+
2698+ // Enhanced event listener for customTokenIn
2699+ const validateTokenIn = debounce ( async function ( ) {
2700+ const address = this . value . trim ( ) ;
2701+ if ( address && address . length === 42 ) { // Valid address length
2702+ // Show loading state
2703+ this . style . borderColor = '#fdba74' ; // orange
2704+ const loadingDiv = document . createElement ( 'div' ) ;
2705+ loadingDiv . className = 'token-info-display mt-2 text-xs text-orange-600 flex items-center gap-2' ;
2706+ loadingDiv . innerHTML = '<span class="spinner"></span> Fetching token info...' ;
2707+ this . parentElement . appendChild ( loadingDiv ) ;
2708+
2709+ const metadata = await fetchTokenMetadata ( address ) ;
2710+
2711+ // Remove loading message
2712+ const loadingElement = this . parentElement . querySelector ( '.token-info-display' ) ;
2713+ if ( loadingElement ) loadingElement . remove ( ) ;
2714+
2715+ // Display results
2716+ displayTokenInfo ( this , metadata ) ;
2717+
2718+ // Update border color based on validity
2719+ if ( metadata . valid ) {
2720+ this . style . borderColor = '#86efac' ; // green
2721+ } else {
2722+ this . style . borderColor = '#fca5a5' ; // red
2723+ }
2724+ }
2725+ } , 800 ) ; // Wait 800ms after user stops typing
2726+
2727+ document . getElementById ( 'customTokenIn' ) . addEventListener ( 'input' , validateTokenIn ) ;
2728+
2729+ // Enhanced event listener for customTokenOut
2730+ const validateTokenOut = debounce ( async function ( ) {
2731+ const address = this . value . trim ( ) ;
2732+ if ( address && address . length === 42 ) { // Valid address length
2733+ // Show loading state
2734+ this . style . borderColor = '#86efac' ; // green tint while loading
2735+ const loadingDiv = document . createElement ( 'div' ) ;
2736+ loadingDiv . className = 'token-info-display mt-2 text-xs text-green-600 flex items-center gap-2' ;
2737+ loadingDiv . innerHTML = '<span class="spinner"></span> Fetching token info...' ;
2738+ this . parentElement . appendChild ( loadingDiv ) ;
2739+
2740+ const metadata = await fetchTokenMetadata ( address ) ;
2741+
2742+ // Remove loading message
2743+ const loadingElement = this . parentElement . querySelector ( '.token-info-display' ) ;
2744+ if ( loadingElement ) loadingElement . remove ( ) ;
2745+
2746+ // Display results
2747+ displayTokenInfo ( this , metadata ) ;
2748+
2749+ // Update border color based on validity
2750+ if ( metadata . valid ) {
2751+ this . style . borderColor = '#86efac' ; // green
2752+ } else {
2753+ this . style . borderColor = '#fca5a5' ; // red
2754+ }
2755+ }
2756+ } , 800 ) ; // Wait 800ms after user stops typing
2757+
2758+ document . getElementById ( 'customTokenOut' ) . addEventListener ( 'input' , validateTokenOut ) ;
2759+
2760+ console . log ( '✅ Custom token metadata validation loaded' ) ;
2761+ </ script >
2762+
2763+ <!-- Add fade-in animation for smooth appearance -->
2764+ < style >
2765+ @keyframes fadeIn {
2766+ from { opacity : 0 ; transform : translateY (-10px ); }
2767+ to { opacity : 1 ; transform : translateY (0 ); }
2768+ }
2769+ .animate-fadeIn {
2770+ animation : fadeIn 0.3s ease-in;
2771+ }
2772+ </ style >
25902773</ body >
25912774</ html >
25922775
0 commit comments