diff --git a/packages/react-app/src/App.js b/packages/react-app/src/App.js
index 42d47f3..4b7e7a2 100644
--- a/packages/react-app/src/App.js
+++ b/packages/react-app/src/App.js
@@ -1,8 +1,8 @@
import React from "react"
import { useEthers } from "@usedapp/core";
-import { usePools } from "./hooks";
import styles from './styles';
+import { usePools } from "./hooks";
import { fhtLogo } from './assets';
import { Exchange, Loader, WalletButton } from "./components";
diff --git a/packages/react-app/src/components/AmountIn.js b/packages/react-app/src/components/AmountIn.js
new file mode 100644
index 0000000..d35be5c
--- /dev/null
+++ b/packages/react-app/src/components/AmountIn.js
@@ -0,0 +1,67 @@
+import React, { useState, useEffect, useRef } from "react";
+
+import { chevronDown } from "../assets";
+import { useOnClickOutside } from "../utils";
+import styles from "../styles";
+
+const AmountIn = ({ value, onChange, currencyValue, onSelect, currencies, isSwapping }) => {
+ const [showList, setShowList] = useState(false);
+ const [activeCurrency, setActiveCurrency] = useState("Select");
+ const ref = useRef()
+
+ useOnClickOutside(ref, () => setShowList(false))
+
+ useEffect(() => {
+ if (Object.keys(currencies).includes(currencyValue))
+ setActiveCurrency(currencies[currencyValue]);
+ else setActiveCurrency("Select");
+ }, [currencies, currencyValue]);
+
+ return (
+
+
typeof onChange === "function" && onChange(e.target.value)}
+ className={styles.amountInput}
+ />
+
+
setShowList(!showList)}>
+
+ {activeCurrency}
+
+
+
+ {showList && (
+
+ {Object.entries(currencies).map(([token, tokenName], index) => (
+ {
+ if (typeof onSelect === "function") onSelect(token);
+ setActiveCurrency(tokenName);
+ setShowList(false);
+ }}
+ >
+ {tokenName}
+
+ ))}
+
+ )}
+
+
+ );
+};
+
+export default AmountIn;
diff --git a/packages/react-app/src/components/AmountOut.js b/packages/react-app/src/components/AmountOut.js
new file mode 100644
index 0000000..6fdf1bb
--- /dev/null
+++ b/packages/react-app/src/components/AmountOut.js
@@ -0,0 +1,67 @@
+import React, { useState, useEffect, useRef } from "react";
+import { formatUnits } from "ethers/lib/utils";
+
+import { chevronDown } from "../assets";
+import { useAmountsOut, useOnClickOutside } from "../utils";
+import styles from "../styles";
+
+const AmountOut = ({ fromToken, toToken, amountIn, pairContract, currencyValue, onSelect, currencies }) => {
+ const [showList, setShowList] = useState(false);
+ const [activeCurrency, setActiveCurrency] = useState("Select");
+ const ref = useRef()
+
+ const amountOut = useAmountsOut(pairContract, amountIn, fromToken, toToken) ?? 0;
+
+ useOnClickOutside(ref, () => setShowList(false))
+
+ useEffect(() => {
+ if (Object.keys(currencies).includes(currencyValue)) {
+ setActiveCurrency(currencies[currencyValue]);
+ } else {
+ setActiveCurrency("Select")
+ }
+ }, [currencyValue, currencies]);
+
+ return (
+
+
+
+
setShowList(!showList)}>
+
+ {activeCurrency}
+
+
+
+ {showList && (
+
+ {Object.entries(currencies).map(([token, tokenName], index) => (
+ {
+ if (typeof onSelect === "function") onSelect(token);
+ setActiveCurrency(tokenName);
+ setShowList(false);
+ }}
+ >
+ {tokenName}
+
+ ))}
+
+ )}
+
+
+ );
+};
+
+export default AmountOut;
diff --git a/packages/react-app/src/components/Balance.js b/packages/react-app/src/components/Balance.js
new file mode 100644
index 0000000..dacba32
--- /dev/null
+++ b/packages/react-app/src/components/Balance.js
@@ -0,0 +1,24 @@
+import React from "react";
+import { formatUnits, parseUnits } from "ethers/lib/utils";
+
+import styles from "../styles";
+
+const Balance = ({ tokenBalance }) => {
+
+ return (
+
+
+ {tokenBalance ? (
+ <>
+ Balance:
+ {formatUnits(tokenBalance ?? parseUnits("0"))}
+ >
+ ) : (
+ ""
+ )}
+
+
+ );
+};
+
+export default Balance;
diff --git a/packages/react-app/src/components/Exchange.js b/packages/react-app/src/components/Exchange.js
index 07da0ba..548218d 100644
--- a/packages/react-app/src/components/Exchange.js
+++ b/packages/react-app/src/components/Exchange.js
@@ -5,12 +5,163 @@ import { ERC20, useContractFunction, useEthers, useTokenAllowance, useTokenBalan
import { ethers } from "ethers";
import { parseUnits } from "ethers/lib/utils";
+import { getAvailableTokens, getCounterpartTokens, findPoolByTokens, isOperationPending, getFailureMessage, getSuccessMessage } from '../utils';
import { ROUTER_ADDRESS } from "../config";
+import AmountIn from "./AmountIn";
+import AmountOut from "./AmountOut";
+import Balance from "./Balance";
+import styles from "../styles";
const Exchange = ({ pools }) => {
- return (
- Exchange
- )
-}
+ const { account } = useEthers();
+ const [fromValue, setFromValue] = useState("0");
+ const [fromToken, setFromToken] = useState(pools[0].token0Address); // initialFromToken
+ const [toToken, setToToken] = useState("");
+ const [resetState, setResetState] = useState(false)
-export default Exchange;
\ No newline at end of file
+ const fromValueBigNumber = parseUnits(fromValue || "0"); // converse the string to bigNumber
+ const availableTokens = getAvailableTokens(pools);
+ const counterpartTokens = getCounterpartTokens(pools, fromToken);
+ const pairAddress = findPoolByTokens(pools, fromToken, toToken)?.address ?? "";
+
+ const routerContract = new Contract(ROUTER_ADDRESS, abis.router02);
+ const fromTokenContract = new Contract(fromToken, ERC20.abi);
+ const fromTokenBalance = useTokenBalance(fromToken, account);
+ const toTokenBalance = useTokenBalance(toToken, account);
+ const tokenAllowance = useTokenAllowance(fromToken, account, ROUTER_ADDRESS) || parseUnits("0");
+ const approvedNeeded = fromValueBigNumber.gt(tokenAllowance);
+ const formValueIsGreaterThan0 = fromValueBigNumber.gt(parseUnits("0"));
+ const hasEnoughBalance = fromValueBigNumber.lte(fromTokenBalance ?? parseUnits("0"));
+
+ // approve initiating a contract call (similar to use state) -> gives the state and the sender...
+ const { state: swapApproveState, send: swapApproveSend } =
+ useContractFunction(fromTokenContract, "approve", {
+ transactionName: "onApproveRequested",
+ gasLimitBufferPercentage: 10,
+ });
+ // swap initiating a contract call (similar to use state) -> gives the state and the sender...
+ const { state: swapExecuteState, send: swapExecuteSend } =
+ useContractFunction(routerContract, "swapExactTokensForTokens", {
+ transactionName: "swapExactTokensForTokens",
+ gasLimitBufferPercentage: 10,
+ });
+
+ const isApproving = isOperationPending(swapApproveState);
+ const isSwapping = isOperationPending(swapExecuteState);
+ const canApprove = !isApproving && approvedNeeded;
+ const canSwap = !approvedNeeded && !isSwapping && formValueIsGreaterThan0 && hasEnoughBalance;
+
+ const successMessage = getSuccessMessage(swapApproveState, swapExecuteState);
+ const failureMessage = getFailureMessage(swapApproveState, swapExecuteState);
+
+ const onApproveRequested = () => {
+ swapApproveSend(ROUTER_ADDRESS, ethers.constants.MaxUint256);
+ };
+
+ // https://docs.uniswap.org/protocol/V2/reference/smart-contracts/router-02#swapexacttokensfortokens
+ const onSwapRequested = () => {
+ swapExecuteSend(
+ fromValueBigNumber,
+ 0,
+ [fromToken, toToken],
+ account,
+ Math.floor(Date.now() / 1000) + 60 * 20
+ ).then((_) => {
+ setFromValue("0");
+ });
+ };
+
+ const onFromValueChange = (value) => {
+ const trimmedValue = value.trim();
+
+ try {
+ trimmedValue && parseUnits(value);
+ setFromValue(value);
+ } catch (e) {}
+ };
+
+ const onFromTokenChange = (value) => {
+ setFromToken(value);
+ };
+
+ const onToTokenChange = (value) => {
+ setToToken(value);
+ };
+
+ useEffect(() => {
+ if(failureMessage || successMessage) {
+ setTimeout(() => {
+ setResetState(true)
+ setFromValue("0")
+ setToToken("")
+ }, 5000)
+ }
+ }, [failureMessage, successMessage])
+
+ return (
+
+
+
+
+
+ {approvedNeeded && !isSwapping ? (
+
+ {isApproving ? "Approving..." : "Approve"}
+
+ ) : (
+
+ {isSwapping
+ ? "Swapping..."
+ : hasEnoughBalance
+ ? "Swap"
+ : "Insufficient balance"}
+
+ )}
+
+ {failureMessage && !resetState ? (
+
{failureMessage}
+ ) : successMessage ? (
+
{successMessage}
+ ) : (
+ ""
+ )}
+
+ );
+};
+
+export default Exchange;
diff --git a/packages/react-app/src/components/Loader.js b/packages/react-app/src/components/Loader.js
index a41289d..b195bc5 100644
--- a/packages/react-app/src/components/Loader.js
+++ b/packages/react-app/src/components/Loader.js
@@ -6,7 +6,11 @@ import {ethereumLogo2 } from "../assets";
const Loader = ({ title }) => {
return (
-
+
{title}
diff --git a/packages/react-app/src/components/index.js b/packages/react-app/src/components/index.js
index de594b8..7d6764c 100644
--- a/packages/react-app/src/components/index.js
+++ b/packages/react-app/src/components/index.js
@@ -1,3 +1,6 @@
export {default as Loader} from './Loader';
export {default as Exchange} from './Exchange';
-export {default as WalletButton} from './WalletButton';
\ No newline at end of file
+export {default as WalletButton} from './WalletButton';
+export { default as AmountIn } from "./AmountIn";
+export { default as AmountOut } from "./AmountOut";
+export { default as Balance } from "./Balance";
\ No newline at end of file