diff --git a/package.json b/package.json index 63f241e..0f3ad71 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@ethersproject/hash": "^5.7.0", "@orbs-network/liquidity-hub-sdk": "^1.0.44", "@orbs-network/swap-ui": "^0.0.14", - "@orbs-network/twap-sdk": "^2.0.38", + "@orbs-network/twap-sdk": "^2.0.40", "@paraswap/sdk": "^6.10.0", "@radix-ui/react-accordion": "^1.2.1", "@radix-ui/react-avatar": "^1.1.0", diff --git a/src/components/header.tsx b/src/components/header.tsx index 9684d96..66e3c37 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -11,7 +11,7 @@ export function Header() {
- +
) diff --git a/src/lib/approveAllowance.ts b/src/lib/approveAllowance.ts deleted file mode 100644 index 6153488..0000000 --- a/src/lib/approveAllowance.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { getErrorMessage, waitForConfirmations } from './utils' -import { wagmiConfig } from './wagmi-config' -import { Address, erc20Abi, maxUint256 } from 'viem' -import { toast } from 'sonner' -import { simulateContract, writeContract } from 'wagmi/actions' - -export async function approveAllowance( - account: string, - inToken: string, - contract: Address -) { - // Simulate the contract to check if there would be any errors - try { - console.log('Approving allowance...') - - const simulatedData = await simulateContract(wagmiConfig, { - abi: erc20Abi, - functionName: 'approve', - args: [contract, maxUint256], - account: account as Address, - address: inToken as Address, - }) - - // Perform the approve contract function - const txHash = await writeContract(wagmiConfig, simulatedData.request) - - // Check for confirmations for a maximum of 20 seconds - await waitForConfirmations(txHash, 1, 20) - console.log('Approved allowance') - - return txHash - } catch (error) { - const errorMessage = getErrorMessage( - error, - 'An error occurred while approving your allowance' - ) - toast.error(errorMessage) - throw new Error(errorMessage) - } -} diff --git a/src/lib/index.ts b/src/lib/index.ts index 1e4a570..14884b1 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -11,3 +11,5 @@ export * from './wagmi-config' export * from './useTokens' export * from './useWrapOrUnwrapOnly' export * from './usePriceUsd' +export * from './useWrapToken' +export * from './useApproveAllowance' diff --git a/src/lib/networks.ts b/src/lib/networks.ts index 7eb1307..26deb95 100644 --- a/src/lib/networks.ts +++ b/src/lib/networks.ts @@ -375,6 +375,34 @@ export const networks = { "0x4300000000000000000000000000000000000003", ], }, + sei: { + id: 1329, + name: "Sei", + shortname: "sei", + native: { + address: zeroAddress, + symbol: "SEI", + decimals: 18, + logoUrl: 'https://raw.githubusercontent.com/dragonswap-app/assets/main/logos/0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE/logo.png', + }, + wToken: { + symbol: "WSEI", + address: "0xE30feDd158A2e3b13e9badaeABaFc5516e95e8C7", + decimals: 18, + weth: false, + logoUrl: "https://raw.githubusercontent.com/dragonswap-app/assets/main/logos/0xE30feDd158A2e3b13e9badaeABaFc5516e95e8C7/logo.png", + }, + publicRpcUrl: "https://evm-rpc.sei-apis.com", + logoUrl: "https://example.com/path-to-sei-logo.svg", + explorer: "https://seitrace.com", + eip1559: false, + baseAssets: [ + '0xE30feDd158A2e3b13e9badaeABaFc5516e95e8C7', + '0x160345fC359604fC6e70E3c5fAcbdE5F7A9342d8', + '0xB75D0B03c06A926e488e2659DF1A861F860bD3d1' + ] + + }, }; export const getNetwork = (chainId: number) => { diff --git a/src/lib/tokens/base.ts b/src/lib/tokens/base.ts new file mode 100644 index 0000000..86f9bd0 --- /dev/null +++ b/src/lib/tokens/base.ts @@ -0,0 +1,435 @@ + export default { + "0x78a087d713Be963Bf307b18F2Ff8122EF9A63ae9": { + decimals: 18, + symbol: "BSWAP", + name: "BaseSwap Token", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x78a087d713Be963Bf307b18F2Ff8122EF9A63ae9", + tokenInfo: { + name: "BaseSwap Token", + symbol: "BSWAP", + address: "0x78a087d713Be963Bf307b18F2Ff8122EF9A63ae9", + chainId: 8453, + decimals: 18, + logoURI: "https://baseswap.fi/images/tokens/0x78a087d713Be963Bf307b18F2Ff8122EF9A63ae9.png", + }, + tags: [], + }, + "0xd5046B976188EB40f6DE40fB527F89c05b323385": { + decimals: 18, + symbol: "BSX", + name: "Base X", + isNative: false, + isToken: true, + chainId: 8453, + address: "0xd5046B976188EB40f6DE40fB527F89c05b323385", + tokenInfo: { + name: "Base X", + symbol: "BSX", + address: "0xd5046B976188EB40f6DE40fB527F89c05b323385", + chainId: 8453, + decimals: 18, + logoURI: "https://baseswap.fi/images/tokens/0xd5046B976188EB40f6DE40fB527F89c05b323385.png", + }, + tags: [], + }, + "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA": { + decimals: 6, + symbol: "USDbC", + name: "USD Base Coin", + isNative: false, + isToken: true, + chainId: 8453, + address: "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA", + tokenInfo: { + name: "USD Base Coin", + symbol: "USDbC", + address: "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA", + chainId: 8453, + decimals: 6, + logoURI: "", + }, + tags: [], + }, + "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb": { + decimals: 18, + symbol: "DAI", + name: "DAI Stablecoin Token", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", + tokenInfo: { + name: "DAI Stablecoin Token", + symbol: "DAI", + address: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", + chainId: 8453, + decimals: 18, + logoURI: "", + }, + tags: [], + }, + "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22": { + decimals: 18, + symbol: "cbETH", + name: "Coinbase Wrapped Staked ETH", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", + tokenInfo: { + name: "Coinbase Wrapped Staked ETH", + symbol: "cbETH", + address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", + chainId: 8453, + decimals: 18, + logoURI: "", + }, + tags: [], + }, + "0x4A3A6Dd60A34bB2Aba60D73B4C88315E9CeB6A3D": { + decimals: 18, + symbol: "MIM", + name: "Magic Internet Money", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x4A3A6Dd60A34bB2Aba60D73B4C88315E9CeB6A3D", + tokenInfo: { + name: "Magic Internet Money", + symbol: "MIM", + address: "0x4A3A6Dd60A34bB2Aba60D73B4C88315E9CeB6A3D", + chainId: 8453, + decimals: 18, + logoURI: "", + }, + tags: [], + }, + "0x8901cB2e82CC95c01e42206F8d1F417FE53e7Af0": { + decimals: 18, + symbol: "YFX", + name: "Yield Farming Index", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x8901cB2e82CC95c01e42206F8d1F417FE53e7Af0", + tokenInfo: { + name: "Yield Farming Index", + symbol: "YFX", + address: "0x8901cB2e82CC95c01e42206F8d1F417FE53e7Af0", + chainId: 8453, + decimals: 18, + logoURI: "", + }, + tags: [], + }, + "0xCd239E01C36d3079c0dAeF355C61cFF591C40DB1": { + decimals: 18, + symbol: "GMD", + name: "GMD Protocol", + isNative: false, + isToken: true, + chainId: 8453, + address: "0xCd239E01C36d3079c0dAeF355C61cFF591C40DB1", + tokenInfo: { + name: "GMD Protocol", + symbol: "GMD", + address: "0xcd239e01c36d3079c0daef355c61cff591c40db1", + chainId: 8453, + decimals: 18, + logoURI: "", + }, + tags: [], + }, + "0xfB825E93822DD971EBDFdB2180A751958dBD5e16": { + decimals: 18, + symbol: "GND", + name: "GND Protocol", + isNative: false, + isToken: true, + chainId: 8453, + address: "0xfB825E93822DD971EBDFdB2180A751958dBD5e16", + tokenInfo: { + name: "GND Protocol", + symbol: "GND", + address: "0xfb825e93822dd971ebdfdb2180a751958dbd5e16", + chainId: 8453, + decimals: 18, + logoURI: "", + }, + tags: [], + }, + "0x1a35EE4640b0A3B87705B0A4B45D227Ba60Ca2ad": { + decimals: 8, + symbol: "axlWBTC", + name: "Axelar Wrapped Bitcoin", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x1a35EE4640b0A3B87705B0A4B45D227Ba60Ca2ad", + tokenInfo: { + name: "Axelar Wrapped Bitcoin", + symbol: "axlWBTC", + address: "0x1a35EE4640b0A3B87705B0A4B45D227Ba60Ca2ad", + chainId: 8453, + decimals: 8, + logoURI: "", + }, + tags: [], + }, + "0x37DEfBC399e5737D53Dfb5533d9954572F5B19bf": { + decimals: 9, + symbol: "BLAZE", + name: "BlazeBot", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x37DEfBC399e5737D53Dfb5533d9954572F5B19bf", + tokenInfo: { + name: "BlazeBot", + symbol: "BLAZE", + address: "0x37DEfBC399e5737D53Dfb5533d9954572F5B19bf", + chainId: 8453, + decimals: 9, + logoURI: "", + }, + tags: [], + }, + "0x6B4712AE9797C199edd44F897cA09BC57628a1CF": { + decimals: 18, + symbol: "UNIDX", + name: "Unidex Exchange", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x6B4712AE9797C199edd44F897cA09BC57628a1CF", + tokenInfo: { + name: "Unidex Exchange", + symbol: "UNIDX", + address: "0x6B4712AE9797C199edd44F897cA09BC57628a1CF", + chainId: 8453, + decimals: 18, + logoURI: "", + }, + tags: [], + }, + "0x0A074378461FB7ed3300eA638c6Cc38246db4434": { + decimals: 18, + symbol: "EDE", + name: "El Dorado Exchange", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x0A074378461FB7ed3300eA638c6Cc38246db4434", + tokenInfo: { + name: "El Dorado Exchange", + symbol: "EDE", + address: "0x0A074378461FB7ed3300eA638c6Cc38246db4434", + chainId: 8453, + decimals: 18, + logoURI: "", + }, + tags: [], + }, + "0xB79DD08EA68A908A97220C76d19A6aA9cBDE4376": { + decimals: 6, + symbol: "USD+", + name: "USD+", + isNative: false, + isToken: true, + chainId: 8453, + address: "0xB79DD08EA68A908A97220C76d19A6aA9cBDE4376", + tokenInfo: { + name: "USD+", + symbol: "USD+", + address: "0xB79DD08EA68A908A97220C76d19A6aA9cBDE4376", + chainId: 8453, + decimals: 6, + logoURI: "", + }, + tags: [], + }, + "0x65a2508C429a6078a7BC2f7dF81aB575BD9D9275": { + decimals: 18, + symbol: "DAI+", + name: "DAI+", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x65a2508C429a6078a7BC2f7dF81aB575BD9D9275", + tokenInfo: { + name: "DAI+", + symbol: "DAI+", + address: "0x65a2508C429a6078a7BC2f7dF81aB575BD9D9275", + chainId: 8453, + decimals: 18, + logoURI: "", + }, + tags: [], + }, + "0x4788de271F50EA6f5D5D2a5072B8D3C61d650326": { + decimals: 18, + symbol: "BASIN", + name: "Basin Protocol", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x4788de271F50EA6f5D5D2a5072B8D3C61d650326", + tokenInfo: { + name: "Basin Protocol", + symbol: "BASIN", + address: "0x4788de271F50EA6f5D5D2a5072B8D3C61d650326", + chainId: 8453, + decimals: 18, + logoURI: "", + }, + tags: [], + }, + "0xAB8a1c03b8E4e1D21c8Ddd6eDf9e07f26E843492": { + decimals: 18, + symbol: "OGRE", + name: "Ogre", + isNative: false, + isToken: true, + chainId: 8453, + address: "0xAB8a1c03b8E4e1D21c8Ddd6eDf9e07f26E843492", + tokenInfo: { + name: "Ogre", + symbol: "OGRE", + address: "0xAB8a1c03b8E4e1D21c8Ddd6eDf9e07f26E843492", + chainId: 8453, + decimals: 18, + logoURI: "", + }, + tags: [], + }, + "0xEB466342C4d449BC9f53A865D5Cb90586f405215": { + decimals: 6, + symbol: "axlUSDC", + name: "axlUSDC", + isNative: false, + isToken: true, + chainId: 8453, + address: "0xEB466342C4d449BC9f53A865D5Cb90586f405215", + tokenInfo: { + name: "axlUSDC", + symbol: "axlUSDC", + address: "0xEB466342C4d449BC9f53A865D5Cb90586f405215", + chainId: 8453, + decimals: 6, + logoURI: "", + }, + tags: [], + }, + "0x4200000000000000000000000000000000000006": { + decimals: 18, + symbol: "WETH", + name: "Wrapped Ether", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x4200000000000000000000000000000000000006", + tokenInfo: { + name: "Wrapped Ether", + symbol: "WETH", + address: "0x4200000000000000000000000000000000000006", + chainId: 8453, + decimals: 18, + logoURI: "https://pancakeswap.finance/images/tokens/0x2170Ed0880ac9A755fd29B2688956BD959F933F8.png", + }, + tags: [], + }, + "0x58Ed4FD0C3d930b674BA50a293f03ef6cD7dE7a3": { + decimals: 18, + symbol: "ARX", + name: "Arbidex", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x58Ed4FD0C3d930b674BA50a293f03ef6cD7dE7a3", + tokenInfo: { + name: "Arbidex", + symbol: "ARX", + address: "0x58Ed4FD0C3d930b674BA50a293f03ef6cD7dE7a3", + chainId: 8453, + decimals: 18, + logoURI: "https://baseswap.fi/images/tokens/0x58Ed4FD0C3d930b674BA50a293f03ef6cD7dE7a3.png", + }, + tags: [], + }, + "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913": { + decimals: 6, + symbol: "USDC", + name: "USDC Stablecoin", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + tokenInfo: { + name: "USDC Stablecoin", + symbol: "USDC", + address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + chainId: 8453, + decimals: 6, + logoURI: "https://baseswap.fi/images/tokens/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913.png", + }, + tags: [], + }, + "0x4621b7A9c75199271F773Ebd9A499dbd165c3191": { + decimals: 6, + symbol: "DOLA", + name: "DOLA USD Stablecoin", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x4621b7A9c75199271F773Ebd9A499dbd165c3191", + tokenInfo: { + name: "DOLA USD Stablecoin", + symbol: "DOLA", + address: "0x4621b7A9c75199271F773Ebd9A499dbd165c3191", + chainId: 8453, + decimals: 6, + logoURI: "18", + }, + tags: [], + }, + "0x1A9132ee02d7E98e51b7389D2e7BB537184867Aa": { + decimals: 18, + symbol: "BULLRUN", + name: "BullRun", + isNative: false, + isToken: true, + chainId: 8453, + address: "0x1A9132ee02d7E98e51b7389D2e7BB537184867Aa", + tokenInfo: { + name: "BullRun", + symbol: "BULLRUN", + address: "0x1A9132ee02d7E98e51b7389D2e7BB537184867Aa", + chainId: 8453, + decimals: 18, + logoURI: "", + }, + tags: [], + }, + "0xbf1aeA8670D2528E08334083616dD9C5F3B087aE": { + decimals: 18, + symbol: "MAI", + name: "MAI Stablecoin ", + isNative: false, + isToken: true, + chainId: 8453, + address: "0xbf1aeA8670D2528E08334083616dD9C5F3B087aE", + tokenInfo: { + name: "MAI Stablecoin ", + symbol: "MAI", + address: "0xbf1aeA8670D2528E08334083616dD9C5F3B087aE", + chainId: 8453, + decimals: 18, + logoURI: "", + }, + tags: [], + }, + }; + \ No newline at end of file diff --git a/src/lib/tokens/index.ts b/src/lib/tokens/index.ts new file mode 100644 index 0000000..0cc417d --- /dev/null +++ b/src/lib/tokens/index.ts @@ -0,0 +1,4 @@ +import base from "./base"; +export const tokenLists = { + base, +}; diff --git a/src/lib/useApproveAllowance.ts b/src/lib/useApproveAllowance.ts new file mode 100644 index 0000000..7a78449 --- /dev/null +++ b/src/lib/useApproveAllowance.ts @@ -0,0 +1,43 @@ +import { isNativeAddress, waitForConfirmations } from "./utils"; +import { Address, erc20Abi, maxUint256 } from "viem"; +import { useMutation } from "@tanstack/react-query"; +import { useAccount, useWriteContract } from "wagmi"; +import { useNetwork } from "@/trade/hooks"; + +export function useApproveAllowance() { + const { address: account } = useAccount(); + const { writeContractAsync } = useWriteContract(); + const wToken = useNetwork()?.wToken.address; + + return useMutation({ + mutationFn: async ({ + token: _token, + spender, + amount, + }: { + token?: string; + spender?: string; + amount?: string; + }) => { + const token = isNativeAddress(_token) ? wToken : _token; + + console.log("Approving allowance..."); + const txHash = await writeContractAsync({ + abi: erc20Abi, + functionName: "approve", + args: [spender as `0x${string}`, amount ? BigInt(amount) : maxUint256], + account: account as Address, + address: token as Address, + }); + + // Check for confirmations for a maximum of 20 seconds + await waitForConfirmations(txHash, 1, 20); + console.log("Approved allowance"); + + return txHash; + }, + onError: (error) => { + throw error; + }, + }); +} diff --git a/src/lib/useDefaultTokens.ts b/src/lib/useDefaultTokens.ts index fa44c93..40d3f0e 100644 --- a/src/lib/useDefaultTokens.ts +++ b/src/lib/useDefaultTokens.ts @@ -1,14 +1,17 @@ +import { useNetwork } from "@/trade/hooks"; import { useMemo } from "react"; import { useSortedTokens } from "./useTokens"; export function useDefaultTokens() { const tokens = useSortedTokens(); - + const wToken = useNetwork()?.wToken.symbol + return useMemo(() => { if (!tokens) return; + return { - inToken: tokens[0], - outToken: tokens[1], + inToken: tokens.find((token) => token.symbol.toLowerCase() === "usdc"), + outToken: tokens.find((token) => token.symbol.toLowerCase() === wToken?.toLowerCase()), }; - }, [tokens]); + }, [tokens, wToken]); } diff --git a/src/lib/useGetRequiresApproval.ts b/src/lib/useGetRequiresApproval.ts index ef8c59b..7d40056 100644 --- a/src/lib/useGetRequiresApproval.ts +++ b/src/lib/useGetRequiresApproval.ts @@ -23,7 +23,7 @@ export function useGetRequiresApproval( args: [account as Address, contractAddress], query: { enabled: Boolean(inTokenAddress && address && contractAddress) }, }); - + return { requiresApproval: (allowance || 0n) < BigInt(inAmount || 0), approvalLoading: isLoading, diff --git a/src/lib/useParaswap.ts b/src/lib/useParaswap.ts index cd716ea..c68a455 100644 --- a/src/lib/useParaswap.ts +++ b/src/lib/useParaswap.ts @@ -67,6 +67,7 @@ export const useParaswapQuote = ({ }, enabled: !!inToken && !!outToken && Number(inAmount) > 0, refetchInterval, + staleTime: Infinity, }) } diff --git a/src/lib/usePriceUsd.ts b/src/lib/usePriceUsd.ts index c273a39..3ba1c0d 100644 --- a/src/lib/usePriceUsd.ts +++ b/src/lib/usePriceUsd.ts @@ -1,62 +1,75 @@ -import { isNativeAddress, getNetwork } from '@/lib' -import { useQuery } from '@tanstack/react-query' -import { useAccount } from 'wagmi' - +import { isNativeAddress, getNetwork, networks } from "@/lib"; +import { useQuery } from "@tanstack/react-query"; +import { useAccount } from "wagmi"; +import BN from "bignumber.js"; +import { useMemo } from "react"; export const usePriceUsd = (address?: string) => { - const {chainId} = useAccount() + const { chainId } = useAccount(); return useQuery({ - queryKey: ['usePriceUSD', chainId, address], - queryFn: async () => { + queryKey: ["usePriceUSD", chainId, address], + queryFn: async () => { if (!address || !chainId) { - return 0 + return 0; } - return (await fetchLLMAPrice(address, chainId)).priceUsd + return (await fetchLLMAPrice(address, chainId)).priceUsd; }, refetchInterval: 30_000, enabled: !!address && !!chainId, - }) -} + staleTime: Infinity, + }); +}; + +export const useUsdAmount = (token?: string, amount?: string) => { + const { data: price } = usePriceUsd(token); + return useMemo(() => { + if (!price || !token) return; + return BN(price) + .multipliedBy(amount || 0) + .toNumber(); + }, [token, amount, price]); +}; const chainIdToName: { [key: number]: string } = { - 56: 'bsc', - 137: 'polygon', - 8453: 'base', // Assuming this ID is another identifier for Polygon as per the user's mapping - 250: 'fantom', - 1: 'ethereum', - 1101: 'zkevm', - 81457: 'blast', - 59144: 'linea', - 42161: 'arbitrum', -} + 56: "bsc", + 137: "polygon", + 8453: "base", // Assuming this ID is another identifier for Polygon as per the user's mapping + 250: "fantom", + 1: "ethereum", + 1101: "zkevm", + 81457: "blast", + 59144: "linea", + 42161: "arbitrum", + [networks.sei.id]: "sei", +}; export async function fetchLLMAPrice(token: string, chainId: number) { const nullPrice = { priceUsd: 0, priceNative: 0, timestamp: Date.now(), - } + }; try { - const chainName = chainIdToName[chainId] || 'Unknown Chain' + const chainName = chainIdToName[chainId] || "Unknown Chain"; if (isNativeAddress(token)) { - token = getNetwork(chainId)?.wToken.address || '' + token = getNetwork(chainId)?.wToken.address || ""; } - const tokenAddressWithChainId = `${chainName}:${token}` - const url = `https://coins.llama.fi/prices/current/${tokenAddressWithChainId}` - const response = await fetch(url) + const tokenAddressWithChainId = `${chainName}:${token}`; + const url = `https://coins.llama.fi/prices/current/${tokenAddressWithChainId}`; + const response = await fetch(url); if (!response.ok) { - return nullPrice + return nullPrice; } - const data = await response.json() - const coin = data.coins[tokenAddressWithChainId] + const data = await response.json(); + const coin = data.coins[tokenAddressWithChainId]; return { - priceUsd: !coin ? 0 : coin.price, - priceNative: !coin ? 0 : coin.price, + priceUsd: !coin ? 0 : coin.price, + priceNative: !coin ? 0 : coin.price, timestamp: Date.now(), - } + }; } catch (error) { - console.error('Failed to fetch Llama price', error) - return nullPrice + console.error("Failed to fetch Llama price", error); + return nullPrice; } } diff --git a/src/lib/useTokens.ts b/src/lib/useTokens.ts index ebf63e7..bb68a91 100644 --- a/src/lib/useTokens.ts +++ b/src/lib/useTokens.ts @@ -7,6 +7,7 @@ import { erc20Abi, Address } from "viem"; import { zeroAddress } from "@orbs-network/liquidity-hub-sdk"; import { eqIgnoreCase } from "./utils"; import { useMemo } from "react"; +import { tokenLists } from "./tokens"; const getFantomTokens = async (signal?: AbortSignal): Promise => { const res = await fetch( @@ -25,6 +26,27 @@ const getFantomTokens = async (signal?: AbortSignal): Promise => { }); }; +const getSeiTokens = async (signal?: AbortSignal): Promise => { + const res = await fetch( + "https://raw.githubusercontent.com/dragonswap-app/assets/main/tokenlist-sei-mainnet.json", + { signal } + ); + const data = await res.json(); + + return data.tokens.map((token: any) => { + return { + address: + token.address === "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + ? zeroAddress + : token.address, + symbol: token.symbol, + decimals: token.decimals, + logoUrl: `https://raw.githubusercontent.com/dragonswap-app/assets/main/logos/${token.address}/logo.png`, + name: token.name, + }; + }); +}; + const getSushiTokens = async ( chainId: number, signal?: AbortSignal @@ -62,31 +84,23 @@ const getLineaTokens = async (signal?: AbortSignal): Promise => { }); }; -const fetchTokens = async ( - chainId: number, - signal?: AbortSignal -): Promise => { - let tokens: Token[] = []; - if (chainId === networks.linea.id) { - tokens = await getLineaTokens(signal); - } else if (chainId === networks.ftm.id) { - tokens = await getFantomTokens(signal); - } else { - tokens = await getSushiTokens(chainId, signal); +const addNativeToken = (tokens: Token[], network: typeof networks.eth) => { + if (tokens.find((t) => eqIgnoreCase(t.address, network.native.address))) { + return tokens; } - const network = getNetwork(chainId); - if (network) { - const nativeToken: Token = { + return [ + { address: network.native.address, symbol: network.native.symbol, decimals: network.native.decimals, logoUrl: network.native.logoUrl, - }; - - tokens = [nativeToken, ...tokens]; - } + }, + ...tokens, + ]; +}; - const baseAssets = getNetwork(chainId)?.baseAssets; +const sortTokens = (tokens: Token[], network: typeof networks.eth) => { + const baseAssets = network.baseAssets; if (!baseAssets) { return tokens; } @@ -102,6 +116,56 @@ const fetchTokens = async ( return sortedTokens; }; +const getBaseTokens = (): Token[] => { + const _tokens = tokenLists.base; + + return Object.values(_tokens).map((token) => { + return { + address: token.address, + symbol: token.symbol, + decimals: token.decimals, + logoUrl: token.tokenInfo.logoURI, + name: token.name, + }; + }); +}; + +const fetchTokens = async ( + chainId: number, + signal?: AbortSignal +): Promise => { + const network = getNetwork(chainId); + if (!network) { + throw new Error(`Network with chainId ${chainId} not found`); + } + + let tokens: Token[] = []; + switch (chainId) { + case networks.linea.id: + tokens = await getLineaTokens(signal); + break; + case networks.ftm.id: + tokens = await getFantomTokens(signal); + break; + case networks.sei.id: + tokens = await getSeiTokens(signal); + break; + case networks.sei.id: + tokens = await getSeiTokens(signal); + break; + case networks.base.id: + tokens = getBaseTokens(); + break; + + default: + tokens = await getSushiTokens(chainId, signal); + break; + } + + tokens = addNativeToken(tokens, network); + return sortTokens(tokens, network); +}; + const useTokensList = () => { const chainId = useAccount().chainId; @@ -128,6 +192,7 @@ export const useTokenBalaces = () => { queryKey: ["useBalances", chainId, account, tokens?.map((t) => t.address)], queryFn: async () => { if (!tokens) return {}; + let native = await getBalance(config, { address: account as Address, chainId, @@ -149,11 +214,10 @@ export const useTokenBalaces = () => { } as const) ), }); - const balances = addresses.reduce( (acc: any, address: any, index: number) => { - acc[address] = multicallResponse[index].result?.toString() || '0'; + acc[address] = multicallResponse[index].result?.toString() || "0"; return acc; }, {} diff --git a/src/lib/useWrapOrUnwrapOnly.ts b/src/lib/useWrapOrUnwrapOnly.ts index e340a25..6f46417 100644 --- a/src/lib/useWrapOrUnwrapOnly.ts +++ b/src/lib/useWrapOrUnwrapOnly.ts @@ -1,24 +1,25 @@ +import { useNetwork } from '@/trade/hooks' import { useMemo } from 'react' -import { networks } from './networks' import { eqIgnoreCase, isNativeAddress } from './utils' export function useWrapOrUnwrapOnly( fromTokenAddress?: string, toTokenAddress?: string ) { + const network = useNetwork()?.wToken.address // Evaluates whether tokens are to be wrapped/unwrapped only return useMemo(() => { return { isWrapOnly: - eqIgnoreCase( - networks.poly.wToken.address || '', + eqIgnoreCase( + network || '', toTokenAddress || '' ) && isNativeAddress(fromTokenAddress || ''), isUnwrapOnly: eqIgnoreCase( - networks.poly.wToken.address || '', + network || '', fromTokenAddress || '' ) && isNativeAddress(toTokenAddress || ''), } - }, [fromTokenAddress, toTokenAddress]) + }, [fromTokenAddress, toTokenAddress, network]) } diff --git a/src/lib/useWrapToken.ts b/src/lib/useWrapToken.ts new file mode 100644 index 0000000..26652be --- /dev/null +++ b/src/lib/useWrapToken.ts @@ -0,0 +1,28 @@ +import { useNetwork } from "@/trade/hooks"; +import { useMutation } from "@tanstack/react-query"; +import { Address } from "viem"; +import { useAccount, useWriteContract } from "wagmi"; +import { IWETHabi } from "./abis"; +import { waitForConfirmations } from "./utils"; + +export const useWrapToken = () => { + const { writeContractAsync } = useWriteContract(); + const { address: account } = useAccount(); + const address = useNetwork()?.wToken.address + return useMutation({ + mutationFn: async (inAmount: string) => { + const txHash = await writeContractAsync({ + abi: IWETHabi, + functionName: "deposit", + account: account as Address, + address: address as Address, + value: BigInt(inAmount.replace(".", "")), + }); + await waitForConfirmations(txHash, 1, 20); + return txHash; + }, + onError: (error) => { + throw error; + }, + }); +}; diff --git a/src/lib/wagmi-config.ts b/src/lib/wagmi-config.ts index 91d14db..2215d15 100644 --- a/src/lib/wagmi-config.ts +++ b/src/lib/wagmi-config.ts @@ -1,13 +1,13 @@ import { getDefaultConfig } from '@rainbow-me/rainbowkit' import { http } from 'viem' -import { polygon, mainnet, arbitrum, bsc, fantom, blast, linea } from 'viem/chains' +import { polygon, mainnet, arbitrum, bsc, fantom, blast, linea, sei, base } from 'viem/chains' const walletConnectProjectId = import.meta.env.VITE_WALLET_CONNECT_PROJECT_ID export const wagmiConfig = getDefaultConfig({ appName: 'DEX Playground', projectId: walletConnectProjectId, - chains: [polygon, mainnet, arbitrum, bsc, fantom, blast, linea], + chains: [polygon, mainnet, arbitrum, bsc, fantom, blast, linea, sei, base], transports: { [mainnet.id]: http(`https://rpcman.orbs.network/rpc?chainId=1&appId=dex-playground`), [polygon.id]: http(), @@ -16,5 +16,7 @@ export const wagmiConfig = getDefaultConfig({ [fantom.id]: http(), [blast.id]: http(), [linea.id]: http(), + [sei.id]: http(), + [base.id]: http(), } }) diff --git a/src/lib/wrapToken.ts b/src/lib/wrapToken.ts deleted file mode 100644 index bdad0a4..0000000 --- a/src/lib/wrapToken.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { toast } from "sonner" -import { Address } from "viem" -import { simulateContract, writeContract } from 'wagmi/actions' -import { IWETHabi } from "./abis" -import { networks } from "./networks" -import { getErrorMessage, waitForConfirmations } from "./utils" -import { wagmiConfig } from "./wagmi-config" - -export const wrapToken = async (account: string, inAmount: string) => { - - try { - const simulatedData = await simulateContract(wagmiConfig, { - abi: IWETHabi, - functionName: 'deposit', - account: account as Address, - address: networks.poly.wToken.address as Address, - value: BigInt(inAmount.replace('.', '')) - }) - - // Perform the deposit contract function - const txHash = await writeContract(wagmiConfig, simulatedData.request) - await waitForConfirmations(txHash, 1, 20) - return txHash - } catch (error) { - const errorMessage = getErrorMessage( - error, - 'An error occurred while wrapping your token' - ) - toast.error(errorMessage) - throw new Error(errorMessage) - } - -} \ No newline at end of file diff --git a/src/trade/liquidity-hub/consts.ts b/src/trade/liquidity-hub/consts.ts new file mode 100644 index 0000000..3693ed8 --- /dev/null +++ b/src/trade/liquidity-hub/consts.ts @@ -0,0 +1 @@ +export const QUOTE_REFETCH_INTERVAL = 20_000; diff --git a/src/trade/liquidity-hub/context.tsx b/src/trade/liquidity-hub/context.tsx index 0780cee..b9c4984 100644 --- a/src/trade/liquidity-hub/context.tsx +++ b/src/trade/liquidity-hub/context.tsx @@ -5,6 +5,7 @@ import { LiquidityHubSDK, Quote, } from "@orbs-network/liquidity-hub-sdk"; +import { OptimalRate } from "@paraswap/sdk"; import { useContext, ReactNode, @@ -12,6 +13,7 @@ import { useCallback, useMemo, createContext, + useEffect, } from "react"; import { useAccount } from "wagmi"; import { useToRawAmount } from "../hooks"; @@ -21,9 +23,11 @@ const initialState: State = { outToken: null, inputAmount: "", acceptedQuote: undefined, + acceptedOptimalRate: undefined, liquidityHubDisabled: false, forceLiquidityHub: false, - showConfirmation: false, + confirmationModalOpen: false, + proceedWithLiquidityHub: false, }; interface State { @@ -33,9 +37,10 @@ interface State { acceptedQuote: Quote | undefined; liquidityHubDisabled: boolean; forceLiquidityHub: boolean; - showConfirmation: boolean; signature?: string; - isLiquidityHubTrade?: boolean; + confirmationModalOpen: boolean; + proceedWithLiquidityHub: boolean; + acceptedOptimalRate?: OptimalRate } type Action = { type: "UPDATE"; payload: Partial } | { type: "RESET" }; @@ -71,6 +76,8 @@ export const LiquidityHubSwapProvider = ({ }) => { const [_state, dispatch] = useReducer(reducer, initialState); const defaultTokens = useDefaultTokens(); + const chainId = useAccount().chainId; + const state = useMemo(() => { return { @@ -80,7 +87,6 @@ export const LiquidityHubSwapProvider = ({ }; }, [_state, defaultTokens]); - const { chainId } = useAccount(); const parsedInputAmount = useToRawAmount( state.inputAmount, @@ -98,6 +104,14 @@ export const LiquidityHubSwapProvider = ({ dispatch({ type: "RESET" }); }, [dispatch]); + useEffect(() => { + if(chainId) { + resetState(); + } + }, [chainId]) + + + const sdk = useMemo( () => constructSDK({ partner: "widget", chainId }), [chainId] diff --git a/src/trade/liquidity-hub/hooks.ts b/src/trade/liquidity-hub/hooks.ts index 2b8e0a8..b103734 100644 --- a/src/trade/liquidity-hub/hooks.ts +++ b/src/trade/liquidity-hub/hooks.ts @@ -1,5 +1,4 @@ import { - useWrapOrUnwrapOnly, useParaswapQuote, useInputError, getMinAmountOut, @@ -9,13 +8,12 @@ import { } from "@/lib"; import { useAppState } from "@/store"; import { permit2Address } from "@orbs-network/liquidity-hub-sdk"; -import { useQueryClient, useQuery } from "@tanstack/react-query"; import { useMemo, useCallback } from "react"; import { Address } from "viem"; import { useAccount } from "wagmi"; import { useLiquidityHubSwapContext } from "./context"; +import { QUOTE_REFETCH_INTERVAL } from "./consts"; -export const QUOTE_REFETCH_INTERVAL = 20_000; export const useParaswapMinAmountOut = () => { const { slippage } = useAppState(); @@ -25,106 +23,7 @@ export const useParaswapMinAmountOut = () => { }, [optimalRate?.destAmount, slippage]); }; -export function useLiquidityHubQuote() { - const queryClient = useQueryClient(); - const { chainId, address: account } = useAccount(); - const { slippage } = useAppState(); - - const { - state: { inToken, outToken, liquidityHubDisabled }, - sdk, - parsedInputAmount, - } = useLiquidityHubSwapContext(); - const dexMinAmountOut = useParaswapMinAmountOut(); - const wToken = useNetwork()?.wToken.address - const inTokenAddress = isNativeAddress(inToken?.address) ? wToken : inToken?.address; - const outTokenAddress = outToken?.address; - // Check if the swap is wrap or unwrap only - const { isUnwrapOnly, isWrapOnly } = useWrapOrUnwrapOnly( - inTokenAddress, - outTokenAddress - ); - const enabled = Boolean( - !liquidityHubDisabled && - chainId && - inTokenAddress && - outTokenAddress && - Number(parsedInputAmount) > 0 && - !isUnwrapOnly && - !isWrapOnly && - account - ); - - const queryKey = useMemo( - () => [ - "quote", - inTokenAddress, - outTokenAddress, - parsedInputAmount, - slippage, - dexMinAmountOut, - ], - [ - inTokenAddress, - parsedInputAmount, - slippage, - outTokenAddress, - dexMinAmountOut, - ] - ); - - const getQuote = useCallback( - ({ signal }: { signal: AbortSignal }) => { - if (!inTokenAddress || !outTokenAddress || !parsedInputAmount) { - return Promise.reject(new Error("Invalid input")); - } - return sdk.getQuote({ - fromToken: inTokenAddress, - toToken: outTokenAddress, - inAmount: parsedInputAmount, - dexMinAmountOut, - account, - slippage, - signal, - }); - }, - [ - sdk, - inTokenAddress, - outTokenAddress, - parsedInputAmount, - account, - slippage, - dexMinAmountOut, - ] - ); - - const query = useQuery({ - queryKey, - queryFn: getQuote, - enabled, - refetchOnWindowFocus: false, - staleTime: Infinity, - gcTime: 0, - retry: 2, - refetchInterval: QUOTE_REFETCH_INTERVAL, - placeholderData: (prev) => prev, - }); - - return useMemo(() => { - return { - // We return the result of getQuote, plus a function to get - // the last fetched quote in react-query cache - ...query, - getLatestQuote: () => - queryClient.ensureQueryData({ - queryKey, - queryFn: getQuote, - }), - }; - }, [query, queryClient, queryKey, getQuote]); -} export const useOptimalRate = () => { const { @@ -196,3 +95,5 @@ export const useParaswapApproval = () => { optimalRate?.srcAmount ); }; + + diff --git a/src/trade/liquidity-hub/liquidity-hub-confirmation-dialog.tsx b/src/trade/liquidity-hub/liquidity-hub-confirmation-dialog.tsx index f5e1a78..9e68a0d 100644 --- a/src/trade/liquidity-hub/liquidity-hub-confirmation-dialog.tsx +++ b/src/trade/liquidity-hub/liquidity-hub-confirmation-dialog.tsx @@ -4,44 +4,31 @@ import { Card } from "@/components/ui/card"; import { SwapStep, SwapStatus } from "@orbs-network/swap-ui"; import { useCallback, useMemo } from "react"; import { DataDetails } from "@/components/ui/data-details"; -import BN from "bignumber.js"; import { format, - getErrorMessage, getLiquidityProviderName, - getSteps, - isNativeAddress, - promiseWithTimeout, toExactAmount, - useParaswapBuildTxCallback, usePriceUsd, - wagmiConfig, - waitForConfirmations, + useTokenBalaces, + useUsdAmount, } from "@/lib"; import { useAccount } from "wagmi"; -import { Address } from "viem"; -import { estimateGas, sendTransaction, signTypedData } from "wagmi/actions"; -import { approveAllowance } from "@/lib/approveAllowance"; -import { useMutation } from "@tanstack/react-query"; import { useLiquidityHubSwapContext } from "./context"; import { useLiquidityHubApproval, - useLiquidityHubQuote, useOptimalRate, useParaswapApproval, } from "./hooks"; import { SwapConfirmationDialog, - SwapProgressState, useSwapProgress, } from "../swap-confirmation-dialog"; -import { wrapToken } from "@/lib/wrapToken"; import { _TypedDataEncoder } from "@ethersproject/hash"; -import { permit2Address, Quote } from "@orbs-network/liquidity-hub-sdk"; -import { TransactionParams } from "@paraswap/sdk"; -import { toast } from "sonner"; -import { useNetwork, useToExactAmount } from "../hooks"; -import { useAppState } from "@/store"; +import { useToExactAmount } from "../hooks"; +import { useLiquidityHubSwapCallback } from "./useLiquidityHubSwapCallback"; +import { useParaswapSwapCallback } from "./useParaswapSwapCallback"; +import { useLiquidityHubQuote } from "./useLiquidityHubQuote"; +import { useIsLiquidityHubTrade } from "./useIsLiquidityHubTrade"; // Construct steps for swap to display in UI const useSteps = (steps?: number[]) => { @@ -79,15 +66,10 @@ const useSteps = (steps?: number[]) => { }, [inToken, steps, signature]); }; -export function LiquidityHubConfirmationDialog({ - isOpen, - onClose: _onClose, -}: { - isOpen: boolean; - onClose: (swapStatus?: SwapStatus) => void; -}) { +export function LiquidityHubConfirmationDialog() { const { - state: { inputAmount, inToken, outToken, isLiquidityHubTrade }, + state: { inputAmount, inToken, outToken, confirmationModalOpen }, + updateState, } = useLiquidityHubSwapContext(); const { @@ -97,6 +79,8 @@ export function LiquidityHubConfirmationDialog({ } = useSwapProgress(); const parsedSteps = useSteps(progressState.steps); + const isLiquidityHubTrade = useIsLiquidityHubTrade(); + const { refetch: refetchBalances } = useTokenBalaces(); const { mutate: swapWithLiquidityHub } = useLiquidityHubSwapCallback(updateProgressState); @@ -124,30 +108,29 @@ export function LiquidityHubConfirmationDialog({ ]); const onClose = useCallback(() => { - _onClose(progressState.swapStatus); + updateState({ + confirmationModalOpen: false, + proceedWithLiquidityHub: false, + acceptedQuote: undefined, + acceptedOptimalRate: undefined, + }); + if (progressState.swapStatus === SwapStatus.SUCCESS) { + updateState({ inputAmount: "" }); + refetchBalances(); + } setTimeout(() => { - if (progressState.currentStep) { - resetProgressState(); - } - }, 500); + resetProgressState(); + }, 5_00); }, [ - _onClose, progressState.swapStatus, progressState.currentStep, resetProgressState, + updateState, + refetchBalances, ]); - const usdValues = useUSDValues(); - - const optimalRate = useOptimalRate().data; - - const quote = useLiquidityHubQuote().data; - - const result = isLiquidityHubTrade - ? quote?.outAmount - : optimalRate?.destAmount; - - const outAmount = useToExactAmount(result, outToken?.decimals); + const usd = useUSD(); + const outAmount = useOutAmount(); return ( { +const useOutAmount = () => { + const _quote = useLiquidityHubQuote().data; + const _optimalRate = useOptimalRate().data; + const isLiquidityHubTrade = useIsLiquidityHubTrade(); + const { outToken, acceptedQuote, acceptedOptimalRate } = + useLiquidityHubSwapContext().state; + const quote = acceptedQuote || _quote; + const optimalRate = acceptedOptimalRate || _optimalRate; + const result = isLiquidityHubTrade + ? quote?.outAmount + : optimalRate?.destAmount; + + return useToExactAmount(result, outToken?.decimals); +}; + +const useUSD = () => { const { - state: { inToken, outToken, inputAmount, isLiquidityHubTrade }, + state: { + inToken, + outToken, + inputAmount, + acceptedQuote, + acceptedOptimalRate, + }, } = useLiquidityHubSwapContext(); - const srcUSD = usePriceUsd(inToken?.address).data; - const destUSD = usePriceUsd(outToken?.address).data; - const optimalRate = useOptimalRate().data; - const quote = useLiquidityHubQuote().data; + const _quote = useLiquidityHubQuote().data; + const _optimalRate = useOptimalRate().data; + const isLiquidityHubTrade = useIsLiquidityHubTrade(); + const quote = acceptedQuote || _quote; + const optimalRate = acceptedOptimalRate || _optimalRate; + + const lhAmountOutExact = useToExactAmount( + acceptedQuote?.outAmount || quote?.outAmount, + outToken?.decimals + ); + + const lhSrcUsd = useUsdAmount(inToken?.address, inputAmount); + const lhDestUsd = useUsdAmount(outToken?.address, lhAmountOutExact); + const srcUSD = isLiquidityHubTrade ? lhSrcUsd : optimalRate?.srcUSD; + const destUSD = isLiquidityHubTrade ? lhDestUsd : optimalRate?.destUSD; return useMemo(() => { - if (!isLiquidityHubTrade) { - return { - srcUSD: optimalRate?.srcUSD, - destUSD: optimalRate?.destUSD, - }; - } return { - srcUSD: BN(inputAmount) - .multipliedBy(srcUSD || 0) - .toString(), - destUSD: BN(toExactAmount(quote?.outAmount, outToken?.decimals)) - .multipliedBy(destUSD || 0) - .toString(), + srcUSD, + destUSD, }; - }, [ - isLiquidityHubTrade, - srcUSD, - destUSD, - optimalRate, - quote, - inputAmount, - outToken?.decimals, - ]); + }, [srcUSD, destUSD]); }; const SubmitSwapButton = ({ @@ -241,9 +238,10 @@ const Details = () => { const quote = useLiquidityHubQuote().data; const address = useAccount().address; const { - state: { outToken, isLiquidityHubTrade }, + state: { outToken }, } = useLiquidityHubSwapContext(); const outTokenUsd = usePriceUsd(outToken?.address).data; + const isLiquidityHubTrade = useIsLiquidityHubTrade(); const gasPrice = useMemo(() => { if (!isLiquidityHubTrade) { @@ -289,308 +287,3 @@ const Details = () => { ); }; - -export const useParaswapSwapCallback = ( - updateSwapProgressState: (value: Partial) => void -) => { - const buildParaswapTxCallback = useParaswapBuildTxCallback(); - const optimalRate = useOptimalRate().data; - const { - state: { inToken }, - } = useLiquidityHubSwapContext(); - const { slippage } = useAppState(); - const wToken = useNetwork()?.wToken.address; - const requiresApproval = useParaswapApproval().requiresApproval; - - const { address } = useAccount(); - - return useMutation({ - mutationFn: async () => { - if (!address) { - throw new Error("Wallet not connected"); - } - - if (!inToken) { - throw new Error("Input token not found"); - } - - if (!optimalRate) { - throw new Error("No optimal rate found"); - } - if (!wToken) { - throw new Error("WToken not found"); - } - - try { - updateSwapProgressState({ swapStatus: SwapStatus.LOADING }); - - const steps = getSteps({ - inTokenAddress: inToken.address, - requiresApproval, - noWrap: true, - }); - updateSwapProgressState({ steps }); - if (requiresApproval) { - updateSwapProgressState({ currentStep: SwapSteps.Approve }); - await approveAllowance( - address, - isNativeAddress(inToken.address) ? wToken : inToken.address, - optimalRate.tokenTransferProxy as Address - ); - } - - updateSwapProgressState({ currentStep: SwapSteps.Swap }); - - let txPayload: unknown | null = null; - - try { - const txData = await buildParaswapTxCallback(optimalRate, slippage); - - txPayload = { - account: txData.from as Address, - to: txData.to as Address, - data: txData.data as `0x${string}`, - gasPrice: BigInt(txData.gasPrice), - gas: txData.gas ? BigInt(txData.gas) : undefined, - value: BigInt(txData.value), - }; - } catch (error) { - // Handle error in UI - console.error(error); - - updateSwapProgressState({ swapStatus: SwapStatus.FAILED }); - } - - if (!txPayload) { - updateSwapProgressState({ swapStatus: SwapStatus.FAILED }); - - throw new Error("Failed to build transaction"); - } - - console.log("Swapping..."); - - await estimateGas(wagmiConfig, txPayload); - - const txHash = await sendTransaction(wagmiConfig, txPayload); - - await waitForConfirmations(txHash, 1, 20); - - updateSwapProgressState({ swapStatus: SwapStatus.SUCCESS }); - - return txHash; - } catch (error) { - console.error(error); - updateSwapProgressState({ swapStatus: SwapStatus.FAILED }); - toast.error("An error occurred while swapping"); - throw error; - } - }, - }); -}; - -// Analytics events are optional for integration but are useful for your business insights -type AnalyticsEvents = { - onRequest: () => void; - onSuccess: (result?: string) => void; - onFailure: (error: string) => void; -}; - -async function wrapTokenCallback( - quote: Quote, - analyticsEvents: AnalyticsEvents -) { - try { - console.log("Wrapping token..."); - analyticsEvents.onRequest(); - - // Perform the deposit contract function - const txHash = await wrapToken(quote.user, quote.inAmount); - - // Check for confirmations for a maximum of 20 seconds - await waitForConfirmations(txHash, 1, 20); - console.log("Token wrapped"); - analyticsEvents.onSuccess(); - - return txHash; - } catch (error) { - analyticsEvents.onFailure( - getErrorMessage(error, "An error occurred while wrapping your token") - ); - throw error; - } -} - -async function approveCallback( - account: string, - inToken: string, - analyticsEvents: AnalyticsEvents -) { - try { - analyticsEvents.onRequest(); - // Perform the approve contract function - const txHash = await approveAllowance(account, inToken, permit2Address); - - analyticsEvents.onSuccess(txHash); - return txHash; - } catch (error) { - analyticsEvents.onFailure( - getErrorMessage(error, "An error occurred while approving the allowance") - ); - throw error; - } -} - -async function signTransaction(quote: Quote, analyticsEvents: AnalyticsEvents) { - // Encode the payload to get signature - const { permitData } = quote; - const populated = await _TypedDataEncoder.resolveNames( - permitData.domain, - permitData.types, - permitData.values, - async (name: string) => name - ); - const payload = _TypedDataEncoder.getPayload( - populated.domain, - permitData.types, - populated.value - ); - - try { - console.log("Signing transaction..."); - analyticsEvents.onRequest(); - - // Sign transaction and get signature - const signature = await promiseWithTimeout( - (signTypedData as any)(wagmiConfig, payload), - 40_000 - ); - - console.log("Transaction signed"); - analyticsEvents.onSuccess(signature); - - return signature; - } catch (error) { - console.error(error); - - analyticsEvents.onFailure( - getErrorMessage(error, "An error occurred while getting the signature") - ); - throw error; - } -} - -export function useLiquidityHubSwapCallback( - updateSwapProgressState: (partial: Partial) => void -) { - const { - sdk: liquidityHub, - state: { inToken }, - updateState, - } = useLiquidityHubSwapContext(); - const { slippage } = useAppState(); - - const buildParaswapTxCallback = useParaswapBuildTxCallback(); - const optimalRate = useOptimalRate().data; - const { getLatestQuote, data: quote } = useLiquidityHubQuote(); - const requiresApproval = useLiquidityHubApproval().requiresApproval; - - const inTokenAddress = inToken?.address; - - return useMutation({ - mutationFn: async () => { - // Fetch latest quote just before swap - if (!inTokenAddress) { - throw new Error("In token address is not set"); - } - - if (!quote || !optimalRate) { - throw new Error("Quote or optimal rate is not set"); - } - // Set swap status for UI - updateSwapProgressState({ swapStatus: SwapStatus.LOADING }); - - try { - // Check if the inToken needs approval for allowance - - // Get the steps required for swap e.g. [Wrap, Approve, Swap] - const steps = getSteps({ - inTokenAddress, - requiresApproval, - }); - - updateSwapProgressState({ steps }); - - // If the inToken needs to be wrapped then wrap - if (steps.includes(SwapSteps.Wrap)) { - updateSwapProgressState({ currentStep: SwapSteps.Wrap }); - await wrapTokenCallback(quote, { - onRequest: liquidityHub.analytics.onWrapRequest, - onSuccess: liquidityHub.analytics.onWrapSuccess, - onFailure: liquidityHub.analytics.onWrapFailure, - }); - } - - // If an appropriate allowance for inToken has not been approved - // then get user to approve - if (steps.includes(SwapSteps.Approve)) { - updateSwapProgressState({ currentStep: SwapSteps.Approve }); - await approveCallback(quote.user, quote.inToken, { - onRequest: liquidityHub.analytics.onApprovalRequest, - onSuccess: liquidityHub.analytics.onApprovalSuccess, - onFailure: liquidityHub.analytics.onApprovalFailed, - }); - } - - // Fetch the latest quote again after the approval - const latestQuote = await getLatestQuote(); - updateState({ acceptedQuote: latestQuote }); - - // Set the current step to swap - updateSwapProgressState({ currentStep: SwapSteps.Swap }); - - // Sign the transaction for the swap - const signature = await signTransaction(latestQuote, { - onRequest: liquidityHub.analytics.onSignatureRequest, - onSuccess: (signature) => - liquidityHub.analytics.onSignatureSuccess(signature || ""), - onFailure: liquidityHub.analytics.onSignatureFailed, - }); - updateState({ signature }); - - // Pass the liquidity provider txData if possible - let paraswapTxData: TransactionParams | undefined; - - try { - paraswapTxData = await buildParaswapTxCallback(optimalRate, slippage); - } catch (error) { - console.error(error); - } - - console.log("Swapping..."); - // Call Liquidity Hub sdk swap and wait for transaction hash - const txHash = await liquidityHub.swap( - latestQuote, - signature as string, - { - data: paraswapTxData?.data, - to: paraswapTxData?.to, - } - ); - - if (!txHash) { - throw new Error("Swap failed"); - } - - // Fetch the successful transaction details - await liquidityHub.getTransactionDetails(txHash, latestQuote); - - console.log("Swapped"); - updateSwapProgressState({ swapStatus: SwapStatus.SUCCESS }); - } catch (error) { - updateSwapProgressState({ swapStatus: SwapStatus.FAILED }); - - throw error; - } - }, - }); -} diff --git a/src/trade/liquidity-hub/liquidity-hub-swap.tsx b/src/trade/liquidity-hub/liquidity-hub-swap.tsx index 9f8322f..1da668f 100644 --- a/src/trade/liquidity-hub/liquidity-hub-swap.tsx +++ b/src/trade/liquidity-hub/liquidity-hub-swap.tsx @@ -1,49 +1,23 @@ import { TokenCard } from "@/components/tokens/token-card"; import { SwitchButton } from "@/components/ui/switch-button"; -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useMemo } from "react"; import { useAccount } from "wagmi"; import { Button } from "@/components/ui/button"; import { _TypedDataEncoder } from "@ethersproject/hash"; -import { SwapStatus } from "@orbs-network/swap-ui"; import { Token } from "@/types"; import { ErrorCodes } from "@/lib"; import "../style.css"; import { useConnectModal } from "@rainbow-me/rainbowkit"; -import BN from "bignumber.js"; import { LiquidityHubSwapProvider, useLiquidityHubSwapContext, } from "./context"; import { useToExactAmount } from "../hooks"; import { LiquidityHubConfirmationDialog } from "./liquidity-hub-confirmation-dialog"; -import { - useLiquidityHubInputError, - useLiquidityHubQuote, - useOptimalRate, - useParaswapMinAmountOut, -} from "./hooks"; +import { useLiquidityHubInputError, useOptimalRate } from "./hooks"; import { SwapDetails } from "./swap-details"; - -export const useIsLiquidityHubTrade = () => { - const { - state: { liquidityHubDisabled }, - } = useLiquidityHubSwapContext(); - const liquidityHubQuote = useLiquidityHubQuote().data; - const paraswapMinAmountOut = useParaswapMinAmountOut(); - - return useMemo(() => { - // Choose between liquidity hub and dex swap based on the min amount out - if (liquidityHubDisabled) return false; - - return BN(liquidityHubQuote?.minAmountOut || 0).gt( - paraswapMinAmountOut || 0 - ); - }, [ - liquidityHubDisabled, - liquidityHubQuote?.minAmountOut, - paraswapMinAmountOut, - ]); -}; +import { useLiquidityHubQuote } from "./useLiquidityHubQuote"; +import { useIsLiquidityHubTrade } from "./useIsLiquidityHubTrade"; function SwapPanel() { return ( @@ -53,7 +27,8 @@ function SwapPanel() { - + + ); @@ -80,22 +55,30 @@ const Switch = () => { ); }; -const ConfirmationModal = () => { +const ShowConfirmationButton = () => { const account = useAccount().address; const openConnectModal = useConnectModal().openConnectModal; const { data: liquidityHubQuote } = useLiquidityHubQuote(); const { data: optimalRate, isLoading: optimalRateLoading } = useOptimalRate(); const inputError = useLiquidityHubInputError(); - - const isLiquidityHubTrade = useIsLiquidityHubTrade(); + const proceedWithLiquidityHub = useIsLiquidityHubTrade(); + const { state: { inputAmount }, updateState, - resetState, } = useLiquidityHubSwapContext(); - const [isOpen, setIsOpen] = useState(false); - const { text, enabled } = useMemo(() => { + const onOpenConfirmation = useCallback(() => { + updateState({ confirmationModalOpen: true, proceedWithLiquidityHub }); + }, [updateState, proceedWithLiquidityHub]); + + const { text, onClick } = useMemo(() => { + if (!account) { + return { + text: "Connect wallet", + onClick: openConnectModal, + }; + } if (inputError === ErrorCodes.InsufficientBalance) { return { text: "Insufficient balance", @@ -115,7 +98,7 @@ const ConfirmationModal = () => { return { text: "Swap", - enabled: true, + onClick: onOpenConfirmation, }; }, [ inputError, @@ -123,39 +106,14 @@ const ConfirmationModal = () => { liquidityHubQuote, optimalRate, optimalRateLoading, + openConnectModal, + onOpenConfirmation, ]); - const onOpen = useCallback(() => { - setIsOpen(true); - updateState({ isLiquidityHubTrade }); - }, [isLiquidityHubTrade]); - - const onClose = useCallback( - (status?: SwapStatus) => { - setIsOpen(false); - updateState({ isLiquidityHubTrade: false }); - if (status === SwapStatus.SUCCESS) { - updateState({ inputAmount: "" }); - } - }, - [resetState] - ); - - if (!account) { - return ( - - ); - } - return ( - <> - - - + ); }; diff --git a/src/trade/liquidity-hub/swap-details.tsx b/src/trade/liquidity-hub/swap-details.tsx index 059eb23..ce6a356 100644 --- a/src/trade/liquidity-hub/swap-details.tsx +++ b/src/trade/liquidity-hub/swap-details.tsx @@ -6,7 +6,7 @@ import { useAccount } from "wagmi"; import { useToExactAmount } from "../hooks"; import { useLiquidityHubSwapContext } from "./context"; import { useOptimalRate, useParaswapMinAmountOut } from "./hooks"; -import { useIsLiquidityHubTrade } from "./liquidity-hub-swap"; +import { useIsLiquidityHubTrade } from "./useIsLiquidityHubTrade"; export function SwapDetails() { const isLiquidityHubTrade = useIsLiquidityHubTrade(); diff --git a/src/trade/liquidity-hub/useIsLiquidityHubTrade.ts b/src/trade/liquidity-hub/useIsLiquidityHubTrade.ts new file mode 100644 index 0000000..6a67e81 --- /dev/null +++ b/src/trade/liquidity-hub/useIsLiquidityHubTrade.ts @@ -0,0 +1,30 @@ +import { useMemo } from "react"; +import { useLiquidityHubSwapContext } from "./context"; +import { useParaswapMinAmountOut } from "./hooks"; +import { useLiquidityHubQuote } from "./useLiquidityHubQuote"; +import BN from "bignumber.js"; + +export const useIsLiquidityHubTrade = () => { + const { + state: { liquidityHubDisabled, proceedWithLiquidityHub }, + } = useLiquidityHubSwapContext(); + const liquidityHubQuote = useLiquidityHubQuote().data; + const paraswapMinAmountOut = useParaswapMinAmountOut(); + + return useMemo(() => { + // Choose between liquidity hub and dex swap based on the min amount out + if (proceedWithLiquidityHub) { + return true; + } + if (liquidityHubDisabled) return false; + + return BN(liquidityHubQuote?.minAmountOut || 0).gt( + paraswapMinAmountOut || 0 + ); + }, [ + liquidityHubDisabled, + liquidityHubQuote?.minAmountOut, + paraswapMinAmountOut, + proceedWithLiquidityHub, + ]); +}; diff --git a/src/trade/liquidity-hub/useLiquidityHubQuote.ts b/src/trade/liquidity-hub/useLiquidityHubQuote.ts new file mode 100644 index 0000000..7d59142 --- /dev/null +++ b/src/trade/liquidity-hub/useLiquidityHubQuote.ts @@ -0,0 +1,110 @@ +import { isNativeAddress, useWrapOrUnwrapOnly } from "@/lib"; +import { useAppState } from "@/store"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useCallback, useMemo } from "react"; +import { useAccount } from "wagmi"; +import { useNetwork } from "../hooks"; +import { QUOTE_REFETCH_INTERVAL } from "./consts"; +import { useLiquidityHubSwapContext } from "./context"; +import { useParaswapMinAmountOut } from "./hooks"; + +export function useLiquidityHubQuote() { + const { chainId, address: account } = useAccount(); + const { slippage } = useAppState(); + const queryClient = useQueryClient(); + + const { + state: { inToken, outToken, liquidityHubDisabled }, + sdk, + parsedInputAmount, + } = useLiquidityHubSwapContext(); + const dexMinAmountOut = useParaswapMinAmountOut(); + const wToken = useNetwork()?.wToken.address; + const inTokenAddress = isNativeAddress(inToken?.address) + ? wToken + : inToken?.address; + const outTokenAddress = outToken?.address; + // Check if the swap is wrap or unwrap only + const { isUnwrapOnly, isWrapOnly } = useWrapOrUnwrapOnly( + inTokenAddress, + outTokenAddress + ); + + const queryKey = useMemo( + () => [ + "quote", + inTokenAddress, + outTokenAddress, + parsedInputAmount, + slippage, + dexMinAmountOut, + ], + [ + inTokenAddress, + outTokenAddress, + parsedInputAmount, + slippage, + dexMinAmountOut, + ] + ); + + const fetchQuote = useCallback( + (signal?: AbortSignal) => { + return sdk.getQuote({ + fromToken: inTokenAddress!, + toToken: outTokenAddress!, + inAmount: parsedInputAmount!, + dexMinAmountOut, + account, + slippage, + signal, + }); + }, + [ + sdk, + inTokenAddress, + outTokenAddress, + parsedInputAmount, + dexMinAmountOut, + account, + slippage, + ] + ); + + const query = useQuery({ + queryKey, + queryFn: ({ signal }) => { + return fetchQuote(signal); + }, + enabled: Boolean( + !liquidityHubDisabled && + chainId && + inTokenAddress && + outTokenAddress && + Number(parsedInputAmount) > 0 && + !isUnwrapOnly && + !isWrapOnly && + account + ), + refetchOnWindowFocus: false, + staleTime: Infinity, + gcTime: 0, + retry: 2, + refetchInterval: QUOTE_REFETCH_INTERVAL, + placeholderData: (prev) => prev, + }); + + + const getLatestQuote = useCallback( + () => { + return queryClient.ensureQueryData({queryKey, queryFn: ({signal}) => fetchQuote(signal)}); + }, + [queryClient, queryKey, fetchQuote], + ) + + return { + ...query, + getLatestQuote, + } + +} diff --git a/src/trade/liquidity-hub/useLiquidityHubSwapCallback.ts b/src/trade/liquidity-hub/useLiquidityHubSwapCallback.ts new file mode 100644 index 0000000..68b9263 --- /dev/null +++ b/src/trade/liquidity-hub/useLiquidityHubSwapCallback.ts @@ -0,0 +1,189 @@ +import { + useParaswapBuildTxCallback, + getSteps, + useWrapToken, + useApproveAllowance, + promiseWithTimeout, +} from "@/lib"; +import { useAppState } from "@/store"; +import { SwapSteps } from "@/types"; +import { _TypedDataEncoder } from "@ethersproject/hash"; +import { permit2Address, Quote } from "@orbs-network/liquidity-hub-sdk"; +import { SwapStatus } from "@orbs-network/swap-ui"; +import { TransactionParams } from "@paraswap/sdk"; +import { useMutation } from "@tanstack/react-query"; +import { useSignTypedData } from "wagmi"; +import { SwapProgressState } from "../swap-confirmation-dialog"; +import { useLiquidityHubSwapContext } from "./context"; +import { useOptimalRate, useLiquidityHubApproval } from "./hooks"; +import { useLiquidityHubQuote } from "./useLiquidityHubQuote"; + +export function useLiquidityHubSwapCallback( + updateSwapProgressState: (values: Partial) => void +) { + const { + sdk: liquidityHub, + state: { inToken }, + updateState, + } = useLiquidityHubSwapContext(); + const { slippage } = useAppState(); + + const buildParaswapTxCallback = useParaswapBuildTxCallback(); + const optimalRate = useOptimalRate().data; + const { getLatestQuote, data: quote } = useLiquidityHubQuote(); + const requiresApproval = useLiquidityHubApproval().requiresApproval; + const { mutateAsync: wrap } = useWrapToken(); + const { mutateAsync: approve } = useApproveAllowance(); + const { mutateAsync: sign } = useSign(); + + const inTokenAddress = inToken?.address; + + return useMutation({ + mutationFn: async () => { + // Fetch latest quote just before swap + if (!inTokenAddress) { + throw new Error("In token address is not set"); + } + + if (!quote || !optimalRate) { + throw new Error("Quote or optimal rate is not set"); + } + // Set swap status for UI + updateSwapProgressState({ swapStatus: SwapStatus.LOADING }); + + try { + // Check if the inToken needs approval for allowance + + // Get the steps required for swap e.g. [Wrap, Approve, Swap] + const steps = getSteps({ + inTokenAddress, + requiresApproval, + }); + + updateSwapProgressState({ steps }); + + // If the inToken needs to be wrapped then wrap + if (steps.includes(SwapSteps.Wrap)) { + updateSwapProgressState({ currentStep: SwapSteps.Wrap }); + try { + liquidityHub.analytics.onWrapRequest; + await wrap(quote.inAmount); + liquidityHub.analytics.onWrapSuccess(); + } catch (error) { + liquidityHub.analytics.onWrapFailure((error as Error).message); + throw error; + } + } + + // If an appropriate allowance for inToken has not been approved + // then get user to approve + if (steps.includes(SwapSteps.Approve)) { + updateSwapProgressState({ currentStep: SwapSteps.Approve }); + try { + liquidityHub.analytics.onApprovalRequest(); + // Perform the approve contract function + const txHash = await approve({ + token: inTokenAddress, + spender: permit2Address, + amount: quote.inAmount, + }); + liquidityHub.analytics.onApprovalSuccess(txHash); + return txHash; + } catch (error) { + liquidityHub.analytics.onApprovalFailed((error as Error).message); + throw error; + } + } + + // Fetch the latest quote again after the approval + let latestQuote = quote; + try { + const result = await getLatestQuote(); + if (result) { + latestQuote = result; + } + } catch (error) { + console.error(error); + } + updateState({ acceptedQuote: latestQuote }); + + // Set the current step to swap + updateSwapProgressState({ currentStep: SwapSteps.Swap }); + + // Sign the transaction for the swap + let signature = ""; + try { + liquidityHub.analytics.onSignatureRequest(); + signature = await sign(latestQuote); + liquidityHub.analytics.onSignatureSuccess(signature); + updateState({ signature }); + } catch (error) { + liquidityHub.analytics.onSignatureFailed((error as Error).message); + } + + // Pass the liquidity provider txData if possible + let paraswapTxData: TransactionParams | undefined; + + try { + paraswapTxData = await buildParaswapTxCallback(optimalRate, slippage); + } catch (error) { + console.error(error); + } + + console.log("Swapping...", latestQuote); + // Call Liquidity Hub sdk swap and wait for transaction hash + const txHash = await liquidityHub.swap( + latestQuote, + signature as string, + { + data: paraswapTxData?.data, + to: paraswapTxData?.to, + } + ); + + if (!txHash) { + throw new Error("Swap failed"); + } + + // Fetch the successful transaction details + await liquidityHub.getTransactionDetails(txHash, latestQuote); + + console.log("Swapped"); + updateSwapProgressState({ swapStatus: SwapStatus.SUCCESS }); + } catch (error) { + updateSwapProgressState({ swapStatus: SwapStatus.FAILED }); + + throw error; + } + }, + }); +} + +const useSign = () => { + const { signTypedDataAsync } = useSignTypedData(); + return useMutation({ + mutationFn: async (quote: Quote) => { + // Encode the payload to get signature + const { permitData } = quote; + const populated = await _TypedDataEncoder.resolveNames( + permitData.domain, + permitData.types, + permitData.values, + async (name: string) => name + ); + const payload = _TypedDataEncoder.getPayload( + populated.domain, + permitData.types, + populated.value + ); + + const signature = await promiseWithTimeout( + (signTypedDataAsync as any)(payload), + 40_000 + ); + + console.log("Transaction signed", signature); + return signature; + }, + }); +}; diff --git a/src/trade/liquidity-hub/useParaswapSwapCallback.ts b/src/trade/liquidity-hub/useParaswapSwapCallback.ts new file mode 100644 index 0000000..451e9c0 --- /dev/null +++ b/src/trade/liquidity-hub/useParaswapSwapCallback.ts @@ -0,0 +1,126 @@ +import { + getSteps, + useApproveAllowance, + useParaswapBuildTxCallback, + waitForConfirmations, +} from "@/lib"; +import { useAppState } from "@/store"; +import { SwapSteps } from "@/types"; +import { SwapStatus } from "@orbs-network/swap-ui"; +import { useMutation } from "@tanstack/react-query"; +import { toast } from "sonner"; +import { useAccount, useSendTransaction } from "wagmi"; +import { useNetwork } from "../hooks"; +import { SwapProgressState } from "../swap-confirmation-dialog"; +import { useLiquidityHubSwapContext } from "./context"; +import { useOptimalRate, useParaswapApproval } from "./hooks"; + +export const useParaswapSwapCallback = ( + updateSwapProgressState: (value: Partial) => void +) => { + const buildParaswapTxCallback = useParaswapBuildTxCallback(); + const { data: optimalRate, refetch: refetchOptimalRate } = useOptimalRate(); + const { + state: { inToken }, + updateState, + } = useLiquidityHubSwapContext(); + const { slippage } = useAppState(); + const wToken = useNetwork()?.wToken.address; + const requiresApproval = useParaswapApproval().requiresApproval; + const { mutateAsync: approve } = useApproveAllowance(); + + const { address } = useAccount(); + const { sendTransactionAsync } = useSendTransaction(); + + return useMutation({ + mutationFn: async () => { + if (!address) { + throw new Error("Wallet not connected"); + } + + if (!inToken) { + throw new Error("Input token not found"); + } + + if (!optimalRate) { + throw new Error("No optimal rate found"); + } + if (!wToken) { + throw new Error("WToken not found"); + } + + try { + updateSwapProgressState({ swapStatus: SwapStatus.LOADING }); + + const steps = getSteps({ + inTokenAddress: inToken.address, + requiresApproval, + noWrap: true, + }); + updateSwapProgressState({ steps }); + if (requiresApproval) { + updateSwapProgressState({ currentStep: SwapSteps.Approve }); + await approve({ + token: inToken.address, + spender: optimalRate.tokenTransferProxy, + amount: optimalRate.srcAmount, + }); + } + + updateSwapProgressState({ currentStep: SwapSteps.Swap }); + + let txPayload: unknown | null = null; + + let acceptedOptimalRate = optimalRate; + + try { + const result = await refetchOptimalRate(); + if (result.data) { + acceptedOptimalRate = result.data; + } + } catch (error) {} + + updateState({ acceptedOptimalRate }); + + try { + const txData = await buildParaswapTxCallback(acceptedOptimalRate, slippage); + + txPayload = { + account: txData.from, + to: txData.to, + data: txData.data, + gasPrice: BigInt(txData.gasPrice), + gas: txData.gas ? BigInt(txData.gas) : undefined, + value: BigInt(txData.value), + }; + } catch (error) { + // Handle error in UI + console.error(error); + + updateSwapProgressState({ swapStatus: SwapStatus.FAILED }); + } + + if (!txPayload) { + updateSwapProgressState({ swapStatus: SwapStatus.FAILED }); + + throw new Error("Failed to build transaction"); + } + + console.log("Swapping..."); + + const txHash = await sendTransactionAsync(txPayload); + + await waitForConfirmations(txHash, 1, 20); + + updateSwapProgressState({ swapStatus: SwapStatus.SUCCESS }); + + return txHash; + } catch (error) { + console.error(error); + updateSwapProgressState({ swapStatus: SwapStatus.FAILED }); + toast.error("An error occurred while swapping"); + throw error; + } + }, + }); +}; diff --git a/src/trade/swap-confirmation-dialog.tsx b/src/trade/swap-confirmation-dialog.tsx index db6e012..cefd8f1 100644 --- a/src/trade/swap-confirmation-dialog.tsx +++ b/src/trade/swap-confirmation-dialog.tsx @@ -59,7 +59,7 @@ export function SwapConfirmationDialogContent() {
} diff --git a/src/trade/twap/components/price-toggle.tsx b/src/trade/twap/components/price-toggle.tsx index 6fdb938..317ab42 100644 --- a/src/trade/twap/components/price-toggle.tsx +++ b/src/trade/twap/components/price-toggle.tsx @@ -6,11 +6,14 @@ export function PriceToggle() { const { isMarketOrder, state: { updateState }, + isLimitPanel } = useTwapContext(); const onMarketOrderChange = useCallback((isMarketOrder: boolean) => { updateState({ isMarketOrder }); }, []); + + if(isLimitPanel) return null; return (
diff --git a/src/trade/twap/context.tsx b/src/trade/twap/context.tsx index a73686f..29a52ed 100644 --- a/src/trade/twap/context.tsx +++ b/src/trade/twap/context.tsx @@ -16,6 +16,7 @@ import { useReducer, useState, } from "react"; +import { useAccount } from "wagmi"; import { useToRawAmount } from "../hooks"; interface Context { @@ -59,8 +60,7 @@ type TwapState = { isTradePriceInverted?: boolean; }; -const initialState = {typedAmount: ''} as TwapState; - +const initialState = { typedAmount: "" } as TwapState; const useTwapState = () => { const [_values, dispatch] = useReducer( (state: TwapState, action: Action) => @@ -70,15 +70,13 @@ const useTwapState = () => { const defaultTokens = useDefaultTokens(); - const values = useMemo(() => { return { ..._values, inToken: _values.inToken || defaultTokens?.inToken || null, outToken: _values.outToken || defaultTokens?.outToken || null, - } - }, [_values, defaultTokens?.inToken, defaultTokens?.outToken]) - + }; + }, [_values, defaultTokens?.inToken, defaultTokens?.outToken]); const updateState = useCallback( (payload: Partial) => { @@ -98,6 +96,16 @@ const useTwapState = () => { }; }; +const useConfig = () => { + const chainId = useAccount().chainId; + return useMemo( + () => + Object.values(Configs).find((it: any) => it.chainId === chainId) || + Configs.QuickSwap, + [chainId] + ); +}; + export const TwapContextProvider = ({ children, isLimitPanel = false, @@ -106,11 +114,18 @@ export const TwapContextProvider = ({ isLimitPanel?: boolean; }) => { const state = useTwapState(); + const chainId = useAccount()?.chainId; + const [currentTime, setCurrentTime] = useState(Date.now()); - const twapSDK = useMemo( - () => constructSDK({ config: Configs.QuickSwap }), - [] - ); + const config = useConfig(); + + const twapSDK = useMemo(() => constructSDK({ config }), [config]); + + useEffect(() => { + if (chainId) { + state.resetState(); + } + }, [chainId]); useEffect(() => { setInterval(() => { diff --git a/src/trade/twap/hooks.ts b/src/trade/twap/hooks.ts index d012b23..8af51f6 100644 --- a/src/trade/twap/hooks.ts +++ b/src/trade/twap/hooks.ts @@ -1,4 +1,6 @@ import { + networks, + toExactAmount, toRawAmount, useInputError, useParaswapQuote, @@ -13,6 +15,7 @@ import { MIN_DURATION_MINUTES, MIN_FILL_DELAY_MINUTES, } from "@orbs-network/twap-sdk"; +import { useAccount } from "wagmi"; export const useDerivedTwapSwapData = () => { const { @@ -27,9 +30,7 @@ export const useDerivedTwapSwapData = () => { state.values; const price = useTradePrice(); - const { data: oneSrcTokenUsd } = usePriceUsd( - inToken?.address - ); + const { data: oneSrcTokenUsd } = usePriceUsd(inToken?.address); const swapValues = twapSDK.derivedSwapValues({ srcAmount: parsedInputAmount, @@ -63,7 +64,17 @@ export const useOptimalRate = () => { }; export const useMarketPrice = () => { - return useOptimalRate().data?.destAmount; + const chainId = useAccount().chainId; + const { + state: { + values: { outToken }, + }, + } = useTwapContext(); + const rate = useOptimalRate().data?.destAmount; + if (chainId === networks.sei.id) { + return toExactAmount("1", outToken?.decimals); + } + return rate; }; export const useTradePrice = () => { @@ -137,9 +148,9 @@ const useTradeSizeWarning = () => { const { warnings } = useDerivedTwapSwapData(); const { twapSDK } = useTwapContext(); return useMemo(() => { - if (warnings.tradeSize) { - return `Trade size must be at least ${twapSDK.config.minChunkSizeUsd}`; - } + // if (warnings.tradeSize) { + // return `Trade size must be at least Z${twapSDK.config.minChunkSizeUsd}`; + // } }, [warnings.tradeSize, twapSDK.config.minChunkSizeUsd]); }; diff --git a/src/trade/twap/orders/orders.tsx b/src/trade/twap/orders/orders.tsx index fb24845..f348324 100644 --- a/src/trade/twap/orders/orders.tsx +++ b/src/trade/twap/orders/orders.tsx @@ -35,11 +35,9 @@ import moment from "moment"; import { useMutation } from "@tanstack/react-query"; import { useTwapContext } from "../context"; import { - writeContract, - simulateContract, - getTransactionReceipt, + waitForTransactionReceipt, } from "wagmi/actions"; -import { useAccount } from "wagmi"; +import { useAccount, useWriteContract } from "wagmi"; export function Orders() { return ( @@ -118,7 +116,7 @@ const OrdersMenu = () => { }; const useToken = (tokenAddress?: string) => { - const tokens = useSortedTokens() + const tokens = useSortedTokens(); return useMemo( () => tokens?.find((it) => eqIgnoreCase(it.address, tokenAddress || "")), @@ -143,38 +141,44 @@ const getOrderTitle = (order: Order) => { const useCancelOrder = () => { const { twapSDK } = useTwapContext(); const { refetch } = useOrdersQuery(); - const {address: account} = useAccount() + const { address: account } = useAccount(); + const { writeContractAsync } = useWriteContract(); return useMutation({ mutationFn: async (orderID: number) => { twapSDK.analytics.onCancelOrderRequest(orderID); - const simulatedData = await simulateContract(wagmiConfig, { + + const hash = await (writeContractAsync as any)({ abi: TwapAbi, functionName: "cancel", address: twapSDK.config.twapAddress as any, account, args: [orderID], }); - - const hash = await writeContract(wagmiConfig, simulatedData.request); await waitForConfirmations(hash, 1, 20); - await getTransactionReceipt(wagmiConfig, { + await waitForTransactionReceipt(wagmiConfig, { hash, }); twapSDK.analytics.onCancelOrderSuccess(); await refetch(); }, onError: (error) => { - twapSDK.analytics.onCancelOrderError(error); }, }); }; - const CancelOrderButton = ({ order }: { order: Order }) => { - const {isPending, mutate} =useCancelOrder(); - return -} + const { isPending, mutate } = useCancelOrder(); + return ( + + ); +}; const SelectedOrder = () => { const { data } = useOrdersQuery(); diff --git a/src/trade/twap/orders/use-orders-query.ts b/src/trade/twap/orders/use-orders-query.ts index 217feb2..f2adc9c 100644 --- a/src/trade/twap/orders/use-orders-query.ts +++ b/src/trade/twap/orders/use-orders-query.ts @@ -20,6 +20,7 @@ export function useOrdersQuery() { queryKey, queryFn: async ({ signal }) => { const orders = await twapSDK.getOrders(address!, signal); + return orders; }, enabled: !!address, diff --git a/src/trade/twap/twap-confirmation-dialog.tsx b/src/trade/twap/twap-confirmation-dialog.tsx index 4e01516..b9562c8 100644 --- a/src/trade/twap/twap-confirmation-dialog.tsx +++ b/src/trade/twap/twap-confirmation-dialog.tsx @@ -3,7 +3,6 @@ import { SwapSteps } from "@/types"; import { useCallback, useMemo } from "react"; import { SwapConfirmationDialog, - SwapProgressState, useSwapProgress, } from "../swap-confirmation-dialog"; import { @@ -15,28 +14,10 @@ import { import { useTwapContext } from "./context"; import { format, useGetRequiresApproval } from "@/lib"; import { OrderDetails } from "@/components/order-details"; -import { useNetwork, useToExactAmount } from "../hooks"; -import { useAccount } from "wagmi"; +import { useToExactAmount } from "../hooks"; import { SwapStatus } from "@orbs-network/swap-ui"; -import { Address, hexToNumber } from "viem"; -import { - getSteps, - isNativeAddress, - isTxRejected, - wagmiConfig, - waitForConfirmations, -} from "@/lib"; -import { approveAllowance } from "@/lib/approveAllowance"; -import { wrapToken } from "@/lib/wrapToken"; -import { TwapAbi, zeroAddress } from "@orbs-network/twap-sdk"; -import { useMutation } from "@tanstack/react-query"; -import { toast } from "sonner"; -import { - getTransactionReceipt, - simulateContract, - writeContract, -} from "wagmi/actions"; -import { useWaitForNewOrderCallback } from "./orders/use-orders-query"; +import { Address } from "viem"; +import { useSubmitOrderCallback } from "./useSubmitOrderCallback"; export function TwapConfirmationDialog({ isOpen, @@ -60,7 +41,7 @@ export function TwapConfirmationDialog({ inToken?.address, parsedInputAmount ); - const { mutate: onCreateOrder } = useCreateOrder( + const { mutate: onCreateOrder } = useSubmitOrderCallback( updateState, requiresApproval ); @@ -199,154 +180,3 @@ const TradePrice = () => { ); }; - -const useWrapCallback = () => { - const { twapSDK } = useTwapContext(); - - return useCallback( - async (account: string, inAmount: string) => { - try { - twapSDK.analytics.onWrapRequest(); - await wrapToken(account, inAmount); - twapSDK.analytics.onWrapSuccess(); - } catch (error) { - twapSDK.analytics.onWrapError(error); - throw error; - } - }, - [twapSDK] - ); -}; - -const useApproveCallback = () => { - const { twapSDK } = useTwapContext(); - const wToken = useNetwork()?.wToken.address; - return useCallback( - async (account: string, inTokenAddress: string) => { - try { - twapSDK.analytics.onApproveRequest(); - - const tokenAddress = isNativeAddress(inTokenAddress) - ? wToken - : inTokenAddress; - - if (!tokenAddress) { - throw new Error("Token address not found"); - } - - await approveAllowance( - account, - tokenAddress, - twapSDK.config.twapAddress as Address - ); - twapSDK.analytics.onApproveSuccess(); - } catch (error) { - twapSDK.analytics.onApproveError(error); - throw error; - } - }, - [twapSDK] - ); -}; - -function useCreateOrder( - updateState: (state: Partial) => void, - requiresApproval: boolean -) { - const { - twapSDK, - parsedInputAmount, - state: { - values: { inToken, outToken }, - }, - } = useTwapContext(); - const derivedValues = useDerivedTwapSwapData(); - const { address: account } = useAccount(); - const { mutateAsync: waitForNewOrder } = useWaitForNewOrderCallback(); - const wrapTokenCallback = useWrapCallback(); - const approveAllowanceCallback = useApproveCallback(); - const wToken = useNetwork()?.wToken.address; - return useMutation({ - mutationFn: async () => { - try { - const srcTokenAddress = isNativeAddress(inToken?.address) - ? wToken - : inToken?.address; - if ( - !inToken || - !account || - !parsedInputAmount || - !outToken || - !srcTokenAddress - ) { - throw new Error("Missing required dependencies"); - } - - updateState({ swapStatus: SwapStatus.LOADING }); - - const steps = getSteps({ - inTokenAddress: inToken.address, - requiresApproval, - }); - updateState({ steps }); - - if (steps.includes(SwapSteps.Wrap)) { - updateState({ currentStep: SwapSteps.Wrap }); - await wrapTokenCallback(account, parsedInputAmount); - // wrap - } - - if (steps.includes(SwapSteps.Approve)) { - updateState({ currentStep: SwapSteps.Approve }); - await approveAllowanceCallback(account, inToken.address); - } - - updateState({ currentStep: SwapSteps.Swap }); - const askParams = twapSDK - .prepareOrderArgs({ - fillDelay: derivedValues.fillDelay, - deadline: derivedValues.deadline, - srcAmount: parsedInputAmount ?? "0", - destTokenMinAmount: derivedValues.destTokenMinAmount, - srcChunkAmount: derivedValues.srcChunkAmount, - srcTokenAddress, - destTokenAddress: isNativeAddress(outToken?.address) - ? zeroAddress - : outToken.address, - }) - .map((it) => it.toString()); - - twapSDK.analytics.onCreateOrderRequest(askParams, account); - const simulatedData = await simulateContract(wagmiConfig, { - abi: TwapAbi, - functionName: "ask", - account: account as Address, - address: twapSDK.config.twapAddress as Address, - args: [askParams], - }); - - const hash = await writeContract(wagmiConfig, simulatedData.request); - await waitForConfirmations(hash, 1, 20); - const receipt = await getTransactionReceipt(wagmiConfig, { - hash, - }); - - const orderID = hexToNumber(receipt.logs[0].topics[1]!); - - await waitForNewOrder(orderID); - twapSDK.analytics.onCreateOrderSuccess(hash); - toast.success("Order created successfully!"); - updateState({ swapStatus: SwapStatus.SUCCESS }); - - return receipt; - } catch (error) { - if (isTxRejected(error)) { - updateState({ swapStatus: undefined }); - } else { - twapSDK.analytics.onCreateOrderError(error); - updateState({ swapStatus: SwapStatus.FAILED }); - } - } - }, - }); -} diff --git a/src/trade/twap/useSubmitOrderCallback.ts b/src/trade/twap/useSubmitOrderCallback.ts new file mode 100644 index 0000000..faded8b --- /dev/null +++ b/src/trade/twap/useSubmitOrderCallback.ts @@ -0,0 +1,178 @@ +import { + waitForConfirmations, + wagmiConfig, + useWrapToken, + useApproveAllowance, + isNativeAddress, + getSteps, + isTxRejected, +} from "@/lib"; +import { SwapSteps } from "@/types"; +import { SwapStatus } from "@orbs-network/swap-ui"; +import { TwapAbi } from "@orbs-network/twap-sdk"; +import { useMutation } from "@tanstack/react-query"; +import { toast } from "sonner"; +import { hexToNumber, zeroAddress } from "viem"; +import { useWriteContract, useAccount } from "wagmi"; +import { waitForTransactionReceipt } from "wagmi/actions"; +import { useNetwork } from "../hooks"; +import { SwapProgressState } from "../swap-confirmation-dialog"; +import { useTwapContext } from "./context"; +import { useDerivedTwapSwapData } from "./hooks"; +import { useWaitForNewOrderCallback } from "./orders/use-orders-query"; + +const useCreateOrder = () => { + const { writeContractAsync } = useWriteContract(); + const derivedValues = useDerivedTwapSwapData(); + const { twapSDK, parsedInputAmount } = useTwapContext(); + const { mutateAsync: waitForNewOrder } = useWaitForNewOrderCallback(); + const { address: account } = useAccount(); + + return useMutation({ + mutationFn: async ({ + srcTokenAddress, + destTokenAddress, + }: { + srcTokenAddress: string; + destTokenAddress: string; + }) => { + const askParams = twapSDK + .prepareOrderArgs({ + fillDelay: derivedValues.fillDelay, + deadline: 1733401003000, + srcAmount: parsedInputAmount ?? "0", + destTokenMinAmount: derivedValues.destTokenMinAmount, + srcChunkAmount: derivedValues.srcChunkAmount, + srcTokenAddress, + destTokenAddress, + }) + .map((it) => it.toString()); + + twapSDK.analytics.onCreateOrderRequest(askParams, account); + + const txHash = await (writeContractAsync as any)({ + abi: TwapAbi, + functionName: "ask", + account: account, + address: twapSDK.config.twapAddress, + args: [askParams], + }); + let receipt: any; + + try { + await waitForConfirmations(txHash, 1, 20); + receipt = await waitForTransactionReceipt(wagmiConfig as any, { + hash: txHash, + }); + } catch (error) { + console.log({ error }); + } + + const orderID = hexToNumber(receipt.logs[0].topics[1]!); + + await waitForNewOrder(orderID); + twapSDK.analytics.onCreateOrderSuccess(txHash); + return { + orderID, + txHash, + }; + }, + onError: (error) => { + twapSDK.analytics.onCreateOrderError(error); + console.error(error); + throw error; + }, + }); +}; + +export function useSubmitOrderCallback( + updateState: (state: Partial) => void, + requiresApproval: boolean +) { + const { + twapSDK, + parsedInputAmount, + state: { + values: { inToken, outToken }, + }, + } = useTwapContext(); + const { mutateAsync: wrap } = useWrapToken(); + const { address: account } = useAccount(); + const { mutateAsync: approve } = useApproveAllowance(); + const { mutateAsync: createOrder } = useCreateOrder(); + const wToken = useNetwork()?.wToken.address; + return useMutation({ + mutationFn: async () => { + try { + const srcTokenAddress = isNativeAddress(inToken?.address) + ? wToken + : inToken?.address; + + const destTokenAddress = isNativeAddress(outToken?.address) + ? zeroAddress + : outToken?.address; + if ( + !inToken || + !account || + !parsedInputAmount || + !outToken || + !srcTokenAddress || + !destTokenAddress + ) { + throw new Error("Missing required dependencies"); + } + + updateState({ swapStatus: SwapStatus.LOADING }); + + const steps = getSteps({ + inTokenAddress: inToken.address, + requiresApproval, + }); + updateState({ steps }); + + if (steps.includes(SwapSteps.Wrap)) { + updateState({ currentStep: SwapSteps.Wrap }); + try { + twapSDK.analytics.onWrapRequest(); + await wrap(parsedInputAmount); + twapSDK.analytics.onWrapSuccess(); + } catch (error) { + twapSDK.analytics.onWrapError(error); + throw error; + } + // wrap + } + + if (steps.includes(SwapSteps.Approve)) { + updateState({ currentStep: SwapSteps.Approve }); + try { + twapSDK.analytics.onApproveRequest(); + await approve({ + spender: twapSDK.config.twapAddress, + token: srcTokenAddress, + amount: parsedInputAmount, + }); + twapSDK.analytics.onApproveSuccess(); + } catch (error) { + twapSDK.analytics.onApproveError(error); + throw error; + } + } + updateState({ currentStep: SwapSteps.Swap }); + const response = await createOrder({ + srcTokenAddress, + destTokenAddress, + }); + updateState({ swapStatus: SwapStatus.SUCCESS }); + return response.orderID; + } catch (error) { + if (isTxRejected(error)) { + updateState({ swapStatus: undefined }); + } else { + updateState({ swapStatus: SwapStatus.FAILED }); + toast.error("Create order failed"); + } + } + }, + }); +} diff --git a/tsconfig.app.tsbuildinfo b/tsconfig.app.tsbuildinfo index 656d038..e55ee4a 100644 --- a/tsconfig.app.tsbuildinfo +++ b/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/root.tsx","./src/store.ts","./src/types.ts","./src/vite-env.d.ts","./src/components/header.tsx","./src/components/order-details.tsx","./src/components/spinner.tsx","./src/components/theme-toggle.tsx","./src/components/tokens/token-card.tsx","./src/components/tokens/token-select.tsx","./src/components/ui/avatar.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/data-details.tsx","./src/components/ui/dialog.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/popover.tsx","./src/components/ui/separator.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch-button.tsx","./src/components/ui/switch.tsx","./src/components/ui/tabs.tsx","./src/components/ui/tooltip.tsx","./src/lib/abis.ts","./src/lib/approveallowance.ts","./src/lib/getrequiresapproval.ts","./src/lib/index.ts","./src/lib/networks.ts","./src/lib/usedebounce.ts","./src/lib/usedefaulttokens.ts","./src/lib/usegetrequiresapproval.ts","./src/lib/usehandleinputerror.ts","./src/lib/useparaswap.ts","./src/lib/usepriceusd.ts","./src/lib/usetokens.ts","./src/lib/usewraporunwraponly.ts","./src/lib/utils.ts","./src/lib/wagmi-config.ts","./src/lib/wraptoken.ts","./src/providers/rainbow-provider.tsx","./src/providers/theme-provider.tsx","./src/trade/hooks.ts","./src/trade/settings.tsx","./src/trade/swap-confirmation-dialog.tsx","./src/trade/liquidity-hub/context.tsx","./src/trade/liquidity-hub/hooks.ts","./src/trade/liquidity-hub/liquidity-hub-confirmation-dialog.tsx","./src/trade/liquidity-hub/liquidity-hub-swap.tsx","./src/trade/liquidity-hub/swap-details.tsx","./src/trade/twap/context.tsx","./src/trade/twap/hooks.ts","./src/trade/twap/twap-confirmation-dialog.tsx","./src/trade/twap/twap.tsx","./src/trade/twap/utils.ts","./src/trade/twap/components/inputs.tsx","./src/trade/twap/components/limit-price-input.tsx","./src/trade/twap/components/price-toggle.tsx","./src/trade/twap/components/src-chunk-size.tsx","./src/trade/twap/orders/orders-context.tsx","./src/trade/twap/orders/orders.tsx","./src/trade/twap/orders/use-orders-query.ts"],"version":"5.6.2"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/root.tsx","./src/store.ts","./src/types.ts","./src/vite-env.d.ts","./src/components/header.tsx","./src/components/order-details.tsx","./src/components/spinner.tsx","./src/components/theme-toggle.tsx","./src/components/tokens/token-card.tsx","./src/components/tokens/token-select.tsx","./src/components/ui/avatar.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/data-details.tsx","./src/components/ui/dialog.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/popover.tsx","./src/components/ui/separator.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch-button.tsx","./src/components/ui/switch.tsx","./src/components/ui/tabs.tsx","./src/components/ui/tooltip.tsx","./src/lib/abis.ts","./src/lib/getrequiresapproval.ts","./src/lib/index.ts","./src/lib/networks.ts","./src/lib/useapproveallowance.ts","./src/lib/usedebounce.ts","./src/lib/usedefaulttokens.ts","./src/lib/usegetrequiresapproval.ts","./src/lib/usehandleinputerror.ts","./src/lib/useparaswap.ts","./src/lib/usepriceusd.ts","./src/lib/usetokens.ts","./src/lib/usewraporunwraponly.ts","./src/lib/usewraptoken.ts","./src/lib/utils.ts","./src/lib/wagmi-config.ts","./src/lib/tokens/base.ts","./src/lib/tokens/index.ts","./src/providers/rainbow-provider.tsx","./src/providers/theme-provider.tsx","./src/trade/hooks.ts","./src/trade/settings.tsx","./src/trade/swap-confirmation-dialog.tsx","./src/trade/liquidity-hub/consts.ts","./src/trade/liquidity-hub/context.tsx","./src/trade/liquidity-hub/hooks.ts","./src/trade/liquidity-hub/liquidity-hub-confirmation-dialog.tsx","./src/trade/liquidity-hub/liquidity-hub-swap.tsx","./src/trade/liquidity-hub/swap-details.tsx","./src/trade/liquidity-hub/useisliquidityhubtrade.ts","./src/trade/liquidity-hub/useliquidityhubquote.ts","./src/trade/liquidity-hub/useliquidityhubswapcallback.ts","./src/trade/liquidity-hub/useparaswapswapcallback.ts","./src/trade/twap/context.tsx","./src/trade/twap/hooks.ts","./src/trade/twap/twap-confirmation-dialog.tsx","./src/trade/twap/twap.tsx","./src/trade/twap/usesubmitordercallback.ts","./src/trade/twap/utils.ts","./src/trade/twap/components/inputs.tsx","./src/trade/twap/components/limit-price-input.tsx","./src/trade/twap/components/price-toggle.tsx","./src/trade/twap/components/src-chunk-size.tsx","./src/trade/twap/orders/orders-context.tsx","./src/trade/twap/orders/orders.tsx","./src/trade/twap/orders/use-orders-query.ts"],"version":"5.6.2"} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 6eaa34b..687e617 100644 --- a/yarn.lock +++ b/yarn.lock @@ -975,18 +975,18 @@ resolved "https://registry.npmjs.org/@orbs-network/swap-ui/-/swap-ui-0.0.14.tgz" integrity sha512-19Blk3JYsxae6Eu8a/nsLA8EbNSMQSJfsgV7Tg52RPs6IglTjEwfGyjy3HUPzRqEYXiHCQFo8xGNHjSKx3HJ5g== -"@orbs-network/twap-sdk@^2.0.38": - version "2.0.38" - resolved "https://registry.npmjs.org/@orbs-network/twap-sdk/-/twap-sdk-2.0.38.tgz" - integrity sha512-U7nBRA7tghIWQPbhdqqPOTm6VyoEVGHmjkZKE1ddYyAvQLSi+V/7Pnn1xi7OP7xYSBCl0YmmA3C/GPYGJGdfVA== +"@orbs-network/twap-sdk@^2.0.40": + version "2.0.40" + resolved "https://registry.yarnpkg.com/@orbs-network/twap-sdk/-/twap-sdk-2.0.40.tgz#db88ecdcb7380af9e8e185faa432338aa0c7a772" + integrity sha512-9tEQXeahzovdKuPHV4uxyA2JEA1vDYqcwPQhmMO5b6Y2pvdviTn/A4k24uqVaQJt+T8GTjgB9L1dHSr65DWV6w== dependencies: "@orbs-network/twap" "^2.0.1" bignumber.js "9.x" "@orbs-network/twap@^2.0.1": - version "2.2.4" - resolved "https://registry.npmjs.org/@orbs-network/twap/-/twap-2.2.4.tgz" - integrity sha512-SeEH45f4KXh6hXjbhhE4lPgTzIvMNx9Mun+pdX9plvdKpWAbcg6f1FKfv2MZ1ghtYxHlhuDcthlx159t8ReiOA== + version "2.2.7" + resolved "https://registry.yarnpkg.com/@orbs-network/twap/-/twap-2.2.7.tgz#5d50c3d2eebf969b0ce51f1fed749a08dc093e1d" + integrity sha512-tS3gpg3bI6hlz/9cQj3YB3C9R5rXLEla6KcM1Aom6O01UUvMQN2dAK56Nqz0uaqTsQoB+fveBGHW27g1qTAkSA== "@paraswap/core@2.2.0": version "2.2.0"