From a0e95a6aa9a956f974586ed5d47ffbb5b1ca5723 Mon Sep 17 00:00:00 2001 From: assetcorp Date: Wed, 24 Jul 2024 07:44:26 +0000 Subject: [PATCH 1/4] refactor: update theme colors --- packages/widget/src/theme/theme-colors.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/widget/src/theme/theme-colors.ts b/packages/widget/src/theme/theme-colors.ts index bbec748..71b8e16 100644 --- a/packages/widget/src/theme/theme-colors.ts +++ b/packages/widget/src/theme/theme-colors.ts @@ -90,6 +90,10 @@ export const themeColors = { default: '#eff1f4', _dark: '#323741' }, + popover: { + default: '#00013a', + _dark: '#ffffff' + }, interactive: { main: { default: '#d7d9e0', _dark: '#494e5a' }, hover: { default: '#c6c8d2', _dark: '#525967' } @@ -110,6 +114,10 @@ export const themeColors = { disabled: { default: 'rgba(0, 0, 0, 0.16)', _dark: 'rgba(255, 255, 255, 0.72)' + }, + popover: { + default: '#FFFFFF', + _dark: '#00013a' } }, misc: { From 184c81f213c0b633ca53437b3c99f597641ac62d Mon Sep 17 00:00:00 2001 From: assetcorp Date: Wed, 24 Jul 2024 07:44:43 +0000 Subject: [PATCH 2/4] refactor: include popover for tokens --- .../src/components/TokenListModal/index.tsx | 111 ++++++++++++++---- 1 file changed, 90 insertions(+), 21 deletions(-) diff --git a/packages/widget/src/components/TokenListModal/index.tsx b/packages/widget/src/components/TokenListModal/index.tsx index 8c8a634..16253c0 100644 --- a/packages/widget/src/components/TokenListModal/index.tsx +++ b/packages/widget/src/components/TokenListModal/index.tsx @@ -2,6 +2,7 @@ import { Box, Flex, HStack, + Icon, Image, Tab, TabIndicator, @@ -9,7 +10,12 @@ import { TabPanel, TabPanels, Tabs, - Text + Text, + Popover, + PopoverTrigger, + PopoverContent, + PopoverArrow, + PopoverBody } from '@chakra-ui/react'; import { useState, useMemo } from 'react'; import { IoMdArrowBack } from 'react-icons/io'; @@ -18,6 +24,8 @@ import { CustomModal, SearchInput } from '../../ui'; import { TokenTag } from '../../ui/TokenTag'; import { WALLETS_INFO, WalletType } from '../../provider/BridgeProvider.tsx'; import { TokenType } from '../../provider/TokensListsProvider.tsx'; +import { LuCopy } from 'react-icons/lu'; +import { shortenAddress } from '../../utils/format.ts'; type TokenListModelProps = { isOpen: boolean; @@ -34,6 +42,7 @@ const tokenMap: Record = { export function TokenListModal({ isOpen, onClose }: TokenListModelProps) { const [tabIndex, setTabIndex] = useState(0); const { tokens, setSelectedToken } = useTokenContext(); + const [popoverOpen, setPopoverOpen] = useState>({}); const tabTokens = useMemo(() => { return tokens.filter(({ type }) => { @@ -90,26 +99,86 @@ export function TokenListModal({ isOpen, onClose }: TokenListModelProps) { /> - {filteredTokens.map(({ id, ...token }) => ( - { - setSelectedToken(id); - onClose(); - }} - _hover={{ - bg: 'bg.border' - }} - > - - - ))} + {filteredTokens.map(({ id, ...token }) => { + const fallbackSrc = `https://api.dicebear.com/7.x/initials/svg?seed=${token.name}`; + + return ( + + setPopoverOpen((prev) => ({ ...prev, [id]: false })) + } + onOpen={() => + setPopoverOpen((prev) => ({ ...prev, [id]: true })) + } + > + + { + setSelectedToken(id); + onClose(); + }} + _hover={{ + bg: 'bg.border' + }} + > + + + + + + + + {token.name} + + {shortenAddress(id, 7, 5)} + + { + navigator.clipboard.writeText(id); + setPopoverOpen((prev) => ({ + ...prev, + [id]: false + })); + }} + background="bg.main" + width="24px" + height="24px" + padding="8px" + borderRadius="8px" + alignItems="center" + justifyContent="center" + _hover={{ + bg: 'bg.module' + }} + > + + + + + + + ); + })} From 3b25e983f9416fef5e48206fb27d0947c3a48895 Mon Sep 17 00:00:00 2001 From: assetcorp Date: Wed, 24 Jul 2024 07:47:26 +0000 Subject: [PATCH 3/4] chore: adjust gap --- packages/widget/src/components/TokenListModal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/widget/src/components/TokenListModal/index.tsx b/packages/widget/src/components/TokenListModal/index.tsx index 16253c0..25e3b6f 100644 --- a/packages/widget/src/components/TokenListModal/index.tsx +++ b/packages/widget/src/components/TokenListModal/index.tsx @@ -140,7 +140,7 @@ export function TokenListModal({ isOpen, onClose }: TokenListModelProps) { width="auto" borderRadius="8px" > - + Date: Wed, 24 Jul 2024 17:36:29 +0600 Subject: [PATCH 4/4] Improved UI and error reporting --- .changeset/tame-icons-hide.md | 6 ++++ apps/widget-demo/src/App.tsx | 2 +- packages/bridge/src/network.ts | 4 +-- packages/bridge/src/tests/utils.ts | 2 +- .../components/BridgeWidget/WidgetForm.tsx | 26 +++++++++++----- .../src/components/TokenListModal/index.tsx | 15 ++++++---- packages/widget/src/main.tsx | 2 +- .../widget/src/provider/BridgeProvider.tsx | 4 +-- .../widget/src/provider/TokensProvider.tsx | 30 ++++++++++++++----- packages/widget/src/urls.ts | 5 ++-- 10 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 .changeset/tame-icons-hide.md diff --git a/.changeset/tame-icons-hide.md b/.changeset/tame-icons-hide.md new file mode 100644 index 0000000..e9885dd --- /dev/null +++ b/.changeset/tame-icons-hide.md @@ -0,0 +1,6 @@ +--- +"@bitfinity-network/bridge": patch +"@bitfinity-network/bridge-widget": patch +--- + +Improved UI and error reporting diff --git a/apps/widget-demo/src/App.tsx b/apps/widget-demo/src/App.tsx index 95c241a..a741818 100644 --- a/apps/widget-demo/src/App.tsx +++ b/apps/widget-demo/src/App.tsx @@ -19,7 +19,7 @@ const networks = [ { name: 'mainnet', icHost: 'https://ic0.app', - ethCain: 355110, + ethChain: 355110, bridges: [ { type: 'icrc_evm', diff --git a/packages/bridge/src/network.ts b/packages/bridge/src/network.ts index 5fb7241..5cf7231 100644 --- a/packages/bridge/src/network.ts +++ b/packages/bridge/src/network.ts @@ -14,8 +14,6 @@ export const BridgeBase = z.object({ type: BridgeType, bftAddress: z.string(), feeChargeAddress: z.string(), - // icHost: z.string(), - // ethCain: z.number() }); export const BridgeIcrc = BridgeBase.extend({ @@ -45,7 +43,7 @@ export const Bridge = z.discriminatedUnion('type', [ export const BridgeNetwork = z.object({ name: z.string(), icHost: z.string(), - ethCain: z.number(), + ethChain: z.number(), bridges: z.array(Bridge) }); export type BridgeNetwork = z.infer; diff --git a/packages/bridge/src/tests/utils.ts b/packages/bridge/src/tests/utils.ts index cd0b71f..1a67e52 100644 --- a/packages/bridge/src/tests/utils.ts +++ b/packages/bridge/src/tests/utils.ts @@ -18,7 +18,7 @@ import { BridgeNetwork, BridgeIcrc } from '../network'; export const testNetwork: BridgeNetwork = BridgeNetwork.parse({ name: 'devnet', icHost: IC_HOST, - ethCain: 355113, + ethChain: 355113, bridges: [ BridgeIcrc.parse({ bftAddress: BFT_ETH_ADDRESS, diff --git a/packages/widget/src/components/BridgeWidget/WidgetForm.tsx b/packages/widget/src/components/BridgeWidget/WidgetForm.tsx index e05bdcc..fa97fe7 100644 --- a/packages/widget/src/components/BridgeWidget/WidgetForm.tsx +++ b/packages/widget/src/components/BridgeWidget/WidgetForm.tsx @@ -113,15 +113,25 @@ export const WidgetForm = () => { return; } - await bridgeTo(token, floatingAmount); + const result = await bridgeTo(token, floatingAmount); - toast({ - title: 'Bridged successfully', - description: 'You received your tokens!', - status: 'success', - duration: 9000, - isClosable: true - }); + if (result.status === 'ok') { + toast({ + title: 'Bridged successfully', + description: 'You received your tokens!', + status: 'success', + duration: 9000, + isClosable: true + }); + } else { + toast({ + title: 'Bridge error', + description: 'Your tokens was not bridged over', + status: 'error', + duration: 9000, + isClosable: true + }); + } setStrAmount(''); } else { diff --git a/packages/widget/src/components/TokenListModal/index.tsx b/packages/widget/src/components/TokenListModal/index.tsx index 25e3b6f..1c737e7 100644 --- a/packages/widget/src/components/TokenListModal/index.tsx +++ b/packages/widget/src/components/TokenListModal/index.tsx @@ -25,7 +25,7 @@ import { TokenTag } from '../../ui/TokenTag'; import { WALLETS_INFO, WalletType } from '../../provider/BridgeProvider.tsx'; import { TokenType } from '../../provider/TokensListsProvider.tsx'; import { LuCopy } from 'react-icons/lu'; -import { shortenAddress } from '../../utils/format.ts'; +import { shortenAddress } from '../../utils'; type TokenListModelProps = { isOpen: boolean; @@ -39,6 +39,10 @@ const tokenMap: Record = { rune: 'btc' }; +const normalize = (str: string) => { + return str.trim().toLowerCase().replace(/-/g, '').replace(/^0x/, ''); +}; + export function TokenListModal({ isOpen, onClose }: TokenListModelProps) { const [tabIndex, setTabIndex] = useState(0); const { tokens, setSelectedToken } = useTokenContext(); @@ -57,11 +61,10 @@ export function TokenListModal({ isOpen, onClose }: TokenListModelProps) { return tabTokens; } - const needle = search.trim().toLowerCase(); - - return tabTokens.filter(({ name, symbol }) => { - const searchable = [name, symbol].map((str) => str.trim().toLowerCase()); - return searchable.some((haystack) => haystack.includes(needle)); + return tabTokens.filter(({ name, symbol, id }) => { + return [name, symbol, id].some((haystack) => + normalize(haystack).includes(normalize(search)) + ); }); }, [search, tabTokens]); diff --git a/packages/widget/src/main.tsx b/packages/widget/src/main.tsx index da24d4d..d06f540 100644 --- a/packages/widget/src/main.tsx +++ b/packages/widget/src/main.tsx @@ -20,7 +20,7 @@ const networks = [ { name: 'mainnet', icHost: 'https://ic0.app', - ethCain: 355110, + ethChain: 355110, bridges: [ { type: 'icrc_evm', diff --git a/packages/widget/src/provider/BridgeProvider.tsx b/packages/widget/src/provider/BridgeProvider.tsx index cef83a0..f507539 100644 --- a/packages/widget/src/provider/BridgeProvider.tsx +++ b/packages/widget/src/provider/BridgeProvider.tsx @@ -532,7 +532,7 @@ export const BridgeProvider = ({ const chainNet = bridgeNetworks.find( ({ name }) => name === networkName - )?.ethCain; + )?.ethChain; const chainWallet = wallet && 'chain' in wallet ? wallet.chain : undefined; @@ -578,7 +578,7 @@ export const BridgeProvider = ({ if (walletsConnected.ic && walletsConnected.eth) { const walletChain = walletsConnected.eth.chain; - if (network.ethCain === walletChain) { + if (network.ethChain === walletChain) { const bridge = connector.getBridge(networkName, 'icrc_evm'); ready.push({ type: 'icrc_evm', bridge, ...BRIDGES_INFO.icrc_evm }); } diff --git a/packages/widget/src/provider/TokensProvider.tsx b/packages/widget/src/provider/TokensProvider.tsx index 030b1e0..46c7721 100644 --- a/packages/widget/src/provider/TokensProvider.tsx +++ b/packages/widget/src/provider/TokensProvider.tsx @@ -24,9 +24,11 @@ import { TANSTACK_GARBAGE_COLLECTION_TIME } from './ReactQuery.tsx'; +export type BridgeResult = { status: 'ok' } | { status: 'err' }; + export type TokensContext = { tokens: Token[]; - bridge: (token: Token, floatingAmount: number) => Promise; + bridge: (token: Token, floatingAmount: number) => Promise; isBridgingInProgress: boolean; nativeEthBalance: bigint; selectedToken: string | undefined; @@ -35,7 +37,9 @@ export type TokensContext = { const defaultCtx = { tokens: [], - async bridge() {}, + async bridge() { + return { status: 'err' }; + }, isBridgingInProgress: false, nativeEthBalance: 0n, selectedToken: undefined, @@ -216,6 +220,12 @@ export const TokensProvider = ({ children }: { children: ReactNode }) => { ({ wrapped }) => wrapped === tokenListed.id ); + let logo: string | undefined = undefined; + + if (!tokenListed.logo) { + logo = tokensListed.find(({ id }) => id === wrapped?.base)?.logo; + } + tokens.push({ ...tokenListed, type: 'evmc', @@ -223,7 +233,8 @@ export const TokensProvider = ({ children }: { children: ReactNode }) => { bridge: bridge.type, id: tokenListed.id, wrapped: wrapped ? wrapped.base : undefined, - balance: 0n + balance: 0n, + logo }); } } @@ -340,21 +351,21 @@ export const TokensProvider = ({ children }: { children: ReactNode }) => { const bridge = useCallback( async (token: Token, floatingAmount: number) => { if (nativeEthBalance <= 0) { - return; + return { status: 'err' } as const; } const from = token.type === 'evmc'; const bridgeInfo = bridges.find((bridge) => bridge.type === token.bridge); if (!bridgeInfo) { - return; + return { status: 'err' } as const; } const { bridge } = bridgeInfo; const amount = fromFloating(floatingAmount, token.decimals); if (token.balance <= amount) { - return; + return { status: 'err' } as const; } let owner: string | undefined; @@ -373,12 +384,14 @@ export const TokensProvider = ({ children }: { children: ReactNode }) => { } if (!(recipient && owner)) { - return; + return { status: 'err' } as const; } // All is ready start bridging itself setIsBridgingInProgress(true); + let error = undefined; + try { if (!from) { let justWrapped: string | undefined; @@ -439,6 +452,7 @@ export const TokensProvider = ({ children }: { children: ReactNode }) => { } } catch (err) { console.error('Error while bridging', err); + error = err; } // Immediately invalidate bridged token old balance @@ -447,6 +461,8 @@ export const TokensProvider = ({ children }: { children: ReactNode }) => { }); setIsBridgingInProgress(false); + + return error ? ({ status: 'err' } as const) : ({ status: 'ok' } as const); }, [ethWallet, icWallet, bridges, nativeEthBalance, tokens] ); diff --git a/packages/widget/src/urls.ts b/packages/widget/src/urls.ts index 0635c72..f62f28a 100644 --- a/packages/widget/src/urls.ts +++ b/packages/widget/src/urls.ts @@ -66,7 +66,7 @@ const networks = { networks: [ { name: 'devnet', - ethCain: 355113, + ethChain: 355113, icHost: IC_HOST, bridges: [ { @@ -79,12 +79,11 @@ const networks = { }, { name: 'devnet 2', - ethCain: 355113, + ethChain: 355113, icHost: IC_HOST, bridges: [ { type: 'icrc_evm', - ethCain: 355113, bftAddress: BFT_ETH_ADDRESS, iCRC2MinterCanisterId: ICRC2_MINTER_CANISTER_ID, feeChargeAddress: FEE_CHARGE_ADDRESS