Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom eclipse connectors #363

Merged
merged 2 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/kind-windows-sparkle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@reservoir0x/relay-kit-ui': patch
---

Allow overriding chain to connector list
2 changes: 1 addition & 1 deletion demo/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import React, { ReactNode, FC, useState, useEffect } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { createConfig, http, WagmiProvider } from 'wagmi'
import { Chain, mainnet } from 'wagmi/chains'
import { RelayKitProvider } from '@reservoir0x/relay-kit-ui'
import { useRelayChains } from '@reservoir0x/relay-kit-hooks'
import {
LogLevel,
Expand All @@ -30,6 +29,7 @@ import { chainIdToAlchemyNetworkMap } from 'utils/chainIdToAlchemyNetworkMap'
import { useWalletFilter, WalletFilterProvider } from 'context/walletFilter'
import { pipe } from '@dynamic-labs/utils'
import { EclipseWalletConnectors } from '@dynamic-labs/eclipse'
import { RelayKitProvider } from '@reservoir0x/relay-kit-ui'

type AppWrapperProps = {
children: ReactNode
Expand Down
9 changes: 6 additions & 3 deletions packages/ui/src/components/common/CustomAddressModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type FC, useState, useEffect, useMemo } from 'react'
import { type FC, useState, useEffect, useMemo, useContext } from 'react'
import { Text, Flex, Button, Input, Pill } from '../primitives/index.js'
import { Modal } from '../common/Modal.js'
import { type Address } from 'viem'
Expand All @@ -17,7 +17,7 @@ import type { AdaptedWallet, RelayChain } from '@reservoir0x/relay-sdk'
import type { LinkedWallet } from '../../types/index.js'
import { truncateAddress } from '../../utils/truncate.js'
import { isValidAddress } from '../../utils/address.js'
import { eclipse, eclipseWallets } from '../../utils/solana.js'
import { ProviderOptionsContext } from '../../providers/RelayKitProvider.js'

type Props = {
open: boolean
Expand Down Expand Up @@ -50,6 +50,8 @@ export const CustomAddressModal: FC<Props> = ({
const connectedAddress = useWalletAddress(wallet, linkedWallets)
const [address, setAddress] = useState('')
const [input, setInput] = useState('')
const providerOptionsContext = useContext(ProviderOptionsContext)
const connectorKeyOverrides = providerOptionsContext.vmConnectorKeyOverrides

const availableWallets = useMemo(
() =>
Expand All @@ -58,7 +60,8 @@ export const CustomAddressModal: FC<Props> = ({
toChain?.vmType,
wallet.address,
toChain?.id,
wallet.connector
wallet.connector,
connectorKeyOverrides
)
),
[toChain, linkedWallets]
Expand Down
28 changes: 23 additions & 5 deletions packages/ui/src/components/common/MultiWalletDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo, useState, type FC } from 'react'
import { useContext, useMemo, useState, type FC } from 'react'
import { Dropdown, DropdownMenuItem } from '../primitives/Dropdown.js'
import { Box, Button, Flex, Text } from '../primitives/index.js'
import type { LinkedWallet } from '../../types/index.js'
Expand All @@ -10,6 +10,7 @@ import { eclipse, eclipseWallets, solana } from '../../utils/solana.js'
import { useENSResolver } from '../../hooks/index.js'
import { EventNames } from '../../constants/events.js'
import { isValidAddress } from '../../utils/address.js'
import { ProviderOptionsContext } from '../../providers/RelayKitProvider.js'

type MultiWalletDropdownProps = {
context: 'origin' | 'destination'
Expand All @@ -33,20 +34,30 @@ export const MultiWalletDropdown: FC<MultiWalletDropdownProps> = ({
setAddressModalOpen
}) => {
const [open, setOpen] = useState(false)
const providerOptionsContext = useContext(ProviderOptionsContext)
const connectorKeyOverrides = providerOptionsContext.vmConnectorKeyOverrides
const filteredWallets = useMemo(() => {
if (!chain) return wallets

let eclipseConnectorKeys: string[] | undefined = undefined
if (connectorKeyOverrides && connectorKeyOverrides[eclipse.id]) {
eclipseConnectorKeys = connectorKeyOverrides[eclipse.id]
} else if (chain.vmType === 'svm') {
eclipseConnectorKeys = eclipseWallets
}

return wallets.filter((wallet) => {
if (wallet.vmType !== chain.vmType) {
return false
}
if (
chain.id === eclipse.id &&
!eclipseWallets.includes(wallet.connector.toLowerCase())
!eclipseConnectorKeys!.includes(wallet.connector.toLowerCase())
) {
return false
} else if (
chain.id === solana.id &&
eclipseWallets.includes(wallet.connector.toLowerCase())
eclipseConnectorKeys!.includes(wallet.connector.toLowerCase())
) {
return false
}
Expand All @@ -65,9 +76,16 @@ export const MultiWalletDropdown: FC<MultiWalletDropdownProps> = ({
chain?.vmType,
selectedWalletAddress,
chain?.id,
selectedWallet?.connector
selectedWallet?.connector,
connectorKeyOverrides
),
[selectedWalletAddress, selectedWallet, chain?.vmType, chain?.id]
[
selectedWalletAddress,
selectedWallet,
chain?.vmType,
chain?.id,
connectorKeyOverrides
]
)

const showDropdown = context !== 'origin' || filteredWallets.length > 0
Expand Down
23 changes: 14 additions & 9 deletions packages/ui/src/components/widgets/SwapWidget/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Flex, Button, Text, Box } from '../../primitives/index.js'
import { useEffect, useState, type FC } from 'react'
import { useContext, useEffect, useState, type FC } from 'react'
import { useMounted, useRelayClient } from '../../../hooks/index.js'
import type { Address } from 'viem'
import { formatUnits, zeroAddress } from 'viem'
Expand Down Expand Up @@ -35,6 +35,7 @@ import {
ASSETS_RELAY_API
} from '@reservoir0x/relay-sdk'
import SwapRouteSelector from '../SwapRouteSelector.js'
import { ProviderOptionsContext } from '../../../providers/RelayKitProvider.js'

type BaseSwapWidgetProps = {
defaultFromToken?: Token
Expand Down Expand Up @@ -99,6 +100,8 @@ const SwapWidget: FC<SwapWidgetProps> = ({
onSwapError
}) => {
const relayClient = useRelayClient()
const providerOptionsContext = useContext(ProviderOptionsContext)
const connectorKeyOverrides = providerOptionsContext.vmConnectorKeyOverrides
const [transactionModalOpen, setTransactionModalOpen] = useState(false)
const [addressModalOpen, setAddressModalOpen] = useState(false)
const isMounted = useMounted()
Expand Down Expand Up @@ -223,7 +226,8 @@ const SwapWidget: FC<SwapWidgetProps> = ({
const supportedAddress = findSupportedWallet(
fromChain,
address,
linkedWallets
linkedWallets,
connectorKeyOverrides
)
if (supportedAddress) {
onSetPrimaryWallet?.(supportedAddress)
Expand All @@ -235,7 +239,8 @@ const SwapWidget: FC<SwapWidgetProps> = ({
address,
linkedWallets,
onSetPrimaryWallet,
isValidFromAddress
isValidFromAddress,
connectorKeyOverrides
])

const promptSwitchRoute =
Expand Down Expand Up @@ -411,9 +416,9 @@ const SwapWidget: FC<SwapWidgetProps> = ({
isSingleChainLocked
? [lockChainId]
: fromToken?.chainId !== undefined &&
fromToken?.chainId === lockChainId
? [fromToken?.chainId]
: undefined
fromToken?.chainId === lockChainId
? [fromToken?.chainId]
: undefined
}
restrictedTokensList={tokens?.filter(
(token) => token.chainId === fromToken?.chainId
Expand Down Expand Up @@ -757,9 +762,9 @@ const SwapWidget: FC<SwapWidgetProps> = ({
isSingleChainLocked
? [lockChainId]
: toToken?.chainId !== undefined &&
toToken?.chainId === lockChainId
? [toToken?.chainId]
: undefined
toToken?.chainId === lockChainId
? [toToken?.chainId]
: undefined
}
restrictedTokensList={tokens?.filter(
(token) => token.chainId === toToken?.chainId
Expand Down
13 changes: 9 additions & 4 deletions packages/ui/src/components/widgets/SwapWidgetRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ const SwapWidgetRenderer: FC<SwapWidgetRendererProps> = ({
onAnalyticEvent
}) => {
const providerOptionsContext = useContext(ProviderOptionsContext)
const connectorKeyOverrides = providerOptionsContext.vmConnectorKeyOverrides
const relayClient = useRelayClient()
const { connector } = useAccount()
const [customToAddress, setCustomToAddress] = useState<
Expand Down Expand Up @@ -207,7 +208,8 @@ const SwapWidgetRenderer: FC<SwapWidgetRendererProps> = ({
toChain?.id,
!customToAddress && _linkedWallet?.address === address
? _linkedWallet?.connector
: undefined
: undefined,
connectorKeyOverrides
)
if (
multiWalletSupportEnabled &&
Expand All @@ -218,7 +220,8 @@ const SwapWidgetRenderer: FC<SwapWidgetRendererProps> = ({
const supportedAddress = findSupportedWallet(
toChain,
customToAddress,
linkedWallets
linkedWallets,
connectorKeyOverrides
)

return supportedAddress
Expand Down Expand Up @@ -326,13 +329,15 @@ const SwapWidgetRenderer: FC<SwapWidgetRendererProps> = ({
fromChain?.vmType,
address ?? '',
fromChain?.id,
linkedWallet?.connector
linkedWallet?.connector,
connectorKeyOverrides
)
const fromAddressWithFallback = addressWithFallback(
fromChain?.vmType,
address,
fromChain?.id,
linkedWallet?.connector
linkedWallet?.connector,
connectorKeyOverrides
)

const isValidToAddress = isValidAddress(toChain?.vmType, recipient ?? '')
Expand Down
36 changes: 25 additions & 11 deletions packages/ui/src/providers/RelayKitProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
import { createContext, useMemo } from 'react'
import type { FC, ReactNode } from 'react'
import { RelayClientProvider } from './RelayClientProvider.js'
import type { RelayClientOptions, paths } from '@reservoir0x/relay-sdk'
import type { ChainVM, RelayClientOptions, paths } from '@reservoir0x/relay-sdk'
import type { RelayKitTheme } from '../themes/index.js'
import { generateCssVars } from '../utils/theme.js'

export type CoinId = {
[key: string]: string
}
export type CoinGecko = {
proxy?: string
apiKey?: string
coinIds?: CoinId
}

export type AppFees =
paths['/quote']['post']['requestBody']['content']['application/json']['appFees']

type RelayKitProviderOptions = {
/**
* The name of the application
*/
appName?: string
/**
* An array of fee objects composing of a recipient address and the fee in BPS
*/
appFees?: AppFees
/**
* This key is used to fetch token balances, to improve the general UX and suggest relevant tokens
* Can be omitted and the ui will continue to function. Refer to the dune docs on how to get an api key
*/
duneApiKey?: string
/**
* Disable the powered by reservoir footer
*/
disablePoweredByReservoir?: boolean
/**
* An objecting mapping either a VM type (evm, svm, bvm) or a chain id to a connector key (metamask, backpacksol, etc).
* Connector keys are used for differentiating which wallet maps to which vm/chain.
* Only relevant for eclipse/solana at the moment.
*/
vmConnectorKeyOverrides?: {
[key in number | 'evm' | 'svm' | 'bvm']?: string[]
}
}

export interface RelayKitProviderProps {
children: ReactNode
options: RelayClientOptions & RelayKitProviderOptions
Expand Down Expand Up @@ -137,7 +150,8 @@ export const RelayKitProvider: FC<RelayKitProviderProps> = function ({
appName: options.appName,
appFees: options.appFees,
duneApiKey: options.duneApiKey,
disablePoweredByReservoir: options.disablePoweredByReservoir
disablePoweredByReservoir: options.disablePoweredByReservoir,
vmConnectorKeyOverrides: options.vmConnectorKeyOverrides
}),
[options]
)
Expand Down
43 changes: 34 additions & 9 deletions packages/ui/src/utils/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,41 @@ import {
solana
} from '../utils/solana.js'
import type { LinkedWallet } from '../types/index.js'
import type { RelayKitProviderProps } from '../providers/RelayKitProvider.js'

export const isValidAddress = (
vmType?: ChainVM,
address?: string,
chainId?: number,
connector?: string
connector?: string,
connectorKeyOverrides?: RelayKitProviderProps['options']['vmConnectorKeyOverrides']
) => {
let eclipseConnectorKeys: string[] | undefined = undefined
if (connectorKeyOverrides && connectorKeyOverrides[eclipse.id]) {
eclipseConnectorKeys = connectorKeyOverrides[eclipse.id]
} else if (vmType === 'svm') {
eclipseConnectorKeys = eclipseWallets
}

if (address) {
if (vmType === 'evm' || !vmType) {
return isAddress(address)
} else if (vmType === 'svm') {
if (chainId && connector) {
if (
chainId === eclipse.id &&
!eclipseWallets.includes(connector.toLowerCase())
!eclipseConnectorKeys!.includes(connector.toLowerCase())
) {
return false
}
if (
chainId === solana.id &&
eclipseWallets.includes(connector.toLowerCase())
eclipseConnectorKeys!.includes(connector.toLowerCase())
) {
return false
}
}
//tood solana

return isSolanaAddress(address)
} else if (vmType === 'bvm') {
return isBitcoinAddress(address)
Expand All @@ -50,17 +59,26 @@ export const addressWithFallback = (
vmType?: ChainVM,
address?: string,
chainId?: number,
connector?: string
connector?: string,
connectorKeyOverrides?: Parameters<typeof isValidAddress>['4']
) => {
return address && isValidAddress(vmType ?? 'evm', address, chainId, connector)
return address &&
isValidAddress(
vmType ?? 'evm',
address,
chainId,
connector,
connectorKeyOverrides
)
? address
: getDeadAddress(vmType, chainId)
}

export function findSupportedWallet(
chain: RelayChain,
currentAddress: string | undefined,
linkedWallets: LinkedWallet[]
linkedWallets: LinkedWallet[],
connectorKeyOverrides?: Parameters<typeof isValidAddress>['4']
): string | undefined {
const currentWallet = linkedWallets.find(
(wallet) => wallet.address === currentAddress
Expand All @@ -72,11 +90,18 @@ export function findSupportedWallet(
chain.vmType,
currentWallet.address,
chain.id,
currentWallet.connector
currentWallet.connector,
connectorKeyOverrides
))
) {
const supportedWallet = linkedWallets.find((wallet) =>
isValidAddress(chain.vmType, wallet.address, chain.id, wallet.connector)
isValidAddress(
chain.vmType,
wallet.address,
chain.id,
wallet.connector,
connectorKeyOverrides
)
)
return supportedWallet?.address
}
Expand Down