Skip to content

Commit

Permalink
Merge pull request #37 from bitfinity-network/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
alexshelkov authored Jul 24, 2024
2 parents 083c30f + 7fc2058 commit 6c078ea
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 52 deletions.
6 changes: 6 additions & 0 deletions .changeset/tame-icons-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@bitfinity-network/bridge": patch
"@bitfinity-network/bridge-widget": patch
---

Improved UI and error reporting
2 changes: 1 addition & 1 deletion apps/widget-demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const networks = [
{
name: 'mainnet',
icHost: 'https://ic0.app',
ethCain: 355110,
ethChain: 355110,
bridges: [
{
type: 'icrc_evm',
Expand Down
4 changes: 1 addition & 3 deletions packages/bridge/src/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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<typeof BridgeNetwork>;
Expand Down
2 changes: 1 addition & 1 deletion packages/bridge/src/tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
26 changes: 18 additions & 8 deletions packages/widget/src/components/BridgeWidget/WidgetForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
124 changes: 98 additions & 26 deletions packages/widget/src/components/TokenListModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ import {
Box,
Flex,
HStack,
Icon,
Image,
Tab,
TabIndicator,
TabList,
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';
Expand All @@ -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';

type TokenListModelProps = {
isOpen: boolean;
Expand All @@ -31,9 +39,14 @@ const tokenMap: Record<TokenType, WalletType> = {
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();
const [popoverOpen, setPopoverOpen] = useState<Record<string, boolean>>({});

const tabTokens = useMemo(() => {
return tokens.filter(({ type }) => {
Expand All @@ -48,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]);

Expand Down Expand Up @@ -90,26 +102,86 @@ export function TokenListModal({ isOpen, onClose }: TokenListModelProps) {
/>
<Box paddingTop={4}>
<Flex gap={2} flexWrap="wrap">
{filteredTokens.map(({ id, ...token }) => (
<Box
borderWidth={1}
borderColor="secondary.alpha12"
borderRadius="8px"
cursor="pointer"
py={3}
px={3}
key={id}
onClick={() => {
setSelectedToken(id);
onClose();
}}
_hover={{
bg: 'bg.border'
}}
>
<TokenTag token={{ id, ...token }} variant="sm" />
</Box>
))}
{filteredTokens.map(({ id, ...token }) => {
const fallbackSrc = `https://api.dicebear.com/7.x/initials/svg?seed=${token.name}`;

return (
<Popover
key={id}
isOpen={popoverOpen[id] || false}
trigger="hover"
onClose={() =>
setPopoverOpen((prev) => ({ ...prev, [id]: false }))
}
onOpen={() =>
setPopoverOpen((prev) => ({ ...prev, [id]: true }))
}
>
<PopoverTrigger>
<Box
borderWidth={1}
borderColor="secondary.alpha12"
borderRadius="8px"
cursor="pointer"
py={3}
px={3}
onClick={() => {
setSelectedToken(id);
onClose();
}}
_hover={{
bg: 'bg.border'
}}
>
<TokenTag token={{ id, ...token }} variant="sm" />
</Box>
</PopoverTrigger>
<PopoverContent width="auto">
<PopoverArrow bg="bg.popover" />
<PopoverBody
bg="bg.popover"
width="auto"
borderRadius="8px"
>
<HStack alignItems="center" gap="16px" padding="8px">
<Image
src={token.logo || fallbackSrc}
fallbackSrc={fallbackSrc}
alt={token.name}
h="20px"
w="20px"
/>
<Text as="span" isTruncated={true} color="text.popover">
{shortenAddress(id, 7, 5)}
</Text>
<HStack
as="button"
onClick={() => {
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'
}}
>
<Icon as={LuCopy} color="text.primary" />
</HStack>
</HStack>
</PopoverBody>
</PopoverContent>
</Popover>
);
})}
</Flex>
</Box>
</TabPanel>
Expand Down
2 changes: 1 addition & 1 deletion packages/widget/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const networks = [
{
name: 'mainnet',
icHost: 'https://ic0.app',
ethCain: 355110,
ethChain: 355110,
bridges: [
{
type: 'icrc_evm',
Expand Down
4 changes: 2 additions & 2 deletions packages/widget/src/provider/BridgeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 });
}
Expand Down
30 changes: 23 additions & 7 deletions packages/widget/src/provider/TokensProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>;
bridge: (token: Token, floatingAmount: number) => Promise<BridgeResult>;
isBridgingInProgress: boolean;
nativeEthBalance: bigint;
selectedToken: string | undefined;
Expand All @@ -35,7 +37,9 @@ export type TokensContext = {

const defaultCtx = {
tokens: [],
async bridge() {},
async bridge() {
return { status: 'err' };
},
isBridgingInProgress: false,
nativeEthBalance: 0n,
selectedToken: undefined,
Expand Down Expand Up @@ -216,14 +220,21 @@ 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',
wallet: 'eth',
bridge: bridge.type,
id: tokenListed.id,
wrapped: wrapped ? wrapped.base : undefined,
balance: 0n
balance: 0n,
logo
});
}
}
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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]
);
Expand Down
8 changes: 8 additions & 0 deletions packages/widget/src/theme/theme-colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' }
Expand All @@ -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: {
Expand Down
Loading

0 comments on commit 6c078ea

Please sign in to comment.