From 7ad2190b738d3bf05a1318a755e43d2769a05887 Mon Sep 17 00:00:00 2001 From: Abheek Tripathy Date: Sun, 19 Jan 2025 04:47:37 +0530 Subject: [PATCH 1/8] checking build errors --- .../sections/bridge/submittransaction.tsx | 4 +- hooks/{useBridge.ts => useZkBridge.ts} | 2 +- package.json | 2 +- pnpm-lock.yaml | 46 +-- services/pallet.ts | 359 +++++++++++------- 5 files changed, 235 insertions(+), 178 deletions(-) rename hooks/{useBridge.ts => useZkBridge.ts} (99%) diff --git a/components/sections/bridge/submittransaction.tsx b/components/sections/bridge/submittransaction.tsx index be91a1a..facb1c2 100644 --- a/components/sections/bridge/submittransaction.tsx +++ b/components/sections/bridge/submittransaction.tsx @@ -1,5 +1,5 @@ import { LoadingButton } from "@/components/ui/loadingbutton"; -import useBridge from "@/hooks/useBridge"; +import useZkBridge from "@/hooks/useZkBridge"; import useSubmitTxnState from "@/hooks/common/useSubmitTxnState"; import { SuccessDialog, useCommonStore } from "@/stores/common"; import { Chain } from "@/types/common"; @@ -33,7 +33,7 @@ export default function SubmitTransaction() { const { selected } = useAvailAccount(); const { address: ethAddress } = useAccount(); const { api } = useApi(); - const { initEthToAvailBridging, initAvailToEthBridging } = useBridge(); + const { initEthToAvailBridging, initAvailToEthBridging } = useZkBridge(); const { initWormholeBridge } = useWormHoleBridge(); const { buttonStatus, isDisabled } = useSubmitTxnState(transactionInProgress); diff --git a/hooks/useBridge.ts b/hooks/useZkBridge.ts similarity index 99% rename from hooks/useBridge.ts rename to hooks/useZkBridge.ts index f8b49e9..19399f1 100644 --- a/hooks/useBridge.ts +++ b/hooks/useZkBridge.ts @@ -31,7 +31,7 @@ import { config } from "@/config/walletConfig"; import { useApi } from "@/stores/api"; import { getTokenBalance } from "@/services/contract"; -export default function useBridge() { +export default function useZkBridge() { const { activeUserAddress, validateandSwitchChain } = useEthWallet(); const { addToLocalTransaction } = useTransactions(); const { writeContractAsync } = useWriteContract(); diff --git a/package.json b/package.json index da49cbf..cb66ed9 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "zustand": "^4.5.5" }, "devDependencies": { - "@polkadot/types": "^11.3.1", + "@polkadot/types": "^10.13.1", "@simbathesailor/babel-plugin-use-what-changed": "^2.1.0", "@simbathesailor/use-what-changed": "^2.0.0", "@talismn/connect-wallets": "^1.2.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6eb5e48..d337b98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -215,8 +215,8 @@ dependencies: devDependencies: '@polkadot/types': - specifier: ^11.3.1 - version: 11.3.1 + specifier: ^10.13.1 + version: 10.13.1 '@simbathesailor/babel-plugin-use-what-changed': specifier: ^2.1.0 version: 2.1.0(@simbathesailor/use-what-changed@2.0.0)(react@18.3.1) @@ -5781,16 +5781,6 @@ packages: '@polkadot/util': 12.6.2 tslib: 2.8.1 - /@polkadot/types-augment@11.3.1: - resolution: {integrity: sha512-eR3HVpvUmB3v7q2jTWVmVfAVfb1/kuNn7ij94Zqadg/fuUq0pKqIOKwkUj3OxRM3A/5BnW3MbgparjKD3r+fyw==} - engines: {node: '>=18'} - dependencies: - '@polkadot/types': 11.3.1 - '@polkadot/types-codec': 11.3.1 - '@polkadot/util': 12.6.2 - tslib: 2.8.1 - dev: true - /@polkadot/types-augment@14.2.2: resolution: {integrity: sha512-FFHgP4TAhJ6XL25FFjT1S2Ai2wfnoGL2VMhYS8TS5XFxH3+1c0DeA4DgPmewJtbDxKwl232UUKzrpJ8jUpCAJg==} engines: {node: '>=18'} @@ -5828,15 +5818,6 @@ packages: '@polkadot/x-bigint': 12.6.2 tslib: 2.8.1 - /@polkadot/types-codec@11.3.1: - resolution: {integrity: sha512-i7IiiuuL+Z/jFoKTA9xeh4wGQnhnNNjMT0+1ohvlOvnFsoKZKFQQOaDPPntGJVL1JDCV+KjkN2uQKZSeW8tguQ==} - engines: {node: '>=18'} - dependencies: - '@polkadot/util': 12.6.2 - '@polkadot/x-bigint': 12.6.2 - tslib: 2.8.1 - dev: true - /@polkadot/types-codec@14.2.2: resolution: {integrity: sha512-ri1U50VQx2FvBK8iJr5kwA8lIg1zlv7OI0x7th35kHtfRr9icPOp2x1jNsOamfObs7OekTsl7+5Uq33Tl0JR+g==} engines: {node: '>=18'} @@ -5871,15 +5852,6 @@ packages: '@polkadot/util': 12.6.2 tslib: 2.8.1 - /@polkadot/types-create@11.3.1: - resolution: {integrity: sha512-pBXtpz5FehcRJ6j5MzFUIUN8ZWM7z6HbqK1GxBmYbJVRElcGcOg7a/rL2pQVphU0Rx1E8bSO4thzGf4wUxSX7w==} - engines: {node: '>=18'} - dependencies: - '@polkadot/types-codec': 11.3.1 - '@polkadot/util': 12.6.2 - tslib: 2.8.1 - dev: true - /@polkadot/types-create@14.2.2: resolution: {integrity: sha512-WN1o/zVhVHHjutaivrpd33bc9EllpDFYVtRMZPOeL7GUy4MjpQGcmT0Vce0lARIKKla8RACQWLIEmkbX3UYxrw==} engines: {node: '>=18'} @@ -5991,20 +5963,6 @@ packages: rxjs: 7.8.1 tslib: 2.8.1 - /@polkadot/types@11.3.1: - resolution: {integrity: sha512-5c7uRFXQTT11Awi6T0yFIdAfD6xGDAOz06Kp7M5S9OGNZY28wSPk5x6BYfNphWPaIBmHHewYJB5qmnrdYQAWKQ==} - engines: {node: '>=18'} - dependencies: - '@polkadot/keyring': 12.6.2(@polkadot/util-crypto@12.6.2)(@polkadot/util@12.6.2) - '@polkadot/types-augment': 11.3.1 - '@polkadot/types-codec': 11.3.1 - '@polkadot/types-create': 11.3.1 - '@polkadot/util': 12.6.2 - '@polkadot/util-crypto': 12.6.2(@polkadot/util@12.6.2) - rxjs: 7.8.1 - tslib: 2.8.1 - dev: true - /@polkadot/types@14.2.2: resolution: {integrity: sha512-iu1UEYdr2BfZr/URizopupvIt4Kmr35As1b2pPmxCqMjyIMdAklRgz6s+Z08GH8RGcA0CPHaA8YJRBiwfb/8dg==} engines: {node: '>=18'} diff --git a/services/pallet.ts b/services/pallet.ts index 2f73ae0..92b9e47 100644 --- a/services/pallet.ts +++ b/services/pallet.ts @@ -1,18 +1,20 @@ -import { isNumber } from "@polkadot/util"; -import { - ApiPromise, - types, - signedExtensions, -} from "avail-js-sdk"; +import { isNumber, stringToU8a, u8aToHex } from "@polkadot/util"; +import { ApiPromise, types, signedExtensions } from "avail-js-sdk"; import { getWalletBySource, WalletAccount } from "@talismn/connect-wallets"; -import { SignerOptions } from "@polkadot/api/types"; import { executeParams, sendMessageParams } from "@/types/transaction"; import { Logger } from "@/utils/logger"; +import { Err, Result, err, ok } from "neverthrow"; +import { ISubmittableResult, Signer } from "@polkadot/types/types"; + +export interface LegacySignerOptions { + app_id: number; + signer?: Signer; +} /** * @description Get injected metadata for extrinsic call - * - * @param api + * + * @param api * @returns injected metadata */ export const getInjectorMetadata = (api: ApiPromise) => { @@ -30,12 +32,11 @@ export const getInjectorMetadata = (api: ApiPromise) => { }; }; - /** * @brief Send message to initiate a AVAIL-> ETH transaction - * - * @param props - * @param account + * + * @param props + * @param account * @returns { status: string, message: string, blockhash?: string } */ export async function sendMessage( @@ -48,77 +49,84 @@ export async function sendMessage( blockhash?: string; txHash?: string; }> { - const injector = getWalletBySource(account.source); - const result: {blockhash: string, txHash: string} = await new Promise((resolve, reject) => { - const unsubscribe = api.tx.vector - .sendMessage(props.message, props.to, props.domain) - .signAndSend( - account.address, - { signer: injector?.signer, app_id: 0 } as Partial, - ({ status, events, txHash }) => { - if (status.isInBlock) { - Logger.info( - `Transaction included at blockHash ${status.asInBlock} ${txHash}` - ); - - events.forEach(({ event }) => { - if (api.events.system.ExtrinsicFailed.is(event)) { - const [dispatchError] = event.data; - let errorInfo: string; - //@ts-ignore - if (dispatchError.isModule) { - const decoded = api.registry.findMetaError( - //@ts-ignore - dispatchError.asModule + const result: { blockhash: string; txHash: string } = await new Promise( + (resolve, reject) => { + const unsubscribe = api.tx.vector + .sendMessage(props.message, props.to, props.domain) + .signAndSend( + account.address, + { + signer: injector?.signer, + app_id: 0, + } as Partial, + ({ status, events, txHash }) => { + if (status.isInBlock) { + Logger.info( + `Transaction included at blockHash ${status.asInBlock} ${txHash}` + ); + + events.forEach(({ event }) => { + if (api.events.system.ExtrinsicFailed.is(event)) { + const [dispatchError] = event.data; + let errorInfo: string; + //@ts-ignore + if (dispatchError.isModule) { + const decoded = api.registry.findMetaError( + //@ts-ignore + dispatchError.asModule + ); + errorInfo = `${decoded.section}.${decoded.name}`; + } else { + errorInfo = dispatchError.toString(); + } + reject( + new Error( + `Transaction failed. Status: ${status} with error: ${errorInfo}` + ) + ); + } + + if (api.events.system.ExtrinsicSuccess.is(event)) { + Logger.info( + `Transaction successful with hash: ${status.asInBlock}` ); - errorInfo = `${decoded.section}.${decoded.name}`; - } else { - errorInfo = dispatchError.toString(); + resolve({ + blockhash: status.asInBlock.toString(), + txHash: txHash.toString(), + }); } - reject( - new Error( - `Transaction failed. Status: ${status} with error: ${errorInfo}` - ) - ); - } - - if (api.events.system.ExtrinsicSuccess.is(event)) { - Logger.info( - `Transaction successful with hash: ${status.asInBlock}` - ); - resolve({blockhash: status.asInBlock.toString(), txHash: txHash.toString()}); - } - }); - //@ts-ignore - unsubscribe(); + }); + //@ts-ignore + unsubscribe(); + } } - } - ) - .catch((error: any) => { - reject(`ERROR_SEND_MESSAGE ${error}`); - return { - status: "failed", - message: error, - }; - }); - }); + ) + .catch((error: any) => { + reject(`ERROR_SEND_MESSAGE ${error}`); + return { + status: "failed", + message: error, + }; + }); + } + ); return { status: "success", message: "Transaction successful", blockhash: result.blockhash, - txHash: result.txHash + txHash: result.txHash, }; } /** - * + * * @brief Execute transaction to finalize/claim a ETH -> AVAIL transaction - * - * @param props - * @param account + * + * @param props + * @param account * @returns { status: string, message: string, blockhash?: string } */ export async function executeTransaction( @@ -133,76 +141,167 @@ export async function executeTransaction( }> { const injector = getWalletBySource(account.source); - const result: {blockhash: string, txHash: string} = await new Promise((resolve, reject) => { - const unsubscribe = api.tx.vector - .execute( - props.slot, - props.addrMessage, - props.accountProof, - props.storageProof - ) - .signAndSend( - account.address, - { - signer: injector?.signer, - app_id: 0, - } as Partial, - ({ status, events, txHash }) => { - if (status.isInBlock) { - Logger.info( - `Transaction included at blockHash ${status.asInBlock}` - ); - - events.forEach(({ event }) => { - if (api.events.system.ExtrinsicFailed.is(event)) { - const [dispatchError] = event.data; - let errorInfo: string; - //@ts-ignore - if (dispatchError.isModule) { - //@ts-ignore - const decoded = api.registry.findMetaError( + const result: { blockhash: string; txHash: string } = await new Promise( + (resolve, reject) => { + const unsubscribe = api.tx.vector + .execute( + props.slot, + props.addrMessage, + props.accountProof, + props.storageProof + ) + .signAndSend( + account.address, + { + signer: injector?.signer, + app_id: 0, + } as Partial, + ({ status, events, txHash }) => { + if (status.isInBlock) { + Logger.info( + `Transaction included at blockHash ${status.asInBlock}` + ); + + events.forEach(({ event }) => { + if (api.events.system.ExtrinsicFailed.is(event)) { + const [dispatchError] = event.data; + let errorInfo: string; //@ts-ignore - dispatchError.asModule + if (dispatchError.isModule) { + //@ts-ignore + const decoded = api.registry.findMetaError( + //@ts-ignore + dispatchError.asModule + ); + errorInfo = `${decoded.section}.${decoded.name}`; + } else { + errorInfo = dispatchError.toString(); + } + Logger.info(`ExtrinsicFailed: ${errorInfo}`); + reject( + new Error( + `Transaction failed. Status: ${status} with error: ${errorInfo}` + ) + ); + } + + if (api.events.system.ExtrinsicSuccess.is(event)) { + Logger.info( + `Transaction successful with hash: ${status.asInBlock}` ); - errorInfo = `${decoded.section}.${decoded.name}`; - } else { - errorInfo = dispatchError.toString(); + resolve({ + blockhash: status.asInBlock.toString(), + txHash: txHash.toString(), + }); } - Logger.info(`ExtrinsicFailed: ${errorInfo}`); - reject( - new Error( - `Transaction failed. Status: ${status} with error: ${errorInfo}` - ) - ); - } - - if (api.events.system.ExtrinsicSuccess.is(event)) { - Logger.info( - `Transaction successful with hash: ${status.asInBlock}` - ); - resolve({blockhash: status.asInBlock.toString(), txHash: txHash.toString()}); - } - }); - //@ts-ignore - unsubscribe(); + }); + //@ts-ignore + unsubscribe(); + } } - } - ) - .catch((error: any) => { - Logger.error(`Error in Execute: ${error}`); - reject(error); - return { - status: "failed", - message: error, - }; - }); - }); + ) + .catch((error: any) => { + Logger.error(`Error in Execute: ${error}`); + reject(error); + return { + status: "failed", + message: error, + }; + }); + } + ); return { status: "success", message: "Transaction successful", blockhash: result.blockhash, - txHash: result.txHash + txHash: result.txHash, }; } +type TransactionStatus = { + status: "success" | "failed"; + blockhash?: string; + txHash?: string; + txIndex?: number; +}; + +export async function transfer( + atomicAmount: string, + account: WalletAccount, + api: ApiPromise +): Promise> { + try { + const injector = getWalletBySource(account.source); + const options = { + signer: injector?.signer, + app_id: 0, + } as Partial; + + const txResult = await new Promise((resolve) => { + api.tx.balances + .transferKeepAlive( + "LIQUIDITY_BRIDGE_ADDRESS", + atomicAmount + ) + .signAndSend(account.address, options, (result: ISubmittableResult) => { + console.log(`Tx status: ${result.status}`); + if (result.isInBlock || result.isError) { + resolve(result); + } + }); + }); + + const error = txResult.dispatchError; + if (txResult.isError) { + return err(new Error(`Transaction failed with error: ${error}`)); + } else if (error != undefined) { + if (error.isModule) { + const decoded = api.registry.findMetaError(error.asModule); + const { docs, name, section } = decoded; + return err(new Error(`${section}.${name}: ${docs.join(" ")}`)); + } else { + return err(new Error(error.toString())); + } + } + + return ok({ + status: "success", + blockhash: txResult.status.asInBlock.toString(), + txHash: txResult.txHash.toString(), + txIndex: txResult.txIndex, + }); + } catch (error) { + return err( + error instanceof Error ? error : new Error("Unknown error occurred") + ); + } +} + +export async function signMessage( + message: string, + account: WalletAccount +): Promise> { + try { + const injector = getWalletBySource(account.source); + console.log(injector); + if (!injector?.signer.signRaw) { + throw new Error( + "Signer not available. Please ensure your wallet extension is installed and accessible." + ); + } + + const messageBytes = stringToU8a(message); + const sign = await injector.signer.signRaw({ + address: account.address, + data: u8aToHex(messageBytes), + type: "bytes", + }); + + return ok(sign.signature as string); + } catch (error) { + return err( + error instanceof Error ? error : new Error("Failed to sign the message") + ); + } +} \ No newline at end of file From 259d30cdd65a93b098ea23d6904c587adeb85eb3 Mon Sep 17 00:00:00 2001 From: Abheek Tripathy Date: Mon, 20 Jan 2025 08:53:58 +0530 Subject: [PATCH 2/8] add utils + fix chain switcher for ui + add flow for avail to eth + update envexample with addresses --- .env.example | 5 + .../chainselector/chainselectormodal.tsx | 138 +++++-------- components/common/utils.ts | 37 +++- components/sections/bridge/types.ts | 3 +- config/default.ts | 15 +- hooks/common/useEthWallet.ts | 22 ++- hooks/common/useSubmitTxnState.ts | 8 +- hooks/useLiquidityBridge.ts | 183 ++++++++++++++++++ hooks/useZkBridge.ts | 23 +-- services/bridgeapi.ts | 18 ++ services/pallet.ts | 8 +- types/common.ts | 20 +- 12 files changed, 360 insertions(+), 120 deletions(-) create mode 100644 hooks/useLiquidityBridge.ts diff --git a/.env.example b/.env.example index 32579d8..e970a99 100644 --- a/.env.example +++ b/.env.example @@ -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="" 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" @@ -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="" + # Datadog Configuration NEXT_PUBLIC_DATADOG_RUM_APPLICATION_ID="" NEXT_PUBLIC_DATADOG_RUM_CLIENT_TOKEN="" diff --git a/components/chainselector/chainselectormodal.tsx b/components/chainselector/chainselectormodal.tsx index a6d1eda..2ed41e6 100644 --- a/components/chainselector/chainselectormodal.tsx +++ b/components/chainselector/chainselectormodal.tsx @@ -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; @@ -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); @@ -66,66 +42,50 @@ const ChainSelectorModal = ({ isOpen, onClose, type }: ChainSelectorModalProps) onClose(); }; - return ( - - -
- -
- - - Select chain to bridge {type} - -
-
-
+ return ( + + +
+ +
+ + + Select chain to bridge {type} + +
+
+
-
- {Object.values(Chain).map((chain) => ( - - - -
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' - } - `} - > -
- {`${chain} - - {capitalizeFirstLetter(chain.toLocaleLowerCase())} - -
-
-
- {isInvalidCombination(chain) && ( - -

Chain combination not allowed

-
- )} -
-
- ))} -
+
+ {Object.values(Chain).map((chain) => ( +
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" + > +
+ {`${chain} + + {capitalizeFirstLetter(chain.toLocaleLowerCase())} + +
+
+ ))} +
-
- looking for the testnet bridge, click here -
-
-
- ); +
+ looking for the testnet bridge, click here +
+
+
+ ); }; -export default ChainSelectorModal; \ No newline at end of file +export default ChainSelectorModal; diff --git a/components/common/utils.ts b/components/common/utils.ts index 3a3cab3..0066784 100644 --- a/components/common/utils.ts +++ b/components/common/utils.ts @@ -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) { @@ -12,4 +14,37 @@ export const getStepStatus = (step: number, status: TxnLifecyle) => { : "waiting"; } return "waiting"; - }; \ No newline at end of file + }; + + 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}`); + } + } + + 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, + } + 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}`); + } + } \ No newline at end of file diff --git a/components/sections/bridge/types.ts b/components/sections/bridge/types.ts index b845dec..2a70bc1 100644 --- a/components/sections/bridge/types.ts +++ b/components/sections/bridge/types.ts @@ -73,7 +73,8 @@ export interface Account { ETH_TO_AVAIL: `${Chain.ETH}-${Chain.AVAIL}`, AVAIL_TO_ETH: `${Chain.AVAIL}-${Chain.ETH}`, BASE_TO_ETH: `${Chain.BASE}-${Chain.ETH}`, - ETH_TO_BASE: `${Chain.ETH}-${Chain.BASE}` + ETH_TO_BASE: `${Chain.ETH}-${Chain.BASE}`, + AVAIL_TO_BASE: `${Chain.AVAIL}-${Chain.BASE}` } as const; diff --git a/config/default.ts b/config/default.ts index 2f93588..da72d10 100644 --- a/config/default.ts +++ b/config/default.ts @@ -13,8 +13,10 @@ type AppConfig = { bridgeIndexerPollingInterval: number, bridgePricePollingInterval: number, bridgeHeadsPollingInterval: number, + liquidityBridgeApiBaseUrl: string, contracts: { ethereum: { + liquidityBridgeAddress: string, availToken: string, bridge: string, manager: string, @@ -24,6 +26,7 @@ type AppConfig = { }, }, base: { + liquidityBridgeAddress: string, availToken: string, manager: string, transceiver: { @@ -31,7 +34,11 @@ type AppConfig = { pauser: string, }, } + avail: { + liquidityBridgeAddress: string, + } }, + } export const appConfig: AppConfig = { @@ -42,12 +49,14 @@ export const appConfig: AppConfig = { base: process.env.NEXT_PUBLIC_ETH_NETWORK === 'mainnet' ? base : baseSepolia, }, bridgeApiBaseUrl: process.env.NEXT_PUBLIC_BRIDGE_API_URL || 'http://0.0.0.0:8080', + liquidityBridgeApiBaseUrl: process.env.NEXT_PUBLIC_LIQUIDITY_BRIDGE_API_URL || 'http://localhost:8080', bridgeIndexerBaseUrl: process.env.NEXT_PUBLIC_BRIDGE_INDEXER_URL ||'http://167.71.41.169:3000', bridgeIndexerPollingInterval: 30, bridgeHeadsPollingInterval: 600, bridgePricePollingInterval: 60, contracts: { ethereum: { + liquidityBridgeAddress: process.env.NEXT_PUBLIC_LP_ADDRESS_ETH || '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', availToken: process.env.NEXT_PUBLIC_AVAIL_TOKEN_ETH || '0xb1c3cb9b5e598d4e95a85870e7812b99f350982d', bridge: process.env.NEXT_PUBLIC_BRIDGE_PROXY_ETH || '0x967F7DdC4ec508462231849AE81eeaa68Ad01389', manager: process.env.NEXT_PUBLIC_MANAGER_ADDRESS_ETH || "0x40E856FD3eCBeE56c33388738f0B1C3aad573353", @@ -57,12 +66,16 @@ export const appConfig: AppConfig = { }, }, base: { + liquidityBridgeAddress: process.env.NEXT_PUBLIC_LP_ADDRESS_BASE || '', availToken: process.env.NEXT_PUBLIC_AVAIL_TOKEN_BASE || '0xf50F2B4D58ce2A24b62e480d795A974eD0f77A58', manager: process.env.NEXT_PUBLIC_MANAGER_ADDRESS_BASE || "0xf4B55457fCD2b6eF6ffd41E5F5b0D65fbE370EA3", transceiver: { wormhole: process.env.NEXT_PUBLIC_WORMHOLE_TRANSCEIVER_BASE || "0xAb9C68eD462f61Fd5fd34e6c21588513d89F603c", pauser: process.env.NEXT_PUBLIC_PAUSER_BASE || "0x0f62A884eDAbD338e92302274e7cE7Cc1D467B74", }, - }, + }, + avail: { + liquidityBridgeAddress: process.env.NEXT_PUBLIC_LP_ADDRESS_AVAIL || '5GppC9pNvTafpCD86gAYxAJPZtLuYkNixFvzByoP1wPg5qKa', + } } }; \ No newline at end of file diff --git a/hooks/common/useEthWallet.ts b/hooks/common/useEthWallet.ts index 1ad7d16..b96bce2 100644 --- a/hooks/common/useEthWallet.ts +++ b/hooks/common/useEthWallet.ts @@ -1,7 +1,11 @@ import { appConfig } from "@/config/default"; +import { config } from "@/config/walletConfig"; import { Chain } from "@/types/common"; -import { useMemo } from "react"; +import { readContract } from "@wagmi/core"; +import { useCallback, useMemo } from "react"; import { useSwitchChain, useAccount } from "wagmi"; +import availTokenAbi from "@/constants/abis/availTokenAbi.json"; +import BigNumber from "bignumber.js"; /** * @desc All the functionalities related to wallet such as connecting, switching network, etc @@ -50,9 +54,25 @@ export default function useEthWallet() { } }; + const getERC20AvailBalance = useCallback(async (chainId: number) => { + const balance = await readContract(config, { + address: appConfig.contracts.ethereum.availToken as `0x${string}`, + abi: availTokenAbi, + functionName: "balanceOf", + args: [activeUserAddress], + chainId + }); + + if (!balance) return new BigNumber(0); + + //@ts-ignore TODO: P2 + return new BigNumber(balance); + }, [activeUserAddress]); + return { activeUserAddress, activeNetworkId, validateandSwitchChain, + getERC20AvailBalance }; } diff --git a/hooks/common/useSubmitTxnState.ts b/hooks/common/useSubmitTxnState.ts index 9f77142..f226d7a 100644 --- a/hooks/common/useSubmitTxnState.ts +++ b/hooks/common/useSubmitTxnState.ts @@ -33,13 +33,13 @@ export default function useSubmitTxnState( }, [fromAmount]); const isValidToAddress = useMemo(() => { - switch (fromChain) { + switch (toChain) { case Chain.AVAIL: - return Boolean(toAddress && validAddress(toAddress, Chain.ETH)); + return Boolean(toAddress && validAddress(toAddress, Chain.AVAIL)); case Chain.BASE: - return Boolean(toAddress && validAddress(toAddress, Chain.ETH)); + return Boolean(toAddress && validAddress(toAddress, Chain.BASE)); case Chain.ETH: - return Boolean(toAddress && (validAddress(toAddress, Chain.BASE) || validAddress(toAddress, Chain.AVAIL))); + return Boolean(toAddress && (validAddress(toAddress, Chain.ETH))); default: return false; } diff --git a/hooks/useLiquidityBridge.ts b/hooks/useLiquidityBridge.ts new file mode 100644 index 0000000..707341f --- /dev/null +++ b/hooks/useLiquidityBridge.ts @@ -0,0 +1,183 @@ +/** + * TODO: params include from chain, to chain, amount, destination address + */ + +import { ONE_POWER_EIGHTEEN } from "@/constants/bigNumber"; +import { sendPayload } from "@/services/bridgeapi"; +import { getTokenBalance } from "@/services/contract"; +import { signMessage, transfer } from "@/services/pallet"; +import { useApi } from "@/stores/api"; +import { useAvailAccount } from "@/stores/availwallet"; +import { Chain } from "@/types/common"; +import { substrateAddressToPublicKey } from "@/utils/common"; +import BigNumber from "bignumber.js"; +import useEthWallet from "./common/useEthWallet"; +import { chainToAddresses, chainToChainId } from "@/components/common/utils"; +import { writeContract } from "@wagmi/core"; +import { config } from "@/config/walletConfig"; +import { appConfig } from "@/config/default"; +import availTokenAbi from "@/constants/abis/availTokenAbi.json"; + +export default function useLiquidityBridge() { + const { selected } = useAvailAccount(); + const { api, ensureConnection } = useApi(); + const { activeUserAddress, validateandSwitchChain, getERC20AvailBalance } = useEthWallet(); + + interface ILiquidtyBridgeParams { + ERC20Chain: Chain; + atomicAmount: string; + destinationAddress: string; + } + + const initAvailToERC20AutomaticBridging = async ({ + ERC20Chain, + atomicAmount, + destinationAddress, + }: ILiquidtyBridgeParams) => { + /** + * 0. initial checks + * 1. balance transfer to pool account + * 2. use blockhash, tx_index and other fields to form a payload + * 3. generate signature (X-Payload-Signature to the Sr25519 signature) + * 3. send payload at /v1/avail_to_eth + * + */ + try { + if (selected === undefined || selected === null) { + throw new Error("No account selected"); + } + + if (!api || !api.isConnected || !api.isReady) await ensureConnection(); + if (!api?.isReady) + throw new Error("Uh oh! Failed to connect to Avail Api"); + + const availBalance = await getTokenBalance( + Chain.AVAIL, + selected.address, + api + ); + if (!availBalance) { + throw new Error("Failed to fetch balance"); + } + + if ( + availBalance && + new BigNumber(atomicAmount).gt( + new BigNumber(availBalance).times(ONE_POWER_EIGHTEEN) + ) + ) { + throw new Error("insufficient avail balance"); + } + + const result = await transfer(atomicAmount, selected, api); + if (result.isErr()) { + throw new Error(`AVAIL_TRANSFER_FAILED ${result.error}`); + } + + if (!result.value.txIndex || !result.value.blockhash) { + throw new Error("Failed to get blockhash and tx_index"); + } + + const payload = { + sender_address: substrateAddressToPublicKey(selected.address), + tx_index: result.value.txIndex, + block_hash: result.value.blockhash, + eth_receiver_address: destinationAddress, + amount: atomicAmount, + }; + + const fakeBody = { + sender_address: + "0xf86aabc41a7238174bbc254d47555ee89e1fa4fe3db0f51d19b1b8849cbcaa59", + tx_index: 1, + block_hash: + "0x87a7aec8963dcd11e94fa2c443484683ecf2bd85e3740191cb2a7120788cd345", + eth_receiver_address: "0xEAfDB6af7c1131Eec88Ef17f1057190A46a6C012", + amount: "120000000000000000", + }; + console.log(fakeBody, "FAKE BODY"); + + const sig = await signMessage(JSON.stringify(fakeBody), selected); + console.log(sig, "SIG"); + if (sig.isErr()) { + throw new Error(`${sig.error} : Failed to sign payload`); + } + + const response = await sendPayload(fakeBody, `${sig.value}`); + console.log(response, "response"); + if (response.isErr()) { + throw new Error(` ${response.error} : Failed to send payload`); + } + + return { + chain: Chain.AVAIL, + hash: result.value.txHash, + }; + } catch (error) { + throw new Error(`Failed to bridge from Avail to ${ERC20Chain}: ${error}`); + } + }; + + async function transferERC20AvailToLiquidityBridge(amount: string, ERC20Chain: Chain) { + try { + const hash = await writeContract(config, { + address: chainToAddresses(ERC20Chain).tokenAddress as `0x${string}`, + abi: availTokenAbi, + functionName: 'transfer', + args: [chainToAddresses(ERC20Chain).liquidityBridgeAddress, amount] + }) + return hash + } catch (error) { + console.error('Transfer To Liquidity Bridge Failed:', error) + throw error + } + } + + + const initERC20toAvailAutomaticBridging = async ({ + ERC20Chain, + atomicAmount, + destinationAddress, + }: ILiquidtyBridgeParams) => { + /** + * 1. initial checks + * 2. balance transfer to pool account + * 3. use blockhash, tx_index and other fields to form a payload + * 4. generate signature (X-Payload-Signature to the ECDSA signature) + * 5. send payload at /v1/eth_to_avail + */ + + try { + if (!activeUserAddress) throw new Error("No account selected"); + + await validateandSwitchChain(ERC20Chain) + + const availBalance = await getERC20AvailBalance(chainToChainId(ERC20Chain)); + if (new BigNumber(atomicAmount).gte(new BigNumber(availBalance))) { + throw new Error("insufficient balance"); + } + + const hash = await transferERC20AvailToLiquidityBridge(atomicAmount, ERC20Chain) + if (!hash) throw new Error("Failed to transfer to liquidity bridge") + + const payload = { + sender_address: activeUserAddress, + tx_index: 1, + block_hash: "0x87a7aec8963dcd11e94fa2c443484683ecf2bd85e3740191cb2a7120788cd345", + eth_receiver_address: destinationAddress, + amount: atomicAmount, + }; + + + + + } catch (error) { + throw new Error(`Failed to bridge from ${ERC20Chain} to Avail: ${error}`); + } + }; + + return { + initAvailToERC20AutomaticBridging, + initERC20toAvailAutomaticBridging, + }; +} diff --git a/hooks/useZkBridge.ts b/hooks/useZkBridge.ts index 19399f1..ec4ae1c 100644 --- a/hooks/useZkBridge.ts +++ b/hooks/useZkBridge.ts @@ -1,5 +1,5 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { useCallback, useEffect } from "react"; +import { useCallback } from "react"; import BigNumber from "bignumber.js"; import { useAccount, useWriteContract } from "wagmi"; import { estimateGas, readContract } from "@wagmi/core"; @@ -32,30 +32,16 @@ import { useApi } from "@/stores/api"; import { getTokenBalance } from "@/services/contract"; export default function useZkBridge() { - const { activeUserAddress, validateandSwitchChain } = useEthWallet(); + const { activeUserAddress, validateandSwitchChain, getERC20AvailBalance } = useEthWallet(); const { addToLocalTransaction } = useTransactions(); const { writeContractAsync } = useWriteContract(); const { selected } = useAvailAccount(); const { address } = useAccount(); - const {api, ensureConnection} = useApi(); + const { api, ensureConnection } = useApi(); const invokeSnap = useInvokeSnap(); const networks = appConfig.networks; /** HELPER FUNCTIONS */ - const getAvailBalanceOnEth = useCallback(async () => { - const balance = await readContract(config, { - address: appConfig.contracts.ethereum.availToken as `0x${string}`, - abi: availTokenAbi, - functionName: "balanceOf", - args: [activeUserAddress], - chainId: networks.ethereum.id, - }); - - if (!balance) return new BigNumber(0); - - //@ts-ignore TODO: P2 - return new BigNumber(balance); - }, [activeUserAddress, networks.ethereum.id]); const getCurrentAllowanceOnEth = useCallback(async () => { try { @@ -186,7 +172,6 @@ export default function useZkBridge() { throw new Error("No account selected"); await validateandSwitchChain(Chain.ETH); - const currentAllowance = await getCurrentAllowanceOnEth(); if (new BigNumber(atomicAmount).gt(currentAllowance)) { await approveOnEth(atomicAmount); @@ -196,7 +181,7 @@ export default function useZkBridge() { }); } - const availBalance = await getAvailBalanceOnEth(); + const availBalance = await getERC20AvailBalance(appConfig.networks.ethereum.id); if (new BigNumber(atomicAmount).gte(new BigNumber(availBalance))) { throw new Error("insufficient balance"); } diff --git a/services/bridgeapi.ts b/services/bridgeapi.ts index b429855..54aeae6 100644 --- a/services/bridgeapi.ts +++ b/services/bridgeapi.ts @@ -1,10 +1,12 @@ import { appConfig } from "@/config/default"; import { LatestBlockInfo } from "@/stores/blockinfo"; +import { LiquidityBridgeTransactionBody, PayloadResponse } from "@/types/common"; import { AccountStorageProof, merkleProof } from "@/types/transaction"; import { Logger } from "@/utils/logger"; import { ApiPromise } from "avail-js-sdk"; import axios from "axios"; import jsonbigint from "json-bigint"; +import { ResultAsync } from "neverthrow"; const JSONBigInt = jsonbigint({ useNativeBigInt: true }); export const getMerkleProof = async (blockhash: string, index: number) => { @@ -71,3 +73,19 @@ export async function fetchTokenPrice({ return Number(data.price[coin][fiat]); }; +export const sendPayload = ( + body: LiquidityBridgeTransactionBody, + sig: string + ): ResultAsync => + ResultAsync.fromPromise( + axios.post( + `${appConfig.liquidityBridgeApiBaseUrl}/v1/avail_to_eth`, + body, + { + headers: { + "X-Payload-Signature": sig, + }, + } + ), + (error) => new Error(`Failed to send payload: ${error}`) + ).map((response) => response.data); \ No newline at end of file diff --git a/services/pallet.ts b/services/pallet.ts index 92b9e47..0b4fa54 100644 --- a/services/pallet.ts +++ b/services/pallet.ts @@ -3,8 +3,10 @@ import { ApiPromise, types, signedExtensions } from "avail-js-sdk"; import { getWalletBySource, WalletAccount } from "@talismn/connect-wallets"; import { executeParams, sendMessageParams } from "@/types/transaction"; import { Logger } from "@/utils/logger"; -import { Err, Result, err, ok } from "neverthrow"; +import { Result, err, ok } from "neverthrow"; import { ISubmittableResult, Signer } from "@polkadot/types/types"; +import { chainToAddresses } from "@/components/common/utils"; +import { Chain } from "@/types/common"; export interface LegacySignerOptions { app_id: number; @@ -241,7 +243,7 @@ export async function transfer( const txResult = await new Promise((resolve) => { api.tx.balances .transferKeepAlive( - "LIQUIDITY_BRIDGE_ADDRESS", + chainToAddresses(Chain.AVAIL).liquidityBridgeAddress, atomicAmount ) .signAndSend(account.address, options, (result: ISubmittableResult) => { @@ -285,7 +287,7 @@ export async function signMessage( try { const injector = getWalletBySource(account.source); console.log(injector); - if (!injector?.signer.signRaw) { + if (!injector?.signer.signPayload) { throw new Error( "Signer not available. Please ensure your wallet extension is installed and accessible." ); diff --git a/types/common.ts b/types/common.ts index 3bba5b8..0d34092 100644 --- a/types/common.ts +++ b/types/common.ts @@ -20,4 +20,22 @@ export interface ethBalance { value: BigInt; } -export type CheckedState = boolean | "indeterminate"; \ No newline at end of file +export type CheckedState = boolean | "indeterminate"; + +export interface LiquidityBridgeTransactionBody { + sender_address: string; + tx_index: number; + block_hash: string; + eth_receiver_address: string; + amount: string; +} + +export interface PayloadResponse { + id: number; + amount: string; + block_hash: string; + eth_receiver_address: string; + sender_address: string; + tx_index: number; + status: "InProgress" | "Completed" | "Failed"; +} \ No newline at end of file From bbc2be31456f3937b3d4ca4bd0b5a9045ae404ee Mon Sep 17 00:00:00 2001 From: Abheek Tripathy Date: Thu, 23 Jan 2025 06:27:35 +0530 Subject: [PATCH 3/8] fix signMessage and verify bug --- components/sections/bridge/types.ts | 3 ++- services/pallet.ts | 33 +++++++++++++++++------------ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/components/sections/bridge/types.ts b/components/sections/bridge/types.ts index 2a70bc1..ddb336c 100644 --- a/components/sections/bridge/types.ts +++ b/components/sections/bridge/types.ts @@ -74,7 +74,8 @@ export interface Account { AVAIL_TO_ETH: `${Chain.AVAIL}-${Chain.ETH}`, BASE_TO_ETH: `${Chain.BASE}-${Chain.ETH}`, ETH_TO_BASE: `${Chain.ETH}-${Chain.BASE}`, - AVAIL_TO_BASE: `${Chain.AVAIL}-${Chain.BASE}` + AVAIL_TO_BASE: `${Chain.AVAIL}-${Chain.BASE}`, + BASE_TO_AVAIL: `${Chain.BASE}-${Chain.AVAIL}` } as const; diff --git a/services/pallet.ts b/services/pallet.ts index 0b4fa54..b7ac960 100644 --- a/services/pallet.ts +++ b/services/pallet.ts @@ -1,4 +1,4 @@ -import { isNumber, stringToU8a, u8aToHex } from "@polkadot/util"; +import { isNumber, stringToHex, stringToU8a, u8aToHex } from "@polkadot/util"; import { ApiPromise, types, signedExtensions } from "avail-js-sdk"; import { getWalletBySource, WalletAccount } from "@talismn/connect-wallets"; import { executeParams, sendMessageParams } from "@/types/transaction"; @@ -7,6 +7,7 @@ import { Result, err, ok } from "neverthrow"; import { ISubmittableResult, Signer } from "@polkadot/types/types"; import { chainToAddresses } from "@/components/common/utils"; import { Chain } from "@/types/common"; +import { cryptoWaitReady, signatureVerify } from '@polkadot/util-crypto'; export interface LegacySignerOptions { app_id: number; @@ -285,25 +286,31 @@ export async function signMessage( account: WalletAccount ): Promise> { try { - const injector = getWalletBySource(account.source); - console.log(injector); - if (!injector?.signer.signPayload) { - throw new Error( - "Signer not available. Please ensure your wallet extension is installed and accessible." - ); + const messageBytes = stringToU8a(message); + const signer = account?.wallet?.signer; + + if (!signer) { + throw new Error("Signer does not support signing raw messages"); } - const messageBytes = stringToU8a(message); - const sign = await injector.signer.signRaw({ - address: account.address, + const { signature } = await signer.signRaw({ + type: 'payload', data: u8aToHex(messageBytes), - type: "bytes", + address: account.address, }); - return ok(sign.signature as string); + const verification = signatureVerify(u8aToHex(messageBytes), signature, account.address); + + if (!verification.isValid) { + throw new Error("Invalid signature generated"); + } + + return ok(signature as string); } catch (error) { + console.error("Error during signing process:", error); return err( error instanceof Error ? error : new Error("Failed to sign the message") ); } -} \ No newline at end of file +} + From 296c9edc7c2eea7f190f5e5e495a6a4900f15408 Mon Sep 17 00:00:00 2001 From: Abheek Tripathy Date: Thu, 23 Jan 2025 08:52:53 +0530 Subject: [PATCH 4/8] feat: complete flows for base <> avail --- .../sections/bridge/submittransaction.tsx | 37 ++++- hooks/common/useEthWallet.ts | 5 +- hooks/useLiquidityBridge.ts | 152 +++++++++--------- hooks/useZkBridge.ts | 2 +- 4 files changed, 114 insertions(+), 82 deletions(-) diff --git a/components/sections/bridge/submittransaction.tsx b/components/sections/bridge/submittransaction.tsx index facb1c2..28e1a6a 100644 --- a/components/sections/bridge/submittransaction.tsx +++ b/components/sections/bridge/submittransaction.tsx @@ -15,6 +15,7 @@ import { useApi } from "@/stores/api"; import { useAvailAccount } from "@/stores/availwallet"; import { useAccount } from "wagmi"; import { isWormholeBridge } from "./utils"; +import useLiquidityBridge from "@/hooks/useLiquidityBridge"; export default function SubmitTransaction() { const [transactionInProgress, setTransactionInProgress] = @@ -34,6 +35,7 @@ export default function SubmitTransaction() { const { address: ethAddress } = useAccount(); const { api } = useApi(); const { initEthToAvailBridging, initAvailToEthBridging } = useZkBridge(); + const { initAvailToERC20AutomaticBridging, initERC20toAvailAutomaticBridging } = useLiquidityBridge(); const { initWormholeBridge } = useWormHoleBridge(); const { buttonStatus, isDisabled } = useSubmitTxnState(transactionInProgress); @@ -108,14 +110,43 @@ export default function SubmitTransaction() { } break; } - default: - throw new Error("Unsupported chain combination"); - } + case ChainPairs.AVAIL_TO_BASE: { + console.log("AVAIL TO BASE"); + const init = await initAvailToERC20AutomaticBridging({ + ERC20Chain: Chain.BASE, + atomicAmount: fromAmountAtomic, + destinationAddress: toAddress!, + }); + + if (init.hash) { + bridgeResult = { + chain: Chain.AVAIL, + hash: init.hash, + }; + } + } + case ChainPairs.BASE_TO_AVAIL: { + console.log("BASE TO AVAIL", fromAmountAtomic, toAddress); + + const init = await initERC20toAvailAutomaticBridging({ + ERC20Chain: Chain.BASE, + atomicAmount: fromAmountAtomic, + destinationAddress: toAddress!, + }); + if (init.hash) { + bridgeResult = { + chain: Chain.BASE, + hash: init.hash, + }; + } + } + } if (bridgeResult) { setDetails(bridgeResult); setOpenDialog(true); } + } catch (error: any) { console.error(error); setError(error); diff --git a/hooks/common/useEthWallet.ts b/hooks/common/useEthWallet.ts index b96bce2..34e6d5b 100644 --- a/hooks/common/useEthWallet.ts +++ b/hooks/common/useEthWallet.ts @@ -6,6 +6,7 @@ import { useCallback, useMemo } from "react"; import { useSwitchChain, useAccount } from "wagmi"; import availTokenAbi from "@/constants/abis/availTokenAbi.json"; import BigNumber from "bignumber.js"; +import { chainToAddresses } from "@/components/common/utils"; /** * @desc All the functionalities related to wallet such as connecting, switching network, etc @@ -54,9 +55,9 @@ export default function useEthWallet() { } }; - const getERC20AvailBalance = useCallback(async (chainId: number) => { + const getERC20AvailBalance = useCallback(async (Chain: Chain) => { const balance = await readContract(config, { - address: appConfig.contracts.ethereum.availToken as `0x${string}`, + address: chainToAddresses(Chain).tokenAddress as `0x${string}`, abi: availTokenAbi, functionName: "balanceOf", args: [activeUserAddress], diff --git a/hooks/useLiquidityBridge.ts b/hooks/useLiquidityBridge.ts index 707341f..0f9b2ab 100644 --- a/hooks/useLiquidityBridge.ts +++ b/hooks/useLiquidityBridge.ts @@ -12,10 +12,9 @@ import { Chain } from "@/types/common"; import { substrateAddressToPublicKey } from "@/utils/common"; import BigNumber from "bignumber.js"; import useEthWallet from "./common/useEthWallet"; -import { chainToAddresses, chainToChainId } from "@/components/common/utils"; -import { writeContract } from "@wagmi/core"; +import { chainToAddresses } from "@/components/common/utils"; +import { waitForTransactionReceipt, writeContract } from "@wagmi/core"; import { config } from "@/config/walletConfig"; -import { appConfig } from "@/config/default"; import availTokenAbi from "@/constants/abis/availTokenAbi.json"; export default function useLiquidityBridge() { @@ -29,6 +28,78 @@ export default function useLiquidityBridge() { destinationAddress: string; } + /** HELPER FUNCTIONS */ + async function transferERC20AvailToLiquidityBridge(amount: string, ERC20Chain: Chain) { + try { + const hash = await writeContract(config, { + address: chainToAddresses(ERC20Chain).tokenAddress as `0x${string}`, + abi: availTokenAbi, + functionName: 'transfer', + args: [chainToAddresses(ERC20Chain).liquidityBridgeAddress, amount] + }) + const transactionReceipt = await waitForTransactionReceipt(config, { + hash, + confirmations: 1, + }) + + return { + txnHash: hash, + blockhash: (transactionReceipt).blockHash + } + } catch (error) { + console.error('Transfer To Liquidity Bridge Failed:', error) + throw error + } + } + + /** BRIDGING FLOWS */ + const initERC20toAvailAutomaticBridging = async ({ + ERC20Chain, + atomicAmount, + destinationAddress, + }: ILiquidtyBridgeParams) => { + /** + * 1. initial checks + * 2. balance transfer to pool account + * 3. use blockhash, tx_index and other fields to form a payload + * 4. generate signature (X-Payload-Signature to the ECDSA signature) + * 5. send payload at /v1/eth_to_avail + */ + + try { + if (!activeUserAddress) throw new Error("No account selected"); + await validateandSwitchChain(ERC20Chain) + + const availBalance = await getERC20AvailBalance(ERC20Chain); + if (new BigNumber(atomicAmount).gte(new BigNumber(availBalance))) { + throw new Error("insufficient balance"); + } + + /**IMPORTANT: FOR BASE WHY IS THERE NO BLOCKHASH THAT SHOWS IN THEIR EXPLORER? */ + const hash = await transferERC20AvailToLiquidityBridge(atomicAmount, ERC20Chain) + if (!hash) throw new Error("Failed to transfer to liquidity bridge") + + const payload = { + sender_address: activeUserAddress, + tx_index: 1, + block_hash: hash.blockhash, + eth_receiver_address: destinationAddress, + amount: atomicAmount, + }; + + console.log('Payload:', payload) + + + return { + chain: ERC20Chain, + hash: hash.txnHash + } + + } catch (error) { + throw new Error(`Failed to bridge from ${ERC20Chain} to Avail: ${error}`); + } + }; + const initAvailToERC20AutomaticBridging = async ({ ERC20Chain, atomicAmount, @@ -86,25 +157,12 @@ export default function useLiquidityBridge() { amount: atomicAmount, }; - const fakeBody = { - sender_address: - "0xf86aabc41a7238174bbc254d47555ee89e1fa4fe3db0f51d19b1b8849cbcaa59", - tx_index: 1, - block_hash: - "0x87a7aec8963dcd11e94fa2c443484683ecf2bd85e3740191cb2a7120788cd345", - eth_receiver_address: "0xEAfDB6af7c1131Eec88Ef17f1057190A46a6C012", - amount: "120000000000000000", - }; - console.log(fakeBody, "FAKE BODY"); - - const sig = await signMessage(JSON.stringify(fakeBody), selected); - console.log(sig, "SIG"); + const sig = await signMessage(JSON.stringify(payload), selected); if (sig.isErr()) { throw new Error(`${sig.error} : Failed to sign payload`); } - const response = await sendPayload(fakeBody, `${sig.value}`); - console.log(response, "response"); + const response = await sendPayload(payload, sig.value); if (response.isErr()) { throw new Error(` ${response.error} : Failed to send payload`); } @@ -118,64 +176,6 @@ export default function useLiquidityBridge() { } }; - async function transferERC20AvailToLiquidityBridge(amount: string, ERC20Chain: Chain) { - try { - const hash = await writeContract(config, { - address: chainToAddresses(ERC20Chain).tokenAddress as `0x${string}`, - abi: availTokenAbi, - functionName: 'transfer', - args: [chainToAddresses(ERC20Chain).liquidityBridgeAddress, amount] - }) - return hash - } catch (error) { - console.error('Transfer To Liquidity Bridge Failed:', error) - throw error - } - } - - - const initERC20toAvailAutomaticBridging = async ({ - ERC20Chain, - atomicAmount, - destinationAddress, - }: ILiquidtyBridgeParams) => { - /** - * 1. initial checks - * 2. balance transfer to pool account - * 3. use blockhash, tx_index and other fields to form a payload - * 4. generate signature (X-Payload-Signature to the ECDSA signature) - * 5. send payload at /v1/eth_to_avail - */ - - try { - if (!activeUserAddress) throw new Error("No account selected"); - - await validateandSwitchChain(ERC20Chain) - - const availBalance = await getERC20AvailBalance(chainToChainId(ERC20Chain)); - if (new BigNumber(atomicAmount).gte(new BigNumber(availBalance))) { - throw new Error("insufficient balance"); - } - - const hash = await transferERC20AvailToLiquidityBridge(atomicAmount, ERC20Chain) - if (!hash) throw new Error("Failed to transfer to liquidity bridge") - - const payload = { - sender_address: activeUserAddress, - tx_index: 1, - block_hash: "0x87a7aec8963dcd11e94fa2c443484683ecf2bd85e3740191cb2a7120788cd345", - eth_receiver_address: destinationAddress, - amount: atomicAmount, - }; - - - - - } catch (error) { - throw new Error(`Failed to bridge from ${ERC20Chain} to Avail: ${error}`); - } - }; - return { initAvailToERC20AutomaticBridging, initERC20toAvailAutomaticBridging, diff --git a/hooks/useZkBridge.ts b/hooks/useZkBridge.ts index ec4ae1c..0b3a1fc 100644 --- a/hooks/useZkBridge.ts +++ b/hooks/useZkBridge.ts @@ -181,7 +181,7 @@ export default function useZkBridge() { }); } - const availBalance = await getERC20AvailBalance(appConfig.networks.ethereum.id); + const availBalance = await getERC20AvailBalance(Chain.ETH); if (new BigNumber(atomicAmount).gte(new BigNumber(availBalance))) { throw new Error("insufficient balance"); } From c417ddb40790e68e63d7ed5de5849c500274d87a Mon Sep 17 00:00:00 2001 From: Abheek Tripathy Date: Fri, 24 Jan 2025 07:57:49 +0530 Subject: [PATCH 5/8] feat: review txn modal barebones --- components/common/container.tsx | 4 + components/sections/bridge/index.tsx | 8 +- ...bmittransaction.tsx => review-section.tsx} | 129 ++++++++++---- .../sections/bridge/submit-transaction.tsx | 47 ++++++ hooks/common/useSubmitTxnState.ts | 13 +- stores/common.ts | 157 ++++++++++-------- 6 files changed, 247 insertions(+), 111 deletions(-) rename components/sections/bridge/{submittransaction.tsx => review-section.tsx} (60%) create mode 100644 components/sections/bridge/submit-transaction.tsx diff --git a/components/common/container.tsx b/components/common/container.tsx index d985d15..de6c396 100644 --- a/components/common/container.tsx +++ b/components/common/container.tsx @@ -8,6 +8,8 @@ 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"; export default function Container() { const [activeTab, setActiveTab] = useState("bridge"); @@ -15,6 +17,7 @@ export default function Container() { const { selected } = useAvailAccount(); const { address } = useAccount(); const { fetchAllTransactions, setTransactionLoader} = useTransactionsStore(); + const { reviewDialog: { isOpen: isModalOpen, onOpenChange: setIsModalOpen } } = useCommonStore(); useEffect(()=>{ (async () => { @@ -92,6 +95,7 @@ export default function Container() { + setIsModalOpen(false)} /> ); } diff --git a/components/sections/bridge/index.tsx b/components/sections/bridge/index.tsx index e1d3a28..74cf9cd 100644 --- a/components/sections/bridge/index.tsx +++ b/components/sections/bridge/index.tsx @@ -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"; @@ -9,10 +9,10 @@ export default function BridgeSection() { return (
- - + + - +
); diff --git a/components/sections/bridge/submittransaction.tsx b/components/sections/bridge/review-section.tsx similarity index 60% rename from components/sections/bridge/submittransaction.tsx rename to components/sections/bridge/review-section.tsx index 28e1a6a..ab81fd7 100644 --- a/components/sections/bridge/submittransaction.tsx +++ b/components/sections/bridge/review-section.tsx @@ -1,3 +1,5 @@ +import { Badge } from "@/components/ui/badge"; +import React from "react"; import { LoadingButton } from "@/components/ui/loadingbutton"; import useZkBridge from "@/hooks/useZkBridge"; import useSubmitTxnState from "@/hooks/common/useSubmitTxnState"; @@ -5,19 +7,27 @@ import { SuccessDialog, useCommonStore } from "@/stores/common"; import { Chain } from "@/types/common"; import { validAddress } from "@/utils/common"; import BigNumber from "bignumber.js"; -import { use, useState } from "react"; +import { useState } from "react"; import useWormHoleBridge from "@/hooks/wormhole/useWormHoleBridge"; import { ChainPairs } from "./types"; import { appConfig } from "@/config/default"; -import { RxArrowTopRight } from "react-icons/rx"; import { useBalanceStore } from "@/stores/balances"; import { useApi } from "@/stores/api"; import { useAvailAccount } from "@/stores/availwallet"; import { useAccount } from "wagmi"; -import { isWormholeBridge } from "./utils"; import useLiquidityBridge from "@/hooks/useLiquidityBridge"; -export default function SubmitTransaction() { +interface TransactionModalProps { + isOpen: boolean; + onClose: () => void; +} + +const TransactionModal: React.FC = ({ + isOpen, + onClose, +}) => { + if (!isOpen) return null; + const [transactionInProgress, setTransactionInProgress] = useState(false); @@ -26,7 +36,7 @@ export default function SubmitTransaction() { toChain, fromAmount, toAddress, - successDialog: { onOpenChange: setOpenDialog, setDetails, setClaimDialog }, + successDialog, errorDialog: { onOpenChange: setErrorOpenDialog, setError }, } = useCommonStore(); @@ -35,14 +45,17 @@ export default function SubmitTransaction() { const { address: ethAddress } = useAccount(); const { api } = useApi(); const { initEthToAvailBridging, initAvailToEthBridging } = useZkBridge(); - const { initAvailToERC20AutomaticBridging, initERC20toAvailAutomaticBridging } = useLiquidityBridge(); + const { + initAvailToERC20AutomaticBridging, + initERC20toAvailAutomaticBridging, + } = useLiquidityBridge(); const { initWormholeBridge } = useWormHoleBridge(); const { buttonStatus, isDisabled } = useSubmitTxnState(transactionInProgress); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - setClaimDialog(false) - + successDialog.setClaimDialog(false); + try { let bridgeResult: SuccessDialog["details"] | null = null; const chainPair = `${fromChain}-${toChain}` as const; @@ -127,7 +140,7 @@ export default function SubmitTransaction() { } case ChainPairs.BASE_TO_AVAIL: { console.log("BASE TO AVAIL", fromAmountAtomic, toAddress); - + const init = await initERC20toAvailAutomaticBridging({ ERC20Chain: Chain.BASE, atomicAmount: fromAmountAtomic, @@ -143,42 +156,92 @@ export default function SubmitTransaction() { } } if (bridgeResult) { - setDetails(bridgeResult); - setOpenDialog(true); + successDialog.setDetails(bridgeResult); + successDialog.onOpenChange(true); } - } catch (error: any) { console.error(error); setError(error); setErrorOpenDialog(true); } finally { setTransactionInProgress(false); + onClose(); await fetchBalance( fromChain === Chain.AVAIL ? selected?.address! : ethAddress!, fromChain, api ); } - } - - return ( - <> - - {buttonStatus} - - {isWormholeBridge(`${fromChain}-${toChain}`) && ( -

- Using Third Party Wormhole Bridge{" "} - -

- )} - - ); }; + return ( +
+
+
+
+
+

+ Review Transaction Details +

+ +
+
+
+ Destination Gas Fee ($) + 1.66 Avail ($1.2) +
+ +
+ Estimated Time + ~5 minutes +
+ +
+ Claim Type + + Auto + +
+ +
+ User will recieve +
+ 11.34 + Avail +
+
+
+ {/** SUBMIT TRANSACTION FLOW */} + + {buttonStatus} + +
+
+
+ ); +}; + +export default TransactionModal; diff --git a/components/sections/bridge/submit-transaction.tsx b/components/sections/bridge/submit-transaction.tsx new file mode 100644 index 0000000..1f55e1a --- /dev/null +++ b/components/sections/bridge/submit-transaction.tsx @@ -0,0 +1,47 @@ +import { LoadingButton } from "@/components/ui/loadingbutton"; +import useSubmitTxnState from "@/hooks/common/useSubmitTxnState"; +import { useCommonStore } from "@/stores/common"; +import { useState } from "react"; +import { RxArrowTopRight } from "react-icons/rx"; +import { isWormholeBridge } from "./utils"; + +export default function SubmitTransaction() { + const [transactionInProgress, setTransactionInProgress] = + useState(false); + + const { + fromChain, + toChain, + reviewDialog: { onOpenChange: setShowReviewModal }, + } = useCommonStore(); + const { buttonStatus, isDisabled } = useSubmitTxnState(transactionInProgress); + + + + const handleReview = (e: React.FormEvent) => { + e.preventDefault(); + setShowReviewModal(true); + }; + + + return ( + <> + + {buttonStatus} + + + {isWormholeBridge(`${fromChain}-${toChain}`) && ( +

+ Using Third Party Wormhole Bridge{" "} + +

+ )} + + ); + }; + diff --git a/hooks/common/useSubmitTxnState.ts b/hooks/common/useSubmitTxnState.ts index f226d7a..f5a9b5e 100644 --- a/hooks/common/useSubmitTxnState.ts +++ b/hooks/common/useSubmitTxnState.ts @@ -12,7 +12,7 @@ export default function useSubmitTxnState( ) { const account = useAccount(); const { selected } = useAvailAccount(); - const { fromChain, toChain, fromAmount, toAddress } = useCommonStore(); + const { fromChain, toChain, fromAmount, toAddress, reviewDialog } = useCommonStore(); const { balances } = useBalanceStore(); const isWalletConnected = useMemo(() => { @@ -76,9 +76,14 @@ export default function useSubmitTxnState( if (hasInsufficientBalance) { return "Insufficient Balance"; } - return `Initiate bridge from ${ - fromChain.charAt(0).toUpperCase() + fromChain.slice(1).toLowerCase() - } to ${toChain.charAt(0).toUpperCase() + toChain.slice(1).toLowerCase()}`; + if(reviewDialog.isOpen) { + return `Initiate bridge from ${ + fromChain.charAt(0).toUpperCase() + fromChain.slice(1).toLowerCase() + } to ${toChain.charAt(0).toUpperCase() + toChain.slice(1).toLowerCase()}`; + } + + return "Review and Confirm Transaction"; + }, [ isWalletConnected, isValidToAddress, diff --git a/stores/common.ts b/stores/common.ts index 2167ffb..66973e4 100644 --- a/stores/common.ts +++ b/stores/common.ts @@ -2,86 +2,103 @@ import { fetchTokenPrice } from "@/services/bridgeapi"; import { Chain } from "@/types/common"; import { create } from "zustand"; -const EMPTY_AMOUNT = '' as const +const EMPTY_AMOUNT = "" as const; interface DialogBase { - isOpen: boolean - onOpenChange: (open: boolean) => void + isOpen: boolean; + onOpenChange: (open: boolean) => void; } export interface SuccessDialog extends DialogBase { - details: { chain: Chain; hash: string, isWormhole?: boolean, } | null - setDetails: (details: { chain: Chain; hash: string, isWormhole?: boolean }) => void - claimDialog: boolean - setClaimDialog: (claimDialog: boolean) => void + details: { chain: Chain; hash: string; isWormhole?: boolean } | null; + setDetails: (details: { + chain: Chain; + hash: string; + isWormhole?: boolean; + }) => void; + claimDialog: boolean; + setClaimDialog: (claimDialog: boolean) => void; } interface ErrorDialog extends DialogBase { - error: Error | string | null - setError: (error: Error | string | null) => void + error: Error | string | null; + setError: (error: Error | string | null) => void; } interface CommonStore { - fromChain: Chain - setFromChain: (fromChain: Chain) => void - dollarAmount: number - setDollarAmount: (dollarAmount: number) => void - fetchDollarAmount: () => Promise - toChain: Chain - setToChain: (toChain: Chain) => void - fromAmount: string - setFromAmount: (fromAmount: string) => void - toAddress: string | undefined - setToAddress: (toAddress: string) => void - successDialog: SuccessDialog - errorDialog: ErrorDialog + fromChain: Chain; + setFromChain: (fromChain: Chain) => void; + dollarAmount: number; + setDollarAmount: (dollarAmount: number) => void; + fetchDollarAmount: () => Promise; + toChain: Chain; + setToChain: (toChain: Chain) => void; + fromAmount: string; + setFromAmount: (fromAmount: string) => void; + toAddress: string | undefined; + setToAddress: (toAddress: string) => void; + successDialog: SuccessDialog; + errorDialog: ErrorDialog; + reviewDialog: DialogBase; + setReviewDialog: (open: boolean) => void; } export const useCommonStore = create((set) => ({ - fromChain: Chain.AVAIL, - setFromChain: (fromChain) => set({ fromChain }), - dollarAmount: 0, - setDollarAmount: (dollarAmount) => set({ dollarAmount }), - fetchDollarAmount: async () => { - const price = await fetchTokenPrice({ - coin: "avail", - fiat: "usd", - }); - set({ dollarAmount: price }); - return price; - }, - toChain: Chain.ETH, - setToChain: (toChain) => set({ toChain }), - fromAmount: EMPTY_AMOUNT, - setFromAmount: (fromAmount) => set({ fromAmount }), - toAddress: undefined, - setToAddress: (toAddress) => set({ toAddress }), - successDialog: { - isOpen: false, - onOpenChange: (open: boolean) => - set((state) => ({ - successDialog: { ...state.successDialog, isOpen: open } - })), - claimDialog: false, - setClaimDialog: (claimDialog: boolean) => - set((state) => ({ - successDialog: { ...state.successDialog, claimDialog } - })), - details: null, - setDetails: (details: { chain: Chain; hash: string }) => - set((state) => ({ - successDialog: { ...state.successDialog, details } - })) - }, - errorDialog: { - isOpen: false, - onOpenChange: (open: boolean) => - set((state) => ({ - errorDialog: { ...state.errorDialog, isOpen: open } - })), - error: null, - setError: (error: Error | string | null) => - set((state) => ({ - errorDialog: { ...state.errorDialog, error } - })) - }, -})) \ No newline at end of file + fromChain: Chain.AVAIL, + setFromChain: (fromChain) => set({ fromChain }), + dollarAmount: 0, + setDollarAmount: (dollarAmount) => set({ dollarAmount }), + fetchDollarAmount: async () => { + const price = await fetchTokenPrice({ + coin: "avail", + fiat: "usd", + }); + set({ dollarAmount: price }); + return price; + }, + toChain: Chain.ETH, + setToChain: (toChain) => set({ toChain }), + fromAmount: EMPTY_AMOUNT, + setFromAmount: (fromAmount) => set({ fromAmount }), + toAddress: undefined, + setToAddress: (toAddress) => set({ toAddress }), + successDialog: { + isOpen: false, + onOpenChange: (open: boolean) => + set((state) => ({ + successDialog: { ...state.successDialog, isOpen: open }, + })), + claimDialog: false, + setClaimDialog: (claimDialog: boolean) => + set((state) => ({ + successDialog: { ...state.successDialog, claimDialog }, + })), + details: null, + setDetails: (details: { chain: Chain; hash: string }) => + set((state) => ({ + successDialog: { ...state.successDialog, details }, + })), + }, + errorDialog: { + isOpen: false, + onOpenChange: (open: boolean) => + set((state) => ({ + errorDialog: { ...state.errorDialog, isOpen: open }, + })), + error: null, + setError: (error: Error | string | null) => + set((state) => ({ + errorDialog: { ...state.errorDialog, error }, + })), + }, + reviewDialog: { + isOpen: false, + onOpenChange: (open: boolean) => + set((state) => ({ + reviewDialog: { ...state.reviewDialog, isOpen: open }, + })), + }, + setReviewDialog: (open: boolean) => + set((state) => ({ + reviewDialog: { ...state.reviewDialog, isOpen: open }, + })), +})); From 8d612abc677ac12db968e17154974aff9020c00c Mon Sep 17 00:00:00 2001 From: Abheek Tripathy Date: Fri, 24 Jan 2025 08:50:11 +0530 Subject: [PATCH 6/8] feat: settings modal + animate review modal --- components/common/container.tsx | 6 +- components/common/settings.tsx | 53 +++++++ components/sections/bridge/review-section.tsx | 149 ++++++++++-------- components/sections/transactions/index.tsx | 4 +- components/ui/switch.tsx | 28 ++++ package.json | 2 + pnpm-lock.yaml | 63 ++++++++ 7 files changed, 238 insertions(+), 67 deletions(-) create mode 100644 components/common/settings.tsx create mode 100644 components/ui/switch.tsx diff --git a/components/common/container.tsx b/components/common/container.tsx index de6c396..89dfb40 100644 --- a/components/common/container.tsx +++ b/components/common/container.tsx @@ -10,6 +10,7 @@ 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"); @@ -50,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 " > - -
+ +

+
diff --git a/components/common/settings.tsx b/components/common/settings.tsx new file mode 100644 index 0000000..a372ff7 --- /dev/null +++ b/components/common/settings.tsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react'; +import { ArrowLeft, Info, 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'); + + return ( + <> + setIsOpen(true)} + /> + + + + +
+ setIsOpen(false)} + /> + Advanced Settings +
+
+ +
+
+ + Automatically claim on destination chain + + +
+
+
+
+ + ); +}; + +export default AdvancedSettings; \ No newline at end of file diff --git a/components/sections/bridge/review-section.tsx b/components/sections/bridge/review-section.tsx index ab81fd7..111cd1f 100644 --- a/components/sections/bridge/review-section.tsx +++ b/components/sections/bridge/review-section.tsx @@ -16,6 +16,7 @@ import { useApi } from "@/stores/api"; import { useAvailAccount } from "@/stores/availwallet"; import { useAccount } from "wagmi"; import useLiquidityBridge from "@/hooks/useLiquidityBridge"; +import { motion, AnimatePresence } from "framer-motion"; interface TransactionModalProps { isOpen: boolean; @@ -175,73 +176,95 @@ const TransactionModal: React.FC = ({ }; return ( -
-
-
-
-
-

- Review Transaction Details -

- -
-
-
- Destination Gas Fee ($) - 1.66 Avail ($1.2) -
- -
- Estimated Time - ~5 minutes -
- -
- Claim Type - - Auto - -
- -
- User will recieve -
- 11.34 - Avail + + {isOpen && ( +
+ + +
+
+

+ Review Transaction Details +

+
+
+ + {buttonStatus} +
-
- {/** SUBMIT TRANSACTION FLOW */} - - {buttonStatus} - +
-
-
+ )} + ); }; export default TransactionModal; + + +const Details = () => { + return ( +
+
+ Destination Gas Fee ($) + 1.66 Avail ($1.2) +
+ +
+ Estimated Time + ~5 minutes +
+ +
+ Claim Type + + Auto + +
+ +
+ User will recieve +
+ 11.34 + Avail +
+
+
+ ) +} \ No newline at end of file diff --git a/components/sections/transactions/index.tsx b/components/sections/transactions/index.tsx index 74f38a3..9db6cf5 100644 --- a/components/sections/transactions/index.tsx +++ b/components/sections/transactions/index.tsx @@ -117,7 +117,7 @@ export default function TransactionSection() { {/* Pagination */}
-

+ Can't find your transaction? @@ -152,7 +152,7 @@ export default function TransactionSection() {

-

+