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