-
+
+
+
diff --git a/src/components/UserBalance/index.tsx b/src/components/UserBalance/index.tsx
new file mode 100644
index 00000000..55c27d2f
--- /dev/null
+++ b/src/components/UserBalance/index.tsx
@@ -0,0 +1,23 @@
+import { InputTokenDetails } from '../../constants/tokenConfig';
+import { useInputTokenBalance } from '../../hooks/useInputTokenBalance';
+
+interface UserBalanceProps {
+ token: InputTokenDetails;
+}
+
+export const UserBalance = ({ token }: UserBalanceProps) => {
+ const inputTokenBalance = useInputTokenBalance({ fromToken: token });
+
+ return (
+
+ Available:{' '}
+ {inputTokenBalance === undefined ? (
+ 'N/A'
+ ) : (
+ <>
+ {inputTokenBalance} {token.assetSymbol}
+ >
+ )}
+
+ );
+};
diff --git a/src/constants/abi/simplifiedERC20ABI.json b/src/constants/abi/simplifiedERC20ABI.json
new file mode 100644
index 00000000..80955653
--- /dev/null
+++ b/src/constants/abi/simplifiedERC20ABI.json
@@ -0,0 +1,34 @@
+[
+ {
+ "inputs": [{ "internalType": "address", "name": "account", "type": "address" }],
+ "name": "balanceOf",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function",
+ "constant": true
+ },
+ {
+ "inputs": [],
+ "name": "decimals",
+ "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
+ "stateMutability": "view",
+ "type": "function",
+ "constant": true
+ },
+ {
+ "inputs": [],
+ "name": "symbol",
+ "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+ "stateMutability": "view",
+ "type": "function",
+ "constant": true
+ },
+ {
+ "inputs": [],
+ "name": "totalSupply",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function",
+ "constant": true
+ }
+]
diff --git a/src/hooks/nabla/useTokenAmountOut.ts b/src/hooks/nabla/useTokenAmountOut.ts
index baf996bd..d39ad73b 100644
--- a/src/hooks/nabla/useTokenAmountOut.ts
+++ b/src/hooks/nabla/useTokenAmountOut.ts
@@ -1,5 +1,7 @@
import BigNumber from 'big.js';
+import Big from 'big.js';
import { UseQueryResult } from '@tanstack/react-query';
+import { UseFormReturn } from 'react-hook-form';
import { activeOptions, cacheKeys } from '../../constants/cache';
import { routerAbi } from '../../contracts/Router';
import {
@@ -13,9 +15,7 @@ import { NABLA_ROUTER } from '../../constants/constants';
import { useContractRead } from './useContractRead';
import { useDebouncedValue } from '../useDebouncedValue';
import { ApiPromise } from '../../services/polkadot/polkadotApi';
-import { UseFormReturn } from 'react-hook-form';
import { useEffect } from 'preact/hooks';
-import Big from 'big.js';
import { INPUT_TOKEN_CONFIG, InputTokenType, OUTPUT_TOKEN_CONFIG, OutputTokenType } from '../../constants/tokenConfig';
import { SwapFormValues } from '../../components/Nabla/schema';
diff --git a/src/hooks/useInputTokenBalance.ts b/src/hooks/useInputTokenBalance.ts
new file mode 100644
index 00000000..bcfe8166
--- /dev/null
+++ b/src/hooks/useInputTokenBalance.ts
@@ -0,0 +1,22 @@
+import { formatUnits } from 'viem';
+import { useAccount, useReadContract } from 'wagmi';
+
+import erc20ABI from '../contracts/ERC20';
+import { InputTokenDetails } from '../constants/tokenConfig';
+import { multiplyByPowerOfTen } from '../helpers/contracts';
+import Big from 'big.js';
+
+export const useInputTokenBalance = ({ fromToken }: { fromToken: InputTokenDetails }): string | undefined => {
+ const { address } = useAccount();
+
+ const { data: balance }: { data: bigint | undefined } = useReadContract({
+ address: fromToken.erc20AddressSourceChain,
+ abi: erc20ABI,
+ functionName: 'balanceOf',
+ args: [address],
+ });
+
+ return address === undefined || balance === undefined
+ ? undefined
+ : multiplyByPowerOfTen(Big(balance.toString()), -fromToken.decimals).toFixed(2, 0);
+};
diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx
index 643ef64c..f0c5fb39 100644
--- a/src/pages/swap/index.tsx
+++ b/src/pages/swap/index.tsx
@@ -1,6 +1,7 @@
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
-import Big from 'big.js';
import { ArrowDownIcon } from '@heroicons/react/20/solid';
+import { useAccount } from 'wagmi';
+import Big from 'big.js';
import { LabeledInput } from '../../components/LabeledInput';
import { BenefitsList } from '../../components/BenefitsList';
@@ -17,11 +18,13 @@ import { config } from '../../config';
import { INPUT_TOKEN_CONFIG, InputTokenType, OUTPUT_TOKEN_CONFIG, OutputTokenType } from '../../constants/tokenConfig';
import { BaseLayout } from '../../layouts';
-import { useMainProcess } from '../../hooks/useMainProcess';
import { multiplyByPowerOfTen, stringifyBigWithSignificantDecimals } from '../../helpers/contracts';
+import { useMainProcess } from '../../hooks/useMainProcess';
import { ProgressPage } from '../progress';
import { SuccessPage } from '../success';
import { FailurePage } from '../failure';
+import { useInputTokenBalance } from '../../hooks/useInputTokenBalance';
+import { UserBalance } from '../../components/UserBalance';
const Arrow = () => (
@@ -32,9 +35,10 @@ const Arrow = () => (
export const SwapPage = () => {
const [isQuoteSubmitted, setIsQuoteSubmitted] = useState(false);
const formRef = useRef
(null);
-
const [api, setApi] = useState(null);
+ const { isDisconnected } = useAccount();
+
useEffect(() => {
const initializeApiManager = async () => {
const manager = await getApiManagerInstance();
@@ -68,8 +72,10 @@ export const SwapPage = () => {
to,
} = useSwapForm();
- const fromToken = from ? INPUT_TOKEN_CONFIG[from] : undefined;
- const toToken = to ? OUTPUT_TOKEN_CONFIG[to] : undefined;
+ const fromToken = INPUT_TOKEN_CONFIG[from];
+ const toToken = OUTPUT_TOKEN_CONFIG[to];
+
+ const userInputTokenBalance = useInputTokenBalance({ fromToken });
const tokenOutData = useTokenOutAmount({
wantsSwap: true,
@@ -142,17 +148,29 @@ export const SwapPage = () => {
const WidthrawNumericInput = useMemo(
() => (
- setIsQuoteSubmitted(true) })}
- tokenType={from}
- tokenSymbol={fromToken?.assetSymbol}
- onClick={() => setModalType('from')}
- />
+ <>
+ setIsQuoteSubmitted(true) })}
+ tokenType={from}
+ tokenSymbol={fromToken?.assetSymbol}
+ onClick={() => setModalType('from')}
+ />
+
+ >
),
- [form, from, fromToken?.assetSymbol, setModalType],
+ [form, from, fromToken, setModalType],
);
function getCurrentErrorMessage() {
+ // Do not show any error if the user is disconnected
+ if (isDisconnected) return;
+
+ if (typeof userInputTokenBalance === 'string') {
+ if (Big(userInputTokenBalance).lt(fromAmount ?? 0)) {
+ return `Insufficient balance. Your balance is ${userInputTokenBalance} ${fromToken?.assetSymbol}.`;
+ }
+ }
+
const amountOut = tokenOutData.data?.amountOut;
if (amountOut !== undefined && toToken !== undefined) {
diff --git a/yarn.lock b/yarn.lock
index a808be0d..86a5e6fb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -13239,93 +13239,6 @@ __metadata:
languageName: node
linkType: hard
-"pendulum-pay@workspace:.":
- version: 0.0.0-use.local
- resolution: "pendulum-pay@workspace:."
- dependencies:
- "@babel/core": "npm:^7.20.12"
- "@babel/plugin-proposal-class-properties": "npm:^7.18.6"
- "@babel/preset-env": "npm:^7.20.2"
- "@babel/preset-typescript": "npm:^7.18.6"
- "@emotion/react": "npm:^11.11.1"
- "@emotion/styled": "npm:^11.11.0"
- "@fontsource/roboto": "npm:^5.0.8"
- "@heroicons/react": "npm:^2.1.3"
- "@hookform/resolvers": "npm:^3.4.2"
- "@material-ui/core": "npm:^4.12.4"
- "@mui/icons-material": "npm:^5.14.19"
- "@mui/material": "npm:^5.14.20"
- "@pendulum-chain/api": "npm:^0.3.1"
- "@pendulum-chain/api-solang": "npm:^0.6.0"
- "@pendulum-chain/types": "npm:^0.2.3"
- "@polkadot/api": "npm:^9.9.1"
- "@polkadot/api-base": "npm:^9.9.1"
- "@polkadot/api-contract": "npm:^9.9.1"
- "@polkadot/api-derive": "npm:^9.9.1"
- "@polkadot/extension-dapp": "npm:^0.47.1"
- "@polkadot/keyring": "npm:^10.1.9"
- "@polkadot/rpc-augment": "npm:^9.9.1"
- "@polkadot/rpc-core": "npm:^9.9.1"
- "@polkadot/rpc-provider": "npm:^9.9.1"
- "@polkadot/types": "npm:^9.9.1"
- "@polkadot/types-augment": "npm:^9.9.1"
- "@polkadot/types-codec": "npm:^9.9.1"
- "@polkadot/types-create": "npm:^9.9.1"
- "@polkadot/types-known": "npm:^9.9.1"
- "@polkadot/util": "npm:^10.1.9"
- "@polkadot/util-crypto": "npm:^12.6.2"
- "@preact/preset-vite": "npm:^2.5.0"
- "@rainbow-me/rainbowkit": "npm:^2.1.2"
- "@talismn/connect-components": "npm:^1.1.8"
- "@talismn/connect-wallets": "npm:^1.2.5"
- "@tanstack/react-query": "npm:^5.45.1"
- "@testing-library/jest-dom": "npm:^6.4.8"
- "@testing-library/preact": "npm:^3.2.3"
- "@testing-library/preact-hooks": "npm:^1.1.0"
- "@testing-library/user-event": "npm:^14.5.2"
- "@types/big.js": "npm:^6"
- "@types/bn.js": "npm:^5"
- "@types/node": "npm:^18.14.1"
- "@typescript-eslint/eslint-plugin": "npm:^5.53.0"
- "@typescript-eslint/parser": "npm:^5.53.0"
- "@walletconnect/modal": "npm:^2.6.2"
- "@walletconnect/universal-provider": "npm:^2.12.2"
- autoprefixer: "npm:^10.4.19"
- babel-preset-vite: "npm:^1.1.3"
- big.js: "npm:^6.2.1"
- bn.js: "npm:^5.2.1"
- buffer: "npm:^6.0.3"
- daisyui: "npm:^4.11.1"
- eslint: "npm:^8.34.0"
- eslint-plugin-react: "npm:^7.32.2"
- eslint-plugin-react-hooks: "npm:^4.6.0"
- framer-motion: "npm:^11.2.14"
- happy-dom: "npm:^14.12.3"
- husky: "npm:>=6"
- lint-staged: "npm:>=10"
- postcss: "npm:^8.4.38"
- preact: "npm:^10.12.1"
- prettier: "npm:^2.8.4"
- react-daisyui: "npm:^5.0.0"
- react-hook-form: "npm:^7.51.5"
- react-native-compat: "npm:^1.0.0"
- react-router-dom: "npm:^6.8.1"
- stellar-base: "npm:^11.0.1"
- stellar-sdk: "npm:^11.3.0"
- tailwind: "npm:^4.0.0"
- tailwindcss: "npm:^3.4.3"
- ts-node: "npm:^10.9.1"
- typescript: "npm:^5.3.3"
- viem: "npm:2.x"
- vite: "npm:^5.3.5"
- vite-plugin-node-polyfills: "npm:^0.22.0"
- vitest: "npm:^2.0.5"
- wagmi: "npm:^2.10.3"
- web3: "npm:^4.10.0"
- yup: "npm:^1.4.0"
- languageName: unknown
- linkType: soft
-
"picocolors@npm:^1, picocolors@npm:^1.0.1":
version: 1.0.1
resolution: "picocolors@npm:1.0.1"
@@ -17013,6 +16926,93 @@ __metadata:
languageName: node
linkType: hard
+"vortex@workspace:.":
+ version: 0.0.0-use.local
+ resolution: "vortex@workspace:."
+ dependencies:
+ "@babel/core": "npm:^7.20.12"
+ "@babel/plugin-proposal-class-properties": "npm:^7.18.6"
+ "@babel/preset-env": "npm:^7.20.2"
+ "@babel/preset-typescript": "npm:^7.18.6"
+ "@emotion/react": "npm:^11.11.1"
+ "@emotion/styled": "npm:^11.11.0"
+ "@fontsource/roboto": "npm:^5.0.8"
+ "@heroicons/react": "npm:^2.1.3"
+ "@hookform/resolvers": "npm:^3.4.2"
+ "@material-ui/core": "npm:^4.12.4"
+ "@mui/icons-material": "npm:^5.14.19"
+ "@mui/material": "npm:^5.14.20"
+ "@pendulum-chain/api": "npm:^0.3.1"
+ "@pendulum-chain/api-solang": "npm:^0.6.0"
+ "@pendulum-chain/types": "npm:^0.2.3"
+ "@polkadot/api": "npm:^9.9.1"
+ "@polkadot/api-base": "npm:^9.9.1"
+ "@polkadot/api-contract": "npm:^9.9.1"
+ "@polkadot/api-derive": "npm:^9.9.1"
+ "@polkadot/extension-dapp": "npm:^0.47.1"
+ "@polkadot/keyring": "npm:^10.1.9"
+ "@polkadot/rpc-augment": "npm:^9.9.1"
+ "@polkadot/rpc-core": "npm:^9.9.1"
+ "@polkadot/rpc-provider": "npm:^9.9.1"
+ "@polkadot/types": "npm:^9.9.1"
+ "@polkadot/types-augment": "npm:^9.9.1"
+ "@polkadot/types-codec": "npm:^9.9.1"
+ "@polkadot/types-create": "npm:^9.9.1"
+ "@polkadot/types-known": "npm:^9.9.1"
+ "@polkadot/util": "npm:^10.1.9"
+ "@polkadot/util-crypto": "npm:^12.6.2"
+ "@preact/preset-vite": "npm:^2.5.0"
+ "@rainbow-me/rainbowkit": "npm:^2.1.2"
+ "@talismn/connect-components": "npm:^1.1.8"
+ "@talismn/connect-wallets": "npm:^1.2.5"
+ "@tanstack/react-query": "npm:^5.45.1"
+ "@testing-library/jest-dom": "npm:^6.4.8"
+ "@testing-library/preact": "npm:^3.2.3"
+ "@testing-library/preact-hooks": "npm:^1.1.0"
+ "@testing-library/user-event": "npm:^14.5.2"
+ "@types/big.js": "npm:^6"
+ "@types/bn.js": "npm:^5"
+ "@types/node": "npm:^18.14.1"
+ "@typescript-eslint/eslint-plugin": "npm:^5.53.0"
+ "@typescript-eslint/parser": "npm:^5.53.0"
+ "@walletconnect/modal": "npm:^2.6.2"
+ "@walletconnect/universal-provider": "npm:^2.12.2"
+ autoprefixer: "npm:^10.4.19"
+ babel-preset-vite: "npm:^1.1.3"
+ big.js: "npm:^6.2.1"
+ bn.js: "npm:^5.2.1"
+ buffer: "npm:^6.0.3"
+ daisyui: "npm:^4.11.1"
+ eslint: "npm:^8.34.0"
+ eslint-plugin-react: "npm:^7.32.2"
+ eslint-plugin-react-hooks: "npm:^4.6.0"
+ framer-motion: "npm:^11.2.14"
+ happy-dom: "npm:^14.12.3"
+ husky: "npm:>=6"
+ lint-staged: "npm:>=10"
+ postcss: "npm:^8.4.38"
+ preact: "npm:^10.12.1"
+ prettier: "npm:^2.8.4"
+ react-daisyui: "npm:^5.0.0"
+ react-hook-form: "npm:^7.51.5"
+ react-native-compat: "npm:^1.0.0"
+ react-router-dom: "npm:^6.8.1"
+ stellar-base: "npm:^11.0.1"
+ stellar-sdk: "npm:^11.3.0"
+ tailwind: "npm:^4.0.0"
+ tailwindcss: "npm:^3.4.3"
+ ts-node: "npm:^10.9.1"
+ typescript: "npm:^5.3.3"
+ viem: "npm:2.x"
+ vite: "npm:^5.3.5"
+ vite-plugin-node-polyfills: "npm:^0.22.0"
+ vitest: "npm:^2.0.5"
+ wagmi: "npm:^2.10.3"
+ web3: "npm:^4.10.0"
+ yup: "npm:^1.4.0"
+ languageName: unknown
+ linkType: soft
+
"wagmi@npm:^2.10.3":
version: 2.10.3
resolution: "wagmi@npm:2.10.3"