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

[WIP] Liquidity Bridge Integration #99

Draft
wants to merge 10 commits into
base: staging
Choose a base branch
from
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ NODE_ENV=test

# API URLs
NEXT_PUBLIC_BRIDGE_API_URL="https://turing-bridge-api.fra.avail.so"
NEXT_PUBLIC_LIQUIDITY_BRIDGE_API_URL=""
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Missing default test environment URL. Should provide a test/staging URL similar to other API URLs in this file.

NEXT_PUBLIC_BRIDGE_INDEXER_URL="https://turing-bridge-indexer.fra.avail.so"
NEXT_PUBLIC_COINGECKO_API_URL="https://api.coingecko.com/api/v3/simple/price"

Expand All @@ -30,6 +31,10 @@ NEXT_PUBLIC_MANAGER_ADDRESS_BASE="0xf4B55457fCD2b6eF6ffd41E5F5b0D65fbE370EA3"
NEXT_PUBLIC_WORMHOLE_TRANSCEIVER_ETH="0x988140794D960fD962329751278Ef0DD2438a64C"
NEXT_PUBLIC_WORMHOLE_TRANSCEIVER_BASE="0xAb9C68eD462f61Fd5fd34e6c21588513d89F603c"

NEXT_PUBLIC_LP_ADDRESS_ETH=""
NEXT_PUBLIC_LP_ADDRESS_BASE=""
NEXT_PUBLIC_LP_ADDRESS_AVAIL=""
Comment on lines +34 to +36
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: LP addresses should include example values for test environment, following the same format as other contract addresses (0x... for ETH/BASE, different format for AVAIL).


# Datadog Configuration
NEXT_PUBLIC_DATADOG_RUM_APPLICATION_ID=""
NEXT_PUBLIC_DATADOG_RUM_CLIENT_TOKEN=""
Expand Down
138 changes: 49 additions & 89 deletions components/chainselector/chainselectormodal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@ import { useCommonStore } from "@/stores/common";
import { ChevronLeft } from "lucide-react";
import useEthWallet from "@/hooks/common/useEthWallet";
import { capitalizeFirstLetter } from "@/hooks/wormhole/helper";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";

type ChainSelectorModalProps = {
isOpen: boolean;
Expand All @@ -24,38 +18,20 @@ type ChainSelectorModalProps = {
};

const ChainSelectorModal = ({ isOpen, onClose, type }: ChainSelectorModalProps) => {
const { setFromChain, setToChain, fromChain, toChain } = useCommonStore();
const { validateandSwitchChain } = useEthWallet();

const isInvalidCombination = (chain: Chain) => {
if (type === "from") {
return (toChain === Chain.BASE && chain === Chain.AVAIL) ||
(toChain === Chain.AVAIL && chain === Chain.BASE);
}
if (type === "to") {
return (fromChain === Chain.BASE && chain === Chain.AVAIL) ||
(fromChain === Chain.AVAIL && chain === Chain.BASE);
}
return false;
};
const { setFromChain, setToChain, fromChain, toChain } = useCommonStore();
const { validateandSwitchChain } = useEthWallet();

const handleChainSelect = async (selectedChain: Chain) => {
if (isInvalidCombination(selectedChain)) return;

if (type === "from") {
const needToAdjustToChain = selectedChain === toChain ||
// Add check for BASE <-> AVAIL combination
(selectedChain === Chain.AVAIL && toChain === Chain.BASE) ||
(selectedChain === Chain.BASE && toChain === Chain.AVAIL);

if (needToAdjustToChain) {
setToChain(selectedChain === Chain.ETH ? Chain.BASE : Chain.ETH);
if (selectedChain === toChain) {
// If selected source chain is same as destination, swap them
setToChain(fromChain);
}

setFromChain(selectedChain);
await validateandSwitchChain(selectedChain);
} else {
if (selectedChain === fromChain) {
// If selected destination chain is same as source, swap them
await validateandSwitchChain(toChain);
setFromChain(toChain);
setToChain(fromChain);
Expand All @@ -66,66 +42,50 @@ const ChainSelectorModal = ({ isOpen, onClose, type }: ChainSelectorModalProps)
onClose();
};

return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="[&_button[type='button']]:hidden bg-[#252A3C] border-none p-0 gap-0 max-w-[25rem]">
<div className="rounded-t-xl px-4 py-5">
<DialogHeader className="p-0 bg-[#252A3C]">
<div className="flex items-center gap-2">
<ChevronLeft
className="h-6 w-6 cursor-pointer text-gray-400 hover:text-white"
onClick={onClose}
/>
<DialogTitle className="font-sm text-white mt-1 font-ppmori">
Select chain to bridge {type}
</DialogTitle>
</div>
</DialogHeader>
</div>
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="[&_button[type='button']]:hidden bg-[#252A3C] border-none p-0 gap-0 max-w-[25rem]">
<div className="rounded-t-xl px-4 py-5">
<DialogHeader className="p-0 bg-[#252A3C]">
<div className="flex items-center gap-2">
<ChevronLeft
className="h-6 w-6 cursor-pointer text-gray-400 hover:text-white"
onClick={onClose}
/>
<DialogTitle className="font-sm text-white mt-1 font-ppmori">
Select chain to bridge {type}
</DialogTitle>
</div>
</DialogHeader>
</div>

<div className="p-4 bg-[#1D2230]">
{Object.values(Chain).map((chain) => (
<TooltipProvider key={chain}>
<Tooltip>
<TooltipTrigger asChild>
<div
onClick={() => handleChainSelect(chain)}
className={`
flex items-center justify-between p-4 mb-2 last:mb-0 rounded-xl
${isInvalidCombination(chain)
? 'opacity-50 cursor-not-allowed bg-[#252A3C]'
: 'cursor-pointer bg-[#252A3C] hover:bg-[#2A2F41] transition-colors duration-200'
}
`}
>
<div className="flex items-center gap-3">
<img
src={`/images/${chain}small.png`}
alt={`${chain} logo`}
className="w-8 h-8"
/>
<span className="text-white text-md font-medium">
{capitalizeFirstLetter(chain.toLocaleLowerCase())}
</span>
</div>
</div>
</TooltipTrigger>
{isInvalidCombination(chain) && (
<TooltipContent side="right">
<p>Chain combination not allowed</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
))}
</div>
<div className="p-4 bg-[#1D2230]">
{Object.values(Chain).map((chain) => (
<div
key={chain}
onClick={() => handleChainSelect(chain)}
className="flex items-center justify-between p-4 mb-2 last:mb-0 rounded-xl cursor-pointer bg-[#252A3C] hover:bg-[#2A2F41] transition-colors duration-200"
>
<div className="flex items-center gap-3">
<img
src={`/images/${chain}small.png`}
alt={`${chain} logo`}
className="w-8 h-8"
/>
<span className="text-white text-md font-medium">
{capitalizeFirstLetter(chain.toLocaleLowerCase())}
</span>
</div>
</div>
))}
</div>

<div className="flex items-center justify-center p-4 text-[#8B8EA3] text-sm border-t bg-[#1D2230] rounded-b-xl border-[#252A3C]">
looking for the testnet bridge, <a href="https://turing.bridge.avail.so/" className="italic cursor-pointer ml-1 underline">click here</a>
</div>
</DialogContent>
</Dialog>
);
<div className="flex items-center justify-center p-4 text-[#8B8EA3] text-sm border-t bg-[#1D2230] rounded-b-xl border-[#252A3C]">
looking for the testnet bridge, <a href="https://turing.bridge.avail.so/" className="italic cursor-pointer ml-1 underline">click here</a>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax: sentence should be capitalized and use proper punctuation

Suggested change
looking for the testnet bridge, <a href="https://turing.bridge.avail.so/" className="italic cursor-pointer ml-1 underline">click here</a>
Looking for the testnet bridge? <a href="https://turing.bridge.avail.so/" className="italic cursor-pointer ml-1 underline">Click here</a>

</div>
</DialogContent>
</Dialog>
);
};

export default ChainSelectorModal;
export default ChainSelectorModal;
10 changes: 8 additions & 2 deletions components/common/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ import BridgeSection from "../sections/bridge";
import { useAvailAccount } from "@/stores/availwallet";
import { useAccount } from "wagmi";
import { useTransactionsStore } from "@/stores/transactions";
import TransactionModal from "../sections/bridge/review-section";
import { useCommonStore } from "@/stores/common";
import AdvancedSettings from "./settings";

export default function Container() {
const [activeTab, setActiveTab] = useState("bridge");

const { selected } = useAvailAccount();
const { address } = useAccount();
const { fetchAllTransactions, setTransactionLoader} = useTransactionsStore();
const { reviewDialog: { isOpen: isModalOpen, onOpenChange: setIsModalOpen } } = useCommonStore();

useEffect(()=>{
(async () => {
Expand Down Expand Up @@ -47,8 +51,8 @@ export default function Container() {
id="container"
className="section_bg p-2 w-screen max-sm:rounded-none max-sm:!border-x-0 !max-w-xl "
>
<TabsList className="flex flex-row items-start justify-start bg-transparent !border-0 p-2 mb-6 mx-2 mt-1">
<div className="flex flex-row pb-[2vh] items-center justify-between">
<TabsList className="flex flex-row items-center justify-between bg-transparent !border-0 p-2 mb-6 mx-2 mt-2">
<div className="flex flex-row items-center justify-between">
<h1 className="font-ppmori items-center flex flex-row space-x-2 text-white text-opacity-80 text-2xl w-full ">
<span className="relative flex flex-row items-center justify-center">
<TabsTrigger
Expand Down Expand Up @@ -80,6 +84,7 @@ export default function Container() {
</span>
</h1>
</div>
<AdvancedSettings/>
</TabsList>
<TabsContent id="bridge" value="bridge" className="flex-1">
<BridgeSection/>
Expand All @@ -92,6 +97,7 @@ export default function Container() {
<TransactionSection />
</TabsContent>
</Tabs>
<TransactionModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
</div>
);
}
Expand Down
53 changes: 53 additions & 0 deletions components/common/settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { useState } from 'react';
import { ArrowLeft, Info, Settings } from 'lucide-react';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Info icon is imported but never used in the component

Suggested change
import { ArrowLeft, Info, Settings } from 'lucide-react';
import { ArrowLeft, Settings } from 'lucide-react';

import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Switch } from "@/components/ui/switch";

const AdvancedSettings = () => {
const [isOpen, setIsOpen] = useState(false);
const [autoClaim, setAutoClaim] = useState(true);
const [slippage, setSlippage] = useState('0.5');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: These states should be moved to a global store (e.g. useCommonStore) since they affect bridge behavior and need to persist between component remounts

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: slippage state is defined but never used in the UI or passed to bridge functions


return (
<>
<Settings
className="hover:text-white hover:cursor-pointer"
onClick={() => setIsOpen(true)}
/>

<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent className="bg-[#1A1C24] border-0 text-white">
<DialogHeader>
<div className="flex items-center gap-2">
<ArrowLeft
className="text-gray-400 hover:text-white hover:cursor-pointer"
onClick={() => setIsOpen(false)}
/>
Comment on lines +27 to +30
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Redundant close handler - the Dialog already handles closing via onOpenChange. Remove the onClick handler to avoid potential race conditions

Suggested change
<ArrowLeft
className="text-gray-400 hover:text-white hover:cursor-pointer"
onClick={() => setIsOpen(false)}
/>
<ArrowLeft
className="text-gray-400 hover:text-white hover:cursor-pointer"
/>

<DialogTitle className="text-lg font-medium">Advanced Settings</DialogTitle>
</div>
</DialogHeader>

<div className="space-y-8 mt-4">
<div className="flex justify-between items-center">
<span className="text-white text-md text-opacity-70 font-ppmori">
Automatically claim on destination chain
</span>
<Switch
className='data-[state=checked]:bg-green-600 data-[state=unchecked]:bg-gray-600'
checked={autoClaim}
onCheckedChange={setAutoClaim}
/>
</div>
</div>
</DialogContent>
</Dialog>
</>
);
};

export default AdvancedSettings;
37 changes: 36 additions & 1 deletion components/common/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { appConfig } from "@/config/default";
import { TxnLifecyle } from "@/hooks/common/useTrackTxnStatus";
import { Chain } from "@/types/common";

export const getStepStatus = (step: number, status: TxnLifecyle) => {
if (step === 1) {
Expand All @@ -12,4 +14,37 @@ export const getStepStatus = (step: number, status: TxnLifecyle) => {
: "waiting";
}
return "waiting";
};
};

export const chainToChainId = (chain: Chain) => {
switch (chain) {
case Chain.ETH:
return appConfig.networks.ethereum.id;
case Chain.BASE:
return appConfig.networks.base.id;
default:
throw new Error(`Unsupported chain: ${chain}`);
}
}
Comment on lines +19 to +28
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Chain.AVAIL case is missing in chainToChainId but exists in chainToAddresses. Consider if this is intentional.


export const chainToAddresses = (chain: Chain) => {
switch (chain) {
case Chain.ETH:
return {
tokenAddress: appConfig.contracts.ethereum.availToken,
bridgeAddress: appConfig.contracts.ethereum.bridge,
liquidityBridgeAddress: appConfig.contracts.ethereum.liquidityBridgeAddress,
}
Comment on lines +32 to +37
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: ETH chain returns different fields (bridgeAddress) compared to BASE and AVAIL. This inconsistency could cause runtime errors if code expects bridgeAddress to exist.

case Chain.BASE:
return {
tokenAddress: appConfig.contracts.base.availToken,
liquidityBridgeAddress: appConfig.contracts.base.liquidityBridgeAddress,
}
case Chain.AVAIL:
return {
liquidityBridgeAddress: appConfig.contracts.avail.liquidityBridgeAddress,
}
default:
throw new Error(`Unsupported chain: ${chain}`);
}
}
8 changes: 4 additions & 4 deletions components/sections/bridge/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import SubmitTransaction from "./submittransaction";
import SubmitTransaction from "./submit-transaction";
import FromField from "./fromfield";
import ToField from "./tofield";
import ChainSwapBtn from "../../chainselector/chainswapbtn";
Expand All @@ -9,10 +9,10 @@ export default function BridgeSection() {
return (
<div className="lg:p-4 p-2">
<div className="md:space-y-4 w-full">
<FromField/>
<ChainSwapBtn/>
<FromField/>
<ChainSwapBtn/>
<ToField />
<SubmitTransaction />
<SubmitTransaction />
</div>
</div>
);
Expand Down
Loading