Skip to content

Commit 60acdf9

Browse files
authored
Merge pull request #821 from relayprotocol/ted/fe-7727-save-destination-address-for-future-use
Save recent custom addresses to local storage
2 parents 4986a10 + f54d1f3 commit 60acdf9

File tree

3 files changed

+104
-17
lines changed

3 files changed

+104
-17
lines changed

.changeset/eighty-apes-jam.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+
Save recent custom addresses to local storage

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

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,19 @@ import { EventNames } from '../../constants/events.js'
1010
import type { Token } from '../../types/index.js'
1111
import {
1212
faCircleCheck,
13-
faTriangleExclamation
13+
faTriangleExclamation,
14+
faClipboard
1415
} from '@fortawesome/free-solid-svg-icons'
1516
import { AnchorButton } from '../primitives/Anchor.js'
1617
import type { AdaptedWallet, RelayChain } from '@relayprotocol/relay-sdk'
1718
import type { LinkedWallet } from '../../types/index.js'
1819
import { truncateAddress } from '../../utils/truncate.js'
1920
import { isValidAddress } from '../../utils/address.js'
2021
import { ProviderOptionsContext } from '../../providers/RelayKitProvider.js'
22+
import {
23+
addCustomAddress,
24+
getCustomAddresses
25+
} from '../../utils/localStorage.js'
2126

2227
type Props = {
2328
open: boolean
@@ -50,6 +55,9 @@ export const CustomAddressModal: FC<Props> = ({
5055
const connectedAddress = useWalletAddress(wallet, linkedWallets)
5156
const [address, setAddress] = useState('')
5257
const [input, setInput] = useState('')
58+
const [recentCustomAddresses, setRecentCustomAddresses] = useState<string[]>(
59+
[]
60+
)
5361
const providerOptionsContext = useContext(ProviderOptionsContext)
5462
const connectorKeyOverrides = providerOptionsContext.vmConnectorKeyOverrides
5563

@@ -67,6 +75,14 @@ export const CustomAddressModal: FC<Props> = ({
6775
[toChain, linkedWallets]
6876
)
6977

78+
const filteredRecentCustomAddresses = useMemo(
79+
() =>
80+
recentCustomAddresses.filter((address) =>
81+
isValidAddress(toChain?.vmType, address, toChain?.id)
82+
),
83+
[recentCustomAddresses, toChain]
84+
)
85+
7086
const connectedAddressSet =
7187
(!address && !toAddress) ||
7288
(toAddress === connectedAddress && address === connectedAddress) ||
@@ -81,6 +97,8 @@ export const CustomAddressModal: FC<Props> = ({
8197
setAddress(toAddress ? toAddress : '')
8298
setInput(toAddress ? toAddress : '')
8399
}
100+
// Load custom addresses when modal opens
101+
setRecentCustomAddresses(getCustomAddresses())
84102
onAnalyticEvent?.(EventNames.ADDRESS_MODAL_OPEN)
85103
}
86104
}, [open])
@@ -210,38 +228,39 @@ export const CustomAddressModal: FC<Props> = ({
210228
)
211229
) : null}
212230

213-
{multiWalletSupportEnabled && availableWallets.length > 0 ? (
231+
{filteredRecentCustomAddresses.length > 0 ? (
214232
<>
215-
<Text style="subtitle2">Use connected wallet address</Text>
233+
<Text style="subtitle2">Recent addresses</Text>
216234
<Flex css={{ gap: '2', flexWrap: 'wrap' }} align="center">
217-
{availableWallets.map((wallet) => (
235+
{filteredRecentCustomAddresses.map((address) => (
218236
<Pill
219-
key={wallet.address}
237+
key={address}
220238
color="transparent"
221239
bordered
222240
radius="squared"
223241
css={{
224242
display: 'flex',
225243
alignItems: 'center',
226244
gap: '6px',
227-
cursor: 'pointer'
245+
cursor: 'pointer',
246+
px: '8px'
228247
}}
229248
onClick={() => {
230-
onConfirmed(wallet.address)
249+
onConfirmed(address)
231250
onOpenChange(false)
232251
onAnalyticEvent?.(EventNames.ADDRESS_MODAL_CONFIRMED, {
233-
address: wallet.address,
234-
context: 'linked_wallet'
252+
address: address,
253+
context: 'custom_address'
235254
})
236255
}}
237256
>
238-
<img
239-
src={wallet.walletLogoUrl}
240-
style={{ width: 16, height: 16, borderRadius: 4 }}
257+
<FontAwesomeIcon
258+
icon={faClipboard}
259+
width={16}
260+
height={16}
261+
color="#9CA3AF"
241262
/>
242-
<Text style="subtitle2">
243-
{truncateAddress(wallet.address)}
244-
</Text>
263+
<Text style="subtitle2">{truncateAddress(address)}</Text>
245264
</Pill>
246265
))}
247266
</Flex>
@@ -254,6 +273,15 @@ export const CustomAddressModal: FC<Props> = ({
254273
css={{ justifyContent: 'center' }}
255274
onClick={() => {
256275
if (isValidAddress(toChain?.vmType, address, toChain?.id)) {
276+
// Save the address to custom addresses if it's not a connected wallet address
277+
const isConnectedWallet = availableWallets.some(
278+
(wallet) => wallet.address === address
279+
)
280+
if (!isConnectedWallet && address !== connectedAddress) {
281+
addCustomAddress(address)
282+
setRecentCustomAddresses(getCustomAddresses())
283+
}
284+
257285
onConfirmed(address)
258286
onAnalyticEvent?.(EventNames.ADDRESS_MODAL_CONFIRMED, {
259287
address: address,

packages/ui/src/utils/localStorage.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,33 @@ interface CacheEntry {
1111

1212
interface RelayUiKitData {
1313
acceptedUnverifiedTokens: string[]
14+
recentCustomAddresses?: string[]
1415
genericCache?: { [key: string]: CacheEntry }
1516
}
1617

1718
export function getRelayUiKitData(): RelayUiKitData {
1819
if (typeof window === 'undefined')
19-
return { acceptedUnverifiedTokens: [], genericCache: {} }
20+
return {
21+
acceptedUnverifiedTokens: [],
22+
recentCustomAddresses: [],
23+
genericCache: {}
24+
}
2025

2126
let data: RelayUiKitData = {
2227
acceptedUnverifiedTokens: [],
28+
recentCustomAddresses: [],
2329
genericCache: {}
2430
}
2531
try {
2632
const localStorageData = localStorage.getItem(RELAY_UI_KIT_KEY)
2733
data = localStorageData ? JSON.parse(localStorageData) : data
28-
// Ensure genericCache exists if loaded data doesn't have it
34+
// Ensure genericCache and recentCustomAddresses exist if loaded data doesn't have them
2935
if (!data.genericCache) {
3036
data.genericCache = {}
3137
}
38+
if (!data.recentCustomAddresses) {
39+
data.recentCustomAddresses = []
40+
}
3241
} catch (e) {
3342
console.warn('Failed to get RelayKitUIData', e)
3443
}
@@ -111,3 +120,48 @@ export const alreadyAcceptedToken = (token: Token) => {
111120
// Ensure acceptedUnverifiedTokens exists before accessing includes
112121
return relayUiKitData.acceptedUnverifiedTokens?.includes(tokenKey) ?? false
113122
}
123+
124+
/**
125+
* Add a custom address to the saved list if it doesn't already exist
126+
* Keeps a maximum of 3 addresses, removing the oldest when limit is exceeded
127+
* @param address - The address to save
128+
*/
129+
export function addCustomAddress(address: string): void {
130+
const data = getRelayUiKitData()
131+
const recentCustomAddresses = data.recentCustomAddresses || []
132+
133+
// Remove address if it already exists to avoid duplicates
134+
const filteredAddresses = recentCustomAddresses.filter(
135+
(addr) => addr !== address
136+
)
137+
138+
// Add the new address to the end
139+
const updatedAddresses = [...filteredAddresses, address]
140+
141+
// Keep only the last 3 addresses (most recent)
142+
const limitedAddresses = updatedAddresses.slice(-3)
143+
144+
setRelayUiKitData({ recentCustomAddresses: limitedAddresses })
145+
}
146+
147+
/**
148+
* Get all saved custom addresses
149+
* @returns Array of custom addresses
150+
*/
151+
export function getCustomAddresses(): string[] {
152+
const data = getRelayUiKitData()
153+
return data.recentCustomAddresses || []
154+
}
155+
156+
/**
157+
* Remove a custom address from the saved list
158+
* @param address - The address to remove
159+
*/
160+
export function removeCustomAddress(address: string): void {
161+
const data = getRelayUiKitData()
162+
const recentCustomAddresses = data.recentCustomAddresses || []
163+
const updatedAddresses = recentCustomAddresses.filter(
164+
(addr) => addr !== address
165+
)
166+
setRelayUiKitData({ recentCustomAddresses: updatedAddresses })
167+
}

0 commit comments

Comments
 (0)