Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

feat: add dropdown to select token on swap #184

Merged
merged 9 commits into from
May 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
[![Github Forks](https://img.shields.io/github/forks/FuelLabs/swayswap)](https://github.com/FuelLabs/swayswap)
[![Github Stars](https://img.shields.io/github/stars/FuelLabs/swayswap)](https://github.com/FuelLabs/swayswap)


## Live links

- [Latest release](https://fuellabs.github.io/swayswap)
Expand Down
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,4 @@
"typescript": "^4.6.4",
"vite": "^2.9.7"
}
}
}
21 changes: 16 additions & 5 deletions packages/app/src/components/CoinSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import cx from "classnames";
import { useState, useEffect } from "react";
import { FiChevronDown } from "react-icons/fi";

Expand All @@ -18,7 +19,11 @@ type CoinSelectorProps = {
onChange?: (coin: Coin) => void;
};

export function CoinSelector({ value, isReadOnly }: CoinSelectorProps) {
export function CoinSelector({
value,
isReadOnly,
onChange,
}: CoinSelectorProps) {
const [selected, setSelected] = useState<Coin | null>(null);
const dialog = useDialog();

Expand All @@ -28,16 +33,18 @@ export function CoinSelector({ value, isReadOnly }: CoinSelectorProps) {
}, [value]);

function handleSelect(assetId: string) {
const next = CoinsMetadata.find((coin) => coin.assetId === assetId)!;
dialog.close();
setSelected(CoinsMetadata.find((coin) => coin.assetId === assetId)!);
setSelected(next);
onChange?.(next);
}

return (
<div className={style.currencySelector}>
<Button
{...dialog.openButtonProps}
size="md"
className="coin-selector"
className={cx("coin-selector", { "coin-selector--empty": !selected })}
isDisabled={isReadOnly}
>
{selected && selected.img && (
Expand All @@ -49,8 +56,12 @@ export function CoinSelector({ value, isReadOnly }: CoinSelectorProps) {
width={20}
/>
)}
<div className="ml-2">{selected?.name}</div>
{!isReadOnly && <FiChevronDown className="text-gray-500" />}
{selected ? (
<div className="ml-2">{selected?.name}</div>
) : (
<div className="ml-2">Select token</div>
)}
{!isReadOnly && <FiChevronDown className="text-current" />}
</Button>
<Dialog {...dialog.dialogProps}>
<Dialog.Content>
Expand Down
8 changes: 7 additions & 1 deletion packages/app/src/components/CoinsListDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ const style = {
noResults: `px-6 py-4 border-t border-gray-700`,
};

function filterByValid(coin: Coin) {
// TODO: remove this in the future
// this is just for now until we have tokens coming from backend
pedronauck marked this conversation as resolved.
Show resolved Hide resolved
return coin.symbol === "DAI";
}

export type CoinListModalProps = {
onSelect?: (assetId: string) => void;
};
Expand All @@ -25,7 +31,7 @@ export function CoinsListDialog({ onSelect }: CoinListModalProps) {
return coin.name?.toLowerCase().includes(value.toLocaleLowerCase());
}

const filtered = CoinsMetadata.filter(filterBySearch);
const filtered = CoinsMetadata.filter(filterByValid).filter(filterBySearch);
const hasResults = Boolean(filtered.length);

return (
Expand Down
9 changes: 6 additions & 3 deletions packages/app/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ body,
*[data-pressed="true"] {
@apply scale-[0.9] disabled:scale-100;
}
.inner-shadow {
box-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.2);
}

/**
* Coin Selector
*/
.coin-selector {
@apply h-10 px-2 rounded-xl gap-1 bg-gray-800;
}
Expand All @@ -79,7 +85,4 @@ body,
.coin-selector[aria-disabled="true"] {
@apply opacity-100;
}
.inner-shadow {
box-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.2);
}
}
23 changes: 11 additions & 12 deletions packages/app/src/lib/asset.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { toNumber } from "fuels"
import { ONE_ASSET } from "~/config"
import { toNumber } from 'fuels';

export const calculateRatio = (fromAmount?: bigint | null, toAmount?: bigint | null) => {
const _fromAmount = fromAmount || BigInt(0);
const _toAmount = toAmount || BigInt(0);
import { ONE_ASSET } from '~/config';

const ratio = (
toNumber(ONE_ASSET * _fromAmount) /
toNumber(_toAmount) /
toNumber(ONE_ASSET)
);
export const calculateRatio = (
initialFromAmount?: bigint | null,
initialToAmount?: bigint | null
) => {
const fromAmount = initialFromAmount || BigInt(0);
const toAmount = initialToAmount || BigInt(0);
const ratio = toNumber(ONE_ASSET * fromAmount) / toNumber(toAmount) / toNumber(ONE_ASSET);

return (isNaN(ratio) || !isFinite(ratio)) ? 0 : ratio;
}
return Number.isNaN(ratio) || !Number.isFinite(ratio) ? 0 : ratio;
};
69 changes: 37 additions & 32 deletions packages/app/src/pages/PoolPage/AddLiquidity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import toast from "react-hot-toast";
import { RiCheckFill } from "react-icons/ri";
import { useMutation, useQuery } from "react-query";

import { poolFromAmountAtom, poolToAmountAtom } from "./jotai";

import { Button } from "~/components/Button";
import { CoinInput, useCoinInput } from "~/components/CoinInput";
import { Spinner } from "~/components/Spinner";
import { DECIMAL_UNITS, ONE_ASSET, SLIPPAGE_TOLERANCE } from "~/config";
import { DECIMAL_UNITS, SLIPPAGE_TOLERANCE } from "~/config";
import { useContract } from "~/context/AppContext";
import { useBalances } from "~/hooks/useBalances";
import { calculateRatio } from "~/lib/asset";
import assets from "~/lib/CoinsMetadata";
import { calculateRatio } from "~/lib/asset";
import type { Coin } from "~/types";
import { Pages } from "~/types/pages";
import { poolFromAmountAtom, poolToAmountAtom } from "./jotai";

const style = {
wrapper: `w-screen flex flex-1 items-center justify-center pb-14`,
Expand Down Expand Up @@ -74,51 +74,57 @@ export default function AddLiquidity() {
]);

const [stage, setStage] = useState(0);
const { data: poolInfo, refetch: refetchPoolInfo } = useQuery("PoolPage-poolInfo", () =>
contract.callStatic.get_info()
const { data: poolInfo, refetch: refetchPoolInfo } = useQuery(
"PoolPage-poolInfo",
() => contract.callStatic.get_info()
);

const handleChangeFromValue = (val: bigint | null) => {
fromInput.setAmount(val);

if (reservesFromToRatio) {
const _val = val || BigInt(0);
const newToValue = Math.round(toNumber(_val) / reservesFromToRatio);
const value = val || BigInt(0);
const newToValue = Math.round(toNumber(value) / reservesFromToRatio);
toInput.setAmount(BigInt(newToValue));
}
}
};
const handleChangeToValue = (val: bigint | null) => {
toInput.setAmount(val);

if (reservesFromToRatio) {
const _val = val || BigInt(0);
const newFromValue = Math.round(toNumber(_val) * reservesFromToRatio);
const value = val || BigInt(0);
const newFromValue = Math.round(toNumber(value) * reservesFromToRatio);
fromInput.setAmount(BigInt(newFromValue));
}
}
};

const fromInput = useCoinInput({
coin: coinFrom,
onChangeCoin: (coin: Coin) => setCoins([coin, coinTo]),
gasFee: BigInt(1),
onChange: handleChangeFromValue
onChange: handleChangeFromValue,
});

const toInput = useCoinInput({
coin: coinTo,
onChangeCoin: (coin: Coin) => setCoins([coin, coinTo]),
onChange: handleChangeToValue
onChange: handleChangeToValue,
});

const reservesFromToRatio = calculateRatio(poolInfo?.eth_reserve, poolInfo?.token_reserve);
const reservesToFromRatio = calculateRatio(poolInfo?.token_reserve, poolInfo?.eth_reserve);
const reservesFromToRatio = calculateRatio(
poolInfo?.eth_reserve,
poolInfo?.token_reserve
);
const reservesToFromRatio = calculateRatio(
poolInfo?.token_reserve,
poolInfo?.eth_reserve
);
const addLiquidityRatio = calculateRatio(fromInput.amount, toInput.amount);

const addLiquidityMutation = useMutation(
async () => {
const fromAmount = fromInput.amount;
const toAmount = toInput.amount;

if (!fromAmount || !toAmount) return;

// TODO: Combine all transactions on single tx leverage by scripts
Expand Down Expand Up @@ -195,13 +201,13 @@ export default function AddLiquidity() {
const minRatio = reservesFromToRatio * (1 - SLIPPAGE_TOLERANCE);
const maxRatio = reservesFromToRatio * (1 + SLIPPAGE_TOLERANCE);

if ( addLiquidityRatio < minRatio || addLiquidityRatio > maxRatio ) {
if (addLiquidityRatio < minRatio || addLiquidityRatio > maxRatio) {
errors.push(`Entered ratio doesn't match pool`);
}
}

return errors;
}
};

const errorsCreatePull = validateCreatePool();

Expand Down Expand Up @@ -249,14 +255,10 @@ export default function AddLiquidity() {
{poolInfo.eth_reserve > 0 && poolInfo.token_reserve > 0 ? (
<div className="flex flex-col">
<span>
<>
ETH/DAI: {reservesFromToRatio.toFixed(6)}
</>
<>ETH/DAI: {reservesFromToRatio.toFixed(6)}</>
</span>
<span>
<>
DAI/ETH: {reservesToFromRatio.toFixed(6)}
</>
<>DAI/ETH: {reservesToFromRatio.toFixed(6)}</>
</span>
</div>
) : null}
Expand All @@ -268,14 +270,17 @@ export default function AddLiquidity() {
isFull
size="lg"
variant="primary"
onPress={errorsCreatePull.length ? undefined : () => addLiquidityMutation.mutate()}
>
{errorsCreatePull.length ?
errorsCreatePull[0] :
reservesFromToRatio ?
'Add liquidity' :
'Create liquidity'
onPress={
errorsCreatePull.length
? undefined
: () => addLiquidityMutation.mutate()
}
>
{errorsCreatePull.length
? errorsCreatePull[0]
: reservesFromToRatio
? "Add liquidity"
: "Create liquidity"}
</Button>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/pages/PoolPage/jotai.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { atom } from 'jotai';

export const poolFromAmountAtom = atom<bigint | null>(null);
export const poolToAmountAtom = atom<bigint | null>(null);
export const poolToAmountAtom = atom<bigint | null>(null);
4 changes: 2 additions & 2 deletions packages/app/src/pages/SwapPage/PricePerToken.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { BigNumber } from "ethers";
import { formatUnits } from "ethers/lib/utils";
import { useAtomValue } from "jotai";
import { useState } from "react";
import { BiRefresh } from "react-icons/bi";
import { AiOutlineSwap } from "react-icons/ai";

import { swapIsTypingAtom } from "./jotai";
import { ActiveInput } from "./types";
Expand Down Expand Up @@ -64,7 +64,7 @@ export function PricePerToken({
<span className="text-gray-200">{pricePerToken}</span> {to}
</div>
<Button size="sm" className="h-auto p-0 border-none" onPress={toggle}>
<BiRefresh size={20} />
<AiOutlineSwap size={20} />
</Button>
</div>
);
Expand Down
Loading