diff --git a/src/components/swap-details.tsx b/src/components/swap-details.tsx
deleted file mode 100644
index c0de6a3..0000000
--- a/src/components/swap-details.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { DataDetails } from '@/components/ui/data-details'
-import { Separator } from '@/components/ui/separator'
-import { format, fromBigNumber, getLiquidityProviderName } from '@/lib'
-import { LiquidityProvider, Token } from '@/types'
-import { OptimalRate } from '@paraswap/sdk'
-import { useMemo } from 'react'
-
-export type SwapDetailsProps = {
- optimalRate?: OptimalRate
- inToken: Token | null
- outToken: Token | null
- account?: string
- minAmountOut?: string
- liquidityProvider: LiquidityProvider
-}
-
-export function SwapDetails({
- inToken,
- outToken,
- account,
- minAmountOut,
- optimalRate,
- liquidityProvider,
-}: SwapDetailsProps) {
- const inPriceUsd = useMemo(() => {
- if (!optimalRate) return 0
- const amount = fromBigNumber(optimalRate.srcAmount, inToken?.decimals)
- return Number(optimalRate.srcUSD) / Number(amount)
- }, [optimalRate, inToken])
-
- const outPriceUsd = useMemo(() => {
- if (!optimalRate) return 0
- const amount = fromBigNumber(optimalRate.destAmount, outToken?.decimals)
- return Number(optimalRate.destUSD) / Number(amount)
- }, [optimalRate, outToken])
-
- if (!inToken || !outToken || !account || !optimalRate) return null
-
- const rate = inPriceUsd / outPriceUsd
-
- let data: Record
= {
- Rate: `1 ${inToken.symbol} ≈ ${format.crypto(rate)} ${outToken.symbol}`,
- }
-
- const minOutAmount = fromBigNumber(minAmountOut, outToken.decimals)
- const outAmount = fromBigNumber(optimalRate.destAmount, outToken.decimals)
- data = {
- ...data,
- 'Est. Received': `${format.crypto(Number(outAmount))} ${outToken.symbol}`,
- 'Min. Received': `${format.crypto(minOutAmount)} ${outToken.symbol}`,
- 'Routing source': getLiquidityProviderName(liquidityProvider),
- }
-
- return (
-
-
-
-
-
Recepient
-
{format.address(account)}
-
-
- )
-}
diff --git a/src/components/tokens/token-card.tsx b/src/components/tokens/token-card.tsx
index c437d87..f1011e6 100644
--- a/src/components/tokens/token-card.tsx
+++ b/src/components/tokens/token-card.tsx
@@ -1,72 +1,68 @@
-import { WalletIcon } from 'lucide-react'
-import { Card } from '../ui/card'
-import { TokenSelect } from './token-select'
-import { Token, TokensWithBalances } from '@/types'
-import { NumericFormat } from 'react-number-format'
-import {
- format,
- cn,
- fromBigNumber,
- ErrorCodes,
-} from '@/lib'
-import { Skeleton } from '../ui/skeleton'
-import { Button } from '../ui/button'
-import { useToExactAmount } from '@/trade/hooks'
-import BN from 'bignumber.js'
+import { WalletIcon } from "lucide-react";
+import { Card } from "../ui/card";
+import { TokenSelect } from "./token-select";
+import { Token } from "@/types";
+import { NumericFormat } from "react-number-format";
+import { format, cn, ErrorCodes, useTokenBalance, toExactAmount } from "@/lib";
+import { Skeleton } from "../ui/skeleton";
+import { Button } from "../ui/button";
+import { useToExactAmount } from "@/trade/hooks";
+import BN from "bignumber.js";
function getTextSize(amountLength: number) {
if (amountLength > 16) {
- return 'text-xl'
+ return "text-xl";
}
if (amountLength > 12 && amountLength <= 16) {
- return 'text-2xl'
+ return "text-2xl";
}
- return 'text-4xl'
+ return "text-4xl";
}
export type TokenCardProps = {
- label: string
- amount: string
- amountUsd?: string
- balance: any
- selectedToken: Token
- tokens: TokensWithBalances
- onSelectToken: (token: Token) => void
- isAmountEditable?: boolean
- onValueChange?: (value: string) => void
- amountLoading?: boolean
- inputError?: string | null
-}
+ label: string;
+ amount: string;
+ amountUsd?: string;
+ selectedToken: Token | null;
+ onSelectToken: (token: Token) => void;
+ isAmountEditable?: boolean;
+ onValueChange?: (value: string) => void;
+ amountLoading?: boolean;
+ inputError?: string | null;
+};
export function TokenCard({
label,
amount,
amountUsd,
- balance,
selectedToken,
- tokens,
onSelectToken,
onValueChange,
isAmountEditable = true,
amountLoading,
inputError,
}: TokenCardProps) {
-
- const balanceError = inputError === ErrorCodes.InsufficientBalance
+ const { balance } = useTokenBalance(selectedToken?.address);
+ const balanceError = inputError === ErrorCodes.InsufficientBalance;
const balanceDisplay = selectedToken
- ? format.crypto(fromBigNumber(balance, selectedToken.decimals))
- : '0'
+ ? format.crypto(Number(toExactAmount(balance, selectedToken.decimals)))
+ : "0";
- const maxBalance = useToExactAmount(balance, selectedToken?.decimals)
- const halfBalance = useToExactAmount(BN(balance || 0).dividedBy(2).toString(), selectedToken?.decimals)
+ const maxBalance = useToExactAmount(balance, selectedToken?.decimals);
+ const halfBalance = useToExactAmount(
+ BN(balance || 0)
+ .dividedBy(2)
+ .toString(),
+ selectedToken?.decimals
+ );
return (
@@ -97,7 +93,7 @@ export function TokenCard({
{amountLoading ? (
) : (
-
+
@@ -126,7 +121,7 @@ export function TokenCard({
) : (
- {format.dollar(Number(amountUsd || '0'))}
+ {format.dollar(Number(amountUsd || "0"))}
)}
@@ -135,5 +130,5 @@ export function TokenCard({
- )
+ );
}
diff --git a/src/components/tokens/token-select.tsx b/src/components/tokens/token-select.tsx
index cbeeca4..110ed72 100644
--- a/src/components/tokens/token-select.tsx
+++ b/src/components/tokens/token-select.tsx
@@ -10,63 +10,44 @@ import {
} from "../ui/dialog";
import { Input } from "../ui/input";
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
-import { Token, TokensWithBalances } from "@/types";
+import { Token } from "@/types";
import { Card } from "../ui/card";
import { useMemo, useState } from "react";
-import { fromBigNumber } from "@/lib";
+import {
+ eqIgnoreCase,
+ format,
+ usePriceUsd,
+ useSortedTokens,
+ useTokenBalance,
+} from "@/lib";
+import { useToExactAmount } from "@/trade/hooks";
+import { Skeleton } from "../ui/skeleton";
+import { Virtuoso } from "react-virtuoso";
+import BN from "bignumber.js";
type TokenSelectProps = {
selectedToken: Token | undefined;
- tokens: TokensWithBalances;
onSelectToken: (token: Token) => void;
};
export function TokenSelect({
selectedToken,
- tokens,
onSelectToken,
}: TokenSelectProps) {
const [open, setOpen] = useState(false);
+ const tokens = useSortedTokens();
const [filterInput, setFilterInput] = useState("");
- const SortedTokens = useMemo(() => {
- return Object.values(tokens)
- .filter((t) => {
- return (
- t.token.symbol.toLowerCase().includes(filterInput.toLowerCase()) ||
- t.token.address.toLowerCase().includes(filterInput.toLowerCase())
- );
- })
- .sort(
- (a, b) =>
- fromBigNumber(b.balance, b.token.decimals) -
- fromBigNumber(a.balance, a.token.decimals)
- )
- .map((t) => (
- {
- onSelectToken(t.token);
- setOpen(false);
- }}
- >
-
-
-
-
- {t.token.symbol.charAt(0)}
-
-
-
-
{t.token.symbol}
-
{t.token.name}
-
-
- {fromBigNumber(t.balance, t.token.decimals).toFixed(5)}
-
- ));
- }, [filterInput, onSelectToken, tokens]);
+ const filteredTokens = useMemo(() => {
+ if (!filterInput) return tokens || [];
+ return (
+ tokens?.filter(
+ (t) =>
+ eqIgnoreCase(t.address, filterInput) ||
+ t.symbol.toLowerCase().includes(filterInput.toLowerCase())
+ ) || []
+ );
+ }, [tokens, filterInput]);
return (
);
}
+
+const TokenDisplay = ({
+ token: t,
+ onSelect,
+}: {
+ token: Token;
+ onSelect: () => void;
+}) => {
+ const { balance, isLoading } = useTokenBalance(t.address);
+ const usd = usePriceUsd(t.address).data || 0;
+ const balanceUi = useToExactAmount(balance, t.decimals) || "0";
+ const usdAmount = BN(balanceUi).multipliedBy(usd).toFixed()
+
+ return (
+
+
+
+
+
+ {t.symbol.charAt(0)}
+
+
+
+
{t.symbol}
+
{t.name}
+
+
+ {isLoading ? (
+
+ ) : (
+
+
{format.crypto(Number(balanceUi))}
+
${format.crypto(Number(usdAmount))}
+
+ )}
+
+ );
+};
diff --git a/src/lib/index.ts b/src/lib/index.ts
index 76d4dcb..1e4a570 100644
--- a/src/lib/index.ts
+++ b/src/lib/index.ts
@@ -3,11 +3,11 @@ export * from './networks'
export * from './useGetRequiresApproval'
export * from './utils'
export * from './useHandleInputError'
-export * from './useTokensWithBalances'
+export * from './useTokens'
export * from './useDefaultTokens'
export * from './useDebounce'
export * from './useParaswap'
export * from './wagmi-config'
-export * from './useTokenList'
+export * from './useTokens'
export * from './useWrapOrUnwrapOnly'
export * from './usePriceUsd'
diff --git a/src/lib/networks.ts b/src/lib/networks.ts
index 472acc1..7eb1307 100644
--- a/src/lib/networks.ts
+++ b/src/lib/networks.ts
@@ -1,54 +1,383 @@
-import { zeroAddress } from 'viem'
+import { zeroAddress } from "viem";
export const networks = {
+ eth: {
+ id: 1,
+ name: "Ethereum",
+ shortname: "eth",
+ native: {
+ address: zeroAddress,
+ symbol: "ETH",
+ decimals: 18,
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/ethereum.svg",
+ },
+ wToken: {
+ symbol: "WETH",
+ address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
+ decimals: 18,
+ weth: true,
+ logoUrl:
+ "https://tokens-data.1inch.io/images/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.png",
+ },
+ publicRpcUrl: "https://eth.llamarpc.com",
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/ethereum.svg",
+ explorer: "https://etherscan.io",
+ eip1559: true,
+ baseAssets: [
+ "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
+ "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
+ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
+ "0xdAC17F958D2ee523a2206206994597C13D831ec7",
+ "0x6B175474E89094C44Da98b954EedeAC495271d0F",
+ "0x5f98805A4E8be255a32880FDeC7F6728C6568bA0",
+ "0x853d955aCEf822Db058eb8505911ED77F175b99e",
+ "0xff56Cc6b1E6dEd347aA0B7676C85AB0B3D08B0FA",
+ ],
+ },
+ bsc: {
+ id: 56,
+ name: "BinanceSmartChain",
+ shortname: "bsc",
+ native: {
+ address: zeroAddress,
+ symbol: "BNB",
+ decimals: 18,
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/bsc_2.svg",
+ },
+ wToken: {
+ symbol: "WBNB",
+ address: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
+ decimals: 18,
+ weth: true,
+ logoUrl:
+ "https://tokens-data.1inch.io/images/0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c.png",
+ },
+ publicRpcUrl: "https://bsc-dataseed.binance.org",
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/bsc_2.svg",
+ explorer: "https://bscscan.com",
+ eip1559: false,
+ baseAssets: [
+ "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
+ "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c",
+ "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
+ "0x55d398326f99059fF775485246999027B3197955",
+ "0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3",
+ "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56",
+ "0x2170Ed0880ac9A755fd29B2688956BD959F933F8",
+ "0xeBd49b26169e1b52c04cFd19FCf289405dF55F80",
+ ],
+ },
poly: {
id: 137,
- name: 'Polygon',
- shortname: 'poly',
+ name: "Polygon",
+ shortname: "poly",
native: {
address: zeroAddress,
- symbol: 'MATIC',
+ symbol: "MATIC",
decimals: 18,
- logoUrl: 'https://app.1inch.io/assets/images/network-logos/polygon.svg',
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/polygon.svg",
},
wToken: {
- symbol: 'WMATIC',
- address: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
+ symbol: "WMATIC",
+ address: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
decimals: 18,
weth: true,
logoUrl:
- 'https://tokens-data.1inch.io/images/0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270.png',
+ "https://tokens-data.1inch.io/images/0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270.png",
},
- publicRpcUrl: 'https://polygon-rpc.com',
- logoUrl: 'https://app.1inch.io/assets/images/network-logos/polygon.svg',
- explorer: 'https://polygonscan.com',
+ publicRpcUrl: "https://polygon-rpc.com",
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/polygon.svg",
+ explorer: "https://polygonscan.com",
eip1559: true,
+ baseAssets: [
+ "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
+ "0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4",
+ "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6",
+ "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
+ "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
+ "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
+ "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619",
+ "0xdAb529f40E671A1D4bF91361c21bf9f0C9712ab7",
+ "0x614389EaAE0A6821DC49062D56BDA3d9d45Fa2ff",
+ ],
+ },
+ arb: {
+ id: 42161,
+ name: "Arbitrum",
+ shortname: "arb",
+ native: {
+ address: zeroAddress,
+ symbol: "ETH",
+ decimals: 18,
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/ethereum.svg",
+ },
+ wToken: {
+ symbol: "WETH",
+ address: "0x82af49447d8a07e3bd95bd0d56f35241523fbab1",
+ decimals: 18,
+ weth: true,
+ logoUrl:
+ "https://tokens-data.1inch.io/images/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.png",
+ },
+ publicRpcUrl: "https://arb1.arbitrum.io/rpc",
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/arbitrum.svg",
+ explorer: "https://arbiscan.io",
+ eip1559: true,
+ baseAssets: [
+ "0x82af49447d8a07e3bd95bd0d56f35241523fbab1",
+ "0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f",
+ "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8",
+ "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9",
+ "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
+ "0x912CE59144191C1204E64559FE8253a0e49E6548",
+ "0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F",
+ "0x4D15a3A2286D883AF0AA1B3f21367843FAc63E07",
+ ],
+ },
+ avax: {
+ id: 43114,
+ name: "Avalanche",
+ shortname: "avax",
+ native: {
+ address: zeroAddress,
+ symbol: "AVAX",
+ decimals: 18,
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/avalanche.svg",
+ },
+ wToken: {
+ symbol: "WAVAX",
+ address: "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7",
+ decimals: 18,
+ weth: true,
+ logoUrl:
+ "https://tokens-data.1inch.io/images/0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7.png",
+ },
+ publicRpcUrl: "https://api.avax.network/ext/bc/C/rpc",
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/avalanche.svg",
+ explorer: "https://snowtrace.io",
+ eip1559: true,
+ baseAssets: [
+ "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7",
+ "0x50b7545627a5162F82A992c33b87aDc75187B218",
+ "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
+ "0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664",
+ "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7",
+ "0xc7198437980c041c805A1EDcbA50c1Ce5db95118",
+ "0x19860CCB0A68fd4213aB9D8266F7bBf05A8dDe98",
+ "0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB",
+ "0xd586E7F844cEa2F87f50152665BCbc2C279D8d70",
+ "0x340fE1D898ECCAad394e2ba0fC1F93d27c7b717A",
+ ],
+ },
+ oeth: {
+ id: 10,
+ name: "Optimism",
+ shortname: "oeth",
+ native: {
+ address: zeroAddress,
+ symbol: "ETH",
+ decimals: 18,
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/ethereum.svg",
+ },
+ wToken: {
+ symbol: "WETH",
+ address: "0x4200000000000000000000000000000000000006",
+ decimals: 18,
+ weth: true,
+ logoUrl:
+ "https://tokens-data.1inch.io/images/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.png",
+ },
+ publicRpcUrl: "https://mainnet.optimism.io",
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/optimism.svg",
+ explorer: "https://optimistic.etherscan.io",
+ eip1559: true,
+ baseAssets: [
+ "0x4200000000000000000000000000000000000006",
+ "0x68f180fcCe6836688e9084f035309E29Bf0A2095",
+ "0x7F5c764cBc14f9669B88837ca1490cCa17c31607",
+ "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58",
+ "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
+ "0x4200000000000000000000000000000000000042",
+ "0x2E3D870790dC77A83DD1d18184Acc7439A53f475",
+ ],
+ },
+ ftm: {
+ id: 250,
+ name: "Fantom",
+ shortname: "ftm",
+ native: {
+ address: zeroAddress,
+ symbol: "FTM",
+ decimals: 18,
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/fantom.svg",
+ },
+ wToken: {
+ symbol: "WFTM",
+ address: "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83",
+ decimals: 18,
+ weth: true,
+ logoUrl:
+ "https://tokens-data.1inch.io/images/0x4e15361fd6b4bb609fa63c81a2be19d873717870.png",
+ },
+ publicRpcUrl: "https://rpc.ftm.tools",
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/fantom.svg",
+ explorer: "https://ftmscan.com",
+ eip1559: true,
+ baseAssets: [
+ "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83",
+ "0x321162Cd933E2Be498Cd2267a90534A804051b11",
+ "0x74b23882a30290451A17c44f4F05243b6b58C76d",
+ "0x04068DA6C83AFCFA0e13ba15A6696662335D5B75",
+ "0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E",
+ "0xdc301622e621166BD8E82f2cA0A26c13Ad0BE355",
+ "0x3E01B7E242D5AF8064cB9A8F9468aC0f8683617c",
+ ],
+ },
+ base: {
+ id: 8453,
+ name: "Base",
+ shortname: "base",
+ native: {
+ address: zeroAddress,
+ symbol: "ETH",
+ decimals: 18,
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/ethereum.svg",
+ },
+ wToken: {
+ symbol: "WETH",
+ address: "0x4200000000000000000000000000000000000006",
+ decimals: 18,
+ weth: true,
+ logoUrl:
+ "https://tokens-data.1inch.io/images/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.png",
+ },
+ publicRpcUrl: "https://mainnet.base.org",
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/base.svg",
+ explorer: "https://basescan.org",
+ eip1559: false,
+ baseAssets: [
+ "0x4200000000000000000000000000000000000006",
+ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
+ ],
+ },
+ linea: {
+ id: 59144,
+ name: "Linea",
+ shortname: "linea",
+ native: {
+ address: zeroAddress,
+ symbol: "ETH",
+ decimals: 18,
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/ethereum.svg",
+ },
+ wToken: {
+ symbol: "WETH",
+ address: "0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f",
+ decimals: 18,
+ weth: true,
+ logoUrl:
+ "https://tokens-data.1inch.io/images/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.png",
+ },
+ publicRpcUrl: "https://rpc.linea.build",
+ logoUrl: "https://lineascan.build/images/logo.svg",
+ explorer: "https://lineascan.build",
+ eip1559: false,
+ baseAssets: [
+ "0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f",
+ "0x176211869cA2b568f2A7D4EE941E073a821EE1ff",
+ "0x4AF15ec2A0BD43Db75dd04E62FAA3B8EF36b00d5",
+ ],
+ },
+ zksync: {
+ id: 324,
+ name: "zksync",
+ shortname: "zksync",
+ native: {
+ address: zeroAddress,
+ symbol: "ETH",
+ decimals: 18,
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/ethereum.svg",
+ },
+ wToken: {
+ symbol: "WETH",
+ address: "0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91",
+ decimals: 18,
+ weth: true,
+ logoUrl:
+ "https://tokens-data.1inch.io/images/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.png",
+ },
+ publicRpcUrl: "https://mainnet.era.zksync.io",
+ logoUrl:
+ "https://raw.githubusercontent.com/matter-labs/zksync/0a4ca2145a0c95b5bafa84c2f095c644907a8825/zkSyncLogo.svg",
+ explorer: "https://explorer.zksync.io/",
+ eip1559: true,
+ baseAssets: [
+ "0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91",
+ "0x3355df6d4c9c3035724fd0e3914de96a5a83aaf4",
+ "0x4b9eb6c0b6ea15176bbf62841c6b2a8a398cb656",
+ ],
+ },
+ zkevm: {
+ id: 1101,
+ name: "zkevm",
+ shortname: "zkevm",
+ native: {
+ address: zeroAddress,
+ symbol: "ETH",
+ decimals: 18,
+ logoUrl: "https://app.1inch.io/assets/images/network-logos/ethereum.svg",
+ },
+ wToken: {
+ symbol: "WETH",
+ address: "0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9",
+ decimals: 18,
+ weth: true,
+ logoUrl:
+ "https://tokens-data.1inch.io/images/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.png",
+ },
+ publicRpcUrl: "https://zkevm-rpc.com",
+ logoUrl:
+ "https://user-images.githubusercontent.com/18598517/235932702-bc47eae5-d672-4dd9-9da2-8ea8f51a93f3.png",
+ explorer: "https://zkevm.polygonscan.com/",
+ eip1559: true,
+ baseAssets: [
+ "0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9",
+ "0xA8CE8aee21bC2A48a5EF670afCc9274C7bbbC035",
+ "0xC5015b9d9161Dca7e18e32f6f25C4aD850731Fd4",
+ ],
},
-}
-
-export const network = {
- 137: {
- id: 137,
- name: 'Polygon',
- shortname: 'poly',
+ blast: {
+ id: 81457,
+ name: "blast",
+ shortname: "blast",
native: {
address: zeroAddress,
- symbol: 'MATIC',
+ symbol: "ETH",
decimals: 18,
- logoUrl: 'https://app.1inch.io/assets/images/network-logos/polygon.svg',
+ logoUrl: "https://icons.llamao.fi/icons/chains/rsz_blast",
},
wToken: {
- symbol: 'WMATIC',
- address: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
+ symbol: "WETH",
+ address: "0x4300000000000000000000000000000000000004",
decimals: 18,
weth: true,
logoUrl:
- 'https://tokens-data.1inch.io/images/0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270.png',
+ "https://tokens-data.1inch.io/images/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.png",
},
- publicRpcUrl: 'https://polygon-rpc.com',
- logoUrl: 'https://app.1inch.io/assets/images/network-logos/polygon.svg',
- explorer: 'https://polygonscan.com',
+ publicRpcUrl: "https://rpc.ankr.com/blast",
+ logoUrl: "https://icons.llamao.fi/icons/chains/rsz_blast",
+ explorer: "https://blastscan.io/",
eip1559: true,
+ baseAssets: [
+ "0x4300000000000000000000000000000000000004",
+ "0x4300000000000000000000000000000000000003",
+ ],
},
-}
\ No newline at end of file
+};
+
+export const getNetwork = (chainId: number) => {
+ return Object.values(networks).find((network) => network.id === chainId);
+};
+
diff --git a/src/lib/useBalances.ts b/src/lib/useBalances.ts
deleted file mode 100644
index 15f870f..0000000
--- a/src/lib/useBalances.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import { useQuery } from '@tanstack/react-query'
-import { getBalance, multicall } from '@wagmi/core'
-import { Config, serialize, useBalance, useConfig } from 'wagmi'
-import { GetBalanceReturnType } from 'wagmi/actions'
-import { Token, TokensWithBalances } from '@/types'
-import { erc20Abi, isAddress, Address } from 'viem'
-import { zeroAddress } from '@orbs-network/liquidity-hub-sdk'
-
-interface QueryBalanceParams {
- chainId: number | undefined
- tokens: Token[]
- account: string | undefined
- nativeBalance?: GetBalanceReturnType
- config: Config
-}
-
-export const queryFnUseBalances = async ({
- chainId,
- tokens,
- account,
- nativeBalance,
- config,
-}: QueryBalanceParams) => {
- if (!account || !chainId || !tokens) return null
-
- let native = nativeBalance
- if (typeof native === 'undefined') {
- native = await getBalance(config, {
- address: account as Address,
- chainId,
- })
- }
-
- const [validatedTokens, validatedTokenAddresses] = tokens.reduce<
- [Token[], Address[]]
- >(
- (acc, tokens) => {
- if (chainId && tokens && isAddress(tokens.address)) {
- acc[0].push(tokens)
- acc[1].push(tokens.address as Address)
- }
-
- return acc
- },
- [[], []]
- )
-
- const data = await multicall(config, {
- contracts: validatedTokenAddresses.map(
- (token) =>
- ({
- chainId,
- address: token,
- abi: erc20Abi,
- functionName: 'balanceOf',
- args: [account],
- } as const)
- ),
- })
-
- const _data = data.reduce((acc, _cur, i) => {
- const amount = data[i].result
- if (typeof amount === 'bigint') {
- acc[validatedTokens[i].address] = {
- token: validatedTokens[i],
- balance: amount,
- }
- }
- return acc
- }, {})
-
- _data[zeroAddress] = {
- token: validatedTokens[0],
- balance: native.value,
- }
-
- return _data
-}
-
-interface UseBalanceParams {
- chainId: number | undefined
- tokens: Token[]
- account: Address | undefined
- enabled?: boolean
-}
-
-export const useBalances = ({
- chainId,
- tokens,
- account,
- enabled = true,
-}: UseBalanceParams) => {
- const { data: nativeBalance, queryKey } = useBalance({
- chainId,
- address: account,
- query: { enabled, refetchInterval: 10000, staleTime: 10000 },
- })
-
- const config = useConfig()
-
- return {
- query: useQuery({
- queryKey: [
- 'useBalances',
- { chainId, tokens, account, nativeBalance: serialize(nativeBalance) },
- ],
- queryFn: () =>
- queryFnUseBalances({
- chainId,
- tokens,
- account,
- nativeBalance,
- config,
- }),
- refetchInterval: 10000,
- staleTime: 10000,
- enabled: Boolean(chainId && account && enabled && tokens),
- }),
- queryKey: [...queryKey, 'useBalances'],
- }
-}
diff --git a/src/lib/useDefaultTokens.ts b/src/lib/useDefaultTokens.ts
index 96b5024..fa44c93 100644
--- a/src/lib/useDefaultTokens.ts
+++ b/src/lib/useDefaultTokens.ts
@@ -1,48 +1,14 @@
-import { Token, TokensWithBalances } from '@/types'
-import { zeroAddress } from '@orbs-network/liquidity-hub-sdk'
-import { useEffect, useMemo } from 'react'
-
-/* Sets default tokens */
-type UseDefaultTokens = {
- inToken: Token | null
- outToken: Token | null
- tokensWithBalances: TokensWithBalances | null | undefined
- setInToken: (token: Token) => void
- setOutToken: (token: Token) => void
-}
-export function useDefaultTokens({
- tokensWithBalances,
- inToken,
- outToken,
- setInToken,
- setOutToken,
-}: UseDefaultTokens) {
- const defaultTokens = useMemo(() => {
- if (!tokensWithBalances) return []
-
- return [
- tokensWithBalances[zeroAddress].token,
- Object.values(tokensWithBalances).find((t) => t.token.symbol === 'USDT')
- ?.token || null,
- ].filter(Boolean) as Token[]
- }, [tokensWithBalances])
-
- useEffect(() => {
- if (!inToken && tokensWithBalances) {
- setInToken(defaultTokens[0])
- }
-
- if (!outToken && tokensWithBalances) {
- setOutToken(defaultTokens[1])
- }
- }, [
- inToken,
- defaultTokens,
- outToken,
- setInToken,
- setOutToken,
- tokensWithBalances,
- ])
-
- return defaultTokens
+import { useMemo } from "react";
+import { useSortedTokens } from "./useTokens";
+
+export function useDefaultTokens() {
+ const tokens = useSortedTokens();
+
+ return useMemo(() => {
+ if (!tokens) return;
+ return {
+ inToken: tokens[0],
+ outToken: tokens[1],
+ };
+ }, [tokens]);
}
diff --git a/src/lib/useGetRequiresApproval.ts b/src/lib/useGetRequiresApproval.ts
index 99c00a7..ef8c59b 100644
--- a/src/lib/useGetRequiresApproval.ts
+++ b/src/lib/useGetRequiresApproval.ts
@@ -1,28 +1,32 @@
-import { useAccount, useReadContract } from 'wagmi'
-import { Address, erc20Abi } from 'viem'
+import { useAccount, useReadContract } from "wagmi";
+import { Address, erc20Abi } from "viem";
+import { useNetwork } from "@/trade/hooks";
+import { isNativeAddress } from "./utils";
/* Determines whether user needs tp approve allowance for quoted token */
export function useGetRequiresApproval(
- contractAddress: Address,
- inTokenAddress = '',
- inAmount = ''
+ contractAddress?: any,
+ inTokenAddress = "",
+ inAmount = ""
) {
- const { address } = useAccount()
+ const { address: account } = useAccount();
+ const wToken = useNetwork()?.wToken.address;
+ const address = isNativeAddress(inTokenAddress) ? wToken : inTokenAddress;
const {
data: allowance,
isLoading,
error,
} = useReadContract({
- address: inTokenAddress as Address,
+ address: address as Address,
abi: erc20Abi,
- functionName: 'allowance',
- args: [address as Address, contractAddress],
+ functionName: "allowance",
+ args: [account as Address, contractAddress],
query: { enabled: Boolean(inTokenAddress && address && contractAddress) },
- })
+ });
return {
requiresApproval: (allowance || 0n) < BigInt(inAmount || 0),
approvalLoading: isLoading,
error,
- }
+ };
}
diff --git a/src/lib/useHandleInputError.ts b/src/lib/useHandleInputError.ts
index 5e4a806..750341d 100644
--- a/src/lib/useHandleInputError.ts
+++ b/src/lib/useHandleInputError.ts
@@ -1,10 +1,10 @@
-import { ErrorCodes, fromBigNumber } from "@/lib/utils";
+import { ErrorCodes } from "@/lib/utils";
import { Token } from "@/types";
import { useMemo } from "react";
-import {
- useTokenBalance,
- useTokensWithBalances,
-} from "./useTokensWithBalances";
+import BN from "bignumber.js";
+import { useTokenBalance } from "./useTokens";
+import { useToRawAmount } from "@/trade/hooks";
+
/* Handles amount input errors */
@@ -15,18 +15,18 @@ export function useInputError({
inToken: Token | null;
inputAmount: string;
}) {
- const tokensWithBalances = useTokensWithBalances();
- const tokenBalance = useTokenBalance(inToken?.address);
+ const {balance} = useTokenBalance(inToken?.address);
+ const parsedInputAmount = useToRawAmount(inputAmount, inToken?.decimals)
return useMemo(() => {
- if (!inToken || !tokensWithBalances) return;
- if (!inputAmount) {
+ if(BN(inputAmount || '0').lte(0)) {
+ return ErrorCodes.EnterAmount;
+ }
+ if (!balance) {
return ErrorCodes.EnterAmount;
}
- const value = Number(inputAmount);
- const balance = fromBigNumber(tokenBalance, inToken.decimals);
- if (value > balance) {
+ if (BN(parsedInputAmount).gt(balance)) {
return ErrorCodes.InsufficientBalance;
}
- }, [inputAmount, inToken, tokenBalance, tokensWithBalances]);
+ }, [inputAmount, inToken, balance, parsedInputAmount]);
}
diff --git a/src/lib/useParaswap.ts b/src/lib/useParaswap.ts
index ce84f49..cd716ea 100644
--- a/src/lib/useParaswap.ts
+++ b/src/lib/useParaswap.ts
@@ -2,19 +2,11 @@ import { useCallback, useMemo } from 'react'
import {
getMinAmountOut,
isNativeAddress,
- resolveNativeTokenAddress,
- waitForConfirmations,
} from '@/lib/utils'
import { constructSimpleSDK, OptimalRate, SwapSide } from '@paraswap/sdk'
-import { useMutation, useQuery } from '@tanstack/react-query'
+import { useQuery } from '@tanstack/react-query'
import { useAccount } from 'wagmi'
-import { wagmiConfig } from './wagmi-config'
-import { estimateGas, sendTransaction } from 'wagmi/actions'
-import { Address } from 'viem'
-import { SwapStatus } from '@orbs-network/swap-ui'
-import { SwapSteps } from '@/types'
-import { approveAllowance } from './approveAllowance'
-import { getRequiresApproval } from './getRequiresApproval'
+
const PARASWAP_NATIVE_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
@@ -35,10 +27,12 @@ export const useParaswapQuote = ({
inToken,
outToken,
inAmount,
+ refetchInterval = 30_000,
}: {
inToken?: string
outToken?: string
inAmount?: string
+ refetchInterval?: number
}) => {
const paraswap = useParaswap()
const { chainId } = useAccount()
@@ -72,7 +66,7 @@ export const useParaswapQuote = ({
return dexQuote
},
enabled: !!inToken && !!outToken && Number(inAmount) > 0,
- refetchInterval: 30_000,
+ refetchInterval,
})
}
@@ -103,102 +97,3 @@ export const useParaswapBuildTxCallback = () => {
)
}
-export const useParaswapSwapCallback = () => {
- const buildParaswapTxCallback = useParaswapBuildTxCallback()
- const { address } = useAccount()
-
- return useMutation({
- mutationFn: async ({
- optimalRate,
- slippage,
- setSwapStatus,
- setCurrentStep,
- onSuccess,
- onFailure,
- }: {
- optimalRate: OptimalRate
- slippage: number
- setSwapStatus: (status?: SwapStatus) => void
- setCurrentStep: (step: SwapSteps) => void
- onSuccess?: () => void
- onFailure?: () => void
- }) => {
- if (!address) {
- throw new Error('Wallet not connected')
- }
-
- try {
- setSwapStatus(SwapStatus.LOADING)
-
- // Check if the inToken needs approval for allowance
- const requiresApproval = await getRequiresApproval(
- optimalRate.tokenTransferProxy,
- resolveNativeTokenAddress(optimalRate.srcToken),
- optimalRate.srcAmount,
- address
- )
-
- if (requiresApproval) {
- setCurrentStep(SwapSteps.Approve)
- await approveAllowance(
- address,
- optimalRate.srcToken,
- optimalRate.tokenTransferProxy as Address
- )
- }
-
- setCurrentStep(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)
- if (onFailure) onFailure()
- setSwapStatus(SwapStatus.FAILED)
- }
-
- if (!txPayload) {
- if (onFailure) onFailure()
- setSwapStatus(SwapStatus.FAILED)
-
- throw new Error('Failed to build transaction')
- }
-
- console.log('Swapping...')
- // Use estimate gas to simulate send transaction
- // if any error occurs, it will be caught and handled
- // without spending any gas
- await estimateGas(wagmiConfig, txPayload)
-
- const txHash = await sendTransaction(wagmiConfig, txPayload)
-
- await waitForConfirmations(txHash, 1, 20)
-
- if (onSuccess) onSuccess()
-
- setSwapStatus(SwapStatus.SUCCESS)
- console.log('Swapped')
-
- return txHash
- } catch (error) {
- console.error(error)
- if (onFailure) onFailure()
- setSwapStatus(SwapStatus.FAILED)
-
- throw error
- }
- },
- })
-}
diff --git a/src/lib/usePriceUsd.ts b/src/lib/usePriceUsd.ts
index f807841..c273a39 100644
--- a/src/lib/usePriceUsd.ts
+++ b/src/lib/usePriceUsd.ts
@@ -1,17 +1,19 @@
-import { networks, isNativeAddress } from '@/lib'
+import { isNativeAddress, getNetwork } from '@/lib'
import { useQuery } from '@tanstack/react-query'
+import { useAccount } from 'wagmi'
-export const usePriceUsd = (chainId: number, address?: string) => {
+export const usePriceUsd = (address?: string) => {
+ const {chainId} = useAccount()
return useQuery({
queryKey: ['usePriceUSD', chainId, address],
- queryFn: async () => {
- if (!address) {
+ queryFn: async () => {
+ if (!address || !chainId) {
return 0
}
return (await fetchLLMAPrice(address, chainId)).priceUsd
},
- refetchInterval: 10_000,
+ refetchInterval: 30_000,
enabled: !!address && !!chainId,
})
}
@@ -38,7 +40,7 @@ export async function fetchLLMAPrice(token: string, chainId: number) {
const chainName = chainIdToName[chainId] || 'Unknown Chain'
if (isNativeAddress(token)) {
- token = networks.poly.wToken.address
+ token = getNetwork(chainId)?.wToken.address || ''
}
const tokenAddressWithChainId = `${chainName}:${token}`
const url = `https://coins.llama.fi/prices/current/${tokenAddressWithChainId}`
@@ -47,10 +49,10 @@ export async function fetchLLMAPrice(token: string, chainId: number) {
return nullPrice
}
const data = await response.json()
- const coin = data.coins[tokenAddressWithChainId]
+ const coin = data.coins[tokenAddressWithChainId]
return {
- priceUsd: coin.price,
- priceNative: coin.price,
+ priceUsd: !coin ? 0 : coin.price,
+ priceNative: !coin ? 0 : coin.price,
timestamp: Date.now(),
}
} catch (error) {
diff --git a/src/lib/useTokenList.ts b/src/lib/useTokenList.ts
deleted file mode 100644
index f887dcd..0000000
--- a/src/lib/useTokenList.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import { networks } from '@/lib/networks'
-import { Token } from '@/types'
-import { zeroAddress } from '@orbs-network/liquidity-hub-sdk'
-import { useQuery } from '@tanstack/react-query'
-
-type PolygonToken = {
- address: string
- chainId: number
- decimals: number
- logoURI: string
- name?: string
- symbol: string
-}
-
-const getPolygonTokens = async (): Promise => {
- const res = await fetch(
- 'https://unpkg.com/quickswap-default-token-list@1.3.16/build/quickswap-default.tokenlist.json'
- )
-
- if (!res.ok) {
- throw new Error('Failed to fetch tokens')
- }
-
- const polyTokens = (await res.json()).tokens as PolygonToken[]
-
- const tokens = polyTokens.filter((it) => it.chainId === networks.poly.id)
-
- const candiesAddresses = [
- zeroAddress,
- '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
- '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4',
- '0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6',
- '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',
- '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
- '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063',
- '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619',
- '0xdAb529f40E671A1D4bF91361c21bf9f0C9712ab7',
- '0x614389EaAE0A6821DC49062D56BDA3d9d45Fa2ff',
- ]
-
- const sorted = tokens.sort((a, b) => {
- const indexA = candiesAddresses.indexOf(a.address)
- const indexB = candiesAddresses.indexOf(b.address)
- return indexB - indexA
- })
-
- return [
- {
- address: zeroAddress,
- symbol: 'MATIC',
- decimals: 18,
- logoURI: 'https://app.1inch.io/assets/images/network-logos/polygon.svg',
- name: 'MATIC',
- },
- ...sorted,
- ].map((token) => {
- return {
- address: token.address,
- symbol: token.symbol,
- decimals: token.decimals,
- logoUrl: token.logoURI?.replace('/logo_24.png', '/logo_48.png'),
- name: token.name,
- }
- })
-}
-
-
-
-export function useTokensList() {
- const chainId = networks.poly.id
-
- return useQuery({
- queryFn: async () => {
- return getPolygonTokens() || []
- },
- queryKey: ['tokens-list', chainId],
- staleTime: Infinity,
- })
-}
diff --git a/src/lib/useTokens.ts b/src/lib/useTokens.ts
new file mode 100644
index 0000000..ebf63e7
--- /dev/null
+++ b/src/lib/useTokens.ts
@@ -0,0 +1,205 @@
+import { Token } from "@/types";
+import { getNetwork, networks } from "./networks";
+import { useQuery } from "@tanstack/react-query";
+import { getBalance, multicall } from "@wagmi/core";
+import { useAccount, useConfig } from "wagmi";
+import { erc20Abi, Address } from "viem";
+import { zeroAddress } from "@orbs-network/liquidity-hub-sdk";
+import { eqIgnoreCase } from "./utils";
+import { useMemo } from "react";
+
+const getFantomTokens = async (signal?: AbortSignal): Promise => {
+ const res = await fetch(
+ "https://raw.githubusercontent.com/viaprotocol/tokenlists/main/tokenlists/ftm.json",
+ { signal }
+ );
+ const data = await res.json();
+ return data.map((token: any) => {
+ return {
+ address: token.address,
+ symbol: token.symbol,
+ decimals: token.decimals,
+ logoUrl: token.logoURI,
+ name: token.name,
+ };
+ });
+};
+
+const getSushiTokens = async (
+ chainId: number,
+ signal?: AbortSignal
+): Promise => {
+ const tokens = await fetch("https://token-list.sushi.com/", { signal }).then(
+ (res) =>
+ res
+ .json()
+ .then((it) => it.tokens.filter((it: any) => it.chainId === chainId))
+ );
+
+ return Object.values(tokens).map((token: any) => {
+ return {
+ address: token.address,
+ symbol: token.symbol,
+ decimals: token.decimals,
+ logoUrl: token.logoURI,
+ name: token.name,
+ };
+ });
+};
+
+const getLineaTokens = async (signal?: AbortSignal): Promise => {
+ const tokens = await fetch("https://api.lynex.fi/api/v1/assets", { signal })
+ .then((res) => res.json())
+ .then((res) => res.data);
+ return tokens.map((token: any) => {
+ return {
+ address: token.address,
+ symbol: token.symbol,
+ decimals: token.decimals,
+ logoUrl: token.logoURI,
+ name: token.name,
+ };
+ });
+};
+
+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 network = getNetwork(chainId);
+ if (network) {
+ const nativeToken: Token = {
+ address: network.native.address,
+ symbol: network.native.symbol,
+ decimals: network.native.decimals,
+ logoUrl: network.native.logoUrl,
+ };
+
+ tokens = [nativeToken, ...tokens];
+ }
+
+ const baseAssets = getNetwork(chainId)?.baseAssets;
+ if (!baseAssets) {
+ return tokens;
+ }
+ const sortedTokens = tokens.sort((a, b) => {
+ const aPriority = baseAssets.includes(a.address) ? 0 : 1;
+ const bPriority = baseAssets.includes(b.address) ? 0 : 1;
+ if (aPriority !== bPriority) {
+ return aPriority - bPriority;
+ }
+ return a.address.localeCompare(b.address);
+ });
+
+ return sortedTokens;
+};
+
+const useTokensList = () => {
+ const chainId = useAccount().chainId;
+
+ return useQuery({
+ queryFn: async ({ signal }) => {
+ const response = await fetchTokens(chainId!, signal);
+ return response;
+ },
+ queryKey: ["useTokensList", chainId],
+ staleTime: Infinity,
+ enabled: !!chainId,
+ });
+};
+
+type BalancesReponse = Record;
+
+export const useTokenBalaces = () => {
+ const { data: tokens } = useTokensList();
+ const { address: account, chainId } = useAccount();
+
+ const config = useConfig();
+
+ return useQuery({
+ queryKey: ["useBalances", chainId, account, tokens?.map((t) => t.address)],
+ queryFn: async () => {
+ if (!tokens) return {};
+ let native = await getBalance(config, {
+ address: account as Address,
+ chainId,
+ });
+
+ const addresses = tokens
+ .map((token) => token.address)
+ .filter((it) => !eqIgnoreCase(it, zeroAddress));
+
+ const multicallResponse = await (multicall as any)(config, {
+ contracts: addresses.map(
+ (address) =>
+ ({
+ chainId,
+ address,
+ abi: erc20Abi,
+ functionName: "balanceOf",
+ args: [account],
+ } as const)
+ ),
+ });
+
+
+ const balances = addresses.reduce(
+ (acc: any, address: any, index: number) => {
+ acc[address] = multicallResponse[index].result?.toString() || '0';
+ return acc;
+ },
+ {}
+ );
+
+ balances[zeroAddress] = native.value.toString();
+
+ return balances;
+ },
+ refetchInterval: 20_000,
+ staleTime: Infinity,
+ enabled: Boolean(chainId && account && tokens?.length),
+ });
+};
+
+export const useTokenBalance = (tokenAddress?: string) => {
+ const { data: balances, isLoading } = useTokenBalaces();
+ return useMemo(() => {
+ if (!tokenAddress) {
+ return {
+ isLoading,
+ balance: "0",
+ };
+ }
+ return {
+ isLoading,
+ balance: balances?.[tokenAddress] || "0",
+ };
+ }, [balances, tokenAddress, isLoading]);
+};
+
+export const useSortedTokens = () => {
+ const { data: tokens } = useTokensList();
+ const { data: balances } = useTokenBalaces();
+ return useMemo(() => {
+ const sorted = tokens?.sort((a, b) => {
+ const balanceA = BigInt(balances?.[a.address] || "0");
+ const balanceB = BigInt(balances?.[b.address] || "0");
+ return balanceB > balanceA ? 1 : balanceB < balanceA ? -1 : 0;
+ });
+
+ const native = sorted?.find((it) => eqIgnoreCase(it.address, zeroAddress));
+ if (native) {
+ sorted?.splice(sorted.indexOf(native), 1);
+ sorted?.unshift(native);
+ }
+ return sorted;
+ }, [tokens, balances]);
+};
diff --git a/src/lib/useTokensWithBalances.ts b/src/lib/useTokensWithBalances.ts
deleted file mode 100644
index 9a93cf4..0000000
--- a/src/lib/useTokensWithBalances.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { useAccount } from 'wagmi'
-import { useTokensList } from './useTokenList'
-import { useBalances } from './useBalances'
-import { networks } from '@/lib/networks'
-
-export function useTokensWithBalances() {
- const account = useAccount()
- const { data: tokens, isLoading: tokensLoading } = useTokensList()
- const {
- query: { data: balances, isLoading: balancesLoading, refetch },
- queryKey,
- } = useBalances({
- chainId: networks.poly.id,
- tokens: tokens || [],
- account: account.address,
- enabled: Boolean(tokens && account.address),
- })
-
- return {
- isLoading: tokensLoading || balancesLoading,
- tokensWithBalances: balances,
- queryKey,
- refetch,
- }
-}
-
-
-export const useTokenBalance = (tokenAddress?: string) => {
- const { tokensWithBalances } = useTokensWithBalances()
- return !tokenAddress ? '' : tokensWithBalances?.[tokenAddress]?.balance.toString()
-}
\ No newline at end of file
diff --git a/src/lib/useWrapOrUnwrapOnly.ts b/src/lib/useWrapOrUnwrapOnly.ts
index 7ad02d3..e340a25 100644
--- a/src/lib/useWrapOrUnwrapOnly.ts
+++ b/src/lib/useWrapOrUnwrapOnly.ts
@@ -3,8 +3,8 @@ import { networks } from './networks'
import { eqIgnoreCase, isNativeAddress } from './utils'
export function useWrapOrUnwrapOnly(
- fromTokenAddress: string,
- toTokenAddress: string
+ fromTokenAddress?: string,
+ toTokenAddress?: string
) {
// Evaluates whether tokens are to be wrapped/unwrapped only
return useMemo(() => {
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 58798b7..d6596f6 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -2,24 +2,17 @@ import { zeroAddress } from "@orbs-network/liquidity-hub-sdk";
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import { wagmiConfig } from "@/lib/wagmi-config";
-import { LiquidityProvider, SwapSteps } from "@/types";
+import { SwapSteps } from "@/types";
import { getTransactionConfirmations } from "wagmi/actions";
-import { networks } from "./networks";
import BN from "bignumber.js";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
-export const toBigInt = (amount: string | number, decimals?: number) => {
- if (!amount) return BigInt(0);
- const num = Number(amount);
- return BigInt((num * 10 ** (decimals || 0)).toFixed(0));
-};
-
export const toExactAmount = (
amount?: string,
decimals?: number,
- decimalScale? : number
+ decimalScale?: number
) => {
if (!decimals || !amount) return "";
const percision = BN(10).pow(decimals || 0);
@@ -27,45 +20,13 @@ export const toExactAmount = (
if (decimalScale) {
return result.toFixed(decimalScale);
}
- return result.toString();
+ return result.toFixed();
};
export const toRawAmount = (amount?: string, decimals?: number) => {
if (!decimals || !amount) return "";
return BN(amount).times(BN(10).pow(decimals)).decimalPlaces(0).toFixed();
};
-export const toBigNumber = (amount: string | number, decimals?: number) => {
- if (amount === "") return "0";
-
- return toBigInt(amount, decimals).toString();
-};
-
-export const fromBigNumberToStr = (
- amount: bigint | string,
- decimals?: number
-) => {
- const numStr = typeof amount === "bigint" ? amount.toString() : amount;
- const precision = decimals || 0;
-
- if (precision > 0) {
- const integerPart = numStr.slice(0, -precision) || "0";
- const fractionalPart = numStr.slice(-precision).padStart(precision, "0");
-
- return `${integerPart}.${fractionalPart}`;
- } else {
- return numStr;
- }
-};
-
-export const fromBigNumber = (
- amount: bigint | string | undefined | null,
- decimals?: number
-) => {
- if (amount === null || typeof amount === "undefined") return 0;
-
- return Number(fromBigNumberToStr(amount, decimals));
-};
-
export const nativeTokenAddresses = [
zeroAddress,
"0x0000000000000000000000000000000000001010",
@@ -81,9 +42,6 @@ export function eqIgnoreCase(a: string, b: string) {
export const isNativeAddress = (address?: string) =>
!!nativeTokenAddresses.find((a) => eqIgnoreCase(a, address || ""));
-export const resolveNativeTokenAddress = (address?: string) =>
- isNativeAddress(address) ? networks.poly.wToken.address : address;
-
const dollarDisplay = Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
@@ -110,6 +68,7 @@ export const format = {
};
export const getMinAmountOut = (slippage: number, _destAmount: string) => {
+
const slippageFactor = BigInt(1000 - Math.floor(slippage * 10)); // 0.5% becomes 995
// Convert priceRoute.destAmount to BigInt
@@ -124,7 +83,8 @@ export const enum ErrorCodes {
EnterAmount = "Enter amount",
}
-export function getQuoteErrorMessage(errorCode: string) {
+export function getQuoteErrorMessage(errorCode?: string) {
+ if (!errorCode) return "";
switch (errorCode) {
case "ldv":
return "Minimum trade amount is $30";
@@ -213,15 +173,11 @@ export function getErrorMessage(
return errorMessage;
}
-export function getLiquidityProviderName(provider: LiquidityProvider) {
- switch (provider) {
- case "paraswap":
- return "ParaSwap";
- case "liquidityhub":
- return "Liquidity Hub";
- default:
- return "Unknown";
+export function getLiquidityProviderName(isLiquidityHubTrade: boolean) {
+ if (isLiquidityHubTrade) {
+ return "Liquidity Hub";
}
+ return "ParaSwap";
}
export const makeElipsisAddress = (address?: string, padding = 6): string => {
diff --git a/src/lib/wagmi-config.ts b/src/lib/wagmi-config.ts
index 68cb342..91d14db 100644
--- a/src/lib/wagmi-config.ts
+++ b/src/lib/wagmi-config.ts
@@ -1,10 +1,20 @@
import { getDefaultConfig } from '@rainbow-me/rainbowkit'
-import { polygon } from 'viem/chains'
+import { http } from 'viem'
+import { polygon, mainnet, arbitrum, bsc, fantom, blast, linea } from 'viem/chains'
const walletConnectProjectId = import.meta.env.VITE_WALLET_CONNECT_PROJECT_ID
export const wagmiConfig = getDefaultConfig({
appName: 'DEX Playground',
projectId: walletConnectProjectId,
- chains: [polygon],
+ chains: [polygon, mainnet, arbitrum, bsc, fantom, blast, linea],
+ transports: {
+ [mainnet.id]: http(`https://rpcman.orbs.network/rpc?chainId=1&appId=dex-playground`),
+ [polygon.id]: http(),
+ [arbitrum.id]: http(),
+ [bsc.id]: http(),
+ [fantom.id]: http(),
+ [blast.id]: http(),
+ [linea.id]: http(),
+ }
})
diff --git a/src/store.ts b/src/store.ts
new file mode 100644
index 0000000..a663c4b
--- /dev/null
+++ b/src/store.ts
@@ -0,0 +1,18 @@
+import { create } from "zustand";
+import { persist } from "zustand/middleware";
+
+interface AppStore {
+ slippage: number;
+ setSlippage: (slippage: number) => void;
+}
+export const useAppState = create(
+ persist(
+ (set) => ({
+ slippage: 0.5,
+ setSlippage: (slippage: number) => set({ slippage }),
+ }),
+ {
+ name: "main-store",
+ }
+ )
+);
diff --git a/src/tokens.json b/src/tokens.json
new file mode 100644
index 0000000..e69de29
diff --git a/src/trade/hooks.ts b/src/trade/hooks.ts
index 9681e40..9cb69f1 100644
--- a/src/trade/hooks.ts
+++ b/src/trade/hooks.ts
@@ -1,4 +1,4 @@
-import { network, toExactAmount, toRawAmount } from "@/lib";
+import { getNetwork, toExactAmount, toRawAmount } from "@/lib";
import { useMemo } from "react";
import { useChainId } from "wagmi";
@@ -15,7 +15,7 @@ export const useNetwork = () => {
return useMemo(() => {
if (!chainId) return;
- return network[chainId as keyof typeof network];
+ return getNetwork(chainId);
}, [chainId]);
};
diff --git a/src/trade/liquidity-hub/context.tsx b/src/trade/liquidity-hub/context.tsx
new file mode 100644
index 0000000..0780cee
--- /dev/null
+++ b/src/trade/liquidity-hub/context.tsx
@@ -0,0 +1,119 @@
+import { useDefaultTokens } from "@/lib";
+import { Token } from "@/types";
+import {
+ constructSDK,
+ LiquidityHubSDK,
+ Quote,
+} from "@orbs-network/liquidity-hub-sdk";
+import {
+ useContext,
+ ReactNode,
+ useReducer,
+ useCallback,
+ useMemo,
+ createContext,
+} from "react";
+import { useAccount } from "wagmi";
+import { useToRawAmount } from "../hooks";
+
+const initialState: State = {
+ inToken: null,
+ outToken: null,
+ inputAmount: "",
+ acceptedQuote: undefined,
+ liquidityHubDisabled: false,
+ forceLiquidityHub: false,
+ showConfirmation: false,
+};
+
+interface State {
+ inToken: Token | null;
+ outToken: Token | null;
+ inputAmount: string;
+ acceptedQuote: Quote | undefined;
+ liquidityHubDisabled: boolean;
+ forceLiquidityHub: boolean;
+ showConfirmation: boolean;
+ signature?: string;
+ isLiquidityHubTrade?: boolean;
+}
+
+type Action = { type: "UPDATE"; payload: Partial } | { type: "RESET" };
+
+const reducer = (state: State, action: Action): State => {
+ switch (action.type) {
+ case "UPDATE":
+ return { ...state, ...action.payload };
+ case "RESET":
+ return initialState;
+ default:
+ return state;
+ }
+};
+
+interface ContextType {
+ state: State;
+ updateState: (payload: Partial) => void;
+ resetState: () => void;
+ sdk: LiquidityHubSDK;
+ parsedInputAmount?: string;
+}
+
+const Context = createContext({} as ContextType);
+export const useLiquidityHubSwapContext = () => {
+ return useContext(Context);
+};
+
+export const LiquidityHubSwapProvider = ({
+ children,
+}: {
+ children: ReactNode;
+}) => {
+ const [_state, dispatch] = useReducer(reducer, initialState);
+ const defaultTokens = useDefaultTokens();
+
+ const state = useMemo(() => {
+ return {
+ ..._state,
+ inToken: _state.inToken || defaultTokens?.inToken || null,
+ outToken: _state.outToken || defaultTokens?.outToken || null,
+ };
+ }, [_state, defaultTokens]);
+
+ const { chainId } = useAccount();
+
+ const parsedInputAmount = useToRawAmount(
+ state.inputAmount,
+ state.inToken?.decimals
+ );
+
+ const updateState = useCallback(
+ (payload: Partial) => {
+ dispatch({ type: "UPDATE", payload });
+ },
+ [dispatch]
+ );
+
+ const resetState = useCallback(() => {
+ dispatch({ type: "RESET" });
+ }, [dispatch]);
+
+ const sdk = useMemo(
+ () => constructSDK({ partner: "widget", chainId }),
+ [chainId]
+ );
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/trade/liquidity-hub/hooks.ts b/src/trade/liquidity-hub/hooks.ts
new file mode 100644
index 0000000..2b8e0a8
--- /dev/null
+++ b/src/trade/liquidity-hub/hooks.ts
@@ -0,0 +1,198 @@
+import {
+ useWrapOrUnwrapOnly,
+ useParaswapQuote,
+ useInputError,
+ getMinAmountOut,
+ useGetRequiresApproval,
+ networks,
+ isNativeAddress,
+} 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";
+
+export const QUOTE_REFETCH_INTERVAL = 20_000;
+
+export const useParaswapMinAmountOut = () => {
+ const { slippage } = useAppState();
+ const optimalRate = useOptimalRate().data;
+ return useMemo(() => {
+ return getMinAmountOut(slippage, optimalRate?.destAmount || "0");
+ }, [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 {
+ parsedInputAmount,
+ state: { inToken, outToken },
+ } = useLiquidityHubSwapContext();
+ return useParaswapQuote({
+ inToken: inToken?.address || "",
+ outToken: outToken?.address || "",
+ inAmount: parsedInputAmount,
+ refetchInterval: QUOTE_REFETCH_INTERVAL,
+ });
+};
+
+export const useLiquidityHubInputError = () => {
+ const {
+ state: { inToken, inputAmount },
+ } = useLiquidityHubSwapContext();
+
+ return useInputError({
+ inputAmount,
+ inToken,
+ });
+};
+
+const useNetwork = () => {
+ const { chainId } = useAccount();
+
+ return useMemo(() => {
+ return Object.values(networks).find((network) => network.id === chainId);
+ }, [chainId]);
+};
+
+const useNativeOrWrapped = (address?: string) => {
+ const callback = useNativeOrWrappedAddressCallback();
+ return useMemo(() => callback(address), [address]);
+};
+
+const useNativeOrWrappedAddressCallback = () => {
+ const network = useNetwork();
+ return useCallback(
+ (address?: string) => {
+ return isNativeAddress(address) ? network?.wToken.address : address;
+ },
+ [network]
+ );
+};
+
+export const useLiquidityHubApproval = () => {
+ const {
+ parsedInputAmount,
+ state: { inToken },
+ } = useLiquidityHubSwapContext();
+ const tokenAddress = useNativeOrWrapped(inToken?.address);
+ return useGetRequiresApproval(
+ permit2Address,
+ tokenAddress,
+ parsedInputAmount
+ );
+};
+
+export const useParaswapApproval = () => {
+ const optimalRate = useOptimalRate().data;
+
+ const tokenAddress = useNativeOrWrapped(optimalRate?.srcToken);
+ return useGetRequiresApproval(
+ optimalRate?.tokenTransferProxy as Address,
+ tokenAddress,
+ 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 db8df57..f5e1a78 100644
--- a/src/trade/liquidity-hub/liquidity-hub-confirmation-dialog.tsx
+++ b/src/trade/liquidity-hub/liquidity-hub-confirmation-dialog.tsx
@@ -1,60 +1,55 @@
-import { Button } from '@/components/ui/button'
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogTitle,
-} from '@/components/ui/dialog'
-import { LiquidityProvider, SwapSteps, Token } from '@/types'
-import { Card } from '@/components/ui/card'
-import { SwapFlow, SwapStep, SwapStatus } from '@orbs-network/swap-ui'
-import { useMemo } from 'react'
-import { DataDetails } from '@/components/ui/data-details'
+import { Button } from "@/components/ui/button";
+import { SwapSteps } from "@/types";
+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,
- fromBigNumber,
+ getErrorMessage,
getLiquidityProviderName,
getSteps,
- resolveNativeTokenAddress,
- toBigNumber,
- useGetRequiresApproval,
-} from '@/lib'
-import { useAccount } from 'wagmi'
-import { Address } from 'viem'
-
-export type SwapConfirmationDialogProps = {
- inToken: Token
- outToken: Token
- isOpen: boolean
- onClose: () => void
- confirmSwap: () => void
- swapStatus?: SwapStatus
- currentStep?: SwapSteps
- signature?: string
- gasAmountOut?: string
- liquidityProvider: LiquidityProvider
- inAmount?: number
- inAmountUsd?: string
- outAmount?: number
- outAmountUsd?: string
- allowancePermitAddress: string
-}
+ isNativeAddress,
+ promiseWithTimeout,
+ toExactAmount,
+ useParaswapBuildTxCallback,
+ usePriceUsd,
+ wagmiConfig,
+ waitForConfirmations,
+} 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";
// Construct steps for swap to display in UI
-const useSteps = (
- liquidityProvider: LiquidityProvider,
- requiresApproval: boolean,
- inToken?: Token,
- signature?: string
-) => {
+const useSteps = (steps?: number[]) => {
+ const {
+ state: { inToken, signature },
+ } = useLiquidityHubSwapContext();
return useMemo((): SwapStep[] => {
- if (!inToken) return []
-
- const steps = getSteps({
- noWrap: liquidityProvider === 'paraswap',
- inTokenAddress: inToken.address,
- requiresApproval,
- })
+ if (!steps || !inToken) return [];
return steps.map((step) => {
if (step === SwapSteps.Wrap) {
@@ -63,7 +58,7 @@ const useSteps = (
title: `Wrap ${inToken.symbol}`,
description: `Wrap ${inToken.symbol}`,
image: inToken?.logoUrl,
- }
+ };
}
if (step === SwapSteps.Approve) {
return {
@@ -71,7 +66,7 @@ const useSteps = (
title: `Approve ${inToken.symbol}`,
description: `Approve ${inToken.symbol}`,
image: inToken?.logoUrl,
- }
+ };
}
return {
id: SwapSteps.Swap,
@@ -79,119 +74,523 @@ const useSteps = (
description: `Swap ${inToken.symbol}`,
image: inToken?.logoUrl,
timeout: signature ? 60_000 : 40_000,
+ };
+ });
+ }, [inToken, steps, signature]);
+};
+
+export function LiquidityHubConfirmationDialog({
+ isOpen,
+ onClose: _onClose,
+}: {
+ isOpen: boolean;
+ onClose: (swapStatus?: SwapStatus) => void;
+}) {
+ const {
+ state: { inputAmount, inToken, outToken, isLiquidityHubTrade },
+ } = useLiquidityHubSwapContext();
+
+ const {
+ state: progressState,
+ resetState: resetProgressState,
+ updateState: updateProgressState,
+ } = useSwapProgress();
+
+ const parsedSteps = useSteps(progressState.steps);
+
+ const { mutate: swapWithLiquidityHub } =
+ useLiquidityHubSwapCallback(updateProgressState);
+ const { mutate: swapWithParaswap } =
+ useParaswapSwapCallback(updateProgressState);
+
+ const paraswapApproval = useParaswapApproval();
+ const liquidityHubApproval = useLiquidityHubApproval();
+
+ const approvalLoading = isLiquidityHubTrade
+ ? liquidityHubApproval.approvalLoading
+ : paraswapApproval.approvalLoading;
+
+ const onSubmit = useCallback(async () => {
+ if (!isLiquidityHubTrade) {
+ swapWithParaswap();
+ } else {
+ swapWithLiquidityHub();
+ }
+ }, [
+ isLiquidityHubTrade,
+ swapWithLiquidityHub,
+ swapWithParaswap,
+ updateProgressState,
+ ]);
+
+ const onClose = useCallback(() => {
+ _onClose(progressState.swapStatus);
+ setTimeout(() => {
+ if (progressState.currentStep) {
+ resetProgressState();
}
- })
- }, [inToken, liquidityProvider, requiresApproval, signature])
+ }, 500);
+ }, [
+ _onClose,
+ progressState.swapStatus,
+ progressState.currentStep,
+ resetProgressState,
+ ]);
+
+ const usdValues = useUSDValues();
+
+ const optimalRate = useOptimalRate().data;
+
+ const quote = useLiquidityHubQuote().data;
+
+ const result = isLiquidityHubTrade
+ ? quote?.outAmount
+ : optimalRate?.destAmount;
+
+ const outAmount = useToExactAmount(result, outToken?.decimals);
+
+ return (
+
+ }
+ details={ }
+ />
+ }
+ />
+ );
}
-export function SwapConfirmationDialog({
- inToken,
- outToken,
- isOpen,
- onClose,
- confirmSwap,
- swapStatus,
- currentStep,
- signature,
- gasAmountOut,
- liquidityProvider,
- inAmount,
- inAmountUsd,
- outAmount,
- outAmountUsd,
- allowancePermitAddress,
-}: SwapConfirmationDialogProps) {
- const { address } = useAccount()
+const useUSDValues = () => {
+ const {
+ state: { inToken, outToken, inputAmount, isLiquidityHubTrade },
+ } = useLiquidityHubSwapContext();
+ const srcUSD = usePriceUsd(inToken?.address).data;
+ const destUSD = usePriceUsd(outToken?.address).data;
+ const optimalRate = useOptimalRate().data;
+ const quote = useLiquidityHubQuote().data;
+
+ 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(),
+ };
+ }, [
+ isLiquidityHubTrade,
+ srcUSD,
+ destUSD,
+ optimalRate,
+ quote,
+ inputAmount,
+ outToken?.decimals,
+ ]);
+};
+
+const SubmitSwapButton = ({
+ onClick,
+ approvalLoading,
+}: {
+ onClick: () => void;
+ approvalLoading: boolean;
+}) => {
+ const {
+ state: { inToken, outToken },
+ } = useLiquidityHubSwapContext();
+ return (
+
+ );
+};
+
+const Details = () => {
+ const optimalRate = useOptimalRate().data;
+ const quote = useLiquidityHubQuote().data;
+ const address = useAccount().address;
+ const {
+ state: { outToken, isLiquidityHubTrade },
+ } = useLiquidityHubSwapContext();
+ const outTokenUsd = usePriceUsd(outToken?.address).data;
const gasPrice = useMemo(() => {
- if (!outAmountUsd || !gasAmountOut) return 0
- const gas = fromBigNumber(gasAmountOut, outToken.decimals)
- const usd = Number(outAmountUsd) / Number(outAmount)
- return Number(gas) * usd
- }, [outAmountUsd, gasAmountOut, outToken.decimals, outAmount])
-
- const { requiresApproval, approvalLoading } = useGetRequiresApproval(
- allowancePermitAddress as Address,
- resolveNativeTokenAddress(inToken?.address),
- toBigNumber(inAmount || 0, inToken?.decimals)
- )
-
- const steps = useSteps(
- liquidityProvider,
- requiresApproval,
- inToken,
- signature
- )
+ if (!isLiquidityHubTrade) {
+ return Number(optimalRate?.gasCostUSD || "0");
+ }
+
+ if (!outToken || !outTokenUsd) return 0;
+ const gas = toExactAmount(quote?.gasAmountOut, outToken.decimals);
+
+ return Number(gas) * outTokenUsd;
+ }, [
+ isLiquidityHubTrade,
+ optimalRate?.gasCostUSD,
+ outToken,
+ outTokenUsd,
+ quote?.gasAmountOut,
+ ]);
return (
-