11import { useEffect , useMemo } from "react" ;
2- import { useAccount , useSwitchChain } from "wagmi" ;
2+ import { Chain } from "viem" ;
3+ import { useSwitchChain } from "wagmi" ;
34import { twMerge } from "tailwind-merge" ;
45import * as Select from "@radix-ui/react-select" ;
6+ import { RelayChain } from "@reservoir0x/relay-sdk" ;
57import { useFrame } from "../../ui/FrameProvider" ;
68import { useTheme } from "../../useTheme" ;
79import { ChevronUpIcon } from "../../icons/ChevronUpIcon" ;
810import { ChevronDownIcon } from "../../icons/ChevronDownIcon" ;
911import { Input } from "../../ui/Input" ;
10- import { ChainBalance } from "./ChainBalance" ;
1112import { ChainIcon } from "./ChainIcon" ;
1213import { useRelay } from "./useRelay" ;
14+ import { useShowQueryError } from "../../errors/useShowQueryError" ;
15+ import { useChainBalances } from "./useChainBalances" ;
16+ import { Balance } from "../../ui/Balance" ;
17+ import { PendingIcon } from "../../icons/PendingIcon" ;
18+
19+ export type ChainWithRelay = Chain & {
20+ relayChain ?: RelayChain & {
21+ icon ?: Record < string , string > ;
22+ } ;
23+ } ;
1324
1425export type Props = {
1526 value : number ;
@@ -19,7 +30,6 @@ export type Props = {
1930export function ChainSelect ( { value, onChange } : Props ) {
2031 const theme = useTheme ( ) ;
2132 const { frame } = useFrame ( ) ;
22- const userAccount = useAccount ( ) ;
2333 const { chains, switchChain } = useSwitchChain ( ) ;
2434 const relay = useRelay ( ) ;
2535 const relayChains = relay . data ?. chains ;
@@ -31,28 +41,40 @@ export function ChainSelect({ value, onChange }: Props) {
3141 return {
3242 ...sourceChain ,
3343 relayChain,
34- } ;
44+ } satisfies ChainWithRelay ;
3545 } )
3646 . filter ( ( c ) => c . relayChain ) ;
3747 } , [ chains , relayChains ] ) ;
3848
3949 const selectedChain = sourceChains . find ( ( c ) => c . id === value ) ! ;
50+ const { data : chainsBalances , isLoading } = useShowQueryError ( useChainBalances ( { chains : sourceChains } ) ) ;
51+
52+ const renderedChains = useMemo ( ( ) => {
53+ if ( ! chainsBalances ) return [ ] ;
54+ const chainsWithBalance = chainsBalances
55+ . filter ( ( { balance } ) => balance . value > 0n )
56+ . map ( ( { chain, balance } ) => ( { chain, balance : balance . value } ) ) ;
57+
58+ return chainsWithBalance . length > 0 ? chainsWithBalance : sourceChains . map ( ( chain ) => ( { chain, balance : 0n } ) ) ;
59+ } , [ chainsBalances , sourceChains ] ) ;
4060
4161 useEffect ( ( ) => {
42- if ( sourceChains . length > 0 && ! selectedChain ) {
43- const defaultChain = sourceChains [ 0 ] ;
62+ if (
63+ renderedChains . length > 0 &&
64+ ( ! selectedChain || ! renderedChains . find ( ( c ) => c . chain . id === selectedChain ?. id ) )
65+ ) {
66+ const defaultChain = renderedChains [ 0 ] . chain ;
4467 onChange ( defaultChain . id ) ;
4568 switchChain ( { chainId : defaultChain . id } ) ;
4669 }
47- } , [ value , selectedChain , sourceChains , onChange , switchChain ] ) ;
70+ } , [ value , selectedChain , renderedChains , onChange , switchChain ] ) ;
4871
4972 return (
5073 < Select . Root
5174 value = { value . toString ( ) }
5275 onValueChange = { ( value ) => {
53- // TODO: figure out why onValueChange triggers twice, once with value and once without
5476 if ( value ) {
55- const chain = sourceChains . find ( ( chain ) => chain . id . toString ( ) === value ) ;
77+ const chain = renderedChains . find ( ( item ) => item . chain . id . toString ( ) === value ) ?. chain ;
5678 if ( ! chain ) throw new Error ( `Unknown chain selected: ${ value } ` ) ;
5779 onChange ( chain . id ) ;
5880 }
@@ -64,7 +86,6 @@ export function ChainSelect({ value, onChange }: Props) {
6486 < ChainIcon
6587 id = { selectedChain ?. id }
6688 name = { selectedChain ?. name }
67- // TODO: define our own set of icons for each chain
6889 url = { selectedChain ?. relayChain ?. icon ?. [ theme ] }
6990 className = "w-8"
7091 />
@@ -80,33 +101,42 @@ export function ChainSelect({ value, onChange }: Props) {
80101
81102 { frame . contentDocument ? (
82103 < Select . Portal container = { frame . contentDocument . body } >
83- { /* TODO: hardcoded width */ }
84- < Select . Content position = "popper" className = "w-[352px] mt-1 animate-in fade-in slide-in-from-top-2" >
104+ < Select . Content
105+ position = "popper"
106+ className = "w-[352px] max-h-[230px] overflow-y-auto mt-1 animate-in fade-in slide-in-from-top-2"
107+ >
85108 < Select . Viewport >
86109 < Select . Group
87110 className = { twMerge (
88111 "flex flex-col border divide-y" ,
89112 "bg-neutral-800 text-neutral-300 border-neutral-700 divide-neutral-700" ,
90113 ) }
91114 >
92- { sourceChains . map ( ( chain ) => (
93- // TODO: figure out why up/down arrow jump to top/bottom rather than cycling through items
94- < Select . Item
95- key = { chain . id }
96- value = { chain . id . toString ( ) }
97- className = { twMerge (
98- "group flex p-2.5 gap-2.5 items-center cursor-pointer outline-none" ,
99- // TODO: different style for checked/active state
100- "text-white focus:bg-neutral-700 data-[state=checked]:bg-neutral-900" ,
101- ) }
102- >
103- < ChainIcon id = { chain . id } name = { chain . name } url = { chain . relayChain ?. icon ?. [ theme ] } />
104- < span className = "flex-grow flex-shrink-0" > { chain . name } </ span >
105- < span className = "flex-shrink-0 font-mono text-sm text-neutral-400" >
106- < ChainBalance chainId = { chain . id } address = { userAccount . address } />
107- </ span >
108- </ Select . Item >
109- ) ) }
115+ { isLoading ? (
116+ < div className = "flex items-center justify-center p-4" >
117+ < PendingIcon className = "h-6 w-6 animate-spin text-gray-400" />
118+ </ div >
119+ ) : (
120+ renderedChains . map ( ( { chain, balance } ) => {
121+ // TODO: figure out why up/down arrow jump to top/bottom rather than cycling through items
122+ return (
123+ < Select . Item
124+ key = { chain . id }
125+ value = { chain . id . toString ( ) }
126+ className = { twMerge (
127+ "group flex p-2.5 gap-2.5 items-center cursor-pointer outline-none" ,
128+ "text-white focus:bg-neutral-700 data-[state=checked]:bg-neutral-900" ,
129+ ) }
130+ >
131+ < ChainIcon id = { chain . id } name = { chain . name } url = { chain . relayChain ?. icon ?. [ theme ] } />
132+ < span className = "flex-grow flex-shrink-0" > { chain . name } </ span >
133+ < span className = "flex-shrink-0 font-mono text-sm text-neutral-400" >
134+ < Balance wei = { balance } />
135+ </ span >
136+ </ Select . Item >
137+ ) ;
138+ } )
139+ ) }
110140 </ Select . Group >
111141 </ Select . Viewport >
112142 </ Select . Content >
0 commit comments