diff --git a/components/app/Testing/SelectComponent2.tsx b/components/app/Testing/SelectComponent2.tsx new file mode 100644 index 0000000..7bff2bc --- /dev/null +++ b/components/app/Testing/SelectComponent2.tsx @@ -0,0 +1,142 @@ +import React, { useState, useEffect } from "react"; +import fullPoolsData from "@/outputs/fullPoolsData.json"; +import { fetchTokenData, getTokenName } from "@/utils/apis/tokenInfo"; + +interface SelectComponentProps { + apiUrl?: string; + setFrom?: (from: string) => void; + setTo?: (to: string) => void; + outputOptions?: string[]; // New prop for filtered output options +} + +const SelectComponent2: React.FC = ({ + apiUrl = "http://localhost:3000/api/tokens", + setFrom, + setTo, + outputOptions = [], // Default to empty array if not provided +}) => { + const [fromTokens, setFromTokens] = useState([]); + const [toTokens, setToTokens] = useState([]); + const [selectedFrom, setSelectedFrom] = useState(""); + const [selectedTo, setSelectedTo] = useState(""); + const [tokenNames, setTokenNames] = useState<{ [key: string]: string }>({}); + + useEffect(() => { + if (setFrom) { + setFrom(selectedFrom); + } + if (setTo) { + setTo(selectedTo); + } + }, [selectedFrom, selectedTo, setFrom, setTo]); + + useEffect(() => { + const fetchAndSetTokenData = async () => { + await fetchTokenData(apiUrl); + + const names = fullPoolsData + .flatMap((pool) => pool.query_result.assets) + .map((asset) => { + const address = + asset.info.token?.contract_addr || asset.info.native_token?.denom; + return address ? [address, getTokenName(address) || ""] : null; + }) + .filter((item): item is [string, string] => item !== null) + .reduce((acc, [address, name]) => { + acc[address] = name; + return acc; + }, {} as { [key: string]: string }); + + setTokenNames(names); + + const fromOptions = fullPoolsData + .flatMap((pool) => pool.query_result.assets) + .map( + (asset) => + asset.info.token?.contract_addr || asset.info.native_token?.denom + ) + .filter( + (addressOrDenom): addressOrDenom is string => + addressOrDenom !== undefined + ) + .filter((address) => address !== "uscrt"); + + setFromTokens(Array.from(new Set(fromOptions))); + }; + + fetchAndSetTokenData(); + }, [apiUrl]); + + const handleFromSelect = (event: React.ChangeEvent) => { + const fromToken = event.target.value; + setSelectedFrom(fromToken); + + if (outputOptions.length > 0) { + setToTokens(outputOptions); + } else { + const toOptions = fullPoolsData + .filter((pool) => + pool.query_result.assets.some( + (asset) => + asset.info.token?.contract_addr === fromToken || + asset.info.native_token?.denom === fromToken + ) + ) + .flatMap((pool) => + pool.query_result.assets.map((asset) => { + const addr = + asset.info.token?.contract_addr || asset.info.native_token?.denom; + return addr && addr !== fromToken && tokenNames[addr] ? addr : null; + }) + ) + .filter((addr): addr is string => addr !== null) + .filter((addr) => addr !== "uscrt"); + + setToTokens(Array.from(new Set(toOptions))); + } + + setSelectedTo(""); // Reset the selected 'to' token + }; + + const handleToSelect = (event: React.ChangeEvent) => { + setSelectedTo(event.target.value); + }; + + return ( +
+
+ + +
+
+ + +
+
+ ); +}; + +export default SelectComponent2; diff --git a/components/app/Testing/SwapResult.tsx b/components/app/Testing/SwapResult.tsx new file mode 100644 index 0000000..6b6165d --- /dev/null +++ b/components/app/Testing/SwapResult.tsx @@ -0,0 +1,170 @@ +import React from "react"; +import { FiCheckCircle, FiAlertTriangle, FiAlertOctagon } from "react-icons/fi"; +import { Tooltip } from "react-tooltip"; +import "react-tooltip/dist/react-tooltip.css"; + +// Props for each SwapResultItem +interface SwapResultItemProps { + label: string; + value: string; + tooltipId: string; + tooltipContent: string; + valueClassName?: string; + icon?: JSX.Element; +} + +// SwapResultItem functional component +const SwapResultItem: React.FC = ({ + label, + value, + tooltipId, + tooltipContent, + valueClassName, + icon, +}) => { + return ( +
+ + {label}: + +
+ + {value} + + {icon && React.cloneElement(icon, { "data-tooltip-id": tooltipId })}{" "} + {/* Apply the tooltip to the icon */} +
+ +
+ ); +}; + +// Main SwapResult component +interface SwapResultProps { + bestRoute: string; + idealOutput: string; + actualOutput: string; + priceImpact: string; + lpFee: string; + gasCost: string; + isMultiHop: boolean; + difference: string; +} + +const SwapResult: React.FC = ({ + bestRoute, + idealOutput, + actualOutput, + priceImpact, + lpFee, + gasCost, + isMultiHop, + difference, +}) => { + // Array to store the data and config for each section + const data = [ + { + label: "Best Route", + value: bestRoute, + tooltipId: "bestRouteTip", + tooltipContent: + "The optimal route for swapping your tokens to minimize slippage and maximize output.", + valueClassName: "text-blue-400", + }, + { + label: "Ideal Output", + value: idealOutput, + tooltipId: "idealOutputTip", + tooltipContent: + "The best possible output you could get without any fees or slippage.", + valueClassName: "text-blue-400", + }, + { + label: "Actual Output", + value: actualOutput, + tooltipId: "actualOutputTip", + tooltipContent: + "The actual amount of tokens you received after fees and slippage.", + valueClassName: "text-green-400", + }, + { + label: "Difference", + value: difference, + tooltipId: "differenceTip", + tooltipContent: + "The difference between the ideal and actual output, caused by fees and slippage.", + valueClassName: "text-red-400", + }, + { + label: "Price Impact", + value: priceImpact, + tooltipId: "priceImpactTip", + tooltipContent: + "The effect your trade has on the market price of the token pair.", + valueClassName: priceImpactColor(priceImpact), + icon: priceImpactIcon(priceImpact), + }, + { + label: "LP Fee", + value: lpFee, + tooltipId: "lpFeeTip", + tooltipContent: + "The fee paid to liquidity providers for facilitating your trade.", + valueClassName: "text-yellow-300", + }, + { + label: "Gas Cost", + value: gasCost, + tooltipId: "gasCostTip", + tooltipContent: isMultiHop + ? "This gas cost includes multiple hops. Each hop incurs a separate gas fee. The total gas cost is the sum of all hops." + : "This gas cost covers a single swap between two tokens in one liquidity pool.", + valueClassName: "text-gray-300", + icon: , // Removed tooltipId here, will be added dynamically + }, + ]; + + return ( +
+
+

Swap Estimate

+
+
+ {data.map((item, index) => ( + + ))} +
+
+ ); +}; + +// Utility functions for dynamic styling +const priceImpactColor = (priceImpact: string) => { + const impact = parseFloat(priceImpact); + if (impact < 1) return "text-green-400"; + if (impact < 3) return "text-yellow-400"; + return "text-red-400"; +}; + +const priceImpactIcon = (priceImpact: string) => { + const impact = parseFloat(priceImpact); + if (impact < 1.1) return ; + if (impact < 3) return ; + return ; +}; + +export default SwapResult; diff --git a/pages/app/testing/doc.html b/components/app/Testing/doc.html similarity index 100% rename from pages/app/testing/doc.html rename to components/app/Testing/doc.html diff --git a/pages/app/testing/fullPoolsData.ts b/components/app/Testing/fullPoolsData.ts similarity index 100% rename from pages/app/testing/fullPoolsData.ts rename to components/app/Testing/fullPoolsData.ts diff --git a/pages/app/testing/getTokenDetails.ts b/components/app/Testing/getTokenDetails.ts similarity index 100% rename from pages/app/testing/getTokenDetails.ts rename to components/app/Testing/getTokenDetails.ts diff --git a/pages/app/testing/queryPoolDetails.ts b/components/app/Testing/queryPoolDetails.ts similarity index 100% rename from pages/app/testing/queryPoolDetails.ts rename to components/app/Testing/queryPoolDetails.ts diff --git a/package.json b/package.json index ce2bf86..45d40dd 100644 --- a/package.json +++ b/package.json @@ -41,11 +41,12 @@ "marked": "^12.0.2", "moment": "^2.30.1", "next": "14.1.3", - "react": "18.2", - "react-dom": "18.2", + "react": "^18.3.1", + "react-dom": "^18.3.1", "react-hook-form": "^7.51.5", "react-icons": "^5.2.1", "react-toastify": "^10.0.5", + "react-tooltip": "^5.28.0", "react-vega": "^7.6.0", "secretjs": "^1.12.5", "seedrandom": "^3.0.5", @@ -68,6 +69,6 @@ "eslint-config-next": "14.1.3", "postcss": "^8.4.38", "tailwindcss": "^3.4.4", - "typescript": "^5.4.5" + "typescript": "^5.5.4" } } diff --git a/pages/app/testing/decimals/index.tsx b/pages/app/testing/decimals/index.tsx index c73adf6..ee42756 100644 --- a/pages/app/testing/decimals/index.tsx +++ b/pages/app/testing/decimals/index.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; import { SecretNetworkClient } from "secretjs"; import { Window as KeplrWindow } from "@keplr-wallet/types"; import { processPoolsData } from "../../../../utils/secretjs/decimals/utils/processPoolData"; -import { fullPoolsData } from "../fullPoolsData"; +import { fullPoolsData } from "../../../../components/app/Testing/fullPoolsData"; const QueryTokenDecimals = () => { const [secretjs, setSecretjs] = useState(null); diff --git a/pages/app/testing/estimate/a.tsx b/pages/app/testing/estimate/a.tsx new file mode 100644 index 0000000..736ee1a --- /dev/null +++ b/pages/app/testing/estimate/a.tsx @@ -0,0 +1,561 @@ +import { Window as KeplrWindow } from "@keplr-wallet/types"; +import { useState, useEffect } from "react"; +import { SecretNetworkClient } from "secretjs"; +import Decimal from "decimal.js"; +import { getTokenDecimals, getTokenName } from "@/utils/apis/tokenInfo"; +import { fullPoolsData } from "../../../../components/app/Testing/fullPoolsData"; +import SelectComponent2 from "@/components/app/Testing/SelectComponent2"; +import SwapResult from "@/components/app/Testing/SwapResult"; + +interface PoolQueryResponse { + assets: { + info: { + token: { + contract_addr: string; + token_code_hash: string; + viewing_key: string; + }; + }; + amount: string; + }[]; + total_share: string; +} + +interface PoolData { + reserves: { + [token: string]: { amount: Decimal; decimals: number }; + }; + fee: number; +} + +interface TokenPoolMap { + [tokenAddress: string]: string[]; // Maps token address to a list of pool addresses +} + +const buildTokenPoolMap = (pools: typeof fullPoolsData): TokenPoolMap => { + const tokenPoolMap: TokenPoolMap = {}; + + pools.forEach((pool) => { + pool.query_result.assets + .filter((asset) => asset.info.token !== undefined) + .forEach((asset) => { + const tokenAddr = asset.info.token!.contract_addr; + if (!tokenPoolMap[tokenAddr]) { + tokenPoolMap[tokenAddr] = []; + } + tokenPoolMap[tokenAddr].push(pool.contract_address); + }); + }); + + return tokenPoolMap; +}; +const getPossibleOutputsForToken = ( + startToken: string, + pools: typeof fullPoolsData, + maxHops: number = 5 +): string[] => { + const tokenPoolMap = buildTokenPoolMap(pools); + const reachableTokens = findAllReachableTokens( + tokenPoolMap, + startToken, + maxHops + ); + return Array.from(reachableTokens); +}; +const findAllReachableTokens = ( + tokenPoolMap: TokenPoolMap, + startToken: string, + maxHops: number = 5 // Allows flexibility in the depth of search +): Set => { + const reachableTokens: Set = new Set(); + const visited: Set = new Set(); + + const dfs = (currentToken: string, hops: number) => { + if (hops > maxHops || visited.has(currentToken)) return; + + visited.add(currentToken); + + const pools = tokenPoolMap[currentToken] || []; + pools.forEach((poolAddress) => { + const poolTokens = fullPoolsData + .find((pool) => pool.contract_address === poolAddress) + ?.query_result.assets.filter((asset) => asset.info.token !== undefined) + .map((asset) => asset.info.token!.contract_addr); + + poolTokens?.forEach((nextToken) => { + if (nextToken !== currentToken && !reachableTokens.has(nextToken)) { + reachableTokens.add(nextToken); + dfs(nextToken, hops + 1); + } + }); + }); + + visited.delete(currentToken); + }; + + dfs(startToken, 0); + + return reachableTokens; +}; +interface Path { + pools: string[]; // Array of pool addresses + tokens: string[]; // Array of token addresses in the path +} + +const findPaths = ( + tokenPoolMap: TokenPoolMap, + startToken: string, + endToken: string, + maxHops: number = 3 +): Path[] => { + const paths: Path[] = []; + const visited: Set = new Set(); + + const dfs = (currentToken: string, path: Path, hops: number) => { + if (hops > maxHops || visited.has(currentToken)) return; + if (currentToken === endToken) { + paths.push({ pools: [...path.pools], tokens: [...path.tokens] }); + return; + } + + visited.add(currentToken); + + const pools = tokenPoolMap[currentToken] || []; + pools.forEach((poolAddress) => { + const poolTokens = fullPoolsData + .find((pool) => pool.contract_address === poolAddress) + ?.query_result.assets.filter((asset) => asset.info.token !== undefined) + .map((asset) => asset.info.token!.contract_addr); + + poolTokens?.forEach((nextToken) => { + if (nextToken !== currentToken) { + path.pools.push(poolAddress); + path.tokens.push(nextToken); + dfs(nextToken, path, hops + 1); + path.pools.pop(); + path.tokens.pop(); + } + }); + }); + + visited.delete(currentToken); + }; + + dfs(startToken, { pools: [], tokens: [startToken] }, 0); + + return paths; +}; +interface PathEstimation { + path: Path; + finalOutput: Decimal; + totalPriceImpact: string; + totalLpFee: Decimal; + totalGasCost: string; + idealOutput: Decimal; +} + +const estimateBestPath = async ( + secretjs: SecretNetworkClient, + paths: Path[], + initialAmountIn: Decimal +): Promise => { + let bestEstimation: PathEstimation | null = null; + + for (const path of paths) { + let amountIn = initialAmountIn; + let totalLpFee = new Decimal(0); + let totalPriceImpact = new Decimal(0); + let cumulativeIdealOutput = new Decimal(0); // Track cumulative ideal output + const hopGasCost = + path.pools.length > 1 ? new Decimal(0.2) : new Decimal(0.12); // 0.12 for single-hop, 0.20 for multi-hop + + try { + for (let i = 0; i < path.pools.length; i++) { + const poolAddress = path.pools[i]; + const inputToken = path.tokens[i]; + const outputToken = path.tokens[i + 1]; + + console.log(`\n--- Hop ${i + 1} ---`); + console.log(`Input Token: ${inputToken}`); + console.log(`Output Token: ${outputToken}`); + + const { output, idealOutput, priceImpact, lpFee } = + await estimateSingleHopOutput( + secretjs, + poolAddress, + amountIn, + inputToken, + outputToken + ); + + console.log(`Output from hop ${i + 1}: ${output.toString()}`); + console.log( + `Ideal Output from hop ${i + 1}: ${idealOutput.toString()}` + ); + + if (output.isNegative()) { + console.error(`Negative output detected after hop ${i + 1}`); + return null; // Abort if a negative output is detected + } + + amountIn = output; // The output of one hop becomes the input for the next + totalLpFee = totalLpFee.add(lpFee); + totalPriceImpact = totalPriceImpact.add(new Decimal(priceImpact)); + cumulativeIdealOutput = idealOutput; // Update cumulative ideal output to reflect the most recent hop + } + + const totalGasCost = + hopGasCost.mul(path.pools.length).toFixed(2) + " SCRT"; + + // Directly use the last output amount without subtracting the lpFee again + const adjustedFinalOutput = amountIn; + + console.log( + `Final Output after all hops: ${adjustedFinalOutput.toString()}` + ); + + if ( + !bestEstimation || + adjustedFinalOutput.greaterThan(bestEstimation.finalOutput) + ) { + bestEstimation = { + path, + finalOutput: adjustedFinalOutput, + totalPriceImpact: totalPriceImpact.toFixed(2), + totalLpFee, // LP Fee is informative, not deducted from output + totalGasCost, + idealOutput: cumulativeIdealOutput, // Store the latest ideal output + }; + } + } catch (error) { + console.error("Error estimating path output:", error); + } + } + + return bestEstimation; +}; + +const calculateSingleHopOutput = ( + amountIn: Decimal, + poolData: PoolData, + inputToken: string, + outputToken: string +): { + output: Decimal; + idealOutput: Decimal; + priceImpact: string; + lpFee: Decimal; +} => { + const rawInputReserve = poolData.reserves[inputToken]; + const rawOutputReserve = poolData.reserves[outputToken]; + + if (!rawInputReserve || !rawOutputReserve) { + throw new Error("Invalid token addresses"); + } + + console.log(`\n--- Calculation Start ---`); + console.log(`Input Token: ${inputToken}`); + console.log(`Output Token: ${outputToken}`); + console.log( + `Raw Input Reserve: ${rawInputReserve.amount.toString()} (Decimals: ${ + rawInputReserve.decimals + })` + ); + console.log( + `Raw Output Reserve: ${rawOutputReserve.amount.toString()} (Decimals: ${ + rawOutputReserve.decimals + })` + ); + console.log(`Amount In: ${amountIn.toString()}`); + + const inputReserve = rawInputReserve.amount; + const outputReserve = rawOutputReserve.amount; + + // Adjust input amount by input token decimals + const amountInAdjusted = amountIn.mul( + Decimal.pow(10, rawInputReserve.decimals) + ); + console.log(`Amount In Adjusted: ${amountInAdjusted.toString()}`); + + // Calculate fee and adjust input amount + const feeMultiplier = new Decimal(1).sub(poolData.fee); + const amountInWithFee = amountInAdjusted.mul(feeMultiplier); + console.log(`Amount In With Fee: ${amountInWithFee.toString()}`); + + // Compute output using constant product formula + const productOfReserves = inputReserve.mul(outputReserve); + const newInputReserve = inputReserve.add(amountInWithFee); + const newOutputReserve = productOfReserves.div(newInputReserve); + let output = outputReserve.sub(newOutputReserve); + console.log(`New Input Reserve: ${newInputReserve.toString()}`); + console.log(`New Output Reserve: ${newOutputReserve.toString()}`); + console.log(`Output Before Decimal Adjustment: ${output.toString()}`); + + // Adjust output by output token decimals + output = output.div(Decimal.pow(10, rawOutputReserve.decimals)); + console.log(`Output After Decimal Adjustment: ${output.toString()}`); + + // Calculate ideal output assuming infinite liquidity (no price impact) + const idealOutput = amountInAdjusted.mul(outputReserve).div(inputReserve); + console.log( + `Ideal Output Before Decimal Adjustment: ${idealOutput.toString()}` + ); + + // Adjust ideal output by output token decimals + let idealOutputAdjusted = idealOutput.div( + Decimal.pow(10, rawOutputReserve.decimals) + ); + console.log( + `Ideal Output After Decimal Adjustment: ${idealOutputAdjusted.toString()}` + ); + + // Ensure ideal output isn't negative + if (idealOutputAdjusted.isNegative()) { + console.warn( + "Calculated ideal output is negative after decimal adjustment." + ); + idealOutputAdjusted = new Decimal(0); + } + + // Calculate price impact + const priceImpact = idealOutputAdjusted + .sub(output) + .div(idealOutputAdjusted) + .mul(100) + .toFixed(2); + console.log(`Price Impact: ${priceImpact}%`); + + // Correct calculation of Liquidity Provider Fee + const lpFee = amountIn.sub( + amountInWithFee.div(Decimal.pow(10, rawInputReserve.decimals)) + ); + console.log(`Liquidity Provider Fee: ${lpFee.toString()}`); + console.log(`--- Calculation End ---\n`); + + // Return the results + return { + output, + idealOutput: idealOutputAdjusted, + priceImpact, // Now priceImpact is properly initialized and returned + lpFee, + }; +}; + +const estimateSingleHopOutput = async ( + secretjs: SecretNetworkClient, + poolAddress: string, + amountIn: Decimal, + inputToken: string, + outputToken: string +): Promise<{ + output: Decimal; + idealOutput: Decimal; + priceImpact: string; + lpFee: Decimal; + gasCost: string; +}> => { + const poolData = await getPoolData(secretjs, poolAddress); + const { output, idealOutput, priceImpact, lpFee } = calculateSingleHopOutput( + amountIn, + poolData, + inputToken, + outputToken + ); + + // Gas cost for a single hop + const gasCost = "0.12 SCRT"; + + return { + output, + idealOutput, + priceImpact, + lpFee, + gasCost, + }; +}; + +const getPoolData = async ( + secretjs: SecretNetworkClient, + poolAddress: string +): Promise => { + const response = (await secretjs.query.compute.queryContract({ + contract_address: poolAddress, + code_hash: + "0dfd06c7c3c482c14d36ba9826b83d164003f2b0bb302f222db72361e0927490", + query: { pool: {} }, + })) as PoolQueryResponse; + + if (typeof response !== "object" || response === null) { + throw new Error("Invalid response from pool contract"); + } + + const reserves = response.assets.reduce( + (acc: { [key: string]: { amount: Decimal; decimals: number } }, asset) => { + const decimals = + asset.info.token?.contract_addr === + "secret1k0jntykt7e4g3y88ltc60czgjuqdy4c9e8fzek" + ? 6 + : getTokenDecimals(asset.info.token.contract_addr) || 0; + console.log({ decimals }); + acc[asset.info.token.contract_addr] = { + amount: new Decimal(asset.amount), + decimals, + }; + return acc; + }, + {} + ); + + return { + reserves, + fee: 0.003, // Assuming a fee of 0.3% + }; +}; + +const SwapPage = () => { + const [amountIn, setAmountIn] = useState(""); + const [estimatedOutput, setEstimatedOutput] = useState(""); + const [secretjs, setSecretjs] = useState(null); + const [inputToken, setInputToken] = useState(""); + const [outputToken, setOutputToken] = useState(""); + const [outputOptions, setOutputOptions] = useState([]); + const [bestPathEstimation, setBestPathEstimation] = + useState(null); + + useEffect(() => { + // setEstimatedOutput(""); + setBestPathEstimation(null); + if (inputToken) { + const possibleOutputs = getPossibleOutputsForToken( + inputToken, + fullPoolsData + ); + setOutputOptions(possibleOutputs); + } + }, [inputToken, outputToken]); + + useEffect(() => { + const connectKeplr = async () => { + if (!window.keplr) { + alert("Please install Keplr extension"); + return; + } + + await window.keplr.enable("secret-4"); + + const offlineSigner = (window as KeplrWindow).getOfflineSigner?.( + "secret-4" + ); + const accounts = await offlineSigner?.getAccounts(); + + if (!accounts || accounts.length === 0) { + alert("No accounts found"); + return; + } + + const client = new SecretNetworkClient({ + chainId: "secret-4", + url: "https://lcd.mainnet.secretsaturn.net", + wallet: offlineSigner, + walletAddress: accounts[0].address, + }); + + setSecretjs(client); + }; + + connectKeplr(); + }, []); + + const handleSwap = async () => { + if (secretjs && amountIn && inputToken && outputToken) { + const amountInDecimal = new Decimal(amountIn); + const tokenPoolMap = buildTokenPoolMap(fullPoolsData); + const paths = findPaths(tokenPoolMap, inputToken, outputToken); + + if (paths.length === 0) { + console.log("No available paths found for the selected tokens."); + return; + } + + const bestPathEstimation = await estimateBestPath( + secretjs, + paths, + amountInDecimal + ); + + if (bestPathEstimation) { + console.log("--- Best Path Estimation in handleSwap ---"); + console.log("Best Path Estimation:", bestPathEstimation); + console.log("Final Output:", bestPathEstimation.finalOutput.toString()); + console.log("Ideal Output:", bestPathEstimation.idealOutput.toString()); + console.log("LP Fee:", bestPathEstimation.totalLpFee.toString()); + console.log("Total Price Impact:", bestPathEstimation.totalPriceImpact); + console.log("Total Gas Cost:", bestPathEstimation.totalGasCost); + console.log("--- End Best Path Estimation in handleSwap ---"); + + setBestPathEstimation(bestPathEstimation); + } else { + setEstimatedOutput("Error in estimating the best route"); + } + } + }; + + return ( +
+
+

+ Secret Swap Estimator +

+
+ + {inputToken && outputToken && ( + <> + setAmountIn(e.target.value)} + placeholder={`Amount of ${getTokenName(inputToken) || "Token"}`} + className="px-4 py-2 border border-gray-700 bg-adamant-app-input rounded-lg focus:outline-none focus:ring-2 focus:ring-offset-adamant-accentBg text-white" + /> + + {bestPathEstimation && ( // prettier-ignore + getTokenName(address)) + .join(" -> ")} + idealOutput={`${bestPathEstimation.idealOutput.toFixed( + 8 + )} ${getTokenName(outputToken)}`} + actualOutput={`${bestPathEstimation.finalOutput.toFixed( + 8 + )} ${getTokenName(outputToken)}`} + priceImpact={bestPathEstimation.totalPriceImpact + "%"} + lpFee={`${bestPathEstimation.totalLpFee.toFixed( + 6 + )} ${getTokenName(inputToken)}`} + gasCost={bestPathEstimation.totalGasCost} + difference={`${bestPathEstimation.idealOutput + .sub(bestPathEstimation.finalOutput) + .toFixed(8)} ${getTokenName(outputToken)}`} + isMultiHop={bestPathEstimation.path.pools.length > 1} + /> + )} + + )} +

{estimatedOutput}

+
+
+
+ ); +}; + +export default SwapPage; diff --git a/pages/app/testing/estimate/index.tsx b/pages/app/testing/estimate/index.tsx index 25d43df..663e067 100644 --- a/pages/app/testing/estimate/index.tsx +++ b/pages/app/testing/estimate/index.tsx @@ -4,7 +4,7 @@ import { SecretNetworkClient } from "secretjs"; import Decimal from "decimal.js"; import SelectComponent from "@/components/app/Testing/SelectComponent"; import { getTokenDecimals, getTokenName } from "@/utils/apis/tokenInfo"; -import { fullPoolsData } from "../fullPoolsData"; +import { fullPoolsData } from "../../../../components/app/Testing/fullPoolsData"; interface PoolQueryResponse { assets: { @@ -118,26 +118,10 @@ const SwapPage = () => { const [amountIn, setAmountIn] = useState(""); const [estimatedOutput, setEstimatedOutput] = useState(""); const [secretjs, setSecretjs] = useState(null); - // const [poolAddress, setPoolAddress] = useState(""); const [inputToken, setInputToken] = useState(""); const [outputToken, setOutputToken] = useState(""); - // function handleSelectChange(from: string, to: string) { - // const poolAddress = fullPoolsData.find((pool) => - // pool.query_result.assets.every( - // (asset) => - // asset.info.token?.contract_addr === from || - // asset.info.token?.contract_addr === to - // ) - // )?.contract_address; - // setPoolAddress(poolAddress || ""); - // setInputToken(from); - // setOutputToken(to); - // setEstimatedOutput(""); - // } - useEffect(() => { - // if there's a change in from or to, empty the estimated output setEstimatedOutput(""); }, [inputToken, outputToken]); diff --git a/yarn.lock b/yarn.lock index 91ada1f..ba00949 100644 --- a/yarn.lock +++ b/yarn.lock @@ -252,6 +252,13 @@ dependencies: "@floating-ui/utils" "^0.2.3" +"@floating-ui/core@^1.6.0": + version "1.6.7" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.7.tgz#7602367795a390ff0662efd1c7ae8ca74e75fb12" + integrity sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g== + dependencies: + "@floating-ui/utils" "^0.2.7" + "@floating-ui/dom@^1.0.0": version "1.6.6" resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.6.tgz#be54c1ab2d19112ad323e63dbeb08185fed0ffd3" @@ -260,6 +267,14 @@ "@floating-ui/core" "^1.0.0" "@floating-ui/utils" "^0.2.3" +"@floating-ui/dom@^1.6.1": + version "1.6.10" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.10.tgz#b74c32f34a50336c86dcf1f1c845cf3a39e26d6f" + integrity sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A== + dependencies: + "@floating-ui/core" "^1.6.0" + "@floating-ui/utils" "^0.2.7" + "@floating-ui/react-dom@^2.0.0": version "2.1.1" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.1.tgz#cca58b6b04fc92b4c39288252e285e0422291fb0" @@ -272,6 +287,11 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.3.tgz#506fcc73f730affd093044cb2956c31ba6431545" integrity sha512-XGndio0l5/Gvd6CLIABvsav9HHezgDFFhDfHk1bvLfr9ni8dojqLSvBbotJEjmIwNHL7vK4QzBJTdBRoB+c1ww== +"@floating-ui/utils@^0.2.7": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.7.tgz#d0ece53ce99ab5a8e37ebdfe5e32452a2bfc073e" + integrity sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA== + "@hookform/resolvers@^3.6.0": version "3.6.0" resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.6.0.tgz#71ae08acf7f7624fb24ea0505de00b9001a63687" @@ -1893,7 +1913,7 @@ class-variance-authority@^0.7.0: dependencies: clsx "2.0.0" -classnames@^2.3.2, classnames@^2.5.1: +classnames@^2.3.0, classnames@^2.3.2, classnames@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== @@ -4051,13 +4071,13 @@ randombytes@^2.0.1: dependencies: safe-buffer "^5.1.0" -react-dom@18.2: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" - integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== +react-dom@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" + integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== dependencies: loose-envify "^1.1.0" - scheduler "^0.23.0" + scheduler "^0.23.2" react-hook-form@^7.51.5: version "7.52.0" @@ -4120,6 +4140,14 @@ react-toastify@^10.0.5: dependencies: clsx "^2.1.0" +react-tooltip@^5.28.0: + version "5.28.0" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.28.0.tgz#c7b5343ab2d740a428494a3d8315515af1f26f46" + integrity sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg== + dependencies: + "@floating-ui/dom" "^1.6.1" + classnames "^2.3.0" + react-vega@^7.6.0: version "7.6.0" resolved "https://registry.yarnpkg.com/react-vega/-/react-vega-7.6.0.tgz#b791c944046b20e02d366c7d0f8dcc21bdb4a6bb" @@ -4130,10 +4158,10 @@ react-vega@^7.6.0: prop-types "^15.8.1" vega-embed "^6.5.1" -react@18.2: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" - integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== +react@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" + integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== dependencies: loose-envify "^1.1.0" @@ -4306,7 +4334,7 @@ safe-regex-test@^1.0.3: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -scheduler@^0.23.0: +scheduler@^0.23.2: version "0.23.2" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== @@ -4813,10 +4841,10 @@ typeforce@^1.11.5: resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc" integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== -typescript@^5.4.5: - version "5.5.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.2.tgz#c26f023cb0054e657ce04f72583ea2d85f8d0507" - integrity sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew== +typescript@^5.5.4: + version "5.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== unbox-primitive@^1.0.2: version "1.0.2"