11import  {  useCallback ,  useMemo  }  from  "react" ; 
2- import  type  {  ThirdwebClient  }  from  "thirdweb" ; 
2+ import  { 
3+   NATIVE_TOKEN_ADDRESS , 
4+   type  ThirdwebClient , 
5+   getAddress , 
6+ }  from  "thirdweb" ; 
37import  {  shortenAddress  }  from  "thirdweb/utils" ; 
8+ import  {  useAllChainsData  }  from  "../../../hooks/chains/allChains" ; 
49import  {  useTokensData  }  from  "../../../hooks/tokens/tokens" ; 
510import  {  replaceIpfsUrl  }  from  "../../../lib/sdk" ; 
611import  {  fallbackChainIcon  }  from  "../../../utils/chain-icons" ; 
12+ import  type  {  TokenMetadata  }  from  "../../api/universal-bridge/tokens" ; 
713import  {  cn  }  from  "../../lib/utils" ; 
814import  {  Badge  }  from  "../ui/badge" ; 
915import  {  Img  }  from  "./Img" ; 
1016import  {  SelectWithSearch  }  from  "./select-with-search" ; 
1117
1218type  Option  =  {  label : string ;  value : string  } ; 
1319
20+ const  checksummedNativeTokenAddress  =  getAddress ( NATIVE_TOKEN_ADDRESS ) ; 
21+ 
1422export  function  TokenSelector ( props : { 
15-   tokenAddress :  string  |  undefined ; 
16-   onChange : ( tokenAddress :  string )  =>  void ; 
23+   selectedToken :  {   chainId :  number ;   address :  string   }  |  undefined ; 
24+   onChange : ( token :  TokenMetadata )  =>  void ; 
1725  className ?: string ; 
1826  popoverContentClassName ?: string ; 
1927  chainId ?: number ; 
2028  side ?: "left"  |  "right"  |  "top"  |  "bottom" ; 
21-   disableChainId ?: boolean ; 
29+   disableAddress ?: boolean ; 
2230  align ?: "center"  |  "start"  |  "end" ; 
2331  placeholder ?: string ; 
2432  client : ThirdwebClient ; 
2533  disabled ?: boolean ; 
2634  enabled ?: boolean ; 
35+   showCheck : boolean ; 
36+   addNativeTokenIfMissing : boolean ; 
2737} )  { 
28-   const  {  tokens ,  isFetching  }  =  useTokensData ( { 
38+   const  tokensQuery  =  useTokensData ( { 
2939    chainId : props . chainId , 
3040    enabled : props . enabled , 
3141  } ) ; 
3242
43+   const  {  idToChain }  =  useAllChainsData ( ) ; 
44+ 
45+   const  tokens  =  useMemo ( ( )  =>  { 
46+     if  ( ! tokensQuery . data )  { 
47+       return  [ ] ; 
48+     } 
49+ 
50+     if  ( props . addNativeTokenIfMissing )  { 
51+       const  hasNativeToken  =  tokensQuery . data . some ( 
52+         ( token )  =>  token . address  ===  checksummedNativeTokenAddress , 
53+       ) ; 
54+ 
55+       if  ( ! hasNativeToken  &&  props . chainId )  { 
56+         return  [ 
57+           { 
58+             name :
59+               idToChain . get ( props . chainId ) ?. nativeCurrency . name  ?? 
60+               "Native Token" , 
61+             symbol :
62+               idToChain . get ( props . chainId ) ?. nativeCurrency . symbol  ??  "ETH" , 
63+             decimals : 18 , 
64+             chainId : props . chainId , 
65+             address : checksummedNativeTokenAddress , 
66+           }  satisfies  TokenMetadata , 
67+           ...tokensQuery . data , 
68+         ] ; 
69+       } 
70+     } 
71+     return  tokensQuery . data ; 
72+   } ,  [ 
73+     tokensQuery . data , 
74+     props . chainId , 
75+     props . addNativeTokenIfMissing , 
76+     idToChain , 
77+   ] ) ; 
78+ 
79+   const  addressChainToToken  =  useMemo ( ( )  =>  { 
80+     const  value  =  new  Map < string ,  TokenMetadata > ( ) ; 
81+     for  ( const  token  of  tokens )  { 
82+       value . set ( `${ token . chainId }  :${ token . address }  ` ,  token ) ; 
83+     } 
84+     return  value ; 
85+   } ,  [ tokens ] ) ; 
86+ 
3387  const  options  =  useMemo ( ( )  =>  { 
34-     return  tokens . allTokens . map ( ( token )  =>  { 
35-       return  { 
36-         label : token . symbol , 
37-         value : `${ token . chainId }  :${ token . address }  ` , 
38-       } ; 
39-     } ) ; 
40-   } ,  [ tokens . allTokens ] ) ; 
88+     return  ( 
89+       tokens . map ( ( token )  =>  { 
90+         return  { 
91+           label : token . symbol , 
92+           value : `${ token . chainId }  :${ token . address }  ` , 
93+         } ; 
94+       } )  ||  [ ] 
95+     ) ; 
96+   } ,  [ tokens ] ) ; 
4197
4298  const  searchFn  =  useCallback ( 
4399    ( option : Option ,  searchValue : string )  =>  { 
44-       const  token  =  tokens . addressChainToToken . get ( option . value ) ; 
100+       const  token  =  addressChainToToken . get ( option . value ) ; 
45101      if  ( ! token )  { 
46102        return  false ; 
47103      } 
@@ -55,12 +111,12 @@ export function TokenSelector(props: {
55111        token . address . toLowerCase ( ) . includes ( searchValue . toLowerCase ( ) ) 
56112      ) ; 
57113    } , 
58-     [ tokens ] , 
114+     [ addressChainToToken ] , 
59115  ) ; 
60116
61117  const  renderOption  =  useCallback ( 
62118    ( option : Option )  =>  { 
63-       const  token  =  tokens . addressChainToToken . get ( option . value ) ; 
119+       const  token  =  addressChainToToken . get ( option . value ) ; 
64120      if  ( ! token )  { 
65121        return  option . label ; 
66122      } 
@@ -87,36 +143,46 @@ export function TokenSelector(props: {
87143            { token . symbol } 
88144          </ span > 
89145
90-           { ! props . disableChainId  &&  ( 
91-             < Badge  variant = "outline"  className = "gap-2 max-sm:hidden" > 
146+           { ! props . disableAddress  &&  ( 
147+             < Badge  variant = "outline"  className = "gap-2 py-1  max-sm:hidden" > 
92148              < span  className = "text-muted-foreground" > Address</ span > 
93149              { shortenAddress ( token . address ,  4 ) } 
94150            </ Badge > 
95151          ) } 
96152        </ div > 
97153      ) ; 
98154    } , 
99-     [ tokens ,  props . disableChainId ,  props . client ] , 
155+     [ addressChainToToken ,  props . disableAddress ,  props . client ] , 
100156  ) ; 
101157
158+   const  selectedValue  =  props . selectedToken 
159+     ? `${ props . selectedToken . chainId }  :${ props . selectedToken . address }  ` 
160+     : undefined ; 
161+ 
102162  return  ( 
103163    < SelectWithSearch 
104164      searchPlaceholder = "Search by name or symbol" 
105-       value = { props . tokenAddress } 
165+       value = { selectedValue } 
106166      options = { options } 
107167      onValueChange = { ( tokenAddress )  =>  { 
108-         props . onChange ( tokenAddress ) ; 
168+         const  token  =  addressChainToToken . get ( tokenAddress ) ; 
169+         if  ( ! token )  { 
170+           return ; 
171+         } 
172+         props . onChange ( token ) ; 
109173      } } 
110174      closeOnSelect = { true } 
111-       showCheck = { false } 
175+       showCheck = { props . showCheck } 
112176      placeholder = { 
113-         isFetching  ? "Loading Tokens..."  : props . placeholder  ||  "Select Token" 
177+         tokensQuery . isPending 
178+           ? "Loading Tokens..." 
179+           : props . placeholder  ||  "Select Token" 
114180      } 
115181      overrideSearchFn = { searchFn } 
116182      renderOption = { renderOption } 
117183      className = { props . className } 
118184      popoverContentClassName = { props . popoverContentClassName } 
119-       disabled = { isFetching  ||  props . disabled } 
185+       disabled = { tokensQuery . isPending  ||  props . disabled } 
120186      side = { props . side } 
121187      align = { props . align } 
122188    /> 
0 commit comments