diff --git a/apps/dcellar-web-ui/public/images/toolbox/icon-toolbox-bg-1.svg b/apps/dcellar-web-ui/public/images/toolbox/icon-toolbox-bg-1.svg
new file mode 100644
index 00000000..aef50769
--- /dev/null
+++ b/apps/dcellar-web-ui/public/images/toolbox/icon-toolbox-bg-1.svg
@@ -0,0 +1,31 @@
+
diff --git a/apps/dcellar-web-ui/public/images/toolbox/icon-toolbox-bg-2.svg b/apps/dcellar-web-ui/public/images/toolbox/icon-toolbox-bg-2.svg
new file mode 100644
index 00000000..9e0c7494
--- /dev/null
+++ b/apps/dcellar-web-ui/public/images/toolbox/icon-toolbox-bg-2.svg
@@ -0,0 +1,9 @@
+
diff --git a/apps/dcellar-web-ui/public/images/toolbox/icon-toolbox-bg-3.svg b/apps/dcellar-web-ui/public/images/toolbox/icon-toolbox-bg-3.svg
new file mode 100644
index 00000000..343abd59
--- /dev/null
+++ b/apps/dcellar-web-ui/public/images/toolbox/icon-toolbox-bg-3.svg
@@ -0,0 +1,9 @@
+
diff --git a/apps/dcellar-web-ui/public/js/iconfont_v0.6.min.js b/apps/dcellar-web-ui/public/js/iconfont_v0.6.min.js
deleted file mode 100644
index 527e0b61..00000000
--- a/apps/dcellar-web-ui/public/js/iconfont_v0.6.min.js
+++ /dev/null
@@ -1 +0,0 @@
-!function(e){var t,n,d,o,i,a,r='';function c(){i||(i=!0,d())}t=function(){var e,t,n;(n=document.createElement("div")).innerHTML=r,r=null,(t=n.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",e=t,(n=document.body).firstChild?(t=n.firstChild).parentNode.insertBefore(e,t):n.appendChild(e))},document.addEventListener?["complete","loaded","interactive"].indexOf(document.readyState)>-1?setTimeout(t,0):(n=function(){document.removeEventListener("DOMContentLoaded",n,!1),t()},document.addEventListener("DOMContentLoaded",n,!1)):document.attachEvent&&(d=t,o=e.document,i=!1,(a=function(){try{o.documentElement.doScroll("left")}catch(e){return void setTimeout(a,50)}c()})(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,c())})}(window);
diff --git a/apps/dcellar-web-ui/public/js/iconfont_v0.5.min.js b/apps/dcellar-web-ui/public/js/iconfont_v0.7.min.js
similarity index 97%
rename from apps/dcellar-web-ui/public/js/iconfont_v0.5.min.js
rename to apps/dcellar-web-ui/public/js/iconfont_v0.7.min.js
index 527e0b61..5c742dd6 100644
--- a/apps/dcellar-web-ui/public/js/iconfont_v0.5.min.js
+++ b/apps/dcellar-web-ui/public/js/iconfont_v0.7.min.js
@@ -1 +1 @@
-!function(e){var t,n,d,o,i,a,r='';function c(){i||(i=!0,d())}t=function(){var e,t,n;(n=document.createElement("div")).innerHTML=r,r=null,(t=n.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",e=t,(n=document.body).firstChild?(t=n.firstChild).parentNode.insertBefore(e,t):n.appendChild(e))},document.addEventListener?["complete","loaded","interactive"].indexOf(document.readyState)>-1?setTimeout(t,0):(n=function(){document.removeEventListener("DOMContentLoaded",n,!1),t()},document.addEventListener("DOMContentLoaded",n,!1)):document.attachEvent&&(d=t,o=e.document,i=!1,(a=function(){try{o.documentElement.doScroll("left")}catch(e){return void setTimeout(a,50)}c()})(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,c())})}(window);
+!function(e){var t,n,d,o,i,a,r='';function c(){i||(i=!0,d())}t=function(){var e,t,n;(n=document.createElement("div")).innerHTML=r,r=null,(t=n.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",e=t,(n=document.body).firstChild?(t=n.firstChild).parentNode.insertBefore(e,t):n.appendChild(e))},document.addEventListener?["complete","loaded","interactive"].indexOf(document.readyState)>-1?setTimeout(t,0):(n=function(){document.removeEventListener("DOMContentLoaded",n,!1),t()},document.addEventListener("DOMContentLoaded",n,!1)):document.attachEvent&&(d=t,o=e.document,i=!1,(a=function(){try{o.documentElement.doScroll("left")}catch(e){return void setTimeout(a,50)}c()})(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,c())})}(window);
\ No newline at end of file
diff --git a/apps/dcellar-web-ui/src/components/layout/Nav/index.tsx b/apps/dcellar-web-ui/src/components/layout/Nav/index.tsx
index 2c48e095..16bf4436 100644
--- a/apps/dcellar-web-ui/src/components/layout/Nav/index.tsx
+++ b/apps/dcellar-web-ui/src/components/layout/Nav/index.tsx
@@ -32,6 +32,11 @@ const MENU_ITEMS = [
text: 'Accounts',
trackId: 'dc.main.nav.accounts.click',
},
+ {
+ icon: 'toolbox',
+ text: 'Toolbox',
+ trackId: 'dc.main.nav.toolbox.click',
+ },
];
const ASIDE = [
diff --git a/apps/dcellar-web-ui/src/constants/paths.ts b/apps/dcellar-web-ui/src/constants/paths.ts
index bd639a9a..c006b277 100644
--- a/apps/dcellar-web-ui/src/constants/paths.ts
+++ b/apps/dcellar-web-ui/src/constants/paths.ts
@@ -10,6 +10,7 @@ export const InternalRoutePaths = {
pricing_calculator: '/pricing-calculator',
accounts: '/accounts',
dashboard: '/dashboard',
+ toolbox: '/toolbox',
};
export const NODEREAL_URL = 'https://nodereal.io';
\ No newline at end of file
diff --git a/apps/dcellar-web-ui/src/context/GlobalContext/PageProtect.tsx b/apps/dcellar-web-ui/src/context/GlobalContext/PageProtect.tsx
index 1e7743b1..c59c28a6 100644
--- a/apps/dcellar-web-ui/src/context/GlobalContext/PageProtect.tsx
+++ b/apps/dcellar-web-ui/src/context/GlobalContext/PageProtect.tsx
@@ -7,8 +7,10 @@ import { isRightChain } from '@/modules/wallet/utils/isRightChain';
import { BSC_CHAIN_ID, GREENFIELD_CHAIN_ID } from '@/base/env';
import { WrongNetworkModal } from '@/components/WrongNetworkModal';
+// protect: GNFD chain, GNFD & BSC chain and no protect.
const protectGNFDPaths = ['/buckets', '/buckets/[...path]', '/groups', '/accounts'];
-const noProtectPaths = ['/', '/terms', '/pricing-calculator'];
+const noProtectPaths = ['/', '/terms', '/pricing-calculator', '/tool-box'];
+
// TODO unify the wallet page protect
export const PageProtect: React.FC = ({ children }) => {
const { chain } = useNetwork();
diff --git a/apps/dcellar-web-ui/src/context/GlobalContext/WalletBalanceContext.tsx b/apps/dcellar-web-ui/src/context/GlobalContext/WalletBalanceContext.tsx
index 866c0fdb..6fa6ce46 100644
--- a/apps/dcellar-web-ui/src/context/GlobalContext/WalletBalanceContext.tsx
+++ b/apps/dcellar-web-ui/src/context/GlobalContext/WalletBalanceContext.tsx
@@ -3,6 +3,8 @@ import { Address, useBalance, useNetwork } from 'wagmi';
import { BSC_CHAIN_ID, GREENFIELD_CHAIN_ID } from '@/base/env';
import { useAppSelector } from '@/store';
+import { CRYPTOCURRENCY_DISPLAY_PRECISION } from '@/modules/wallet/constants';
+import { BN } from '@/utils/math';
type TChainBalance = {
chainId: number;
@@ -51,13 +53,13 @@ export const WalletBalanceProvider: React.FC = ({ children }) => {
chainId: BSC_CHAIN_ID,
isLoading: isBscLoading,
isError: isBscError,
- availableBalance: bscBalance?.formatted,
+ availableBalance: BN(bscBalance?.formatted ?? 0).dp(CRYPTOCURRENCY_DISPLAY_PRECISION, 1).toString(),
},
{
chainId: GREENFIELD_CHAIN_ID,
isLoading: isGnfdLoading,
isError: isGnfdError,
- availableBalance: gnfdBalance?.formatted,
+ availableBalance: BN(gnfdBalance?.formatted ?? 0).dp(CRYPTOCURRENCY_DISPLAY_PRECISION, 1).toString(),
},
],
};
diff --git a/apps/dcellar-web-ui/src/facade/wallet.ts b/apps/dcellar-web-ui/src/facade/wallet.ts
index 2d69d174..28844562 100644
--- a/apps/dcellar-web-ui/src/facade/wallet.ts
+++ b/apps/dcellar-web-ui/src/facade/wallet.ts
@@ -1,5 +1,10 @@
import { Connector } from 'wagmi';
import { signTypedDataV4 } from '@/utils/coder';
+import { ethers } from 'ethers';
+import { ErrorResponse, commonFault } from './error';
+import { resolve } from './common';
+import { BN } from '@/utils/math';
+import BigNumber from 'bignumber.js';
export const signTypedDataCallback = (connector: Connector) => {
return async (addr: string, message: string) => {
@@ -7,3 +12,55 @@ export const signTypedDataCallback = (connector: Connector) => {
return await signTypedDataV4(provider, addr, message);
};
};
+
+export const calTransferInFee = async (
+ params: {
+ amount: string,
+ crossChainContractAddress: string;
+ tokenHubContract: string
+ crossChainAbi: any;
+ tokenHubAbi: any;
+ address: string;
+ },
+ signer: ethers.providers.JsonRpcSigner,
+ provider: ethers.providers.JsonRpcProvider | ethers.providers.FallbackProvider,
+): Promise => {
+ const crossChainContract = new ethers.Contract(
+ params.crossChainContractAddress,
+ params.crossChainAbi,
+ signer!,
+ );
+ const [fee, error1] = await crossChainContract.getRelayFees().then(resolve, commonFault);
+ if (error1) return [null, error1];
+ const [relayFee, ackRelayFee] = fee;
+ const relayerFee = relayFee.add(ackRelayFee);
+ const fData = await provider.getFeeData();
+ const amountInFormat = ethers.utils.parseEther(String(params.amount));
+ const transferInAmount = amountInFormat;
+
+ const totalAmount = amountInFormat.add(ackRelayFee).add(relayFee);
+
+ const tokenHubContract = new ethers.Contract(
+ params.tokenHubContract,
+ params.tokenHubAbi,
+ signer!,
+ );
+
+ const [estimateGas, error2] = await tokenHubContract.estimateGas.transferOut(
+ params.address,
+ transferInAmount,
+ {
+ value: totalAmount,
+ },
+ ).then(resolve, commonFault);
+ if (!estimateGas || error2) return [null, error2];
+
+ const gasFee = fData.gasPrice && estimateGas.mul(fData.gasPrice);
+
+ const finalData = {
+ gasFee: BN(gasFee ? ethers.utils.formatEther(gasFee) : '0'),
+ relayerFee: BN(ethers.utils.formatEther(relayerFee)),
+ };
+
+ return [finalData, null];
+}
\ No newline at end of file
diff --git a/apps/dcellar-web-ui/src/modules/toolbox/components/Common.tsx b/apps/dcellar-web-ui/src/modules/toolbox/components/Common.tsx
new file mode 100644
index 00000000..d849f837
--- /dev/null
+++ b/apps/dcellar-web-ui/src/modules/toolbox/components/Common.tsx
@@ -0,0 +1,44 @@
+import { Center, CircleProps, Flex, FlexProps, LinkProps, Tooltip } from '@totejs/uikit';
+
+export const Card = ({ children, ...props }: FlexProps) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export const CircleLink = ({ children, href, title, ...props }: CircleProps & LinkProps) => {
+ return (
+
+
+ {children}
+
+
+ );
+};
diff --git a/apps/dcellar-web-ui/src/modules/toolbox/components/TellUsCard.tsx b/apps/dcellar-web-ui/src/modules/toolbox/components/TellUsCard.tsx
new file mode 100644
index 00000000..bd0ebd1e
--- /dev/null
+++ b/apps/dcellar-web-ui/src/modules/toolbox/components/TellUsCard.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import { Card } from './Common';
+import { Box, Text, Tooltip } from '@totejs/uikit';
+import { DCButton } from '@/components/common/DCButton';
+import { assetPrefix } from '@/base/env';
+
+export const TellUsCard = () => {
+ const onNavigateExternal = (url: string) => {
+ window.open(url, '_blank', 'noreferrer');
+ };
+ return (
+
+
+ Start Building with DCellar Now
+
+
+
+ DCellar offers a full set of open source toolkits for developers to start build on
+ Greenfield at ease.
+
+
+
+ onNavigateExternal('#')}
+ >
+ Explorer
+
+
+
+ );
+};
diff --git a/apps/dcellar-web-ui/src/modules/toolbox/components/UploadkitCard.tsx b/apps/dcellar-web-ui/src/modules/toolbox/components/UploadkitCard.tsx
new file mode 100644
index 00000000..46b03b3c
--- /dev/null
+++ b/apps/dcellar-web-ui/src/modules/toolbox/components/UploadkitCard.tsx
@@ -0,0 +1,36 @@
+import { IconFont } from '@/components/IconFont';
+import { Badge, Flex, Text } from '@totejs/uikit';
+import { Card, CircleLink } from './Common';
+
+export const UploadKitCard = () => {
+ return (
+
+
+
+
+ Greenfield UploadKit
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Component
+
+
+ Greenfield Upload UIKit is offered by NodeReal, it's fully open sourced, developers can
+ easily integrate into their WebUI dApps.
+
+
+ );
+};
diff --git a/apps/dcellar-web-ui/src/modules/toolbox/page/index.tsx b/apps/dcellar-web-ui/src/modules/toolbox/page/index.tsx
new file mode 100644
index 00000000..a0c15750
--- /dev/null
+++ b/apps/dcellar-web-ui/src/modules/toolbox/page/index.tsx
@@ -0,0 +1,19 @@
+import { Box, Flex, Text } from '@totejs/uikit';
+import { TellUsCard } from '../components/TellUsCard';
+import { UploadKitCard } from '../components/UploadkitCard';
+
+export const ToolBoxPage = () => {
+ return (
+ <>
+
+
+ Toolbox
+
+
+
+
+
+
+ >
+ );
+};
\ No newline at end of file
diff --git a/apps/dcellar-web-ui/src/modules/wallet/Send/index.tsx b/apps/dcellar-web-ui/src/modules/wallet/Send/index.tsx
index 32150cdb..fe0bd064 100644
--- a/apps/dcellar-web-ui/src/modules/wallet/Send/index.tsx
+++ b/apps/dcellar-web-ui/src/modules/wallet/Send/index.tsx
@@ -414,8 +414,10 @@ export const Send = memo(function Send() {
register={register}
disabled={isSubmitting}
watch={watch}
+ bankBalance={bankBalance}
feeData={feeData}
setValue={setValue}
+ settlementFee={settlementFee}
maxDisabled={isLoading}
/>
{isShowFee() ? (
diff --git a/apps/dcellar-web-ui/src/modules/wallet/TransferIn/index.tsx b/apps/dcellar-web-ui/src/modules/wallet/TransferIn/index.tsx
index 43f4e560..2dded9bb 100644
--- a/apps/dcellar-web-ui/src/modules/wallet/TransferIn/index.tsx
+++ b/apps/dcellar-web-ui/src/modules/wallet/TransferIn/index.tsx
@@ -1,11 +1,9 @@
import { Box, Divider, Flex, useDisclosure } from '@totejs/uikit';
-import { memo, useCallback, useEffect, useMemo, useState } from 'react';
-import { useNetwork } from 'wagmi';
+import { memo, useCallback, useMemo, useState } from 'react';
import { useForm } from 'react-hook-form';
import { ethers } from 'ethers';
import { isEmpty } from 'lodash-es';
import { useRouter } from 'next/router';
-import BigNumber from 'bignumber.js';
import { ChainBox } from '../components/ChainBox';
import Amount from '../components/Amount';
@@ -16,9 +14,7 @@ import Container from '../components/Container';
import { BSC_CHAIN_ID, BSC_EXPLORER_URL, GREENFIELD_CHAIN_ID } from '@/base/env';
import { WalletButton } from '../components/WalletButton';
import { Fee } from '../components/Fee';
-import { EOperation, TCalculateGas, TFeeData, TTransferInFromValues } from '../type';
-import { CROSS_CHAIN_ABI, INIT_FEE_DATA, TOKENHUB_ABI, WalletOperationInfos } from '../constants';
-import { isRightChain } from '../utils/isRightChain';
+import { TTransferInFromValues } from '../type';
import { GAClick } from '@/components/common/GATracker';
import { useAppSelector } from '@/store';
import { useChainsBalance } from '@/context/GlobalContext/WalletBalanceContext';
@@ -27,26 +23,16 @@ import { removeTrailingSlash } from '@/utils/string';
import { broadcastFault } from '@/facade/error';
import { Faucet } from '../components/Faucet';
import { LargeAmountTip } from '../components/LargeAmountTip';
-import { useEthersProvider, useEthersSigner } from '../hooks';
+import { useTransferInFee } from '../hooks';
interface TransferInProps {}
-
export const TransferIn = memo(function TransferIn() {
- const {
- TOKEN_HUB_CONTRACT_ADDRESS: APOLLO_TOKEN_HUB_CONTRACT_ADDRESS,
- CROSS_CHAIN_CONTRACT_ADDRESS: APOLLO_CROSS_CHAIN_CONTRACT_ADDRESS,
- } = useAppSelector((root) => root.apollo);
const { transType } = useAppSelector((root) => root.wallet);
const { isOpen, onClose, onOpen } = useDisclosure();
const [status, setStatus] = useState('success');
const [errorMsg, setErrorMsg] = useState('Oops, something went wrong');
const router = useRouter();
const [viewTxUrl, setViewTxUrl] = useState('');
- const { loginAccount: address } = useAppSelector((root) => root.persist);
- const provider = useEthersProvider({ chainId: BSC_CHAIN_ID });
- const signer = useEthersSigner({chainId: BSC_CHAIN_ID})
- const [feeData, setFeeData] = useState(INIT_FEE_DATA);
- const [isGasLoading, setIsGasLoading] = useState(false);
const { all } = useChainsBalance();
const {
handleSubmit,
@@ -59,68 +45,22 @@ export const TransferIn = memo(function TransferIn() {
} = useForm({
mode: 'all',
});
- const { chain } = useNetwork();
- const curInfo = WalletOperationInfos[transType];
- const isRight = useMemo(() => {
- return isRightChain(chain?.id, curInfo?.chainId);
- }, [chain?.id, curInfo?.chainId]);
const inputAmount = getValues('amount');
const balance = useMemo(() => {
return all.find((item) => item.chainId === BSC_CHAIN_ID)?.availableBalance || '';
}, [all]);
- const getFee = useCallback(
- async ({ amountIn, type = 'content_value' }: { amountIn: string; type?: TCalculateGas }) => {
- if (signer && amountIn) {
- try {
- setIsGasLoading(true);
- const crossChainContract = new ethers.Contract(
- APOLLO_CROSS_CHAIN_CONTRACT_ADDRESS || '',
- CROSS_CHAIN_ABI,
- signer!,
- );
- const [relayFee, ackRelayFee] = await crossChainContract.getRelayFees();
- const relayerFee = relayFee.add(ackRelayFee);
- const fData = await provider.getFeeData();
- const amountInFormat = ethers.utils.parseEther(String(amountIn));
-
- // bsc simulate gas fee need real amount.
- const transferInAmount =
- type === 'content_value'
- ? amountInFormat
- : amountInFormat.sub(ackRelayFee).sub(relayFee);
- const totalAmount =
- type === 'content_value'
- ? amountInFormat.add(ackRelayFee).add(relayFee)
- : amountInFormat;
- const tokenHubContract = new ethers.Contract(
- APOLLO_TOKEN_HUB_CONTRACT_ADDRESS || '',
- TOKENHUB_ABI,
- signer!,
- );
- const estimateGas = await tokenHubContract.estimateGas.transferOut(
- address,
- transferInAmount,
- {
- value: totalAmount,
- },
- );
- const gasFee = fData.gasPrice && estimateGas.mul(fData.gasPrice);
- const finalData = {
- gasFee: BigNumber(gasFee ? ethers.utils.formatEther(gasFee) : '0'),
- relayerFee: BigNumber(ethers.utils.formatEther(relayerFee)),
- };
- setIsGasLoading(false);
- setFeeData(finalData);
- } catch (e) {
- // eslint-disable-next-line no-console
- console.log('getGas error', e);
- setIsGasLoading(false);
- }
- }
- },
- [address, provider, signer],
- );
+ const {
+ isLoading: isGasLoading,
+ feeData,
+ signer,
+ getFee,
+ loginAccount: address,
+ tokenHubContract,
+ tokenHubAbi,
+ crossChainAbi,
+ crossChainContract,
+ } = useTransferInFee();
const isShowFee = useCallback(() => {
return isEmpty(errors) && !isEmpty(inputAmount);
@@ -131,22 +71,20 @@ export const TransferIn = memo(function TransferIn() {
onOpen();
try {
- const crossChainContract = new ethers.Contract(
- APOLLO_CROSS_CHAIN_CONTRACT_ADDRESS || '',
- CROSS_CHAIN_ABI,
- signer!,
- );
- const tokenHubContract = new ethers.Contract(
- APOLLO_TOKEN_HUB_CONTRACT_ADDRESS || '',
- TOKENHUB_ABI,
+ const cInstance = new ethers.Contract(
+ crossChainContract,
+ crossChainAbi,
signer!,
);
+ const tInstance = new ethers.Contract(tokenHubContract, tokenHubAbi, signer!);
+
const transferInAmount = data.amount;
const amount = ethers.utils.parseEther(transferInAmount.toString());
- const [relayFee, ackRelayFee] = await crossChainContract.getRelayFees();
+ const [relayFee, ackRelayFee] = await cInstance.getRelayFees();
const relayerFee = relayFee.add(ackRelayFee);
const totalAmount = relayerFee.add(amount);
- const tx = await tokenHubContract.transferOut(address, amount, {
+
+ const tx = await tInstance.transferOut(address, amount, {
value: totalAmount,
});
@@ -174,19 +112,6 @@ export const TransferIn = memo(function TransferIn() {
onClose();
};
- useEffect(() => {
- if (
- !isEmpty(errors) ||
- !isRight ||
- isEmpty(inputAmount) ||
- transType !== EOperation.transfer_in
- ) {
- return;
- }
-
- getFee({ amountIn: inputAmount });
- }, [getFee, isRight, transType, inputAmount, errors]);
-
return (
<>
@@ -207,7 +132,7 @@ export const TransferIn = memo(function TransferIn() {
watch={watch}
feeData={feeData}
setValue={setValue}
- getGasFee={getFee}
+ refreshFee={getFee}
maxDisabled={isGasLoading}
/>
{isShowFee() ? (
diff --git a/apps/dcellar-web-ui/src/modules/wallet/components/Amount.tsx b/apps/dcellar-web-ui/src/modules/wallet/components/Amount.tsx
index dbd1f307..a004f83b 100644
--- a/apps/dcellar-web-ui/src/modules/wallet/components/Amount.tsx
+++ b/apps/dcellar-web-ui/src/modules/wallet/components/Amount.tsx
@@ -10,7 +10,7 @@ import {
Link,
Text,
} from '@totejs/uikit';
-import React, { useCallback } from 'react';
+import { useCallback, useMemo } from 'react';
import { useNetwork } from 'wagmi';
import { isEmpty } from 'lodash-es';
import BigNumber from 'bignumber.js';
@@ -19,10 +19,11 @@ import { FieldErrors, UseFormRegister, UseFormSetValue, UseFormWatch } from 'rea
import {
CRYPTOCURRENCY_DISPLAY_PRECISION,
DECIMAL_NUMBER,
+ DefaultTransferFee,
MIN_AMOUNT,
WalletOperationInfos,
} from '../constants';
-import { EOperation, GetFeeType, TFeeData, TWalletFromValues } from '../type';
+import { EOperation, TFeeData, TWalletFromValues } from '../type';
import { useChainsBalance } from '@/context/GlobalContext/WalletBalanceContext';
import { useAppSelector } from '@/store';
import { selectBnbPrice } from '@/store/slices/global';
@@ -32,27 +33,35 @@ import { currencyFormatter } from '@/utils/formatter';
import { BN } from '@/utils/math';
import { IconFont } from '@/components/IconFont';
import { displayTokenSymbol } from '@/utils/wallet';
+import { isRightChain } from '../utils/isRightChain';
+import { MaxButton } from './MaxButton';
+import { ErrorResponse } from '@/facade/error';
+import { setMaxAmount } from '../utils/common';
type AmountProps = {
disabled: boolean;
feeData: TFeeData;
errors: FieldErrors;
+ bankBalance?: string;
+ settlementFee?: string;
+ refreshFee?: (transferAmount: string) => Promise;
register: UseFormRegister;
watch: UseFormWatch;
setValue: UseFormSetValue;
- getGasFee?: GetFeeType;
maxDisabled?: boolean;
txType?: TxType;
balance: string;
};
const AmountErrors = {
+ validateWithdrawStaticBalance: "The payment account doesn't have enough balance to pay settlement fee.",
+ validateWithdrawBankBalance: "The owner account doesn't have enough balance to pay gas fee.",
validateBalance: 'Insufficient balance.',
validateFormat: 'Invalid amount.',
- validateNum: `The maximum precision is ${CRYPTOCURRENCY_DISPLAY_PRECISION} digits.`,
+ validatePrecision: `The maximum precision is ${CRYPTOCURRENCY_DISPLAY_PRECISION} digits.`,
required: 'Amount is required.',
min: 'Please enter a minimum amount of 0.00000001.',
- withdrawError: (
+ validateWithdrawMaxAmountError: (
<>
No withdrawals allowed over 100 {displayTokenSymbol()}.{' '}
{
const bnbPrice = useAppSelector(selectBnbPrice);
const { transType } = useAppSelector((root) => root.wallet);
@@ -90,9 +103,9 @@ export const Amount = ({
const { gasFee, relayerFee } = feeData;
const { isLoading } = useChainsBalance();
const { chain } = useNetwork();
- // const isRight = useMemo(() => {
- // return isRightChain(chain?.id, curInfo?.chainId);
- // }, [chain?.id, curInfo?.chainId]);
+ const isShowMaxButton = useMemo(() => {
+ return isRightChain(chain?.id, curInfo?.chainId);
+ }, [chain?.id, curInfo?.chainId]);
const isSendPage = transType === 'send';
const Balance = useCallback(() => {
@@ -113,17 +126,78 @@ export const Amount = ({
);
}, [balance, bnbPrice, curInfo?.chainName, isLoading]);
- // const onMaxClick = async () => {
- // if (balance && feeData) {
- // getGasFee && (await getGasFee({ amountIn: balance?.formatted, type: 'total_value' }));
- // const availableBalance = BigNumber(balance.formatted)
- // .minus(feeData.gasFee)
- // .minus(feeData.relayerFee)
- // .dp(CRYPTOCURRENCY_DISPLAY_PRECISION, 1);
- // const availableStr = availableBalance.toString(DECIMAL_NUMBER);
- // setValue('amount', availableStr, { shouldValidate: true });
- // }
- // };
+ const onMaxClick = async () => {
+ if (!balance || !feeData) return setValue('amount', '0', { shouldValidate: true });
+ if (txType === 'withdraw_from_payment_account') {
+ const cal = BN(balance).minus(settlementFee || '0').dp(CRYPTOCURRENCY_DISPLAY_PRECISION, 1).toString();
+ const maxAmount = BN(cal).lt(0) ? '0' : cal;
+
+ return setValue('amount', maxAmount, {
+ shouldValidate: true,
+ });
+ }
+
+ if (transType === 'transfer_in' && refreshFee) {
+ const [realTimeFee, error] = await refreshFee(
+ BN(balance).minus(DefaultTransferFee.transfer_in.total).toString(),
+ );
+ realTimeFee && setMaxAmount(balance, realTimeFee, setValue);
+ return;
+ }
+
+ setMaxAmount(balance, feeData, setValue);
+ };
+
+ const validateBalance = (val: string) => {
+ if (txType === 'withdraw_from_payment_account') {
+ return BN(balance).isGreaterThanOrEqualTo(BN(val).plus(settlementFee || '0'));
+ }
+
+ let totalAmount = BigNumber(0);
+ const balanceVal = BigNumber(balance || 0);
+ if (transType === EOperation.send) {
+ totalAmount =
+ gasFee.toString() === '0'
+ ? BigNumber(val).plus(BigNumber(defaultFee))
+ : BigNumber(val).plus(gasFee);
+ } else {
+ totalAmount =
+ gasFee.toString() === '0' && relayerFee.toString() === '0'
+ ? BigNumber(val).plus(BigNumber(defaultFee))
+ : BigNumber(val).plus(gasFee).plus(relayerFee);
+ }
+
+ return balanceVal.isGreaterThanOrEqualTo(totalAmount);
+ };
+
+ const validatePrecision = (val: string) => {
+ const precisionStr = val.split('.')[1];
+ return !precisionStr || precisionStr.length <= CRYPTOCURRENCY_DISPLAY_PRECISION;
+ };
+ const validateWithdrawBankBalance = () => {
+ if (txType !== 'withdraw_from_payment_account') return true;
+ return BN(bankBalance as string).isGreaterThanOrEqualTo(gasFee);
+ };
+ const validateWithdrawMaxAmountError = (val: string) => {
+ if (txType !== 'withdraw_from_payment_account') return true;
+ return BN(val).lt(100);
+ };
+ const validateWithdrawStaticBalance = (val: string) => {
+ if (txType !== 'withdraw_from_payment_account') return true;
+ return BN(balance).isGreaterThanOrEqualTo(BN(settlementFee || '0').plus(val));
+ }
+ const onPaste = (e: any) => {
+ e.stopPropagation();
+ e.preventDefault();
+
+ const clipboardData = e.clipboardData || window.clipboardData;
+ const pastedData = clipboardData.getData('Text');
+ if (/^\d+(\.\d+)?$/.test(pastedData)) {
+ return setValue('amount', pastedData, { shouldValidate: true });
+ }
+ e.preventDefault();
+ };
+
watch('amount');
return (
<>
@@ -137,18 +211,7 @@ export const Amount = ({
>
Amount
- {/* {isRight && (
-
- )} */}
+ {isShowMaxButton && }
@@ -170,50 +233,18 @@ export const Amount = ({
e.preventDefault();
}
}}
- onPaste={(e) => {
- e.stopPropagation();
- e.preventDefault();
-
- const clipboardData = e.clipboardData || window.clipboardData;
- const pastedData = clipboardData.getData('Text');
- if (/^\d+(\.\d+)?$/.test(pastedData)) {
- setValue('amount', pastedData, { shouldValidate: true });
-
- return;
- }
- e.preventDefault();
- }}
+ onPaste={onPaste}
onWheel={(event) => event.currentTarget.blur()}
color={!isEmpty(errors?.amount) ? '#EA412E' : '#1E2026'}
{...register('amount', {
required: true,
min: MIN_AMOUNT,
validate: {
- validateBalance: (val: string) => {
- let totalAmount = BigNumber(0);
- const balanceVal = BigNumber(balance || 0);
- if (transType === EOperation.send) {
- totalAmount =
- gasFee.toString() === '0'
- ? BigNumber(val).plus(BigNumber(defaultFee))
- : BigNumber(val).plus(gasFee);
- } else {
- totalAmount =
- gasFee.toString() === '0' && relayerFee.toString() === '0'
- ? BigNumber(val).plus(BigNumber(defaultFee))
- : BigNumber(val).plus(gasFee).plus(relayerFee);
- }
- return totalAmount.comparedTo(balanceVal) <= 0;
- },
- validateNum: (val: string) => {
- const precisionStr = val.split('.')[1];
-
- return !precisionStr || precisionStr.length <= CRYPTOCURRENCY_DISPLAY_PRECISION;
- },
- withdrawError: (val: string) => {
- if (!txType || txType !== 'withdraw_from_payment_account') return true;
- return BN(val).lt(100);
- },
+ validateWithdrawBankBalance,
+ validateWithdrawStaticBalance,
+ validateBalance,
+ validatePrecision,
+ validateWithdrawMaxAmountError,
},
})}
/>
diff --git a/apps/dcellar-web-ui/src/modules/wallet/components/Fee.tsx b/apps/dcellar-web-ui/src/modules/wallet/components/Fee.tsx
index 3fa77820..6836a7cc 100644
--- a/apps/dcellar-web-ui/src/modules/wallet/components/Fee.tsx
+++ b/apps/dcellar-web-ui/src/modules/wallet/components/Fee.tsx
@@ -6,6 +6,7 @@ import { EOperation, TFeeData } from '../type';
import {
CRYPTOCURRENCY_DISPLAY_PRECISION,
DECIMAL_NUMBER,
+ DefaultTransferFee,
FIAT_CURRENCY_DISPLAY_PRECISION,
INIT_FEE_DATA,
} from '../constants';
@@ -20,19 +21,6 @@ import { renderFee } from '@/utils/common';
import { displayTokenSymbol } from '@/utils/wallet';
import { isEmpty } from 'lodash-es';
-const DefaultFee = {
- // TODO temp down limit fee
- transfer_in: 0.00008 + 0.002,
- transfer_out: 0.000006 + 0.001,
- send: 0.000006,
-};
-const DefaultGasRelayerFee = {
- // TODO temp down limit fee
- transfer_in: { gasFee: 0.00008, relayerFee: 0.002 },
- transfer_out: { gasFee: 0.000006, relayerFee: 0.001 },
- send: { gasFee: 0, relayerFee: 0 },
-};
-
interface FeeProps {
amount: string;
showSettlement?: boolean;
@@ -60,8 +48,7 @@ export const Fee = memo(function Fee({
const { price: exchangeRate } = useAppSelector((root) => root.global.bnb);
const { transType } = useAppSelector((root) => root.wallet);
const { gasFee, relayerFee } = feeData;
- const defaultFee = DefaultFee[transType];
- const defaultGasRelayerFee = DefaultGasRelayerFee[transType];
+ const defaultTransferFee = DefaultTransferFee[transType];
const totalFee = gasFee.plus(relayerFee);
const isShowDefault = gasFee.toString() === '0' && relayerFee.toString() === '0';
const feeUsdPrice = totalFee && totalFee.times(BigNumber(bnbPrice));
@@ -84,7 +71,7 @@ export const Fee = memo(function Fee({
//show defalut fee if cannot get fee data in 3000ms
const defaultFeeUsdPrice = currencyFormatter(
- BigNumber(defaultFee)
+ BigNumber(defaultTransferFee.total)
.times(BigNumber(bnbPrice))
.dp(FIAT_CURRENCY_DISPLAY_PRECISION)
.toString(DECIMAL_NUMBER),
@@ -92,7 +79,7 @@ export const Fee = memo(function Fee({
const TotalFeeContent = useMemo(() => {
let total = totalFee;
if (isShowDefault) {
- total = BigNumber(defaultFee);
+ total = BigNumber(defaultTransferFee.total);
return `~${total
.dp(CRYPTOCURRENCY_DISPLAY_PRECISION, 1)
.toString(DECIMAL_NUMBER)} ${TOKEN_SYMBOL} (${defaultFeeUsdPrice})`;
@@ -100,7 +87,7 @@ export const Fee = memo(function Fee({
return `${totalFee
.dp(CRYPTOCURRENCY_DISPLAY_PRECISION, 1)
.toString(DECIMAL_NUMBER)} ${TOKEN_SYMBOL} (${formatFeeUsdPrice})`;
- }, [TOKEN_SYMBOL, defaultFee, formatFeeUsdPrice, isShowDefault, totalFee, defaultFeeUsdPrice]);
+ }, [TOKEN_SYMBOL, defaultTransferFee, formatFeeUsdPrice, isShowDefault, totalFee, defaultFeeUsdPrice]);
const TotalAmountContent = `${totalAmount} ${TOKEN_SYMBOL} (${formatTotalUsdPrice})`;
const TipContent = useMemo(() => {
@@ -112,7 +99,7 @@ export const Fee = memo(function Fee({
Gas fee:{' '}
{gasFee.toString() === '0'
- ? BigNumber(defaultGasRelayerFee.gasFee)
+ ? BigNumber(defaultTransferFee.gasFee)
.dp(CRYPTOCURRENCY_DISPLAY_PRECISION, 1)
.toString(DECIMAL_NUMBER)
: gasFee.dp(CRYPTOCURRENCY_DISPLAY_PRECISION, 1).toString(DECIMAL_NUMBER)}{' '}
@@ -121,7 +108,7 @@ export const Fee = memo(function Fee({
Relayer fee:{' '}
{gasFee.toString() === '0'
- ? BigNumber(defaultGasRelayerFee.relayerFee)
+ ? BigNumber(defaultTransferFee.relayerFee)
.dp(CRYPTOCURRENCY_DISPLAY_PRECISION, 1)
.toString(DECIMAL_NUMBER)
: relayerFee.dp(CRYPTOCURRENCY_DISPLAY_PRECISION, 1).toString()}{' '}
@@ -136,8 +123,7 @@ export const Fee = memo(function Fee({
}, [
transType,
gasFee,
- defaultGasRelayerFee.gasFee,
- defaultGasRelayerFee.relayerFee,
+ defaultTransferFee,
TOKEN_SYMBOL,
relayerFee,
]);
diff --git a/apps/dcellar-web-ui/src/modules/wallet/components/MaxButton.tsx b/apps/dcellar-web-ui/src/modules/wallet/components/MaxButton.tsx
new file mode 100644
index 00000000..4c73caef
--- /dev/null
+++ b/apps/dcellar-web-ui/src/modules/wallet/components/MaxButton.tsx
@@ -0,0 +1,27 @@
+import { DCButton } from '@/components/common/DCButton';
+import React from 'react';
+
+type MaxButtonProps = {
+ disabled?: boolean;
+ onMaxClick: () => void;
+};
+export const MaxButton = ({ disabled = false, onMaxClick }: MaxButtonProps) => {
+ return (
+
+ Max
+
+ );
+};
diff --git a/apps/dcellar-web-ui/src/modules/wallet/constants.ts b/apps/dcellar-web-ui/src/modules/wallet/constants.ts
index 3015fbca..b49094ca 100644
--- a/apps/dcellar-web-ui/src/modules/wallet/constants.ts
+++ b/apps/dcellar-web-ui/src/modules/wallet/constants.ts
@@ -59,6 +59,24 @@ export const INIT_FEE_DATA = {
relayerFee: BigNumber('0'),
};
+export const DefaultTransferFee = {
+ transfer_in: {
+ total: 0.00208,
+ gasFee: 0.00008,
+ relayerFee: 0.002
+ },
+ transfer_out: {
+ total: 0.001006,
+ gasFee: 0.00006,
+ relayerFee: 0.001,
+ },
+ send: {
+ total: 0.00006,
+ gasFee: 0.00006,
+ relayerFee: 0,
+ },
+};
+
export const CROSS_CHAIN_ABI = [
{
anonymous: false,
diff --git a/apps/dcellar-web-ui/src/modules/wallet/hooks.ts b/apps/dcellar-web-ui/src/modules/wallet/hooks.ts
index f4bf93b8..8919e160 100644
--- a/apps/dcellar-web-ui/src/modules/wallet/hooks.ts
+++ b/apps/dcellar-web-ui/src/modules/wallet/hooks.ts
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { useNetwork, usePublicClient, useWalletClient } from 'wagmi';
import BigNumber from 'bignumber.js';
-import { INIT_FEE_DATA, MIN_AMOUNT, WalletOperationInfos } from './constants';
+import { CROSS_CHAIN_ABI, DefaultTransferFee, MIN_AMOUNT, TOKENHUB_ABI, WalletOperationInfos } from './constants';
import { EOperation, TFeeData } from './type';
import { getRelayFeeBySimulate } from './utils/simulate';
import { isRightChain } from './utils/isRightChain';
@@ -12,6 +12,10 @@ import { genTransferOutTx } from './utils/genTransferOutTx';
import { useAppSelector } from '@/store';
import { getClient } from '@/facade';
import { publicClientToProvider, walletClientToSigner } from './utils/ethers';
+import { BSC_CHAIN_ID } from '@/base/env';
+import { calTransferInFee } from '@/facade/wallet';
+import { useAsyncEffect } from 'ahooks';
+import { ErrorResponse } from '@/facade/error';
export const useGetFeeBasic = () => {
const { transType } = useAppSelector((root) => root.wallet);
@@ -32,7 +36,10 @@ export const useGetFeeBasic = () => {
export const useTransferOutFee = () => {
const { type, isRight, address } = useGetFeeBasic();
- const [feeData, setFeeData] = useState(INIT_FEE_DATA);
+ const [feeData, setFeeData] = useState({
+ gasFee: BigNumber(DefaultTransferFee['transfer_out'].gasFee),
+ relayerFee: BigNumber(DefaultTransferFee['transfer_out'].relayerFee),
+ });
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
@@ -55,9 +62,9 @@ export const useTransferOutFee = () => {
const relayFee = relayFeeInfo.params
? getRelayFeeBySimulate(
- relayFeeInfo.params.bscTransferOutAckRelayerFee,
- relayFeeInfo.params.bscTransferOutRelayerFee,
- )
+ relayFeeInfo.params.bscTransferOutAckRelayerFee,
+ relayFeeInfo.params.bscTransferOutRelayerFee,
+ )
: '0';
const newData = {
@@ -86,7 +93,10 @@ export const useTransferOutFee = () => {
export const useSendFee = () => {
const { type, address, isRight } = useGetFeeBasic();
- const [feeData, setFeeData] = useState(INIT_FEE_DATA);
+ const [feeData, setFeeData] = useState({
+ gasFee: BigNumber(DefaultTransferFee['send'].gasFee),
+ relayerFee: BigNumber(DefaultTransferFee['send'].relayerFee),
+ });
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
@@ -110,7 +120,7 @@ export const useSendFee = () => {
denom: 'BNB',
});
setFeeData({
- ...INIT_FEE_DATA,
+ relayerFee: BigNumber(DefaultTransferFee['send'].relayerFee),
gasFee: BigNumber(simulateTxInfo.gasFee),
});
@@ -145,3 +155,67 @@ export function useEthersSigner({ chainId }: { chainId?: number } = {}) {
const { data: walletClient } = useWalletClient({ chainId });
return useMemo(() => (walletClient ? walletClientToSigner(walletClient) : undefined), [walletClient]);
}
+
+export const useTransferInFee = () => {
+ const [feeData, setFeeData] = useState({
+ gasFee: BigNumber(DefaultTransferFee['transfer_in'].gasFee),
+ relayerFee: BigNumber(DefaultTransferFee['transfer_in'].relayerFee),
+ });
+ const { loginAccount } = useAppSelector((root) => root.persist);
+ const [isGasLoading, setIsGasLoading] = useState(false);
+ const {
+ TOKEN_HUB_CONTRACT_ADDRESS: APOLLO_TOKEN_HUB_CONTRACT_ADDRESS,
+ CROSS_CHAIN_CONTRACT_ADDRESS: APOLLO_CROSS_CHAIN_CONTRACT_ADDRESS,
+ } = useAppSelector((root) => root.apollo);
+ const provider = useEthersProvider({ chainId: BSC_CHAIN_ID });
+ const signer = useEthersSigner({ chainId: BSC_CHAIN_ID });
+ const getFee = useCallback(
+ async (transferAmount: string): Promise => {
+ if (!signer || !provider) return [null, 'no signer or provider'];
+ setIsGasLoading(true);
+ const params = {
+ amount: transferAmount,
+ address: loginAccount,
+ crossChainContractAddress: APOLLO_CROSS_CHAIN_CONTRACT_ADDRESS,
+ tokenHubContract: APOLLO_TOKEN_HUB_CONTRACT_ADDRESS,
+ crossChainAbi: CROSS_CHAIN_ABI,
+ tokenHubAbi: TOKENHUB_ABI,
+ };
+ const [data, error] = await calTransferInFee(
+ params,
+ signer,
+ provider,
+ );
+ setIsGasLoading(false);
+ if (!data) {
+ return [null, error]
+ }
+
+ setFeeData(data);
+ return [data, error];
+ },
+ [
+ APOLLO_CROSS_CHAIN_CONTRACT_ADDRESS,
+ APOLLO_TOKEN_HUB_CONTRACT_ADDRESS,
+ loginAccount,
+ provider,
+ signer,
+ ],
+ );
+
+ useAsyncEffect(async () => {
+ await getFee(MIN_AMOUNT)
+ }, [getFee])
+
+ return {
+ feeData,
+ isLoading: isGasLoading,
+ crossChainContract: APOLLO_CROSS_CHAIN_CONTRACT_ADDRESS,
+ signer,
+ tokenHubContract: APOLLO_TOKEN_HUB_CONTRACT_ADDRESS,
+ crossChainAbi: CROSS_CHAIN_ABI,
+ tokenHubAbi: TOKENHUB_ABI,
+ loginAccount,
+ getFee,
+ }
+}
\ No newline at end of file
diff --git a/apps/dcellar-web-ui/src/modules/wallet/index.tsx b/apps/dcellar-web-ui/src/modules/wallet/index.tsx
index 7663b9c4..0c2c05c1 100644
--- a/apps/dcellar-web-ui/src/modules/wallet/index.tsx
+++ b/apps/dcellar-web-ui/src/modules/wallet/index.tsx
@@ -13,6 +13,7 @@ import styled from '@emotion/styled';
interface WalletProps {}
+// TODO: Refactor
export const Wallet = memo(function Wallet() {
const { transType } = useAppSelector((root) => root.wallet);
const router = useRouter();
diff --git a/apps/dcellar-web-ui/src/modules/wallet/type.ts b/apps/dcellar-web-ui/src/modules/wallet/type.ts
index 054b48e5..caa5110b 100644
--- a/apps/dcellar-web-ui/src/modules/wallet/type.ts
+++ b/apps/dcellar-web-ui/src/modules/wallet/type.ts
@@ -2,7 +2,6 @@ import BigNumber from 'bignumber.js';
export type TOperation = 'send' | 'transfer_in' | 'transfer_out';
-export type TCalculateGas = 'content_value' | 'total_value';
export enum EOperation {
'send' = 'send',
@@ -31,12 +30,6 @@ export type TSendFromValues = TAmountFieldValue & TAddressFieldValue;
export type TWalletFromValues = TTransferInFromValues | TTransferOutFromValues | TSendFromValues;
-export type GetFeeType = ({
- amountIn,
- type,
-}: {
- amountIn: string;
- type?: TCalculateGas | undefined;
-}) => Promise;
+export type GetFeeType = (amount: string) => Promise;
export type TNormalObject = { [key: string]: string };
diff --git a/apps/dcellar-web-ui/src/modules/wallet/utils/common.ts b/apps/dcellar-web-ui/src/modules/wallet/utils/common.ts
new file mode 100644
index 00000000..a5c178d3
--- /dev/null
+++ b/apps/dcellar-web-ui/src/modules/wallet/utils/common.ts
@@ -0,0 +1,15 @@
+import { BN } from '@/utils/math';
+import { TFeeData, TWalletFromValues } from '../type';
+import { CRYPTOCURRENCY_DISPLAY_PRECISION } from '../constants';
+import { UseFormSetValue } from 'react-hook-form';
+
+export const setMaxAmount = (balance: string, feeData: TFeeData, setValue: UseFormSetValue) => {
+ const availableBalance = BN(balance)
+ .minus(feeData.gasFee)
+ .minus(feeData.relayerFee)
+ .dp(CRYPTOCURRENCY_DISPLAY_PRECISION, 1)
+ .toNumber();
+
+ const availableStr = availableBalance < 0 ? '0' : availableBalance.toString();
+ setValue('amount', availableStr, { shouldValidate: true });
+}
\ No newline at end of file
diff --git a/apps/dcellar-web-ui/src/pages/_document.tsx b/apps/dcellar-web-ui/src/pages/_document.tsx
index 07fd2648..6bc9ef0c 100644
--- a/apps/dcellar-web-ui/src/pages/_document.tsx
+++ b/apps/dcellar-web-ui/src/pages/_document.tsx
@@ -25,7 +25,7 @@ export default function Document() {
__html: `window.__ASSET_PREFIX = ${JSON.stringify(assetPrefix)}`,
}}
>
-
+
diff --git a/apps/dcellar-web-ui/src/pages/toolbox/index.tsx b/apps/dcellar-web-ui/src/pages/toolbox/index.tsx
new file mode 100644
index 00000000..079e6979
--- /dev/null
+++ b/apps/dcellar-web-ui/src/pages/toolbox/index.tsx
@@ -0,0 +1,5 @@
+import { ToolBoxPage } from '@/modules/toolbox/page';
+
+export const ToolBox = () => ;
+
+export default ToolBox;
\ No newline at end of file