diff --git a/cspell.json b/cspell.json index 059350f412..af1ce1eda0 100644 --- a/cspell.json +++ b/cspell.json @@ -37,11 +37,13 @@ "ethersproject", "ethtx", "extralight", + "fastbridge", "ftmscan", "getids", "gitbook", "gorm", "headlessui", + "hyperliquid", "incentivized", "interchain", "ipfs", diff --git a/docs/bridge/blog-posts/2024-12-12-fastbridgev2-post.md b/docs/bridge/blog-posts/2024-12-12-fastbridgev2-post.md index c91263f22a..3e87bf8d69 100644 --- a/docs/bridge/blog-posts/2024-12-12-fastbridgev2-post.md +++ b/docs/bridge/blog-posts/2024-12-12-fastbridgev2-post.md @@ -87,7 +87,7 @@ The real-time nature of WebSockets dramatically reduces quote latency. Rather th :::info -The Synapse Intent Network is backwards combatible with the original Fastbridge Contracts. +The Synapse Intent Network is backwards compatible with the original Fastbridge Contracts. ::: diff --git a/packages/synapse-interface/assets/chains/hyperliquid.svg b/packages/synapse-interface/assets/chains/hyperliquid.svg new file mode 100644 index 0000000000..c9eb0bd097 --- /dev/null +++ b/packages/synapse-interface/assets/chains/hyperliquid.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/synapse-interface/components/HyperliquidDepositInfo.tsx b/packages/synapse-interface/components/HyperliquidDepositInfo.tsx new file mode 100644 index 0000000000..6d04bd8e1d --- /dev/null +++ b/packages/synapse-interface/components/HyperliquidDepositInfo.tsx @@ -0,0 +1,181 @@ +import { ARBITRUM } from '@/constants/chains/master' + +export const HyperliquidDepositInfo = ({ + fromChainId, + isOnArbitrum, + hasDepositedOnHyperliquid, +}) => { + if (fromChainId !== ARBITRUM.id) { + return ( +
+
+
+
Step 1
+
+ +
Bridge (Arbitrum)
+
+
+
+
Step 2
+
+ +
Deposit (Hyperliquid)
+
+
+
+
+ ) + } + + if (hasDepositedOnHyperliquid) { + return ( +
+
+
+
Step 1
+
+ +
Bridge (Arbitrum)
+
+
+
+
Step 2
+
+ +
Deposit (Hyperliquid)
+
+
+
+
+ ) + } + + if (fromChainId === ARBITRUM.id && isOnArbitrum) { + return ( +
+
+
+
Step 1
+
+ +
Bridge (Arbitrum)
+
+
+
+
Step 2
+
+ +
Deposit (Hyperliquid)
+
+
+
+
+ ) + } +} + +const CompletedCheckMarkCircle = () => { + return ( + + + + + + ) +} + +const GreenStep1Circle = () => { + return ( + + + + + ) +} + +const GreenStep2Circle = () => { + return ( + + + + + ) +} + +const GrayStep2Circle = () => { + return ( + + + + + ) +} diff --git a/packages/synapse-interface/components/StateManagedBridge/BridgeQuoteResetTimer.tsx b/packages/synapse-interface/components/StateManagedBridge/BridgeQuoteResetTimer.tsx index 86717650ba..4c511d5951 100644 --- a/packages/synapse-interface/components/StateManagedBridge/BridgeQuoteResetTimer.tsx +++ b/packages/synapse-interface/components/StateManagedBridge/BridgeQuoteResetTimer.tsx @@ -42,7 +42,7 @@ const AnimatedLoadingCircle = () => { fill="none" className="absolute block -rotate-90" > - + - + HYPERLIQUID_MINIMUM_DEPOSIT + ? true + : false + : true + const { hasValidInput, hasValidQuote, @@ -78,7 +87,8 @@ export const BridgeTransactionButton = ({ (isConnected && !hasValidQuote) || (isConnected && !hasSufficientBalance) || (isConnected && isQuoteStale) || - (destinationAddress && !isAddress(destinationAddress)) + (destinationAddress && !isAddress(destinationAddress)) || + !hasHyperliquidMinDeposit let buttonProperties @@ -138,6 +148,11 @@ export const BridgeTransactionButton = ({ label: t('Amount must be greater than fee'), onClick: null, } + } else if (!hasHyperliquidMinDeposit) { + buttonProperties = { + label: `${HYPERLIQUID_MINIMUM_DEPOSIT} USDC Minimum`, + onClick: null, + } } else if ( bridgeQuote.bridgeModuleName !== null && !isLoading && diff --git a/packages/synapse-interface/components/StateManagedBridge/HyperliquidDepositButton.tsx b/packages/synapse-interface/components/StateManagedBridge/HyperliquidDepositButton.tsx new file mode 100644 index 0000000000..f089a15ca2 --- /dev/null +++ b/packages/synapse-interface/components/StateManagedBridge/HyperliquidDepositButton.tsx @@ -0,0 +1,187 @@ +import { useEffect, useState } from 'react' +import { useAccount, useAccountEffect, useSwitchChain } from 'wagmi' +import { useConnectModal } from '@rainbow-me/rainbowkit' +import { useTranslations } from 'next-intl' +import { erc20Abi } from 'viem' +import { + simulateContract, + waitForTransactionReceipt, + writeContract, +} from '@wagmi/core' + +import { wagmiConfig } from '@/wagmiConfig' +import { useAppDispatch } from '@/store/hooks' +import { useWalletState } from '@/slices/wallet/hooks' +import { useBridgeState } from '@/slices/bridge/hooks' +import { TransactionButton } from '@/components/buttons/TransactionButton' +import { useBridgeValidations } from './hooks/useBridgeValidations' +import { USDC } from '@/constants/tokens/bridgeable' +import { ARBITRUM, HYPERLIQUID } from '@/constants/chains/master' +import { stringToBigInt } from '@/utils/bigint/format' +import { fetchAndStoreSingleNetworkPortfolioBalances } from '@/slices/portfolio/hooks' +import { segmentAnalyticsEvent } from '@/contexts/SegmentAnalyticsProvider' +import { addPendingBridgeTransaction } from '@/slices/transactions/actions' +import { getUnixTimeMinutesFromNow } from '@/utils/time' +import { HYPERLIQUID_MINIMUM_DEPOSIT } from '@/constants' + +const HYPERLIQUID_DEPOSIT_ADDRESS = '0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7' + +const deposit = async (amount: bigint) => { + try { + const { request } = await simulateContract(wagmiConfig, { + chainId: ARBITRUM.id, + address: USDC.addresses[ARBITRUM.id], + abi: erc20Abi, + functionName: 'transfer', + args: [HYPERLIQUID_DEPOSIT_ADDRESS, amount], + }) + + const hash = await writeContract(wagmiConfig, request) + + const txReceipt = await waitForTransactionReceipt(wagmiConfig, { hash }) + + return txReceipt + } catch (error) { + console.error('Confirmation error:', error) + throw error + } +} + +export const HyperliquidTransactionButton = ({ + isTyping, + hasDepositedOnHyperliquid, + setHasDepositedOnHyperliquid, +}) => { + const [isDepositing, setIsDepositing] = useState(false) + + const { address } = useAccount() + + const dispatch = useAppDispatch() + const { openConnectModal } = useConnectModal() + const [isConnected, setIsConnected] = useState(false) + + const { isConnected: isConnectedInit } = useAccount() + const { chains, switchChain } = useSwitchChain() + + const { fromToken, fromChainId, debouncedFromValue } = useBridgeState() + + const { isWalletPending } = useWalletState() + + const { hasValidInput, hasSufficientBalance, onSelectedChain } = + useBridgeValidations() + + const depositingMinimumAmount = + Number(debouncedFromValue) >= HYPERLIQUID_MINIMUM_DEPOSIT + + const t = useTranslations('Bridge') + + const amount = stringToBigInt( + debouncedFromValue, + fromToken?.decimals[fromChainId] + ) + + const handleDeposit = async () => { + setIsDepositing(true) + const currentTimestamp: number = getUnixTimeMinutesFromNow(0) + try { + const txReceipt = await deposit(amount) + + setHasDepositedOnHyperliquid(true) + segmentAnalyticsEvent(`[Hyperliquid Deposit]`, { + inputAmount: debouncedFromValue, + }) + dispatch( + fetchAndStoreSingleNetworkPortfolioBalances({ + address, + chainId: ARBITRUM.id, + }) + ) + dispatch( + addPendingBridgeTransaction({ + id: currentTimestamp, + originChain: ARBITRUM, + originToken: fromToken, + originValue: debouncedFromValue, + destinationChain: HYPERLIQUID, + destinationToken: undefined, + transactionHash: txReceipt.transactionHash, + timestamp: undefined, + isSubmitted: false, + estimatedTime: undefined, + bridgeModuleName: undefined, + destinationAddress: undefined, + routerAddress: undefined, + }) + ) + } catch (error) { + console.error('Deposit error:', error) + } finally { + setIsDepositing(false) + } + } + + useAccountEffect({ + onDisconnect() { + setIsConnected(false) + }, + }) + + useEffect(() => { + setIsConnected(isConnectedInit) + }, [isConnectedInit]) + + const isButtonDisabled = + isTyping || + isDepositing || + !depositingMinimumAmount || + isWalletPending || + !hasValidInput || + (isConnected && !hasSufficientBalance) + + let buttonProperties + + if (isConnected && !hasSufficientBalance) { + buttonProperties = { + label: t('Insufficient balance'), + onClick: null, + } + } else if (!depositingMinimumAmount) { + buttonProperties = { + label: `${HYPERLIQUID_MINIMUM_DEPOSIT} USDC Minimum`, + onClick: null, + } + } else if (!isConnected && hasValidInput) { + buttonProperties = { + label: t('Connect Wallet to Bridge'), + onClick: openConnectModal, + } + } else if (!onSelectedChain && hasValidInput) { + buttonProperties = { + label: t('Switch to {chainName}', { + chainName: chains.find((c) => c.id === fromChainId)?.name, + }), + onClick: () => switchChain({ chainId: fromChainId }), + pendingLabel: t('Switching chains'), + } + } else { + buttonProperties = { + onClick: handleDeposit, + label: t('Deposit {symbol}', { symbol: fromToken?.symbol }), + pendingLabel: t('Depositing'), + } + } + + return ( + buttonProperties && ( + <> +
+ +
+ + ) + ) +} diff --git a/packages/synapse-interface/components/StateManagedBridge/OutputContainer.tsx b/packages/synapse-interface/components/StateManagedBridge/OutputContainer.tsx index 5af733cb91..6fed2c1b97 100644 --- a/packages/synapse-interface/components/StateManagedBridge/OutputContainer.tsx +++ b/packages/synapse-interface/components/StateManagedBridge/OutputContainer.tsx @@ -16,6 +16,7 @@ import { useWalletState } from '@/slices/wallet/hooks' import { useBridgeQuoteState } from '@/slices/bridgeQuote/hooks' import { useBridgeValidations } from './hooks/useBridgeValidations' import { useTranslations } from 'next-intl' +import { ARBITRUM, HYPERLIQUID } from '@/constants/chains/master' interface OutputContainerProps { isQuoteStale: boolean @@ -26,6 +27,7 @@ export const OutputContainer = ({ isQuoteStale }: OutputContainerProps) => { const { bridgeQuote, isLoading } = useBridgeQuoteState() const { showDestinationAddress } = useBridgeDisplayState() const { hasValidInput, hasValidQuote } = useBridgeValidations() + const { debouncedFromValue, fromChainId, toChainId } = useBridgeState() const showValue = useMemo(() => { if (!hasValidInput) { @@ -43,7 +45,7 @@ export const OutputContainer = ({ isQuoteStale }: OutputContainerProps) => {
- {showDestinationAddress ? ( + {showDestinationAddress && toChainId !== HYPERLIQUID.id ? ( ) : null}
@@ -52,7 +54,11 @@ export const OutputContainer = ({ isQuoteStale }: OutputContainerProps) => { diff --git a/packages/synapse-interface/components/StateManagedBridge/hooks/useBridgeValidations.ts b/packages/synapse-interface/components/StateManagedBridge/hooks/useBridgeValidations.ts index b3f31ab0f6..58084cf65b 100644 --- a/packages/synapse-interface/components/StateManagedBridge/hooks/useBridgeValidations.ts +++ b/packages/synapse-interface/components/StateManagedBridge/hooks/useBridgeValidations.ts @@ -8,6 +8,7 @@ import { BridgeQuoteState } from '@/slices/bridgeQuote/reducer' import { EMPTY_BRIDGE_QUOTE } from '@/constants/bridge' import { hasOnlyZeroes } from '@/utils/hasOnlyZeroes' import { useBridgeSelections } from './useBridgeSelections' +import { ARBITRUM, HYPERLIQUID } from '@/constants/chains/master' export const useBridgeValidations = () => { const { chainId } = useAccount() @@ -66,7 +67,7 @@ export const useBridgeValidations = () => { debouncedFromValue, fromChainId, fromToken, - toChainId, + toChainId === HYPERLIQUID.id ? ARBITRUM.id : toChainId, toToken ) }, [debouncedFromValue, fromChainId, fromToken, toChainId, toToken]) diff --git a/packages/synapse-interface/components/_Transaction/_Transaction.tsx b/packages/synapse-interface/components/_Transaction/_Transaction.tsx index 1aea90352b..4ffb331305 100644 --- a/packages/synapse-interface/components/_Transaction/_Transaction.tsx +++ b/packages/synapse-interface/components/_Transaction/_Transaction.tsx @@ -20,6 +20,7 @@ import { RightArrow } from '@/components/icons/RightArrow' import { Address } from 'viem' import { useIsTxReverted } from './helpers/useIsTxReverted' import { useTxRefundStatus } from './helpers/useTxRefundStatus' +import { HYPERLIQUID } from '@/constants/chains/master' interface _TransactionProps { connectedAddress: string @@ -185,13 +186,15 @@ export const _Transaction = ({ iconUrl={originChain?.explorerImg} /> )} - {!isNull(destExplorerAddressLink) && !isTxReverted && ( - - )} + {destinationChain.id !== HYPERLIQUID.id && + !isNull(destExplorerAddressLink) && + !isTxReverted && ( + + )} ) diff --git a/packages/synapse-interface/components/layouts/LandingPageWrapper/index.tsx b/packages/synapse-interface/components/layouts/LandingPageWrapper/index.tsx index aef82543a5..b92be9138f 100644 --- a/packages/synapse-interface/components/layouts/LandingPageWrapper/index.tsx +++ b/packages/synapse-interface/components/layouts/LandingPageWrapper/index.tsx @@ -59,10 +59,10 @@ export function LandingPageWrapper({ children }: { children: any }) { style={TODO_REMOVE_wrapperStyle} > diff --git a/packages/synapse-interface/constants/chains/master.tsx b/packages/synapse-interface/constants/chains/master.tsx index ad44fa27a6..5c687828bb 100644 --- a/packages/synapse-interface/constants/chains/master.tsx +++ b/packages/synapse-interface/constants/chains/master.tsx @@ -11,6 +11,7 @@ import dfkImg from '@assets/chains/dfk.svg' import dogechainImg from '@assets/chains/dogechain.svg' import ethImg from '@assets/chains/ethereum.svg' import fantomImg from '@assets/chains/fantom.svg' +import hyperliquidImg from '@assets/chains/hyperliquid.svg' import harmonyImg from '@assets/chains/harmony.svg' import klaytnImg from '@assets/chains/klaytn.svg' import metisImg from '@assets/chains/metis.svg' @@ -615,5 +616,31 @@ export const WORLDCHAIN: Chain = { icon: ethImg, }, color: 'black', +} + +export const HYPERLIQUID: Chain = { + priorityRank: 99, + id: 998, // this is Hyperliquid Testnet from their docs + chainSymbol: 'HYPERLIQUID', + name: 'Hyperliquid', + chainImg: hyperliquidImg, + layer: 2, + blockTime: 300, + rpcUrls: { + primary: + 'https://arb-mainnet.g.alchemy.com/v2/7kjdkqKTh1zQ1mRYGi4nJJbxbyJXHkef', + fallback: 'https://arb1.arbitrum.io/rpc', + }, + nativeCurrency: { + name: 'Ethereum', + symbol: 'ETH', + decimals: 18, + address: zeroAddress, + icon: ethImg, + }, + explorerUrl: 'https://arbiscan.io', + explorerName: 'Arbiscan', + explorerImg: arbitrumExplorerImg, + color: 'gray', isNew: true, } diff --git a/packages/synapse-interface/constants/existingBridgeRoutes.ts b/packages/synapse-interface/constants/existingBridgeRoutes.ts index a1ec85b06f..3f1213399a 100644 --- a/packages/synapse-interface/constants/existingBridgeRoutes.ts +++ b/packages/synapse-interface/constants/existingBridgeRoutes.ts @@ -1,5 +1,8 @@ +import _ from 'lodash' + import { BRIDGE_MAP } from '@/constants/bridgeMap' import { flattenPausedTokens } from '@/utils/flattenPausedTokens' +import { HYPERLIQUID } from './chains/master' export type BridgeRoutes = Record @@ -46,9 +49,19 @@ const constructJSON = (swappableMap, exclusionList) => { return result } +const addUSDCHyperLiquid = (routes) => { + const usdcHyperliquid = `USDC-${HYPERLIQUID.id}` + + return _.mapValues(routes, (innerList, key) => { + // If the key is USDC-42161 OR if the innerList includes USDC-42161 + if (key === 'USDC-42161' || innerList.includes('USDC-42161')) { + return [...innerList, usdcHyperliquid] + } + return innerList + }) +} const PAUSED_TOKENS = flattenPausedTokens() -export const EXISTING_BRIDGE_ROUTES: BridgeRoutes = constructJSON( - BRIDGE_MAP, - PAUSED_TOKENS +export const EXISTING_BRIDGE_ROUTES: BridgeRoutes = addUSDCHyperLiquid( + constructJSON(BRIDGE_MAP, PAUSED_TOKENS) ) diff --git a/packages/synapse-interface/constants/index.ts b/packages/synapse-interface/constants/index.ts index 85e1dee41c..bcbf8a4ed2 100644 --- a/packages/synapse-interface/constants/index.ts +++ b/packages/synapse-interface/constants/index.ts @@ -2,3 +2,5 @@ export const MAX_UINT256 = 115792089237316195423570985008687907853269984665640564039457584007913129639935n export const ETHEREUM_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' + +export const HYPERLIQUID_MINIMUM_DEPOSIT = 5 diff --git a/packages/synapse-interface/messages/ar.json b/packages/synapse-interface/messages/ar.json index 09f07631d4..91739c4166 100644 --- a/packages/synapse-interface/messages/ar.json +++ b/packages/synapse-interface/messages/ar.json @@ -18,6 +18,8 @@ "Please select Destination network": "يرجى اختيار شبكة الوجهة", "Please select an Origin token": "يرجى اختيار رمز المصدر", "Bridge {symbol}": "جسر {symbol}", + "Deposit {symbol}": "إيداع {symbol}", + "Depositing": "الإيداع", "Connect Wallet to Bridge": "اتصل بالمحفظة للجسر", "Amount must be greater than fee": "يجب أن يكون المبلغ أكبر من الرسوم", "Error in bridge quote": "خطأ في عرض الجسر", diff --git a/packages/synapse-interface/messages/en-US.json b/packages/synapse-interface/messages/en-US.json index 8452406b49..c510d85681 100644 --- a/packages/synapse-interface/messages/en-US.json +++ b/packages/synapse-interface/messages/en-US.json @@ -18,6 +18,8 @@ "Please select Destination network": "Please select Destination network", "Please select an Origin token": "Please select an Origin token", "Bridge {symbol}": "Bridge {symbol}", + "Deposit {symbol}": "Deposit {symbol}", + "Depositing": "Depositing", "Connect Wallet to Bridge": "Connect Wallet to Bridge", "Amount must be greater than fee": "Amount must be greater than fee", "Error in bridge quote": "Error in bridge quote", diff --git a/packages/synapse-interface/messages/es.json b/packages/synapse-interface/messages/es.json index 5c121dfbe6..d36bd2ed97 100644 --- a/packages/synapse-interface/messages/es.json +++ b/packages/synapse-interface/messages/es.json @@ -18,6 +18,8 @@ "Please select Destination network": "Por favor, selecciona la red de Destino", "Please select an Origin token": "Por favor, selecciona un token de Origen", "Bridge {symbol}": "Puente {symbol}", + "Deposit {symbol}": "Depósito {symbol}", + "Depositing": "Depositando", "Connect Wallet to Bridge": "Conecta la Wallet para usar el Puente", "Amount must be greater than fee": "La cantidad debe ser mayor que la comisión", "Error in bridge quote": "Error en la cotización del puente", diff --git a/packages/synapse-interface/messages/fr.json b/packages/synapse-interface/messages/fr.json index 5f4608d38e..1294c50566 100644 --- a/packages/synapse-interface/messages/fr.json +++ b/packages/synapse-interface/messages/fr.json @@ -18,6 +18,8 @@ "Please select Destination network": "Veuillez sélectionner le réseau de destination", "Please select an Origin token": "Veuillez sélectionner un jeton d'origine", "Bridge {symbol}": "Bridge {symbol}", + "Deposit {symbol}": "Dépôt {symbol}", + "Depositing": "Dépôt", "Connect Wallet to Bridge": "Connecter le portefeuille au bridge", "Amount must be greater than fee": "Le montant doit être supérieur aux frais", "Error in bridge quote": "Erreur dans la citation du bridge", diff --git a/packages/synapse-interface/messages/jp.json b/packages/synapse-interface/messages/jp.json index ee05d2a385..bfc4354507 100644 --- a/packages/synapse-interface/messages/jp.json +++ b/packages/synapse-interface/messages/jp.json @@ -18,6 +18,8 @@ "Please select Destination network": "宛先ネットワークを選択してください", "Please select an Origin token": "オリジントークンを選択してください", "Bridge {symbol}": "{symbol}をブリッジ", + "Deposit {symbol}": "デポジット {symbol}", + "Depositing": "入金", "Connect Wallet to Bridge": "ウォレットを接続してブリッジ", "Amount must be greater than fee": "金額は手数料より大きくなければなりません", "Error in bridge quote": "ブリッジ見積もりでエラーが発生しました", diff --git a/packages/synapse-interface/messages/tr.json b/packages/synapse-interface/messages/tr.json index d51a91667a..a281269809 100644 --- a/packages/synapse-interface/messages/tr.json +++ b/packages/synapse-interface/messages/tr.json @@ -18,6 +18,8 @@ "Please select Destination network": "Lütfen Hedef ağı seçin", "Please select an Origin token": "Lütfen bir Kaynak token seçin", "Bridge {symbol}": "{symbol} Köprüsü", + "Deposit {symbol}": "Depozito {symbol}", + "Depositing": "Para yatırma", "Connect Wallet to Bridge": "Köprü için Cüzdanı Bağla", "Amount must be greater than fee": "Miktar ücretten büyük olmalıdır", "Error in bridge quote": "Köprü teklifinde hata", diff --git a/packages/synapse-interface/messages/zh-CN.json b/packages/synapse-interface/messages/zh-CN.json index d6a864354b..72f05a5048 100644 --- a/packages/synapse-interface/messages/zh-CN.json +++ b/packages/synapse-interface/messages/zh-CN.json @@ -18,6 +18,8 @@ "Please select Destination network": "请选择目标网络", "Please select an Origin token": "请选择来源代币", "Bridge {symbol}": "桥接 {symbol}", + "Deposit {symbol}": "存入 {symbol}", + "Depositing": "存款", "Connect Wallet to Bridge": "连接钱包以桥接", "Amount must be greater than fee": "金额必须大于费用", "Error in bridge quote": "桥接报价错误", diff --git a/packages/synapse-interface/pages/state-managed-bridge/index.tsx b/packages/synapse-interface/pages/state-managed-bridge/index.tsx index f328d668e7..f4c3f63298 100644 --- a/packages/synapse-interface/pages/state-managed-bridge/index.tsx +++ b/packages/synapse-interface/pages/state-managed-bridge/index.tsx @@ -9,6 +9,7 @@ import { getWalletClient, getPublicClient, waitForTransactionReceipt, + switchChain, } from '@wagmi/core' import { useTranslations } from 'next-intl' @@ -47,7 +48,10 @@ import { Token } from '@/utils/types' import { txErrorHandler } from '@/utils/txErrorHandler' import { approveToken } from '@/utils/approveToken' import { stringToBigInt } from '@/utils/bigint/format' -import { fetchAndStoreSingleNetworkPortfolioBalances } from '@/slices/portfolio/hooks' +import { + fetchAndStoreSingleNetworkPortfolioBalances, + usePortfolioState, +} from '@/slices/portfolio/hooks' import { updatePendingBridgeTransaction, addPendingBridgeTransaction, @@ -69,10 +73,17 @@ import { isTransactionUserRejectedError } from '@/utils/isTransactionUserRejecte import { BridgeQuoteResetTimer } from '@/components/StateManagedBridge/BridgeQuoteResetTimer' import { useBridgeValidations } from '@/components/StateManagedBridge/hooks/useBridgeValidations' import { useStaleQuoteUpdater } from '@/components/StateManagedBridge/hooks/useStaleQuoteUpdater' +import { ARBITRUM, HYPERLIQUID } from '@/constants/chains/master' +import { HyperliquidTransactionButton } from '@/components/StateManagedBridge/HyperliquidDepositButton' +import { USDC } from '@/constants/tokens/bridgeable' +import { CheckCircleIcon } from '@heroicons/react/outline' +import Image from 'next/image' +import { HyperliquidDepositInfo } from '@/components/HyperliquidDepositInfo' const StateManagedBridge = () => { const dispatch = useAppDispatch() - const { address, isConnected } = useAccount() + const { address, isConnected, chain: connectedChain } = useAccount() + const { balances } = usePortfolioState() const { synapseSDK } = useSynapseContext() const router = useRouter() const { query, pathname } = router @@ -86,6 +97,9 @@ const StateManagedBridge = () => { const [isTyping, setIsTyping] = useState(false) + const [hasDepositedOnHyperliquid, setHasDepositedOnHyperliquid] = + useState(false) + const { fromChainId, toChainId, @@ -154,7 +168,7 @@ const StateManagedBridge = () => { fetchBridgeQuote({ synapseSDK, fromChainId, - toChainId, + toChainId: toChainId === HYPERLIQUID.id ? ARBITRUM.id : toChainId, fromToken, toToken, debouncedFromValue, @@ -182,7 +196,10 @@ const StateManagedBridge = () => { quoteToastRef.current.id = toast(message, { duration: 3000 }) } - if (fetchBridgeQuote.rejected.match(result)) { + if ( + fetchBridgeQuote.rejected.match(result) && + !(fromChainId === ARBITRUM.id && toChainId === HYPERLIQUID.id) + ) { const message = t( 'No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}', { @@ -275,7 +292,8 @@ const StateManagedBridge = () => { { id: bridgeQuote.id, originChainId: fromChainId, - destinationChainId: toChainId, + destinationChainId: + toChainId === HYPERLIQUID.id ? ARBITRUM.id : toChainId, inputAmount: debouncedFromValue, expectedReceivedAmount: bridgeQuote.outputAmountString, slippage: bridgeQuote.exchangeRate, @@ -294,7 +312,8 @@ const StateManagedBridge = () => { originChain: CHAINS_BY_ID[fromChainId], originToken: fromToken, originValue: debouncedFromValue, - destinationChain: CHAINS_BY_ID[toChainId], + destinationChain: + CHAINS_BY_ID[toChainId === HYPERLIQUID.id ? ARBITRUM.id : toChainId], destinationToken: toToken, transactionHash: undefined, timestamp: undefined, @@ -319,7 +338,7 @@ const StateManagedBridge = () => { toAddress, bridgeQuote.routerAddress, fromChainId, - toChainId, + toChainId === HYPERLIQUID.id ? ARBITRUM.id : toChainId, fromToken?.addresses[fromChainId as keyof Token['addresses']], stringToBigInt(debouncedFromValue, fromToken?.decimals[fromChainId]), bridgeQuote.originQuery, @@ -356,7 +375,8 @@ const StateManagedBridge = () => { segmentAnalyticsEvent(`[Bridge] bridges successfully`, { id: bridgeQuote.id, originChainId: fromChainId, - destinationChainId: toChainId, + destinationChainId: + toChainId === HYPERLIQUID.id ? ARBITRUM.id : toChainId, inputAmount: debouncedFromValue, expectedReceivedAmount: bridgeQuote.outputAmountString, slippage: bridgeQuote.exchangeRate, @@ -411,6 +431,13 @@ const StateManagedBridge = () => { timeout: 60_000, }) + if (toChainId === HYPERLIQUID.id) { + dispatch(setFromChainId(ARBITRUM.id)) + dispatch(setFromToken(USDC)) + dispatch(setToChainId(HYPERLIQUID.id)) + switchChain(wagmiConfig, { chainId: ARBITRUM.id }) + } + /** Update Origin Chain token balances after resolved tx or timeout reached */ /** Assume tx has been actually resolved if above times out */ dispatch( @@ -489,17 +516,34 @@ const StateManagedBridge = () => { - + {!( + fromChainId === ARBITRUM.id && toChainId === HYPERLIQUID.id + ) && } + {toChainId === HYPERLIQUID.id && ( + + )}
- + {fromChainId === ARBITRUM.id && toChainId === HYPERLIQUID.id ? ( + + ) : ( + + )}