Skip to content

Commit 492e417

Browse files
authored
Add support for lighter evm address lookup (#901)
* wip * Update logic * feat: changeset * Add auto-selecting lighter account + better resolving logic * Add back pl to multi wallet dropdown * cleanup
1 parent 0367d66 commit 492e417

File tree

9 files changed

+343
-29
lines changed

9 files changed

+343
-29
lines changed

.changeset/slick-nights-act.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@relayprotocol/relay-kit-ui': patch
3+
---
4+
5+
Add support for lighter evm address lookup

packages/ui/src/components/common/CustomAddressModal.tsx

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { type FC, useState, useEffect, useMemo, useContext } from 'react'
22
import { Text, Flex, Button, Input, Pill } from '../primitives/index.js'
33
import { Modal } from '../common/Modal.js'
4-
import { type Address } from 'viem'
4+
import { type Address, isAddress } from 'viem'
55
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
6-
import { useENSResolver, useWalletAddress } from '../../hooks/index.js'
6+
import {
7+
useENSResolver,
8+
useWalletAddress,
9+
useLighterAccount
10+
} from '../../hooks/index.js'
711
import { isENSName } from '../../utils/ens.js'
812
import { LoadingSpinner } from '../common/LoadingSpinner.js'
913
import { EventNames } from '../../constants/events.js'
@@ -24,6 +28,7 @@ import {
2428
addCustomAddress,
2529
getCustomAddresses
2630
} from '../../utils/localStorage.js'
31+
import { isLighterAddress } from '../../utils/lighter.js'
2732

2833
type Props = {
2934
open: boolean
@@ -62,6 +67,27 @@ export const CustomAddressModal: FC<Props> = ({
6267
const providerOptionsContext = useContext(ProviderOptionsContext)
6368
const connectorKeyOverrides = providerOptionsContext.vmConnectorKeyOverrides
6469

70+
// Lighter: allow resolving EVM address to Lighter account ID (and vice versa)
71+
const isLighterChain = toChain?.vmType === 'lvm'
72+
const isEvmInput = isAddress(input)
73+
const isLighterIndexInput = isLighterAddress(input)
74+
75+
const {
76+
data: lighterAccount,
77+
isLoading: isResolvingLighter,
78+
isError: isLighterError
79+
} = useLighterAccount(
80+
isLighterChain && (isEvmInput || isLighterIndexInput) ? input : undefined
81+
)
82+
83+
const resolvedLighterIndex = lighterAccount?.index?.toString()
84+
85+
const didResolveLighterFromEvm =
86+
isLighterChain &&
87+
isEvmInput &&
88+
!!resolvedLighterIndex &&
89+
isLighterAddress(resolvedLighterIndex)
90+
6591
const availableWallets = useMemo(
6692
() =>
6793
linkedWallets.filter((wallet) =>
@@ -84,10 +110,22 @@ export const CustomAddressModal: FC<Props> = ({
84110
[recentCustomAddresses, toChain]
85111
)
86112

113+
// For Lighter: check if the EVM address (input or resolved) matches connected wallet
114+
const isLighterConnectedWallet =
115+
isLighterChain &&
116+
!!lighterAccount &&
117+
// User entered EVM address - check if it matches connected wallet
118+
((isEvmInput && input.toLowerCase() === connectedAddress?.toLowerCase()) ||
119+
// User entered Lighter index - check if resolved l1_address matches connected wallet
120+
(isLighterIndexInput &&
121+
lighterAccount.l1_address?.toLowerCase() ===
122+
connectedAddress?.toLowerCase()))
123+
87124
const connectedAddressSet =
88125
(!address && !toAddress) ||
89126
(toAddress === connectedAddress && address === connectedAddress) ||
90-
availableWallets.some((wallet) => wallet.address === toAddress)
127+
availableWallets.some((wallet) => wallet.address === toAddress) ||
128+
isLighterConnectedWallet
91129

92130
useEffect(() => {
93131
if (!open) {
@@ -104,19 +142,30 @@ export const CustomAddressModal: FC<Props> = ({
104142
}
105143
}, [open])
106144

107-
const { data: resolvedENS, isLoading } = useENSResolver(
145+
const { data: resolvedENS, isLoading: isLoadingENS } = useENSResolver(
108146
isENSName(input) ? input : ''
109147
)
110148

149+
const isLoading = isLoadingENS || isResolvingLighter
150+
111151
useEffect(() => {
112-
if (isValidAddress(toChain?.vmType, input, toChain?.id)) {
152+
if (isLighterChain && isEvmInput) {
153+
setAddress(resolvedLighterIndex ?? '')
154+
} else if (isValidAddress(toChain?.vmType, input, toChain?.id)) {
113155
setAddress(input)
114156
} else if (resolvedENS?.address) {
115157
setAddress(resolvedENS.address)
116158
} else {
117159
setAddress('')
118160
}
119-
}, [input, resolvedENS])
161+
}, [
162+
input,
163+
resolvedENS,
164+
resolvedLighterIndex,
165+
isLighterChain,
166+
isEvmInput,
167+
toChain
168+
])
120169

121170
return (
122171
<Modal
@@ -164,7 +213,9 @@ export const CustomAddressModal: FC<Props> = ({
164213
? 'Enter address'
165214
: toChain.vmType === 'evm'
166215
? 'Address or ENS'
167-
: `Enter ${toChain.displayName} address`
216+
: isLighterChain
217+
? `${toChain.displayName} address or EVM address`
218+
: `Enter ${toChain.displayName} address`
168219
}
169220
value={input}
170221
onChange={(e) => {
@@ -212,12 +263,40 @@ export const CustomAddressModal: FC<Props> = ({
212263
/>
213264
)}
214265
</Flex>
215-
{!address && input.length ? (
266+
{isLighterError ? (
267+
<Text color="red" style="subtitle2">
268+
Failed to resolve Lighter address
269+
</Text>
270+
) : isLighterChain &&
271+
isEvmInput &&
272+
!isResolvingLighter &&
273+
!resolvedLighterIndex ? (
274+
<Text color="red" style="subtitle2">
275+
No Lighter account found for this EVM address
276+
</Text>
277+
) : !address && input.length && !isLoading ? (
216278
<Text color="red" style="subtitle2">
217279
Not a valid address
218280
</Text>
219281
) : null}
220282

283+
{didResolveLighterFromEvm && resolvedLighterIndex ? (
284+
<Flex
285+
css={{ bg: 'green2', p: '2', borderRadius: 8, gap: '2' }}
286+
align="center"
287+
>
288+
<FontAwesomeIcon
289+
icon={faCircleCheck}
290+
color="#30A46C"
291+
width={16}
292+
height={16}
293+
/>
294+
<Text style="subtitle3">
295+
Lighter Account ID: {resolvedLighterIndex}
296+
</Text>
297+
</Flex>
298+
) : null}
299+
221300
{!connectedAddressSet && address && isConnected ? (
222301
<Flex
223302
css={{ bg: 'amber2', p: '2', borderRadius: 8, gap: '2' }}
@@ -304,7 +383,9 @@ export const CustomAddressModal: FC<Props> = ({
304383
</Flex>
305384
<Button
306385
cta={true}
307-
disabled={!isValidAddress(toChain?.vmType, address, toChain?.id)}
386+
disabled={
387+
isLoading || !isValidAddress(toChain?.vmType, address, toChain?.id)
388+
}
308389
css={{ justifyContent: 'center' }}
309390
onClick={() => {
310391
if (isValidAddress(toChain?.vmType, address, toChain?.id)) {

packages/ui/src/components/common/MultiWalletDropdown.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,7 @@ export const MultiWalletDropdown: FC<MultiWalletDropdownProps> = ({
147147
corners="pill"
148148
css={{
149149
gap: '2',
150-
pl: '0 !important',
151-
pr: '2 !important',
150+
px: '2 !important',
152151
py: '1',
153152
cursor: 'pointer',
154153
display: 'flex',
@@ -160,7 +159,12 @@ export const MultiWalletDropdown: FC<MultiWalletDropdownProps> = ({
160159
{isSupportedSelectedWallet && selectedWallet?.walletLogoUrl ? (
161160
<img
162161
src={selectedWallet.walletLogoUrl}
163-
style={{ width: 16, height: 16, borderRadius: 4, flexShrink: 0 }}
162+
style={{
163+
width: 16,
164+
height: 16,
165+
borderRadius: 4,
166+
flexShrink: 0
167+
}}
164168
/>
165169
) : selectedWalletAddress && !selectedWallet ? (
166170
<Box css={{ color: 'amber11', flexShrink: 0 }}>

packages/ui/src/components/widgets/OnrampWidget/widget/OnrampWidgetRenderer.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import {
1111
} from 'react'
1212
import {
1313
useRelayClient,
14-
useENSResolver,
1514
useIsPassthrough,
1615
useSupportedMoonPayCurrencyCode,
17-
useFallbackState
16+
useFallbackState,
17+
useDisplayName
1818
} from '../../../../hooks/index.js'
1919
import { zeroAddress } from 'viem'
2020
import { type ChainVM, type RelayChain } from '@relayprotocol/relay-sdk'
@@ -176,10 +176,11 @@ const OnrampWidgetRenderer: FC<OnrampWidgetRendererProps> = ({
176176
const [recipient, setRecipient] = useState<string | undefined>(
177177
defaultWalletAddress
178178
)
179-
const { displayName: toDisplayName } = useENSResolver(recipient, {
180-
refetchOnWindowFocus: false,
181-
enabled: toChain?.vmType === 'evm'
182-
})
179+
const { displayName: toDisplayName } = useDisplayName(
180+
recipient,
181+
toChain?.vmType,
182+
toChain?.id
183+
)
183184

184185
const [fiatCurrency, setFiatCurrency] = useState<FiatCurrency>({
185186
name: 'US Dollar',

packages/ui/src/components/widgets/SwapWidgetRenderer.tsx

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { Dispatch, FC, ReactNode, SetStateAction } from 'react'
22
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
33
import {
44
useCurrencyBalance,
5-
useENSResolver,
65
useRelayClient,
76
useDebounceState,
87
useWalletAddress,
@@ -11,7 +10,9 @@ import {
1110
useIsWalletCompatible,
1211
useFallbackState,
1312
useGasTopUpRequired,
14-
useEOADetection
13+
useEOADetection,
14+
useDisplayName,
15+
useLighterAccount
1516
} from '../../hooks/index.js'
1617
import type { Address, WalletClient } from 'viem'
1718
import { formatUnits, parseUnits } from 'viem'
@@ -45,6 +46,7 @@ import { errorToJSON } from '../../utils/errors.js'
4546
import { useSwapButtonCta } from '../../hooks/widget/useSwapButtonCta.js'
4647
import { sha256 } from '../../utils/hashing.js'
4748
import { get15MinuteInterval } from '../../utils/time.js'
49+
import { isLighterAddress } from '../../utils/lighter.js'
4850
import type { FeeBreakdown } from '../../types/FeeBreakdown.js'
4951

5052
export type TradeType = 'EXACT_INPUT' | 'EXPECTED_OUTPUT'
@@ -425,9 +427,11 @@ const SwapWidgetRenderer: FC<SwapWidgetRendererProps> = ({
425427
toChain?.id
426428
)
427429

428-
const { displayName: toDisplayName } = useENSResolver(recipient, {
429-
enabled: toChain?.vmType === 'evm' && isValidToAddress
430-
})
430+
const { displayName: toDisplayName } = useDisplayName(
431+
recipient,
432+
toChain?.vmType,
433+
toChain?.id
434+
)
431435

432436
const [currentSlippageTolerance, setCurrentSlippageTolerance] = useState<
433437
string | undefined
@@ -648,6 +652,23 @@ const SwapWidgetRenderer: FC<SwapWidgetRendererProps> = ({
648652
setCustomToAddress(undefined)
649653
})
650654

655+
// Auto-select Lighter account when switching to LVM chain
656+
const isLighterChain = toChain?.vmType === 'lvm'
657+
const { data: connectedLighterAccount } = useLighterAccount(
658+
isLighterChain && address ? address : undefined
659+
)
660+
661+
useEffect(() => {
662+
if (
663+
isLighterChain &&
664+
connectedLighterAccount?.index &&
665+
// Only auto-set if no valid Lighter address is already set
666+
(!customToAddress || !isLighterAddress(customToAddress))
667+
) {
668+
setCustomToAddress(connectedLighterAccount.index.toString())
669+
}
670+
}, [isLighterChain, connectedLighterAccount, customToAddress])
671+
651672
useEffect(() => {
652673
if (tradeType === 'EXACT_INPUT') {
653674
const amountOut = quote?.details?.currencyOut?.amount ?? ''

packages/ui/src/components/widgets/TokenWidget/widget/TokenWidgetRenderer.tsx

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ import type { Dispatch, FC, ReactNode, SetStateAction } from 'react'
22
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
33
import {
44
useCurrencyBalance,
5-
useENSResolver,
65
useRelayClient,
76
useDebounceState,
87
useWalletAddress,
98
useDisconnected,
109
usePreviousValueChange,
1110
useIsWalletCompatible,
1211
useFallbackState,
13-
useEOADetection
12+
useEOADetection,
13+
useDisplayName,
14+
useLighterAccount
1415
} from '../../../../hooks/index.js'
1516
import type { Address, WalletClient } from 'viem'
1617
import { formatUnits, parseUnits } from 'viem'
@@ -44,6 +45,7 @@ import { errorToJSON } from '../../../../utils/errors.js'
4445
import { useSwapButtonCta } from '../../../../hooks/widget/useSwapButtonCta.js'
4546
import { sha256 } from '../../../../utils/hashing.js'
4647
import { get15MinuteInterval } from '../../../../utils/time.js'
48+
import { isLighterAddress } from '../../../../utils/lighter.js'
4749
import type { FeeBreakdown } from '../../../../types/FeeBreakdown.js'
4850

4951
export type TradeType = 'EXACT_INPUT' | 'EXPECTED_OUTPUT'
@@ -518,9 +520,11 @@ const TokenWidgetRenderer: FC<TokenWidgetRendererProps> = ({
518520
toChain?.id
519521
)
520522

521-
const { displayName: toDisplayName } = useENSResolver(recipient, {
522-
enabled: toChain?.vmType === 'evm' && isValidToAddress
523-
})
523+
const { displayName: toDisplayName } = useDisplayName(
524+
recipient,
525+
toChain?.vmType,
526+
toChain?.id
527+
)
524528

525529
const [currentSlippageTolerance, setCurrentSlippageTolerance] = useState<
526530
string | undefined
@@ -740,6 +744,23 @@ const TokenWidgetRenderer: FC<TokenWidgetRendererProps> = ({
740744
setDestinationAddressOverride(undefined)
741745
})
742746

747+
// Auto-select Lighter account when switching to LVM chain
748+
const isLighterChain = toChain?.vmType === 'lvm'
749+
const { data: connectedLighterAccount } = useLighterAccount(
750+
isLighterChain && address ? address : undefined
751+
)
752+
753+
useEffect(() => {
754+
if (
755+
isLighterChain &&
756+
connectedLighterAccount?.index &&
757+
// Only auto-set if no valid Lighter address is already set
758+
(!customToAddress || !isLighterAddress(customToAddress))
759+
) {
760+
setCustomToAddress(connectedLighterAccount.index.toString())
761+
}
762+
}, [isLighterChain, connectedLighterAccount, customToAddress])
763+
743764
useEffect(() => {
744765
if (tradeType === 'EXACT_INPUT') {
745766
const amountOut = quote?.details?.currencyOut?.amount ?? ''

packages/ui/src/hooks/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import useHyperliquidBalance from './useHyperliquidBalance.js'
2525
import useEOADetection from './useEOADetection.js'
2626
import useTransactionCount from './useTransactionCount.js'
2727
import useTronBalance from './useTronBalance.js'
28+
import useLighterAccount from './useLighterAccount.js'
29+
import useDisplayName from './useDisplayName.js'
2830

2931
export {
3032
useMounted,
@@ -53,5 +55,7 @@ export {
5355
useHyperliquidBalance,
5456
useEOADetection,
5557
useTransactionCount,
56-
useTronBalance
58+
useTronBalance,
59+
useLighterAccount,
60+
useDisplayName
5761
}

0 commit comments

Comments
 (0)