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(tangle-dapp): Add Solana bridge route with UI enhancements #2709

Merged
merged 5 commits into from
Dec 20, 2024
Merged
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 @@ -3,6 +3,7 @@ import {
Button,
Input,
isEvmAddress,
isSolanaAddress,
isSubstrateAddress,
} from '@webb-tools/webb-ui-components';
import { FC, ReactNode, useCallback, useEffect, useRef, useState } from 'react';
Expand Down Expand Up @@ -57,13 +58,14 @@ const AddressInput: FC<AddressInputProps> = ({

const isEvm = isEvmAddress(newValue);
const isSubstrate = isSubstrateAddress(newValue);
const isSolana = isSolanaAddress(newValue);

if (!isEvm && !isSubstrate) {
setErrorMessage('Invalid address');
} else if (type === AddressType.EVM && !isEvm) {
if (type === AddressType.EVM && !isEvm) {
setErrorMessage('Invalid EVM address');
} else if (type === AddressType.Substrate && !isSubstrate) {
setErrorMessage('Invalid Substrate address');
} else if (type === AddressType.Solana && !isSolana) {
setErrorMessage('Invalid Solana address');
} else {
setErrorMessage(null);
}
Expand Down
1 change: 1 addition & 0 deletions apps/tangle-dapp/src/components/AddressInput/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export enum AddressType {
EVM,
Substrate,
Solana,
Both,
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
useRef,
useState,
} from 'react';
import { SubstrateAddress } from '@webb-tools/webb-ui-components/types/address';

import {
ContainerSkeleton,
Expand All @@ -42,6 +43,7 @@ import { PayoutAllTxContainer } from '../PayoutAllTxContainer';
import { StopNominationTxContainer } from '../StopNominationTxContainer';
import { UpdateNominationsTxContainer } from '../UpdateNominationsTxContainer';
import { UpdatePayeeTxContainer } from '../UpdatePayeeTxContainer';
import type { DeriveSessionProgress } from '@polkadot/api-derive/session/types';

const PAGE_SIZE = 10;

Expand Down Expand Up @@ -278,7 +280,7 @@ const DelegationsPayoutsContainer: FC = () => {
<PayoutTable
data={fetchedPayouts ?? []}
pageSize={PAGE_SIZE}
sessionProgress={progress}
sessionProgress={progress as unknown as DeriveSessionProgress}
historyDepth={historyDepth}
epochDuration={epochDuration}
/>
Expand All @@ -295,7 +297,11 @@ const DelegationsPayoutsContainer: FC = () => {
isModalOpen={isUpdateNominationsModalOpen}
setIsModalOpen={setIsUpdateNominationsModalOpen}
// TODO: Need to pass down the explicit `Optional<T>` type here, instead of defaulting to `[]`, because that will lead to a situation where the lower component things the value is still loading and displays a loading state forever.
currentNominations={currentNominationAddresses?.value ?? []}
currentNominations={
currentNominationAddresses?.value?.map(
(addr) => addr as SubstrateAddress,
) ?? []
}
/>

<UpdatePayeeTxContainer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import {
ModalHeader,
} from '@webb-tools/webb-ui-components/components/Modal';
import { Typography } from '@webb-tools/webb-ui-components/typography';
import { shortenHex } from '@webb-tools/webb-ui-components/utils';
import {
isSolanaAddress,
shortenHex,
shortenString,
} from '@webb-tools/webb-ui-components/utils';
import cx from 'classnames';
import { FC, useCallback } from 'react';

Expand Down Expand Up @@ -319,7 +323,9 @@ const ConfirmationItem: FC<{
Account
</Typography>
<Typography variant="h5" fw="bold">
{shortenHex(accAddress, 10)}
{isSolanaAddress(accAddress)
? shortenString(accAddress, 10)
: shortenHex(accAddress, 10)}
</Typography>
</div>
<div className="flex items-center justify-between">
Expand Down
14 changes: 14 additions & 0 deletions apps/tangle-dapp/src/pages/bridge/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,17 @@ export const BRIDGE_TOKENS: Record<PresetTypedChainId, BridgeTokenType[]> = {
chainId: PresetTypedChainId.TangleTestnetEVM,
},
],
[PresetTypedChainId.SolanaMainnet]: [
{
tokenSymbol: 'routerTNT',
tokenType: EVMTokenEnum.TNT,
bridgeType: EVMTokenBridgeEnum.Router,
address: 'FcermohxLgTo8xnJXpPyW6D2swUMepVjQVNiiNLw38pC',
abi: [],
decimals: 18,
chainId: PresetTypedChainId.SolanaMainnet,
},
],
};

export const BRIDGE_CHAINS: BridgeChainsConfigType = {
Expand All @@ -148,6 +159,9 @@ export const BRIDGE_CHAINS: BridgeChainsConfigType = {
[PresetTypedChainId.BSC]: {
supportedTokens: BRIDGE_TOKENS[PresetTypedChainId.BSC],
},
[PresetTypedChainId.SolanaMainnet]: {
supportedTokens: BRIDGE_TOKENS[PresetTypedChainId.SolanaMainnet],
},
},
[PresetTypedChainId.EthereumMainNet]: {
[PresetTypedChainId.TangleMainnetEVM]: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
} from '@webb-tools/webb-ui-components';
import cx from 'classnames';
import { Decimal } from 'decimal.js';
import { useCallback, useEffect, useMemo } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { twMerge } from 'tailwind-merge';
import { formatEther } from 'viem';

Expand Down Expand Up @@ -138,6 +138,10 @@ export default function BridgeContainer({ className }: BridgeContainerProps) {
close: closeConfirmBridgeModal,
} = useModal(false);

const [addressInputErrorMessage, setAddressInputErrorMessage] = useState<
string | null
>(null);

const sourceTypedChainId = useMemo(() => {
return calculateTypedChainId(
selectedSourceChain.chainType,
Expand Down Expand Up @@ -167,12 +171,17 @@ export default function BridgeContainer({ className }: BridgeContainerProps) {
? ROUTER_NATIVE_TOKEN_ADDRESS
: selectedToken.address;

const toTokenChainId =
selectedDestinationChain.name === 'Solana'
? 'solana'
: selectedDestinationChain.id.toString();

const routerQuoteParams = {
fromTokenAddress,
toTokenAddress,
amountInWei: amount.toString(),
fromTokenChainId: selectedSourceChain.id.toString(),
toTokenChainId: selectedDestinationChain.id.toString(),
toTokenChainId,
};

return routerQuoteParams;
Expand All @@ -181,8 +190,9 @@ export default function BridgeContainer({ className }: BridgeContainerProps) {
sourceTypedChainId,
selectedToken.address,
destinationTypedChainId,
selectedSourceChain.id,
selectedDestinationChain.name,
selectedDestinationChain.id,
selectedSourceChain.id,
]);

const hyperlaneQuoteParams: HyperlaneQuoteProps | null = useMemo(() => {
Expand Down Expand Up @@ -361,7 +371,7 @@ export default function BridgeContainer({ className }: BridgeContainerProps) {
sourceTypedChainId !== PresetTypedChainId.TangleMainnetEVM
? tokenExplorerUrl
: undefined,
address: token.address,
address: token.address as `0x${string}`,
assetBridgeType:
sourceTypedChainId === PresetTypedChainId.TangleMainnetEVM
? EVMTokenBridgeEnum.None
Expand Down Expand Up @@ -708,7 +718,11 @@ export default function BridgeContainer({ className }: BridgeContainerProps) {
<div className="flex flex-col gap-2">
<AddressInput
id="bridge-destination-address-input"
type={AddressType.EVM}
type={
selectedDestinationChain.name === 'Solana'
? AddressType.Solana
: AddressType.EVM
}
title="Recipient"
wrapperOverrides={{
isFullWidth: true,
Expand All @@ -717,9 +731,10 @@ export default function BridgeContainer({ className }: BridgeContainerProps) {
value={destinationAddress ?? ''}
setValue={setDestinationAddress}
placeholder="0x..."
setErrorMessage={(error) =>
setIsAddressInputError(error ? true : false)
}
setErrorMessage={(error) => {
setIsAddressInputError(error ? true : false);
setAddressInputErrorMessage(error);
}}
showErrorMessage={false}
inputClassName={cx(
'placeholder:text-2xl !text-2xl',
Expand All @@ -741,7 +756,7 @@ export default function BridgeContainer({ className }: BridgeContainerProps) {
variant="body1"
className="text-red-70 dark:text-red-50 text-lg"
>
{isAddressInputError ? 'Invalid EVM address' : ''}
{addressInputErrorMessage}
</Typography>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion libs/dapp-config/src/chains/chain-config.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export type ChainGroup =
| 'bsc'
| 'base'
| 'linea'
| 'webb-dev';
| 'webb-dev'
| 'solana';

/**
* The extended chain interface that includes the chain type and group
Expand Down
2 changes: 2 additions & 0 deletions libs/dapp-config/src/chains/chain-config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { ChainConfig } from './chain-config.interface';
import { chainsConfig as EvmNetworks } from './evm';
import { chainsConfig as SubstrateNetworks } from './substrate';
import { chainsConfig as SolanaNetworks } from './solana';

export const chainsConfig: Record<number, ChainConfig> = {
...EvmNetworks,
...SubstrateNetworks,
...SolanaNetworks,
};
30 changes: 30 additions & 0 deletions libs/dapp-config/src/chains/solana/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { PresetTypedChainId, SolanaChainId } from '@webb-tools/dapp-types';
import { ChainConfig } from '../chain-config.interface';
import { ChainType } from '@webb-tools/dapp-types/TypedChainId';

export const chainsConfig = {
[PresetTypedChainId.SolanaMainnet]: {
chainType: ChainType.Solana,
name: 'Solana',
group: 'solana',
tag: 'live',
id: SolanaChainId.SolanaMainnet,
nativeCurrency: {
name: 'Solana Mainnet Token',
symbol: 'SOL',
decimals: 9,
},
rpcUrls: {
default: {
http: ['https://api.mainnet-beta.solana.com'],
webSocket: [],
},
},
blockExplorers: {
default: {
name: 'Solana Explorer',
url: 'https://explorer.solana.com',
},
},
} satisfies ChainConfig,
} as const satisfies Record<number, ChainConfig>;
10 changes: 8 additions & 2 deletions libs/dapp-types/src/ChainId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { calculateTypedChainId, ChainType } from './TypedChainId';

import EVMChainId from './EVMChainId';
import SubstrateChainId from './SubstrateChainId';
import SolanaChainId from './SolanaChainId';

export interface TypedChainId {
chainType: ChainType;
chainId: EVMChainId | SubstrateChainId;
chainId: EVMChainId | SubstrateChainId | SolanaChainId;
}

// Pre-calculated TypedChainId values that are supported in the dapp
Expand Down Expand Up @@ -140,6 +141,11 @@ export enum PresetTypedChainId {
ChainType.EVM,
EVMChainId.TangleMainnetEVM,
),

SolanaMainnet = calculateTypedChainId(
ChainType.Solana,
SolanaChainId.SolanaMainnet,
),
}

export { EVMChainId, SubstrateChainId };
export { EVMChainId, SubstrateChainId, SolanaChainId };
5 changes: 5 additions & 0 deletions libs/dapp-types/src/SolanaChainId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enum SolanaChainId {
SolanaMainnet = 101,
}

export default SolanaChainId;
23 changes: 23 additions & 0 deletions libs/icons/src/chains/solana.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ const WalletDropdown: FC<{

<DropdownBody
isPortal
className="mt-2 md:w-[480px] p-4 space-y-4 dark:bg-mono-180"
className="mt-2 md:w-[540px] p-4 space-y-4 dark:bg-mono-180"
>
<div className="flex flex-col justify-between gap-2 md:flex-row md:items-center">
<div className="flex space-x-2">
<div className="flex space-x-2 items-center">
{wallet.Logo}

<div>
Expand All @@ -89,12 +89,15 @@ const WalletDropdown: FC<{
keyValue={accountAddress}
size="sm"
labelVariant="body1"
valueVariant="body1"
valueVariant="h5"
displayCharCount={5}
/>

{accountExplorerUrl !== null && (
<ExternalLinkIcon href={accountExplorerUrl.toString()} />
<ExternalLinkIcon
href={accountExplorerUrl.toString()}
size="md"
/>
)}
</div>
</div>
Expand All @@ -108,10 +111,11 @@ const WalletDropdown: FC<{
leftIcon={
<LoginBoxLineIcon
className="fill-current dark:fill-current"
size="lg"
size="md"
/>
}
variant="link"
className="text-lg"
>
Disconnect
</Button>
Expand Down Expand Up @@ -160,8 +164,9 @@ const SwitchAccountButton: FC = () => {
return activeApi instanceof WebbWeb3Provider ? (
<Button
onClick={handleSwitchAccount}
leftIcon={<WalletLineIcon className="!fill-current" size="lg" />}
leftIcon={<WalletLineIcon className="!fill-current" size="md" />}
variant="link"
className="text-lg"
>
Switch
</Button>
Expand Down
2 changes: 1 addition & 1 deletion libs/tangle-shared-ui/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export interface BridgeTokenType {
tokenSymbol: string;
tokenType: EVMTokenEnum;
bridgeType: EVMTokenBridgeEnum;
address: `0x${string}`;
address: string;
abi: Abi;
decimals: number;
chainId: PresetTypedChainId;
Expand Down
Loading
Loading