@@ -3,14 +3,21 @@ import { isProd } from "@/constants/env-utils";
3
3
import { useQuery } from "@tanstack/react-query" ;
4
4
import { XIcon } from "lucide-react" ;
5
5
import Link from "next/link" ;
6
- import { type ThirdwebClient , defineChain , toTokens } from "thirdweb" ;
6
+ import {
7
+ NATIVE_TOKEN_ADDRESS ,
8
+ type ThirdwebClient ,
9
+ defineChain ,
10
+ getAddress ,
11
+ toTokens ,
12
+ } from "thirdweb" ;
7
13
import {
8
14
Blobbie ,
9
15
TokenIcon ,
10
16
TokenProvider ,
11
17
useActiveAccount ,
12
18
useActiveWalletChain ,
13
19
} from "thirdweb/react" ;
20
+ import { getWalletBalance } from "thirdweb/wallets" ;
14
21
import { ChainIconClient } from "../../../../../components/icons/ChainIcon" ;
15
22
import { useAllChainsData } from "../../../../../hooks/chains/allChains" ;
16
23
import { nebulaAppThirdwebClient } from "../../utils/nebulaThirdwebClient" ;
@@ -45,7 +52,7 @@ export function AssetsSectionUI(props: {
45
52
{ ! props . isPending &&
46
53
props . data . map ( ( asset ) => (
47
54
< AssetItem
48
- key = { asset . token_address }
55
+ key = { ` ${ asset . chain_id } - ${ asset . token_address } ` }
49
56
asset = { asset }
50
57
client = { props . client }
51
58
/>
@@ -78,6 +85,8 @@ function AssetItem(props: {
78
85
} ) {
79
86
const { idToChain } = useAllChainsData ( ) ;
80
87
const chainMeta = idToChain . get ( props . asset . chain_id ) ;
88
+ const isNativeToken = props . asset . token_address === NATIVE_TOKEN_ADDRESS ;
89
+
81
90
return (
82
91
< TokenProvider
83
92
address = { props . asset . token_address }
@@ -88,7 +97,7 @@ function AssetItem(props: {
88
97
< div className = "relative flex h-[48px] items-center gap-2.5 rounded-lg px-2 py-1 hover:bg-accent" >
89
98
< div className = "relative" >
90
99
< TokenIcon
91
- className = "size-8 rounded-full"
100
+ className = "size-8 rounded-full border "
92
101
loadingComponent = {
93
102
< Blobbie
94
103
address = { props . asset . token_address }
@@ -102,25 +111,31 @@ function AssetItem(props: {
102
111
/>
103
112
}
104
113
/>
105
- < div className = "-right-0.5 -bottom-0.5 absolute rounded-full border bg-background p-0.5" >
106
- < ChainIconClient
107
- client = { props . client }
108
- className = "size-3.5"
109
- src = { chainMeta ?. icon ?. url || "" }
110
- />
111
- </ div >
114
+ { ! isNativeToken && (
115
+ < div className = "-right-0.5 -bottom-0.5 absolute rounded-full border bg-background p-0.5" >
116
+ < ChainIconClient
117
+ client = { props . client }
118
+ className = "size-3.5"
119
+ src = { chainMeta ?. icon ?. url || "" }
120
+ />
121
+ </ div >
122
+ ) }
112
123
</ div >
113
124
114
125
< div className = "flex min-w-0 flex-col text-sm" >
115
126
< Link
116
- href = { `https://thirdweb.com/${ props . asset . chain_id } /${ props . asset . token_address } ` }
127
+ href = {
128
+ isNativeToken
129
+ ? `https://thirdweb.com/${ props . asset . chain_id } `
130
+ : `https://thirdweb.com/${ props . asset . chain_id } /${ props . asset . token_address } `
131
+ }
117
132
target = "_blank"
118
133
className = "truncate font-medium before:absolute before:inset-0"
119
134
>
120
135
{ props . asset . name }
121
136
</ Link >
122
137
123
- < p className = "text-muted-foreground text-sm" >
138
+ < p className = "truncate text-muted-foreground text-sm" >
124
139
{ `${ toTokens ( BigInt ( props . asset . balance ) , props . asset . decimals ) } ${ props . asset . symbol } ` }
125
140
</ p >
126
141
</ div >
@@ -158,16 +173,85 @@ export function AssetsSection(props: {
158
173
data : AssetBalance [ ] ;
159
174
} ;
160
175
161
- return json . data ;
176
+ const tokensToShowOnTop = new Set (
177
+ [
178
+ // base
179
+ "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" , // usdc,
180
+ "0x0555E30da8f98308EdB960aa94C0Db47230d2B9c" , // wbtc
181
+ "0x4200000000000000000000000000000000000006" , // wrapped eth
182
+ // ethereum
183
+ "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" , // usdc
184
+ "0xdac17f958d2ee523a2206206994597c13d831ec7" , // usdt
185
+ "0xB8c77482e45F1F44dE1745F52C74426C631bDD52" , // bnb
186
+ "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" , // weth
187
+ "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599" , // wbtc
188
+ // optimism
189
+ "0x4200000000000000000000000000000000000042" , // op token
190
+ "0xdc6ff44d5d932cbd77b52e5612ba0529dc6226f1" , // world coin
191
+ "0x94b008aa00579c1307b0ef2c499ad98a8ce58e58" , // usdt
192
+ "0x0b2c639c533813f4aa9d7837caf62653d097ff85" , // usdc
193
+ "0x4200000000000000000000000000000000000006" , // wrapped eth
194
+ // polygon
195
+ "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619" , // weth
196
+ "0xc2132d05d31c914a87c6611c10748aeb04b58e8f" , // usdt
197
+ "0x3BA4c387f786bFEE076A58914F5Bd38d668B42c3" , // bnb
198
+ "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" , // usdc
199
+ "0x2791bca1f2de4661ed88a30c99a7a9449aa84174" , // usdc.e
200
+ ] . map ( ( x ) => getAddress ( x ) ) ,
201
+ ) ;
202
+
203
+ return json . data . sort ( ( a , b ) => {
204
+ if ( tokensToShowOnTop . has ( getAddress ( a . token_address ) ) ) {
205
+ return - 1 ;
206
+ }
207
+ if ( tokensToShowOnTop . has ( getAddress ( b . token_address ) ) ) {
208
+ return 1 ;
209
+ }
210
+ return 0 ;
211
+ } ) ;
162
212
} ,
163
213
enabled : ! ! account && ! ! activeChain ,
164
214
} ) ;
165
215
216
+ const nativeBalances = useQuery ( {
217
+ queryKey : [ "getWalletBalance" , account ?. address , activeChain ?. id ] ,
218
+ queryFn : async ( ) => {
219
+ if ( ! account || ! activeChain ) {
220
+ return [ ] ;
221
+ }
222
+
223
+ const chains = [ ...new Set ( [ 1 , 8453 , 10 , 137 , activeChain . id ] ) ] ;
224
+
225
+ const result = await Promise . allSettled (
226
+ chains . map ( ( chain ) =>
227
+ getWalletBalance ( {
228
+ // eslint-disable-next-line no-restricted-syntax
229
+ chain : defineChain ( chain ) ,
230
+ client : props . client ,
231
+ address : account . address ,
232
+ } ) ,
233
+ ) ,
234
+ ) ;
235
+
236
+ return result
237
+ . filter ( ( r ) => r . status === "fulfilled" )
238
+ . map ( ( r ) => ( {
239
+ chain_id : r . value . chainId ,
240
+ token_address : r . value . tokenAddress ,
241
+ balance : r . value . value . toString ( ) ,
242
+ name : r . value . name ,
243
+ symbol : r . value . symbol ,
244
+ decimals : r . value . decimals ,
245
+ } ) )
246
+ . filter ( ( x ) => x . balance !== "0" ) as AssetBalance [ ] ;
247
+ } ,
248
+ } ) ;
249
+
250
+ const isPending = assetsQuery . isPending || nativeBalances . isPending ;
251
+
252
+ const data = [ ...( nativeBalances . data ?? [ ] ) , ...( assetsQuery . data ?? [ ] ) ] ;
253
+
166
254
return (
167
- < AssetsSectionUI
168
- data = { assetsQuery . data ?? [ ] }
169
- isPending = { assetsQuery . isPending }
170
- client = { props . client }
171
- />
255
+ < AssetsSectionUI data = { data } isPending = { isPending } client = { props . client } />
172
256
) ;
173
257
}
0 commit comments