Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c6d72c5
feat: erc20 bridge initilized
norugpull Jul 1, 2025
a94378c
feat(web): bridge flow sdk
norugpull Jul 14, 2025
e549204
fix: build error
norugpull Jul 17, 2025
1e4d6d6
fix: chain id type
norugpull Jul 17, 2025
da35523
feat: add missing files
norugpull Jul 22, 2025
095aa1a
Merge branch 'develop' into erc-20-bridge
norugpull Jul 28, 2025
13446a8
fix: bridge ui issues
norugpull Jul 28, 2025
5d83ca3
fix: aggregator balance issue
norugpull Jul 29, 2025
9622f41
fix: review screen data
norugpull Jul 29, 2025
2dffee7
feat: bridge receive flow
norugpull Aug 3, 2025
beacd81
fix: bridge send flow issues
norugpull Aug 11, 2025
988522a
fix: ethereum asset addresses
norugpull Aug 13, 2025
524d57f
Merge branch 'develop' into erc-20-bridge
norugpull Aug 18, 2025
d9b990e
fix: assets invalid contract address.
norugpull Aug 18, 2025
42f63ce
Merge branch 'develop' into erc-20-bridge
norugpull Aug 29, 2025
1051aea
fix: erc20 bridge improvements
norugpull Aug 29, 2025
e06857f
fix: erc20 tx hash link
norugpull Aug 29, 2025
f4eef85
fix: adjust erc20 bridge
norugpull Sep 1, 2025
636eb96
fix: erc20 bridge issues
norugpull Sep 4, 2025
8c82c97
Merge branch 'develop' into erc-20-bridge
norugpull Sep 9, 2025
487db3d
fix: bridge issues
norugpull Sep 16, 2025
2d9f3d1
Merge branch 'develop' into erc-20-bridge
norugpull Sep 18, 2025
1d165ee
fix: erc20 bridge issues
norugpull Sep 18, 2025
a090f5c
fix: update bridge service
norugpull Sep 23, 2025
aafa613
Merge branch 'develop' into erc-20-bridge
norugpull Oct 6, 2025
980a51d
Merge branch 'develop' into erc-20-bridge
norugpull Oct 10, 2025
85f69e2
fix: bridge ui issues
norugpull Oct 10, 2025
b489231
fix: bridge receiver address
grinry Oct 10, 2025
05fb778
fix: remove un-used types
norugpull Oct 10, 2025
2021d0b
fix: bridge issues
norugpull Oct 10, 2025
8b50d04
fix: erc20 bridge switch network to RSK on close
norugpull Oct 14, 2025
f9d7e29
fix: remove BSC and ETH from dropdown
norugpull Oct 14, 2025
95ea506
fix: remove switch network from erc20 bridge
norugpull Oct 15, 2025
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
12 changes: 10 additions & 2 deletions apps/frontend/src/app/1_atoms/GoBackButton/GoBackButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@ import { Icon, IconNames } from '@sovryn/ui';

type GoBackButtonProps = {
onClick: () => void;
disabled?: boolean;
};

export const GoBackButton: React.FC<GoBackButtonProps> = ({ onClick }) => (
<button onClick={onClick} className="mb-12 md:mb-0">
export const GoBackButton: React.FC<GoBackButtonProps> = ({
onClick,
disabled,
}) => (
<button
onClick={onClick}
disabled={disabled}
className="mb-12 md:mb-0 disabled:opacity-60"
>
<Icon icon={IconNames.ARROW_LEFT} className="w-3 h-3" />
</button>
);
27 changes: 27 additions & 0 deletions apps/frontend/src/app/1_atoms/Stepper/Stepper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';

import classNames from 'classnames';

export interface StepperProps {
steps: number;
activeStep: number;
className?: string;
}

export const Stepper: React.FC<StepperProps> = ({
steps,
activeStep,
className,
}) => (
<div className={classNames('flex items-center gap-2 w-full', className)}>
{Array.from({ length: steps }, (_, index) => (
<div
className={classNames('h-1 flex-1 rounded-full transition-colors', {
'bg-primary-20': index < activeStep,
'bg-gray-40': index >= activeStep,
})}
key={index}
/>
))}
</div>
);
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const NetworkPicker: FC<NetworkPickerProps> = ({ className }) => {
dropdownClassName="z-[10000000]"
>
<Menu>
{APP_CHAIN_LIST.map(item => (
{APP_CHAIN_LIST.filter(item => item.isVisible).map(item => (
<MenuItem
key={item.id}
text={
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { QueryClient } from '@tanstack/react-query';

export const ACTIVE_CLASSNAME = 'border-t-primary-30';

export const queryClient = new QueryClient();
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { QueryClientProvider } from '@tanstack/react-query';

import React, { useCallback, useEffect, useMemo, useState } from 'react';

import { t } from 'i18next';

import {
AddressBadge,
Button,
ButtonStyle,
Dialog,
DialogSize,
Heading,
Tabs,
VerticalTabs,
} from '@sovryn/ui';

import { RSK_CHAIN_ID } from '../../../config/chains';

import { MobileCloseButton } from '../../1_atoms/MobileCloseButton/MobileCloseButton';
import { useAccount } from '../../../hooks/useAccount';
import { useChainStore } from '../../../hooks/useChainStore';
import { useIsMobile } from '../../../hooks/useIsMobile';
import { translations } from '../../../locales/i18n';
import { ACTIVE_CLASSNAME, queryClient } from './ERC20BridgeDialog.constants';
import { ReceiveFlow } from './components/ReceiveFlow/ReceiveFlow';
import { SendFlow } from './components/SendFlow/SendFlow';
import { ReceiveFlowContextProvider } from './contextproviders/ReceiveFlowContext';
import { SendFlowContextProvider } from './contextproviders/SendFlowContext';

const translation = translations.erc20Bridge.mainScreen;

type ERC20BridgeDialogProps = {
isOpen: boolean;
onClose: () => void;
step?: number;
};

export const ERC20BridgeDialog: React.FC<ERC20BridgeDialogProps> = ({
isOpen,
onClose,
step = 0,
}) => {
const [index, setIndex] = useState(step);
const { account } = useAccount();
const { isMobile } = useIsMobile();
const { currentChainId, setCurrentChainId } = useChainStore();
const isWrongChain = RSK_CHAIN_ID !== currentChainId;

const handleClose = useCallback(() => {
if (isWrongChain) {
setCurrentChainId(RSK_CHAIN_ID);
}
onClose();
}, [onClose, isWrongChain, setCurrentChainId]);

useEffect(() => {
setIndex(step);
}, [step]);

const items = useMemo(() => {
return [
{
label: t(translation.tabs.receiveLabel),
infoText: t(translation.tabs.receiveInfoText),
content: (
<ReceiveFlowContextProvider>
<ReceiveFlow onClose={handleClose} />
<MobileCloseButton onClick={handleClose} />
</ReceiveFlowContextProvider>
),
activeClassName: ACTIVE_CLASSNAME,
dataAttribute: 'erc20-bridge-receive',
},
{
label: t(translation.tabs.sendLabel),
infoText: t(translation.tabs.sendInfoText),
content: (
<SendFlowContextProvider>
<SendFlow onClose={handleClose} />
<MobileCloseButton onClick={handleClose} />
</SendFlowContextProvider>
),
activeClassName: ACTIVE_CLASSNAME,
dataAttribute: 'erc20-bridge-send',
},
];
}, [handleClose]);

const onChangeIndex = useCallback((index: number | null) => {
index !== null ? setIndex(index) : setIndex(0);
}, []);

const dialogSize = useMemo(
() => (isMobile ? DialogSize.md : DialogSize.xl3),
[isMobile],
);

useEffect(() => {
setIndex(0);
}, [account]);

return (
<Dialog
isOpen={isOpen}
width={dialogSize}
className="p-4 flex items-center sm:p-0"
disableFocusTrap
closeOnEscape={false}
>
<QueryClientProvider client={queryClient}>
<Tabs
index={index}
items={items}
onChange={onChangeIndex}
className="w-full md:hidden"
contentClassName="pt-9 px-6 pb-7 h-full"
/>
<VerticalTabs
items={items}
onChange={onChangeIndex}
selectedIndex={index}
tabsClassName="min-h-[42rem] h-auto self-stretch block pt-0 relative flex-1"
headerClassName="pb-0 pt-5"
footerClassName="absolute bottom-5 left-5"
contentClassName="px-9 pb-10 pt-6 flex-1"
className="hidden md:flex"
header={() => (
<>
<div className="rounded bg-gray-60 px-2 py-1 w-fit mb-9">
<AddressBadge address={account} />
</div>
<Heading className="mb-20">{t(translation.title)}</Heading>
</>
)}
footer={() => (
<Button
text={t(translations.common.buttons.close)}
onClick={handleClose}
style={ButtonStyle.ghost}
dataAttribute="erc20-bridge-close"
/>
)}
/>
</QueryClientProvider>
</Dialog>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';

import { t } from 'i18next';
import { Trans } from 'react-i18next';

import { Heading, HeadingType, Link } from '@sovryn/ui';

import { HELPDESK_LINK } from '../../../../constants/links';
import { translations } from '../../../../locales/i18n';

type InstructionsProps = {
isReceive?: boolean;
};

export const Instructions: React.FC<InstructionsProps> = () => {
return (
<>
<Heading type={HeadingType.h2} className="font-medium leading-[1.375rem]">
{t(translations.erc20Bridge.instructions.title)}:
</Heading>

<ul className="list-disc list-inside text-xs leading-5 font-medium text-gray-30 mt-6 mb-12">
<li className="mb-2">
{t(translations.erc20Bridge.instructions['1'])}
</li>
<li className="mb-2">
{t(translations.erc20Bridge.instructions['2'])}
</li>
<li className="mb-2">
{t(translations.erc20Bridge.instructions['3'])}
</li>
<li className="mb-2">
{t(translations.erc20Bridge.instructions['4'])}
</li>
<li className="mb-2">
<Trans
i18nKey={t(translations.erc20Bridge.instructions['5'])}
tOptions={{ hours: 1.5 }}
components={[
<Link
text={t(
translations.erc20Bridge.instructions.createSupportTicketCta,
)}
className="md:ml-4"
href={HELPDESK_LINK}
/>,
]}
/>
</li>
</ul>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React, { useCallback, useState } from 'react';

import { t } from 'i18next';

import { ChainId } from '@sovryn/ethers-provider';
import { Accordion, SimpleTable, SimpleTableRow } from '@sovryn/ui';

import { AmountRenderer } from '../../../2_molecules/AmountRenderer/AmountRenderer';
import { getTokenDisplayName } from '../../../../constants/tokens';
import { translations } from '../../../../locales/i18n';
import { useBridgeLimits } from '../hooks/useBridgeLimits';

const translation = translations.erc20Bridge.limits;

type LimitsProps = {
sourceChain?: ChainId;
targetChain?: ChainId;
asset?: string;
className?: string;
};

export const Limits: React.FC<LimitsProps> = ({
sourceChain,
targetChain,
asset,
className,
}) => {
const [open, setOpen] = useState(false);
const { data: limits } = useBridgeLimits(sourceChain, targetChain, asset);
const onClick = useCallback((toOpen: boolean) => setOpen(toOpen), []);

if (!asset) {
return null;
}

return (
<>
<Accordion
label={t(translation.title)}
disabled={!asset || !sourceChain || !targetChain}
children={
limits ? (
<SimpleTable border>
<SimpleTableRow
label={t(translation.minimumAmount)}
value={
<AmountRenderer
value={limits.minPerToken}
decimals={0}
suffix={getTokenDisplayName(asset)}
/>
}
/>
<SimpleTableRow
label={t(translation.maximumAmount)}
value={
<AmountRenderer
value={limits.maxTokensAllowed}
decimals={0}
suffix={getTokenDisplayName(asset)}
/>
}
/>
<SimpleTableRow
label={t(translation.dailyLimit)}
value={
<AmountRenderer
value={limits.dailyLimit}
decimals={0}
suffix={getTokenDisplayName(asset)}
/>
}
/>
<SimpleTableRow
label={t(translation.dailyLimitSpent)}
value={
<AmountRenderer
value={limits.spentToday}
decimals={0}
suffix={getTokenDisplayName(asset)}
/>
}
/>
<SimpleTableRow
label={t(translation.fee)}
value={
<AmountRenderer
value={limits.feePerToken}
decimals={0}
suffix={getTokenDisplayName(asset)}
/>
}
/>
</SimpleTable>
) : (
<span className="h-10 w-full block bg-gray-50 rounded animate-pulse" />
)
}
className={className}
open={open}
onClick={onClick}
labelClassName="font-medium"
/>
</>
);
};
Loading