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

Commit

Permalink
feat: disable max button after setting max balance (#360)
Browse files Browse the repository at this point in the history
  • Loading branch information
LuizAsFight authored Jul 4, 2022
1 parent d8e79f2 commit e2fedbf
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 11 deletions.
10 changes: 8 additions & 2 deletions packages/app/src/systems/Core/components/CoinBalance.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import cx from "classnames";
import { useMemo } from "react";

import type { CoinBalanceProps } from "../hooks";
Expand All @@ -12,6 +13,7 @@ export const CoinBalance = ({
showBalance = true,
showMaxButton = true,
onSetMaxBalance,
isMaxButtonDisabled,
}: CoinBalanceProps) => {
const { data: balances } = useBalances({ enabled: true });

Expand Down Expand Up @@ -40,8 +42,12 @@ export const CoinBalance = ({
aria-label="Set Maximun Balance"
size="sm"
onPress={onSetMaxBalance}
className="text-xs py-0 px-1 h-auto bg-primary-800/60 text-primary-500 hover:bg-primary-800"
variant="ghost"
className={cx(`text-xs py-0 px-1 h-auto`, {
"bg-primary-800/60 text-primary-500 hover:bg-primary-800":
!isMaxButtonDisabled,
})}
variant="primary"
isDisabled={isMaxButtonDisabled}
>
Max
</Button>
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/systems/Core/hooks/useCoinInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export type CoinBalanceProps = {
showBalance?: boolean;
showMaxButton?: boolean;
onSetMaxBalance?: () => void;
isMaxButtonDisabled?: boolean;
};

type DisplayType = 'input' | 'text';
Expand Down
10 changes: 10 additions & 0 deletions packages/app/src/systems/Swap/hooks/useSwapMaxButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { useSelector } from "@xstate/react";
import type { SwapMachineState } from "../machines/swapMachine";
import type { SwapMachineContext } from "../types";
import { SwapDirection } from "../types";
import { calculateMaxBalanceToSwap } from "../utils";

import { useSwapContext } from "./useSwap";
import { useSwapCoinInput } from "./useSwapCoinInput";

import type { CoinBalanceProps, CoinSelectorProps } from "~/systems/Core";
import { safeBigInt } from "~/systems/Core";
Expand Down Expand Up @@ -35,17 +37,24 @@ const selectors = {
const balance = getRightBalance(dir, state.context);
return balance && balance > 0 && ethBalance > txCost.fee;
},
getMaxBalanceToSwap: (dir: SwapDirection) => (state: SwapMachineState) =>
calculateMaxBalanceToSwap({ direction: dir, ctx: state.context }),
};

export function useSwapMaxButton(direction: SwapDirection): CoinSelectorProps {
const { service, send } = useSwapContext();
const { value: coinAmount } = useSwapCoinInput(direction);
const isFrom = direction === SwapDirection.fromTo;
const coinSelector = isFrom ? selectors.coinFrom : selectors.coinTo;
const coin = useSelector(service, coinSelector);
const gasFee = useSelector(service, selectors.gasFee);
const hasBalance = useSelector(service, selectors.hasBalance(direction));
const maxButtonSelector = selectors.showMaxButton(direction);
const showMaxButton = useSelector(service, maxButtonSelector);
const maxBalanceToSwap = useSelector(
service,
selectors.getMaxBalanceToSwap(direction)
);

return {
coin,
Expand All @@ -55,5 +64,6 @@ export function useSwapMaxButton(direction: SwapDirection): CoinSelectorProps {
onSetMaxBalance: () => {
send("SET_MAX_VALUE", { data: { direction } });
},
isMaxButtonDisabled: maxBalanceToSwap.value === coinAmount,
} as CoinBalanceProps;
}
8 changes: 3 additions & 5 deletions packages/app/src/systems/Swap/machines/swapMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { assign, createMachine, InterpreterFrom, StateFrom } from 'xstate';
import type { SwapMachineContext } from '../types';
import { SwapDirection } from '../types';
import {
calculateMaxBalanceToSwap,
createAmount,
hasEnoughBalance,
hasEthForNetworkFee,
Expand All @@ -15,7 +16,7 @@ import {
ZERO_AMOUNT,
} from '../utils';

import { handleError, isCoinEth, safeBigInt, toNumber, ZERO } from '~/systems/Core';
import { handleError, isCoinEth, safeBigInt, toNumber } from '~/systems/Core';
import { txFeedback } from '~/systems/Core/utils/feedback';
import type { TransactionCost } from '~/systems/Core/utils/gas';
import { emptyTransactionCost, getTransactionCost } from '~/systems/Core/utils/gas';
Expand Down Expand Up @@ -399,10 +400,7 @@ export const swapMachine =
}),
setMaxValue: assign((ctx, { data: { direction } }) => {
const isFrom = direction === FROM_TO;
const balance = safeBigInt(isFrom ? ctx.coinFromBalance : ctx.coinToBalance);
const networkFee = safeBigInt(ctx.txCost?.fee);
const nextValue = balance > ZERO ? balance - networkFee : balance;
const amount = createAmount(nextValue);
const amount = calculateMaxBalanceToSwap({ direction, ctx });

return {
...ctx,
Expand Down
43 changes: 40 additions & 3 deletions packages/app/src/systems/Swap/pages/SwapPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,15 @@ async function findSwapBtn() {
return waitFor(() => screen.findByLabelText(/swap button/i));
}

async function clickOnMaxBalance() {
async function findMaxBalanceBtn(input: "from" | "to" = "from") {
const button = await screen.findAllByLabelText(/Set Maximun Balance/i);
return button[input === "from" ? 0 : 1];
}

async function clickOnMaxBalance(input: "from" | "to" = "from") {
return waitFor(async () => {
const button = await screen.findAllByLabelText(/Set Maximun Balance/i);
fireEvent.click(button[0]);
const button = await findMaxBalanceBtn(input);
fireEvent.click(button);
});
}

Expand Down Expand Up @@ -223,5 +228,37 @@ describe("SwapPage", () => {
expect(value).toBeGreaterThan(1);
});
});

it("should disable max button after setting max FROM balance", async () => {
renderWithRouter(<App />, { route: "/swap?from=ETH&to=DAI" });

await clickOnMaxBalance();

await waitFor(async () => {
const submitBtn = await findSwapBtn();
expect(submitBtn.textContent).toMatch(/Loading/i);
});

await waitFor(async () => {
const maxBalanceBtn = await findMaxBalanceBtn();
expect(maxBalanceBtn.getAttribute("aria-disabled")).toEqual("true");
});
});

it("should disable max button after setting max FROM balance", async () => {
renderWithRouter(<App />, { route: "/swap?from=ETH&to=DAI" });

await clickOnMaxBalance("to");

await waitFor(async () => {
const submitBtn = await findSwapBtn();
expect(submitBtn.textContent).toMatch(/Loading/i);
});

await waitFor(async () => {
const maxBalanceBtn = await findMaxBalanceBtn("to");
expect(maxBalanceBtn.getAttribute("aria-disabled")).toEqual("true");
});
});
});
});
68 changes: 68 additions & 0 deletions packages/app/src/systems/Swap/utils/helpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { SwapDirection } from '../types';

import { calculateMaxBalanceToSwap } from './helpers';

import { TOKENS } from '~/systems/Core';

const TX_COST = {
byte: BigInt(1000),
fee: BigInt(2),
gas: BigInt(704176),
total: BigInt(705176),
};

const CTX_FROM_ETH_TO_DAI = {
coinFrom: TOKENS[0],
coinTo: TOKENS[1],
coinFromBalance: BigInt(399999990),
coinToBalance: BigInt(119567935145),
txCost: TX_COST,
};

const CTX_FROM_DAI_TO_ETH = {
coinFrom: TOKENS[1],
coinTo: TOKENS[0],
coinFromBalance: BigInt(119567935145),
coinToBalance: BigInt(399999990),
txCost: TX_COST,
};

describe('Swap Helpers', () => {
describe('calculateMaxBalanceToSwap', () => {
it('should discount network fee from ETH balance when ETH is from coin', () => {
const maxBalanceToSwap = calculateMaxBalanceToSwap({
direction: SwapDirection.fromTo,
ctx: CTX_FROM_ETH_TO_DAI,
});

expect(maxBalanceToSwap.value).toEqual('0.399999988');
});

it('should discount network fee from ETH balance when ETH is to coin', () => {
const maxBalanceToSwap = calculateMaxBalanceToSwap({
direction: SwapDirection.toFrom,
ctx: CTX_FROM_DAI_TO_ETH,
});

expect(maxBalanceToSwap.value).toEqual('0.399999988');
});

it('should not discount network fee from DAI balance when DAI is from coin', () => {
const maxBalanceToSwap = calculateMaxBalanceToSwap({
direction: SwapDirection.fromTo,
ctx: CTX_FROM_DAI_TO_ETH,
});

expect(maxBalanceToSwap.value).toEqual('119.567935145');
});

it('should not discount network fee from DAI balance when DAI is to coin', () => {
const maxBalanceToSwap = calculateMaxBalanceToSwap({
direction: SwapDirection.toFrom,
ctx: CTX_FROM_ETH_TO_DAI,
});

expect(maxBalanceToSwap.value).toEqual('119.567935145');
});
});
});
25 changes: 24 additions & 1 deletion packages/app/src/systems/Swap/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SwapDirection } from '../types';

import { DECIMAL_UNITS } from '~/config';
import { isCoinEth } from '~/systems/Core';
import type { TransactionCost } from '~/systems/Core/utils/gas';
import {
ZERO,
toNumber,
Expand All @@ -16,7 +17,7 @@ import {
toBigInt,
safeBigInt,
} from '~/systems/Core/utils/math';
import type { Maybe } from '~/types';
import type { Coin, Maybe } from '~/types';

export const ZERO_AMOUNT = { value: '', raw: ZERO };

Expand Down Expand Up @@ -152,3 +153,25 @@ export const hasEthForNetworkFee = (params: SwapMachineContext) => {
*/
return balance > txCostTotal;
};

export interface CalculateMaxBalanceToSwapParams {
direction: SwapDirection;
ctx: {
coinFrom?: Coin;
coinTo?: Coin;
coinFromBalance?: Maybe<bigint>;
coinToBalance?: Maybe<bigint>;
txCost?: TransactionCost;
};
}

export const calculateMaxBalanceToSwap = ({ direction, ctx }: CalculateMaxBalanceToSwapParams) => {
const isFrom = direction === SwapDirection.fromTo;

const shouldUseNetworkFee =
(isFrom && isCoinEth(ctx.coinFrom)) || (!isFrom && isCoinEth(ctx.coinTo));
const balance = safeBigInt(isFrom ? ctx.coinFromBalance : ctx.coinToBalance);
const networkFee = safeBigInt(ctx.txCost?.fee);
const nextValue = balance > ZERO && shouldUseNetworkFee ? balance - networkFee : balance;
return createAmount(nextValue);
};

0 comments on commit e2fedbf

Please sign in to comment.