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

feat: xswap UI updates #1735

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default function CrossChainSwapLayout({
}

return (
<Providers>
<Providers chainId={chainId}>
<SidebarContainer
selectedNetwork={chainId}
supportedNetworks={SUSHIXSWAP_2_SUPPORTED_CHAIN_IDS}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ import { getCrossChainSwapEdgeConfig } from './get-cross-chain-swap-edge-config'

import { EdgeProvider } from 'src/providers/edge-config-provider'
import { DerivedstateCrossChainSwapProvider } from 'src/ui/swap/cross-chain/derivedstate-cross-chain-swap-provider'
import { ChainId } from 'sushi/chain'

export async function Providers({ children }: { children: React.ReactNode }) {
export async function Providers({
children,
chainId,
}: { children: React.ReactNode; chainId: ChainId }) {
const config = await getCrossChainSwapEdgeConfig()

return (
<EdgeProvider config={config}>
<DerivedstateCrossChainSwapProvider>
<DerivedstateCrossChainSwapProvider defaultChainId={chainId}>
{children}
</DerivedstateCrossChainSwapProvider>
</EdgeProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export default function TokenSelector({
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className="!flex flex-col justify-start min-h-[85vh]">
<DialogHeader>
<DialogHeader className="!text-left">
<DialogTitle>Select a token</DialogTitle>
<DialogDescription>
Select a token from our default list or search for a token by symbol
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const TokenSelector = ({
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className="!flex flex-col justify-start min-h-[85vh]">
<DialogHeader>
<DialogHeader className="!text-left">
<DialogTitle>Select a token</DialogTitle>
<DialogDescription>
Select a token from our default list or search for a token by symbol
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const NEW_CHAIN_IDS = [
NonStandardChainId.TRON,
] as const

const PREFERRED_CHAINID_ORDER = [
export const PREFERRED_CHAINID_ORDER = [
ChainId.ETHEREUM,
NonStandardChainId.TRON,
ChainId.BSC,
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/lib/hooks/react-query/tokens/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './useCoinGeckoTokenInfo'
export * from './useTokenSecurity'
118 changes: 118 additions & 0 deletions apps/web/src/lib/hooks/react-query/tokens/useCoinGeckoTokenInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { keepPreviousData, useQuery } from '@tanstack/react-query'
import { ChainId } from 'sushi/chain'
import { Token } from 'sushi/currency'
import { z } from 'zod'

const COINGECKO_CHAIN_ID_BY_NAME = {
[ChainId.ETHEREUM]: 'eth',
[ChainId.BSC]: 'bsc',
[ChainId.POLYGON]: 'polygon_pos',
[ChainId.AVALANCHE]: 'avax',
[ChainId.CRONOS]: 'cro',
[ChainId.HARMONY]: 'one',
[ChainId.BOBA]: 'boba',
[ChainId.FANTOM]: 'ftm',
[ChainId.METIS]: 'metis',
[ChainId.ARBITRUM]: 'arbitrum',
[ChainId.FUSE]: 'fuse',
[ChainId.OKEX]: 'okexchain',
[ChainId.CELO]: 'celo',
[ChainId.GNOSIS]: 'xdai',
[ChainId.HECO]: 'heco',
[ChainId.MOONBEAM]: 'glmr',
[ChainId.OPTIMISM]: 'optimism',
[ChainId.TELOS]: 'tlos',
[ChainId.BTTC]: 'bttc',
[ChainId.KAVA]: 'kava',
[ChainId.THUNDERCORE]: 'thundercore',
[ChainId.ARBITRUM_NOVA]: 'arbitrum_nova',
[ChainId.CORE]: 'core',
[ChainId.FILECOIN]: 'filecoin',
[ChainId.ZKSYNC_ERA]: 'zksync',
[ChainId.POLYGON_ZKEVM]: 'polygon-zkevm',
[ChainId.LINEA]: 'linea',
[ChainId.BASE]: 'base',
[ChainId.SCROLL]: 'scroll',
[ChainId.ZETACHAIN]: 'zetachain',
[ChainId.BLAST]: 'blast',
[ChainId.BOBA_BNB]: 'boba-bnb',
[ChainId.ROOTSTOCK]: 'rootstock',
[ChainId.SKALE_EUROPA]: 'skale-europa',
[ChainId.MANTLE]: 'mantle',
[ChainId.MANTA]: 'manta-pacific',
[ChainId.MODE]: 'mode',
[ChainId.TAIKO]: 'taiko',
[ChainId.ZKLINK]: 'zklink-nova',
[ChainId.APE]: 'apechain',
} as const

type CoinGeckoChainId = keyof typeof COINGECKO_CHAIN_ID_BY_NAME

const isCoinGeckoChainId = (chainId: ChainId): chainId is CoinGeckoChainId =>
Object.keys(COINGECKO_CHAIN_ID_BY_NAME).includes(chainId.toString())

const coinGeckoSchema = z.object({
market_cap_rank: z.number(),
market_data: z.object({
current_price: z.object({
usd: z.number(),
}),
total_volume: z.object({
usd: z.number(),
}),
market_cap: z.object({
usd: z.number(),
}),
ath: z.object({
usd: z.number(),
}),
atl: z.object({
usd: z.number(),
}),
circulating_supply: z.number(),
total_supply: z.number(),
}),
})

const fetchCoinGeckoTokenInfoQueryFn = async (token?: Token) => {
if (!token || !isCoinGeckoChainId(token.chainId))
throw new Error('Invalid token')

const response = await fetch(
`https://api.coingecko.com/api/v3/coins/${
COINGECKO_CHAIN_ID_BY_NAME[token.chainId]
}/contract/${token.address}`,
)

const data = await response.json()

const parsed = coinGeckoSchema.parse(data)

return {
price: parsed.market_data.current_price.usd,
rank: parsed.market_cap_rank,
volume24h: parsed.market_data.total_volume.usd,
marketCap: parsed.market_data.market_cap.usd,
ath: parsed.market_data.ath.usd,
atl: parsed.market_data.atl.usd,
circulatingSupply: parsed.market_data.circulating_supply,
totalSupply: parsed.market_data.total_supply,
}
}

export const useCoinGeckoTokenInfo = ({
token,
enabled = true,
}: {
enabled?: boolean
token?: Token
}) => {
return useQuery({
queryKey: ['useCoinGeckoTokenInfo', token?.id],
queryFn: () => fetchCoinGeckoTokenInfoQueryFn(token),
enabled: !!token && enabled,
placeholderData: keepPreviousData,
staleTime: 900000, // 15 mins
gcTime: 86400000, // 24hs
})
}
37 changes: 37 additions & 0 deletions apps/web/src/lib/hooks/react-query/tokens/useTokenSecurity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,43 @@ export const TokenSecurityLabel: Record<keyof TokenSecurity, string> = {
trust_list: 'Trusted',
}

export const isTokenSecurityIssue = {
// Contract security
is_open_source: (value: TokenSecurity['is_open_source']) => value === false,
is_proxy: (value: TokenSecurity['is_proxy']) => value === true,
is_mintable: (value: TokenSecurity['is_mintable']) => value === true,
can_take_back_ownership: (value: TokenSecurity['can_take_back_ownership']) =>
value === true,
owner_change_balance: (value: TokenSecurity['owner_change_balance']) =>
value === true,
gas_abuse: (value: TokenSecurity['gas_abuse']) => value === true,
hidden_owner: (value: TokenSecurity['hidden_owner']) => value === true,
selfdestruct: (value: TokenSecurity['selfdestruct']) => value === true,
external_call: (value: TokenSecurity['external_call']) => value === true,
trust_list: (value: TokenSecurity['trust_list']) => value === false,
// Trading security
buy_tax: (value: TokenSecurity['buy_tax']) => value === true,
sell_tax: (value: TokenSecurity['sell_tax']) => value === true,
is_buyable: (value: TokenSecurity['is_buyable']) => value === false,
is_sell_limit: (value: TokenSecurity['is_sell_limit']) => value === true,
slippage_modifiable: (value: TokenSecurity['slippage_modifiable']) =>
value === true,
is_honeypot: (value: TokenSecurity['is_honeypot']) => value === true,
transfer_pausable: (value: TokenSecurity['transfer_pausable']) =>
value === true,
is_blacklisted: (value: TokenSecurity['is_blacklisted']) => value === true,
is_whitelisted: (value: TokenSecurity['is_whitelisted']) => value === true,
is_anti_whale: (value: TokenSecurity['is_anti_whale']) => value === true,
trading_cooldown: (value: TokenSecurity['trading_cooldown']) =>
value === true,
// Info security
is_fake_token: (value: TokenSecurity['is_fake_token']) => value === true,
is_airdrop_scam: (value: TokenSecurity['is_airdrop_scam']) => value === true,
} as Record<
NonNullable<keyof TokenSecurity>,
(value: TokenSecurity[keyof TokenSecurity]) => boolean
>

export const TokenSecurityMessage: Record<keyof TokenSecurity, string> = {
//Contract Security
is_open_source:
Expand Down
38 changes: 1 addition & 37 deletions apps/web/src/lib/wagmi/components/token-security-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,10 @@ import {
TokenSecurityLabel,
TokenSecurityMessage,
TokenSecurityResponse,
isTokenSecurityIssue,
} from 'src/lib/hooks/react-query'
import { Token } from 'sushi/currency'

const isTokenSecurityIssue = {
// Contract security
is_open_source: (value: TokenSecurity['is_open_source']) => value === false,
is_proxy: (value: TokenSecurity['is_proxy']) => value === true,
is_mintable: (value: TokenSecurity['is_mintable']) => value === true,
can_take_back_ownership: (value: TokenSecurity['can_take_back_ownership']) =>
value === true,
owner_change_balance: (value: TokenSecurity['owner_change_balance']) =>
value === true,
gas_abuse: (value: TokenSecurity['gas_abuse']) => value === true,
hidden_owner: (value: TokenSecurity['hidden_owner']) => value === true,
selfdestruct: (value: TokenSecurity['selfdestruct']) => value === true,
external_call: (value: TokenSecurity['external_call']) => value === true,
trust_list: (value: TokenSecurity['trust_list']) => value === true,
// Trading security
buy_tax: (value: TokenSecurity['buy_tax']) => value === true,
sell_tax: (value: TokenSecurity['sell_tax']) => value === true,
is_buyable: (value: TokenSecurity['is_buyable']) => value === false,
is_sell_limit: (value: TokenSecurity['is_sell_limit']) => value === true,
slippage_modifiable: (value: TokenSecurity['slippage_modifiable']) =>
value === true,
is_honeypot: (value: TokenSecurity['is_honeypot']) => value === true,
transfer_pausable: (value: TokenSecurity['transfer_pausable']) =>
value === true,
is_blacklisted: (value: TokenSecurity['is_blacklisted']) => value === true,
is_whitelisted: (value: TokenSecurity['is_whitelisted']) => value === true,
is_anti_whale: (value: TokenSecurity['is_anti_whale']) => value === true,
trading_cooldown: (value: TokenSecurity['trading_cooldown']) =>
value === true,
// Info security
is_fake_token: (value: TokenSecurity['is_fake_token']) => value === true,
is_airdrop_scam: (value: TokenSecurity['is_airdrop_scam']) => value === true,
} as Record<
NonNullable<keyof TokenSecurity>,
(value: TokenSecurity[keyof TokenSecurity]) => boolean
>

export const TokenSecurityView = ({
tokenSecurityResponse,
token,
Expand Down
Loading
Loading