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

Hyperlane Testnet Transfer #2579

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
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
37 changes: 9 additions & 28 deletions apps/tangle-dapp/app/bridge/BridgeContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,34 +95,15 @@ const BridgeContainer: FC<BridgeContainerProps> = ({ className }) => {
{!hideFeeDetails && <FeeDetails />}
</div>

<div className="flex flex-col items-end gap-2">
{/* {errorMessage && (
<div className="flex items-center gap-1">
<Typography
variant="body2"
className="text-red-70 dark:text-red-50"
>
* {errorMessage.text}
</Typography>
{errorMessage.tooltip && (
<InfoIconWithTooltip
content={errorMessage.tooltip}
className="fill-red-70 dark:fill-red-50"
overrideTooltipBodyProps={{ className: 'max-w-[200px]' }}
/>
)}
</div>
)} */}
<Button
isFullWidth
isDisabled={isDisabled}
isLoading={isLoading}
onClick={buttonAction}
loadingText={buttonLoadingText}
>
{buttonText}
</Button>
</div>
<Button
isFullWidth
isDisabled={isDisabled}
isLoading={isLoading}
onClick={buttonAction}
loadingText={buttonLoadingText}
>
{buttonText}
</Button>
</div>
</div>

Expand Down
84 changes: 84 additions & 0 deletions apps/tangle-dapp/app/bridge/ChainList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use client';

import { calculateTypedChainId } from '@webb-tools/sdk-core/typed-chain-id';
import ChainListCard from '@webb-tools/webb-ui-components/components/ListCard/ChainListCard';
import { type ComponentProps, useMemo } from 'react';
import { twMerge } from 'tailwind-merge';

import { useBridge } from '../../context/BridgeContext';

type Props = Partial<ComponentProps<typeof ChainListCard>> & {
selectedChain: 'source' | 'destination';
};

const ChainList = ({ className, onClose, selectedChain, ...props }: Props) => {
const {
selectedSourceChain,
selectedDestinationChain,
sourceChainOptions,
destinationChainOptions,
setSelectedSourceChain,
setSelectedDestinationChain,
} = useBridge();

const chains = useMemo(
() =>
(selectedChain === 'source'
? sourceChainOptions
: destinationChainOptions
).map((chain) => ({
typedChainId: calculateTypedChainId(chain.chainType, chain.id),
name: chain.name,
tag: chain.tag,
needSwitchWallet: false,
})),
[selectedChain, sourceChainOptions, destinationChainOptions],
);

const activeChain =
selectedChain === 'source' ? selectedSourceChain : selectedDestinationChain;

const handleChange = (chain: (typeof chains)[number]) => {
const selectedChainConfig =
selectedChain === 'source' ? sourceChainOptions : destinationChainOptions;
const newChain = selectedChainConfig.find(
(c) => calculateTypedChainId(c.chainType, c.id) === chain.typedChainId,
);
if (newChain) {
if (selectedChain === 'source') {
setSelectedSourceChain(newChain);
} else {
setSelectedDestinationChain(newChain);
}
}
onClose?.();
};

return (
<ChainListCard
chainType={selectedChain === 'destination' ? 'source' : 'dest'}
disclaimer=""
overrideTitleProps={{
variant: 'h4',
}}
chains={chains}
activeTypedChainId={calculateTypedChainId(
activeChain.chainType,
activeChain.id,
)}
defaultCategory={activeChain.tag}
isConnectingToChain={false}
onChange={handleChange}
{...props}
onClose={onClose}
className={twMerge(
'h-full mx-auto dark:bg-[var(--restake-card-bg-dark)]',
className,
)}
/>
);
};

ChainList.displayName = 'ChainList';

export default ChainList;
159 changes: 62 additions & 97 deletions apps/tangle-dapp/app/bridge/ChainSelectors.tsx
Original file line number Diff line number Diff line change
@@ -1,93 +1,68 @@
'use client';

import { DropdownMenuTrigger as DropdownTrigger } from '@radix-ui/react-dropdown-menu';
import { ChainConfig } from '@webb-tools/dapp-config/chains/chain-config.interface';
import { ArrowRight } from '@webb-tools/icons/ArrowRight';
import { ChainIcon } from '@webb-tools/icons/ChainIcon';
import { calculateTypedChainId } from '@webb-tools/sdk-core/typed-chain-id';
import ChainOrTokenButton from '@webb-tools/webb-ui-components/components/buttons/ChainOrTokenButton';
import {
Dropdown,
DropdownBody,
DropdownMenuItem,
} from '@webb-tools/webb-ui-components/components/Dropdown';
import { ScrollArea } from '@webb-tools/webb-ui-components/components/ScrollArea';
import assert from 'assert';
import { FC, useCallback } from 'react';
import { FC, useCallback, useState } from 'react';

import { BRIDGE } from '../../constants/bridge';
import { useBridge } from '../../context/BridgeContext';

interface ChainSelectorProps {
selectedChain: ChainConfig;
chainOptions: ChainConfig[];
onSelectChain: (chain: ChainConfig) => void;
className?: string;
}
import ChainList from './ChainList';
import TokenList from './TokenList';

const ChainSelectors: FC = () => {
const {
selectedSourceChain,
setSelectedSourceChain,
selectedDestinationChain,
setSelectedSourceChain,
setSelectedDestinationChain,
sourceChainOptions,
destinationChainOptions,
setAmount,
selectedToken,
setSelectedToken,
} = useBridge();

const [isSourceChainListOpen, setIsSourceChainListOpen] = useState(false);
const [isDestinationChainListOpen, setIsDestinationChainListOpen] =
useState(false);
const [isTokenListOpen, setIsTokenListOpen] = useState(false);

const onSwitchChains = useCallback(() => {
const newSelectedDestinationChain = selectedSourceChain;
const newSelectedSourceChain = selectedDestinationChain;

setAmount(null);

assert(
sourceChainOptions.find(
(chain) =>
calculateTypedChainId(chain.chainType, chain.id) ===
calculateTypedChainId(
newSelectedSourceChain.chainType,
newSelectedSourceChain.id,
),
) !== undefined,
'New source chain is not available in source chain options when switching chains',
);
setSelectedSourceChain(newSelectedSourceChain);

const newDestinationChainOptions =
BRIDGE[
calculateTypedChainId(
newSelectedSourceChain.chainType,
newSelectedSourceChain.id,
)
];
const newDestinationChainPresetTypedChainId = calculateTypedChainId(
newSelectedDestinationChain.chainType,
newSelectedDestinationChain.id,
);
assert(
newDestinationChainPresetTypedChainId in newDestinationChainOptions,
'New destination chain is not available in destination chain options when switching chains',
);
setSelectedDestinationChain(newSelectedDestinationChain);
}, [
setSelectedSourceChain,
setSelectedDestinationChain,
selectedDestinationChain,
selectedSourceChain,
sourceChainOptions,
setAmount,
]);

return (
<div className="flex flex-col md:flex-row justify-between items-center gap-3">
<ChainSelector
selectedChain={selectedSourceChain}
chainOptions={sourceChainOptions}
onSelectChain={setSelectedSourceChain}
className="flex-1 w-full md:w-auto"
/>
<Dropdown className="flex-1 w-full md:w-auto">
<DropdownTrigger asChild>
<ChainOrTokenButton
value={selectedSourceChain.name}
className="w-full !p-4 bg-mono-20 dark:bg-mono-160 border-0 hover:bg-mono-20 dark:hover:bg-mono-160"
iconType="chain"
onClick={() => setIsSourceChainListOpen(true)}
/>
</DropdownTrigger>
<DropdownBody className="p-0 border-0">
<ChainList
selectedChain="source"
onClose={() => setIsSourceChainListOpen(false)}
isOpen={isSourceChainListOpen}
/>
</DropdownBody>
</Dropdown>

<div
className="cursor-pointer p-1 rounded-full hover:bg-mono-20 dark:hover:bg-mono-160"
Expand All @@ -96,51 +71,41 @@ const ChainSelectors: FC = () => {
<ArrowRight size="lg" className="rotate-90 md:rotate-0" />
</div>

<ChainSelector
selectedChain={selectedDestinationChain}
chainOptions={destinationChainOptions}
onSelectChain={setSelectedDestinationChain}
className="flex-1 w-full md:w-auto"
/>
</div>
);
};
<Dropdown className="flex-1 w-full md:w-auto">
<DropdownTrigger asChild>
<ChainOrTokenButton
value={selectedDestinationChain.name}
className="w-full !p-4 bg-mono-20 dark:bg-mono-160 border-0 hover:bg-mono-20 dark:hover:bg-mono-160"
iconType="chain"
onClick={() => setIsDestinationChainListOpen(true)}
/>
</DropdownTrigger>
<DropdownBody className="p-0 border-0">
<ChainList
selectedChain="destination"
onClose={() => setIsDestinationChainListOpen(false)}
isOpen={isDestinationChainListOpen}
/>
</DropdownBody>
</Dropdown>

const ChainSelector: FC<ChainSelectorProps> = ({
selectedChain,
chainOptions,
onSelectChain,
className,
}) => {
return (
<Dropdown className={className}>
<DropdownTrigger asChild>
<ChainOrTokenButton
value={selectedChain.name}
className="w-full !p-4 bg-mono-20 dark:bg-mono-160 border-0 hover:bg-mono-20 dark:hover:bg-mono-160"
iconType="chain"
/>
</DropdownTrigger>
<DropdownBody className="border-0">
<ScrollArea className="max-h-[300px] w-[calc(100vw-74px)] md:w-[259px]">
<ul>
{chainOptions.map((chain) => {
return (
<li key={`${chain.chainType}-${chain.id}`}>
<DropdownMenuItem
leftIcon={<ChainIcon size="lg" name={chain.name} />}
onSelect={() => onSelectChain(chain)}
className="py-2.5"
>
{chain.name}
</DropdownMenuItem>
</li>
);
})}
</ul>
</ScrollArea>
</DropdownBody>
</Dropdown>
<Dropdown className="flex-1 w-full md:w-auto">
<DropdownTrigger asChild>
<ChainOrTokenButton
value={selectedToken?.symbol || 'Select Token'}
className="w-full !p-4 bg-mono-20 dark:bg-mono-160 border-0 hover:bg-mono-20 dark:hover:bg-mono-160"
iconType="token"
onClick={() => setIsTokenListOpen(true)}
/>
</DropdownTrigger>
<DropdownBody className="p-0 border-0">
<TokenList
onClose={() => setIsTokenListOpen(false)}
isOpen={isTokenListOpen}
/>
</DropdownBody>
</Dropdown>
</div>
);
};

Expand Down
61 changes: 61 additions & 0 deletions apps/tangle-dapp/app/bridge/TokenList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use client';

import { TokenListCard } from '@webb-tools/webb-ui-components/components/ListCard/TokenListCard';
import { type ComponentProps, useMemo } from 'react';
import { twMerge } from 'tailwind-merge';

import { BRIDGE_SUPPORTED_TOKENS } from '../../constants/bridge';
import { useBridge } from '../../context/BridgeContext';

type Props = Partial<ComponentProps<typeof TokenListCard>> & {
isOpen: boolean;
};

const TokenList = ({ className, onClose, isOpen, ...props }: Props) => {
const { selectedSourceChain, selectedToken, setSelectedTokenId } =
useBridge();

const tokens = useMemo(() => {
return Object.entries(BRIDGE_SUPPORTED_TOKENS)
.filter(([, token]) => token.chains.includes(selectedSourceChain.id))
.map(([tokenId, token]) => ({
id: tokenId,
name: token.name,
symbol: token.symbol,
logoURI: token.logoURI,
}));
}, [selectedSourceChain.id]);

const handleChange = (token: (typeof tokens)[number]) => {
setSelectedTokenId(token.id);
onClose?.();
};

return (
<TokenListCard
type="token"
overrideTitleProps={{
variant: 'h4',
}}
selectTokens={tokens}
popularTokens={[]}
unavailableTokens={[]}
title="Select a Token"
onChange={handleChange}
value={selectedToken}
{...props}
onClose={onClose}
className={twMerge(
'h-full mx-auto dark:bg-[var(--restake-card-bg-dark)]',
className,
)}
overrideInputProps={{
placeholder: 'Search for a token',
}}
/>
);
};

TokenList.displayName = 'TokenList';

export default TokenList;
Loading
Loading