diff --git a/.env b/.env index 5646d575d..1c5ff054d 100644 --- a/.env +++ b/.env @@ -5,7 +5,7 @@ VITE_QUERY_SWAP_EXCHANGE_PROJECT=https://api.subquery.network/sq/subquery/kepler VITE_TOP_100_INDEXERS=https://leaderboard-api.subquery.network/graphql VITE_CONSUMER_HOST_ENDPOINT=https://kepler-chs.subquery.network VITE_FORUM_DOMAIN=https://forum.subquery.network -VITE_TOKEN=kSQT +VITE_TOKEN=SQT VITE_STABLE_TOKEN=USDC.e VITE_STABLE_TOKEN_ADDRESS=0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 VITE_FLEXPLAN_ENABLED=true diff --git a/.env.staging b/.env.staging index 7efa37c3f..d36f722cb 100644 --- a/.env.staging +++ b/.env.staging @@ -5,7 +5,7 @@ VITE_QUERY_SWAP_EXCHANGE_PROJECT=https://api.subquery.network/sq/subquery/kepler VITE_TOP_100_INDEXERS=https://leaderboard-api.subquery.network/graphql VITE_CONSUMER_HOST_ENDPOINT=https://kepler-chs.subquery.network VITE_FORUM_DOMAIN=https://forum.subquery.network -VITE_TOKEN=kSQT +VITE_TOKEN=SQT VITE_STABLE_TOKEN=USDC.e VITE_STABLE_TOKEN_ADDRESS=0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 VITE_FLEXPLAN_ENABLED=true diff --git a/.env.testnet b/.env.testnet index ee34db3f5..cf17d2094 100644 --- a/.env.testnet +++ b/.env.testnet @@ -3,7 +3,7 @@ VITE_QUERY_REGISTRY_PROJECT=https://api.subquery.network/sq/subquery/kepler-test VITE_SUBSCRIPTION_REGISTRY_PROJECT=wss://api.subquery.network/sq/subquery/kepler-testnet VITE_TOP_100_INDEXERS=https://leaderboard-api.thechaindata.com/graphql VITE_CONSUMER_HOST_ENDPOINT=https://dev-chs.thechaindata.com -VITE_TOKEN=kSQT +VITE_TOKEN=SQT VITE_STABLE_TOKEN=USDC.e VITE_STABLE_TOKEN_ADDRESS=0x7E65A71046170A5b1AaB5C5cC64242EDF95CaBEA VITE_NETWORK=testnet diff --git a/package.json b/package.json index 5b52d0924..006507629 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@rainbow-me/rainbowkit": "^1.2.0", "@sentry/react": "^7.57.0", "@subql/apollo-links": "^1.2.3", - "@subql/components": "1.0.3-22", + "@subql/components": "1.0.3-25", "@subql/contract-sdk": "0.112.0", "@subql/network-clients": "^0.112.1-0", "@subql/network-config": "^0.112.0", diff --git a/src/components/AccountActions/AccountActions.module.css b/src/components/AccountActions/AccountActions.module.css deleted file mode 100644 index d63845810..000000000 --- a/src/components/AccountActions/AccountActions.module.css +++ /dev/null @@ -1,9 +0,0 @@ -.downIcon { - margin-left: 3px; -} - -.address { - padding: 12px; - border: 1px solid var(--gradient-to); - border-radius: 36px; -} diff --git a/src/components/AccountActions/AccountActions.module.less b/src/components/AccountActions/AccountActions.module.less new file mode 100644 index 000000000..cb352c936 --- /dev/null +++ b/src/components/AccountActions/AccountActions.module.less @@ -0,0 +1,43 @@ +.downIcon { + margin-left: 3px; +} + +.address { + padding: 12px; + border: 1px solid var(--gradient-to); + border-radius: 36px; + display: flex; + align-items: center; + cursor: pointer; +} + +.accountActionDropdown { + width: 380px; + + :global { + .ant-dropdown-menu { + padding: 16px 0; + border-radius: 8px; + box-shadow: 0px 9px 28px 8px rgba(0, 0, 0, 0.05), 0px 6px 16px 0px rgba(0, 0, 0, 0.08), 0px 3px 6px -4px rgba(0, 0, 0, 0.12); + + &-item { + padding: 0!important; + + &:hover { + background-color: transparent!important; + } + } + } + } + + .dropdownItemInner { + padding: 8px 16px; + width: 100%; + display: flex; + align-items: center; + &:hover { + background: rgba(67, 136, 221, 0.10); + } + } +} + diff --git a/src/components/AccountActions/AccountActions.tsx b/src/components/AccountActions/AccountActions.tsx index 6aa9f7b84..31a40ea5f 100644 --- a/src/components/AccountActions/AccountActions.tsx +++ b/src/components/AccountActions/AccountActions.tsx @@ -4,22 +4,25 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { AiOutlineDown } from 'react-icons/ai'; -import { BsBoxArrowLeft } from 'react-icons/bs'; +import { BsBoxArrowInUpRight, BsBoxArrowLeft } from 'react-icons/bs'; import { useNavigate } from 'react-router'; +import { InfoCircleOutlined } from '@ant-design/icons'; import { SQT_TOKEN_ADDRESS } from '@containers/Web3'; -import { Address } from '@subql/components'; +import { Address, Typography } from '@subql/components'; +import { Button, Dropdown, Tooltip } from 'antd'; import { useDisconnect, useWalletClient } from 'wagmi'; +import { BRIDGE_URL } from 'src/const/bridge'; + import { useSQToken } from '../../containers'; import { formatEther, ROUTES, STABLE_TOKEN, STABLE_TOKEN_ADDRESS, TOKEN, tokenDecimals } from '../../utils'; -import { Dropdown } from '../Dropdown'; -import styles from './AccountActions.module.css'; +import styles from './AccountActions.module.less'; export const AccountActions: React.FC<{ account: string }> = ({ account }) => { const { t } = useTranslation(); const { disconnect } = useDisconnect(); const navigate = useNavigate(); - const { balance } = useSQToken(); + const { balance, ethSqtBalance, consumerHostBalance } = useSQToken(); const { data: walletClient } = useWalletClient(); const handleDisconnect = () => disconnect(); @@ -55,24 +58,158 @@ export const AccountActions: React.FC<{ account: string }> = ({ account }) => { }; const menu = [ - { key: 'walletBalance', label: t('header.walletBalance', { balance: formatEther(balance.data, 4) }) }, - { key: 'addToken', label: t('header.importToken'), onClick: handleAddToken }, - { key: 'addStableToken', label: t('header.importStableToken'), onClick: handleAddStableToken }, - { key: 'myProfile', label: t('indexer.myProfile'), onClick: handleNavAccount }, - { key: 'rewards', label: t('indexer.rewards'), onClick: handleNavRewards }, - { key: 'withdrawn', label: t('indexer.withdrawn'), onClick: handleNavWithdrawn }, - { key: 'disconnect', label: t('header.disconnect'), icon: , onClick: handleDisconnect }, + { + key: 'header', + label: ( +
+
+
+ ), + }, + { + key: 'walletBalance', + label: ( +
+
+ + Wallet balance on Polygon + + + {formatEther(balance.data, 4)} {TOKEN} + +
+
+ + Wallet balance on Ethereum + + SubQuery launched on Polygon network, To move assets from the Ethereum network to Polygon network, + you'll need to connect to a bridge and deposit funds. + + } + overlayInnerStyle={{ + padding: 16, + }} + > + + + + + {formatEther(ethSqtBalance.data, 4)} {TOKEN} + +
+
+ ), + }, + { + key: 'bridge tokens', + label: ( +
+ + + +
+ ), + }, + { + key: 'billingBalance', + label: ( +
+
+ Billing Balance: + + {formatEther(consumerHostBalance.data?.balance, 4)} {TOKEN} + +
+
+ ), + }, + { + key: 'addTokens', + label: ( +
+
+ {t('header.importToken')} +
+
+ {t('header.importStableToken')} +
+
+ ), + }, + { + key: 'myProfile', + label: ( +
+
+ {t('indexer.myProfile')} +
+
+ {t('indexer.rewards')} +
+
+ {t('indexer.withdrawn')} +
+
+ ), + }, + { + key: 'disconnect', + label: ( +
+
+ {t('header.disconnect')} +
+
+ ), + }, ]; return ( -
- - - } - /> + overlayClassName={styles.accountActionDropdown} + menu={{ items: menu }} + placement="bottom" + arrow={{ pointAtCenter: true }} + > +
+
+ +
+ ); }; diff --git a/src/components/ConnectWallet/ChainStatus.tsx b/src/components/ConnectWallet/ChainStatus.tsx index 09053981a..63dbc2f46 100644 --- a/src/components/ConnectWallet/ChainStatus.tsx +++ b/src/components/ConnectWallet/ChainStatus.tsx @@ -10,12 +10,14 @@ import { useNetwork, useSwitchNetwork } from 'wagmi'; import { ECOSYSTEM_NETWORK } from '../../containers/Web3'; import styles from './ChainStatus.module.css'; +const tipsChainIds = import.meta.env.MODE === 'testnet' ? [80001] : [137]; + export const ChainStatus: React.FC = ({ children }) => { const { chain } = useNetwork(); const { chains, switchNetwork } = useSwitchNetwork(); const { t } = useTranslation(); - if (chain?.unsupported) { + if (!tipsChainIds.includes(chain?.id || 0)) { return (
diff --git a/src/components/IndexerDetails/PlansTable.tsx b/src/components/IndexerDetails/PlansTable.tsx index 072c5eb0d..9dffcd446 100644 --- a/src/components/IndexerDetails/PlansTable.tsx +++ b/src/components/IndexerDetails/PlansTable.tsx @@ -4,14 +4,15 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { LazyQueryResult } from '@apollo/client'; +import TokenTooltip from '@components/TokenTooltip/TokenTooltip'; import { NETWORK_NAME } from '@containers/Web3'; import { BigNumber } from '@ethersproject/bignumber'; import { ContractTransaction } from '@ethersproject/contracts'; -import { Button, Spinner, Typography } from '@subql/components'; +import { Spinner, Typography } from '@subql/components'; import { PlansNodeFieldsFragment as Plan } from '@subql/network-query'; import { PlanTemplateFieldsFragment as PlanTemplate } from '@subql/network-query'; import { useStableCoin } from '@subql/react-hooks'; -import { Table, TableProps } from 'antd'; +import { Button, Table, TableProps } from 'antd'; import { last } from 'ramda'; import { useWeb3Store } from 'src/stores'; @@ -28,7 +29,7 @@ import { IndexerName } from './IndexerName'; export type PlansTableProps = { loadPlans: () => void; - asyncPlans: LazyQueryResult; + asyncPlans: LazyQueryResult; } & Omit; type DoPurchaseProps = { @@ -88,18 +89,22 @@ const DoPurchase: React.FC = ({ }, { label: t('plans.headers.dailyReqCap'), - value: plan.planTemplate?.dailyReqCap, + value: (plan.planTemplate?.dailyReqCap || 0).toString(), }, { label: t('plans.headers.rateLimit'), - value: plan.planTemplate?.rateLimit, + value: (plan.planTemplate?.rateLimit || 0).toString(), }, { label: t('plans.headers.deploymentId'), value: deploymentId, }, { - label: t('plans.purchase.yourBalance'), + label: ( +
+ {t('plans.purchase.yourBalance')} +
+ ), value: renderAsync(balance, { loading: () => , error: () => {t('plans.purchase.failToLoadBalance')}, @@ -141,21 +146,27 @@ const DoPurchase: React.FC = ({ {error}
); diff --git a/src/components/IndexerDetails/Progress.tsx b/src/components/IndexerDetails/Progress.tsx index a3528dfba..99238581c 100644 --- a/src/components/IndexerDetails/Progress.tsx +++ b/src/components/IndexerDetails/Progress.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { ProgressBar, Typography } from '@subql/components'; +import { SubqlProgress, Typography } from '@subql/components'; import { indexingProgress } from '@subql/network-clients'; import { strip } from '@utils'; @@ -33,7 +33,7 @@ const Progress: React.FC<{ startBlock?: number; currentBlock: number; targetBloc return (
- + {blocksBehind > 0 && (
diff --git a/src/components/IndexerProgress/IndexerProgress.tsx b/src/components/IndexerProgress/IndexerProgress.tsx index 42766c616..a21b0f1b9 100644 --- a/src/components/IndexerProgress/IndexerProgress.tsx +++ b/src/components/IndexerProgress/IndexerProgress.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import * as React from 'react'; -import { ProgressBar } from '@subql/components'; +import { SubqlProgress } from '@subql/components'; import styles from './IndexerProgress.module.css'; @@ -40,7 +40,7 @@ const IndexerProgress: React.FC = ({ indexerStatus, chainBlockHeight, sta return (
{status} - +
); }; diff --git a/src/components/Modal/Modal.module.css b/src/components/Modal/Modal.module.css index 5d0380d4f..4f1d9b3b0 100644 --- a/src/components/Modal/Modal.module.css +++ b/src/components/Modal/Modal.module.css @@ -7,7 +7,6 @@ } .description { - margin: 1rem 0; font-size: 16px; } diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx index c12d34010..f4fa087db 100644 --- a/src/components/Modal/Modal.tsx +++ b/src/components/Modal/Modal.tsx @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import * as React from 'react'; -import { Modal as SubqlModal } from '@subql/components'; -import { Steps as AntDSteps, Typography } from 'antd'; +import { Modal as SubqlModal, Typography } from '@subql/components'; +import { Steps as AntDSteps } from 'antd'; import clsx from 'clsx'; import Spinner from '../Spinner'; @@ -43,15 +43,15 @@ export const Modal: React.FC = ({ width = '45%', className = '', }) => { - const Title = () => ( - - {title || 'Modal'} - - ); + const Title = () => {title || 'Modal'}; const Description = () => { if (!description) return
; - return {description}; + return ( + + {description} + + ); }; return ( diff --git a/src/components/ModalApproveToken/ModalApproveToken.module.css b/src/components/ModalApproveToken/ModalApproveToken.module.css index bb72d9b7d..7145233ec 100644 --- a/src/components/ModalApproveToken/ModalApproveToken.module.css +++ b/src/components/ModalApproveToken/ModalApproveToken.module.css @@ -6,7 +6,7 @@ } .submitBtn { - background: var(--gradient-to) !important; + background: var(--sq-blue600) !important; color: white !important; min-width: 30%; } diff --git a/src/components/NumberInput/NumberInput.tsx b/src/components/NumberInput/NumberInput.tsx index a37a125ac..8a7695fdf 100644 --- a/src/components/NumberInput/NumberInput.tsx +++ b/src/components/NumberInput/NumberInput.tsx @@ -3,7 +3,9 @@ import * as React from 'react'; import { useMemo } from 'react'; +import TokenTooltip from '@components/TokenTooltip/TokenTooltip'; import { Typography } from '@subql/components'; +import { TOKEN } from '@utils'; import { Button, InputNumber, InputNumberProps } from 'antd'; import BigNumber from 'bignumber.js'; import { BigNumberish } from 'ethers'; @@ -21,13 +23,13 @@ interface NumberInputProps extends InputNumberProps { errorMsg?: string; onClickMax?: (amount: number | BigNumberish) => void; maxAmount?: number | BigNumberish; - maxAmountText?: string; + maxAmountText?: React.ReactNode; } export const NumberInput: React.FC = ({ title, tooltip, - unit = 'kSQT', + unit = TOKEN, maxAmountText, maxAmount = 0, onClickMax, @@ -37,8 +39,9 @@ export const NumberInput: React.FC = ({ inputParams, // TODO: 1) avoid to use this one in future. Refactor existing one. ...inputNumberProps }) => { - const Suffix = React.useCallback( - () => ( + // this component would re-render every times + const Suffix = React.useCallback(() => { + return (
{BigNumber(maxAmount.toString()).gt(0) && ( )} {unit && ( - + {unit} )}
- ), - [maxAmount, inputParams, unit], - ); - const maxText = useMemo( - () => - BigNumber(maxAmount.toString()).gt(0) - ? `Current ${unit === '%' ? 'rate' : 'balance'}: ${maxAmount ?? ''} ${unit ?? ''}` - : undefined, - [maxAmount, unit], - ); + ); + }, [maxAmount, inputParams, unit]); + + const maxText = useMemo(() => { + if (BigNumber(maxAmount.toString()).gt(0)) { + const text = `Current ${unit === '%' ? 'rate' : 'balance'}: ${maxAmount ?? ''} ${unit ?? ''}`; + if (unit !== '%') { + return ( + <> + {text} + + ); + } + return text; + } + return ''; + }, [maxAmount, unit]); const inputBottomText = useMemo(() => description ?? maxAmountText ?? maxText, [description, maxAmountText]); const DescriptionText = ({ text }: { text: string }) => ( diff --git a/src/components/ProjectCard/ProjectCard.tsx b/src/components/ProjectCard/ProjectCard.tsx index 8308939a2..52c96000e 100644 --- a/src/components/ProjectCard/ProjectCard.tsx +++ b/src/components/ProjectCard/ProjectCard.tsx @@ -5,15 +5,12 @@ import * as React from 'react'; import { Address, Typography } from '@subql/components'; import { ProjectFieldsFragment } from '@subql/network-query'; import dayjs from 'dayjs'; -import relativeTime from 'dayjs/plugin/relativeTime'; import { ProjectMetadata } from 'src/models'; import IPFSImage from '../IPFSImage'; import styles from './ProjectCard.module.css'; -dayjs.extend(relativeTime); - type Props = { project: { metadata: ProjectMetadata | undefined } & Omit; onClick?: () => void; diff --git a/src/components/ProjectDeployments/ProjectDeployments.tsx b/src/components/ProjectDeployments/ProjectDeployments.tsx index df71e76f4..3bbee9e31 100644 --- a/src/components/ProjectDeployments/ProjectDeployments.tsx +++ b/src/components/ProjectDeployments/ProjectDeployments.tsx @@ -9,15 +9,12 @@ import { parseError } from '@utils'; import { Form, Radio } from 'antd'; import { useForm } from 'antd/es/form/Form'; import dayjs from 'dayjs'; -import utc from 'dayjs/plugin/utc'; import { NewDeployment } from '../../models'; import { Table, TableBody, TableCell, TableHead, TableRow } from '../Table'; import { Copy } from '..'; import styles from './ProjectDeployments.module.less'; -dayjs.extend(utc); - type Deployment = NewDeployment & { createdAt?: Date }; type Props = { diff --git a/src/components/SummaryList/SummaryList.tsx b/src/components/SummaryList/SummaryList.tsx index 5bb068423..6d8b0438a 100644 --- a/src/components/SummaryList/SummaryList.tsx +++ b/src/components/SummaryList/SummaryList.tsx @@ -10,8 +10,8 @@ import { clsx } from 'clsx'; import styles from './SummaryList.module.css'; interface List { - label: string; - value: string | React.ReactNode | any; + label: React.ReactNode; + value: React.ReactNode; strong?: boolean; tooltip?: string; } @@ -26,8 +26,8 @@ export const SummaryList: React.FC = ({ title, list }) => {
{title && {title}}
- {list.map((listItem) => ( -
+ {list.map((listItem, index) => ( +
= (props) => { + const { ethSqtBalance } = useSQToken(); + return ( + +
+ + You have {TOKEN} on Ethereum +
+ + Eth Wallet Balance: {formatEther(ethSqtBalance.data, 4)} {TOKEN} + + + Your SQT needs to be on Polygon in order to be used on the SubQuery Network. To move SQT from the Ethereum + to Polygon, you’ll need to bridge them across. + + + + + +
+ } + > + {props.children ? props.children : } + + ); +}; +export default TokenTooltip; diff --git a/src/components/TokenTooltip/index.module.less b/src/components/TokenTooltip/index.module.less new file mode 100644 index 000000000..99f77ede6 --- /dev/null +++ b/src/components/TokenTooltip/index.module.less @@ -0,0 +1,9 @@ +.tokenTooltip { + + :global { + .ant-tooltip-inner { + padding: 16px; + width: 305px; + } + } +} \ No newline at end of file diff --git a/src/config/dayjsConf.ts b/src/config/dayjsConf.ts index 6e1f611d3..32240fcee 100644 --- a/src/config/dayjsConf.ts +++ b/src/config/dayjsConf.ts @@ -5,13 +5,17 @@ import dayjs from 'dayjs'; import advancedFormat from 'dayjs/plugin/advancedFormat'; import customParseFormat from 'dayjs/plugin/customParseFormat'; import localeData from 'dayjs/plugin/localeData'; +import relativeTime from 'dayjs/plugin/relativeTime'; +import utc from 'dayjs/plugin/utc'; import weekday from 'dayjs/plugin/weekday'; import weekOfYear from 'dayjs/plugin/weekOfYear'; import weekYear from 'dayjs/plugin/weekYear'; +dayjs.extend(utc); dayjs.extend(customParseFormat); dayjs.extend(advancedFormat); dayjs.extend(weekday); dayjs.extend(localeData); dayjs.extend(weekOfYear); dayjs.extend(weekYear); +dayjs.extend(relativeTime); diff --git a/src/config/rainbowConf.tsx b/src/config/rainbowConf.tsx index efaad1eb2..e0ebeb1f1 100644 --- a/src/config/rainbowConf.tsx +++ b/src/config/rainbowConf.tsx @@ -4,12 +4,14 @@ import React from 'react'; import { connectorsForWallets, RainbowKitProvider } from '@rainbow-me/rainbowkit'; import { metaMaskWallet, rainbowWallet, talismanWallet, walletConnectWallet } from '@rainbow-me/rainbowkit/wallets'; -import { configureChains, createConfig, WagmiConfig } from 'wagmi'; -import { polygon, polygonMumbai } from 'wagmi/chains'; +import { configureChains, createConfig, mainnet, WagmiConfig } from 'wagmi'; +import { goerli, polygon, polygonMumbai } from 'wagmi/chains'; import { publicProvider } from 'wagmi/providers/public'; import '@rainbow-me/rainbowkit/styles.css'; -const supportedChains = import.meta.env.VITE_NETWORK === 'testnet' ? [polygonMumbai] : [polygon]; + +// goerli and mainnet just for get data actually not supported +const supportedChains = import.meta.env.VITE_NETWORK === 'testnet' ? [polygonMumbai, goerli] : [polygon, mainnet]; // This should ok. It seems is a bug of Ts. // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/src/pages/swap/index.ts b/src/const/bridge.ts similarity index 61% rename from src/pages/swap/index.ts rename to src/const/bridge.ts index 23be6f9aa..a39f52ba3 100644 --- a/src/pages/swap/index.ts +++ b/src/const/bridge.ts @@ -1,4 +1,4 @@ // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors // SPDX-License-Identifier: Apache-2.0 -export * from './Swap'; +export const BRIDGE_URL = 'https://portal.polygon.technology/'; diff --git a/src/containers/ProjectMetadata.ts b/src/containers/ProjectMetadata.ts index 2e2dbda4e..f7c860f2a 100644 --- a/src/containers/ProjectMetadata.ts +++ b/src/containers/ProjectMetadata.ts @@ -18,8 +18,8 @@ function useProjectMetadataImpl() { const result = await catSingle(cid); const rawMeta = JSON.parse(Buffer.from(result).toString('utf8')); - console.warn(rawMeta); - return projectMetadataSchema.validate({ type: 'SUBQUERY', ...rawMeta }); + + return projectMetadataSchema.validate({ ...rawMeta }); }; const uploadMetadata = async (meta: ProjectMetadata): Promise => { diff --git a/src/containers/ProjectRegistry.ts b/src/containers/ProjectRegistry.ts index c835355ac..1ca5afebf 100644 --- a/src/containers/ProjectRegistry.ts +++ b/src/containers/ProjectRegistry.ts @@ -12,23 +12,13 @@ import { useWeb3Store } from 'src/stores'; import { bytes32ToCid } from '../utils'; import { createContainer, Logger } from './Container'; -function projectTypeTransform(type: InputProjectType): ProjectType { - switch (type) { - case InputProjectType.SUBQUERY: - return ProjectType.SUBQUERY; - case InputProjectType.RPC: - return ProjectType.RPC; - default: - return ProjectType.SUBQUERY; - } -} - type QueryDetails = { queryId: BigNumber; owner: string; metadata: string; // IPFS Cid deployment: string; // IPFS Cid version: string; // IPFS Cid + type: ProjectType; }; function useProjectRegistryImpl(logger: Logger) { @@ -51,7 +41,7 @@ function useProjectRegistryImpl(logger: Logger) { metadataCid, cidToBytes32(deploymentMetadata), cidToBytes32(deploymentId), - projectTypeTransform(type), + type, ); }; @@ -134,36 +124,16 @@ function useProjectRegistryImpl(logger: Logger) { metadata: uri.replace(/^ipfs:\/\//, ''), deployment: bytes32ToCid(project.latestDeploymentId), version: bytes32ToCid(deploymentInfo.metadata), + type: project.projectType, }; } return projectCache.current[BigNumber.from(id).toString()]; }; - // const getUserQueries = React.useCallback( - // async (address: string): Promise => { - // if (!pendingContracts) { - // throw new Error('ProjectRegistry contract not available'); - // // return []; - // } - - // const contracts = await pendingContracts; - - // const count = await contracts.projectRegistry.queryInfoCountByOwner(address); - - // return await Promise.all( - // Array.from(new Array(count.toNumber()).keys()).map((_, index) => - // contracts.projectRegistry.queryInfoIdsByOwner(address, index), - // ), - // ); - // }, - // [pendingContracts], - // ); - return { registerProject, getQuery, - // getUserQueries, updateQueryMetadata, updateDeployment, }; diff --git a/src/containers/SQToken.ts b/src/containers/SQToken.ts index dbbb2b591..126aed9bd 100644 --- a/src/containers/SQToken.ts +++ b/src/containers/SQToken.ts @@ -12,7 +12,7 @@ import { createContainer } from './Container'; function useSQTokenImpl() { const { address: account } = useAccount(); - const { contracts } = useWeb3Store(); + const { contracts, rootContracts } = useWeb3Store(); const balance = useAsyncMemo(async () => { assert(contracts, 'Contracts not available'); assert(account, 'Account not available'); @@ -20,6 +20,13 @@ function useSQTokenImpl() { return limitContract(() => contracts.sqToken.balanceOf(account)); }, [account, contracts]); + const ethSqtBalance = useAsyncMemo(async () => { + assert(rootContracts, 'Contracts not available'); + assert(account, 'Account not available'); + + return limitContract(() => rootContracts.sqToken.balanceOf(account)); + }, [account, rootContracts]); + const consumerHostBalance = useAsyncMemo(async () => { assert(contracts, 'Contracts not available'); assert(account, 'Account not available'); @@ -64,6 +71,7 @@ function useSQTokenImpl() { return { balance, + ethSqtBalance, consumerHostBalance, stakingAllowance, planAllowance, diff --git a/src/containers/Web3.tsx b/src/containers/Web3.tsx index a30632024..aa0780d2f 100644 --- a/src/containers/Web3.tsx +++ b/src/containers/Web3.tsx @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 import React from 'react'; -import { SubqueryNetwork } from '@subql/contract-sdk'; import mainnetJSON from '@subql/contract-sdk/publish/mainnet.json'; import testnetJSON from '@subql/contract-sdk/publish/testnet.json'; import { NETWORKS_CONFIG_INFO, SQNetworks } from '@subql/network-config'; diff --git a/src/hooks/useCreateProject.tsx b/src/hooks/useCreateProject.tsx index ec3d50ef7..6cf6aa612 100644 --- a/src/hooks/useCreateProject.tsx +++ b/src/hooks/useCreateProject.tsx @@ -3,12 +3,11 @@ import * as React from 'react'; import { BigNumberish } from '@ethersproject/bignumber'; -import { ProjectType } from '@subql/network-query'; import { useIPFS, useProjectMetadata, useProjectRegistry } from '../containers'; -import { FormCreateProjectMetadata, ProjectMetadata } from '../models'; +import { FormCreateProjectMetadata, ProjectMetadata, ProjectType } from '../models'; -type P = FormCreateProjectMetadata & { versionDescription: string }; +type P = FormCreateProjectMetadata & { versionDescription: string; type: ProjectType }; export function useCreateProject(): (params: P) => Promise { const { uploadMetadata, uploadVersionMetadata } = useProjectMetadata(); diff --git a/src/hooks/useInitContracts.ts b/src/hooks/useInitContracts.ts index 9dc78d6bb..017b3c6de 100644 --- a/src/hooks/useInitContracts.ts +++ b/src/hooks/useInitContracts.ts @@ -3,9 +3,12 @@ import React from 'react'; import { NETWORK_NAME } from '@containers/Web3'; -import { ContractSDK } from '@subql/contract-sdk'; +import { ContractSDK, SQToken__factory } from '@subql/contract-sdk'; +import mainnetJSON from '@subql/contract-sdk/publish/mainnet.json'; +import testnetJSON from '@subql/contract-sdk/publish/testnet.json'; import { ContractClient } from '@subql/network-clients'; import { parseError } from '@utils'; +import { goerli, mainnet } from 'viem/chains'; import { useWeb3Store } from 'src/stores'; @@ -13,9 +16,12 @@ import { useEthersProviderWithPublic, useEthersSigner } from './useEthersProvide export function useInitContracts(): { loading: boolean } { const [isLoading, setIsLoading] = React.useState(false); - const { setContracts, setContractClient } = useWeb3Store(); + const { setContracts, setRootContracts, setContractClient } = useWeb3Store(); const { signer } = useEthersSigner(); const provider = useEthersProviderWithPublic(); + const ethereumProvider = useEthersProviderWithPublic({ + chainId: import.meta.env.MODE === 'testnet' ? goerli.id : mainnet.id, + }); React.useEffect(() => { function initContract() { @@ -32,6 +38,17 @@ export function useInitContracts(): { loading: boolean } { parseError(error); } } + + if (ethereumProvider) { + const sqTokenContract = SQToken__factory.connect( + import.meta.env.MODE === 'testnet' ? testnetJSON.root.SQToken.address : mainnetJSON.root.SQToken.address, + ethereumProvider, + ); + const rootContractInstance = { + sqToken: sqTokenContract, + }; + setRootContracts(rootContractInstance); + } } setIsLoading(true); initContract(); diff --git a/src/hooks/useProject.tsx b/src/hooks/useProject.tsx index bf13e82e9..41b3164b0 100644 --- a/src/hooks/useProject.tsx +++ b/src/hooks/useProject.tsx @@ -65,6 +65,7 @@ export function useProject(id: string): AsyncData { version: query.version, metadata, deploymentId: query.deployment, + type: query.type, }; }, [id, catSingle, getMetadataFromCid, getQuery, cacheBreak]); } diff --git a/src/hooks/useSortedIndexerDeployments.tsx b/src/hooks/useSortedIndexerDeployments.tsx index 5622616a3..c4e018110 100644 --- a/src/hooks/useSortedIndexerDeployments.tsx +++ b/src/hooks/useSortedIndexerDeployments.tsx @@ -65,7 +65,6 @@ export function useSortedIndexerDeployments(indexer: string): AsyncDatahere<1>', topRowData: 'Top row of the data represents the data in current era.', secondRowData: 'Data displayed after means the data that will take into effect from next era.', @@ -103,7 +103,7 @@ const translation = { myDelegators: { noDelegatorsTitle: 'You don’t have any Delegators yet', noDelegatorsDescription: - 'Once Delegators have delegated their kSQT to you, they will appear here. First you need to register as an Indexer and begin indexing SubQuery projects, Delegators can then delegate their kSQT to you to earn rewards. Delegators are more likely to delegate their kSQT to high performing Indexers and Consumers will be attracted to Indexers with more kSQT delegated as it indicates they are reliable.', + 'Once Delegators have delegated their SQT to you, they will appear here. First you need to register as an Indexer and begin indexing SubQuery projects, Delegators can then delegate their SQT to you to earn rewards. Delegators are more likely to delegate their SQT to high performing Indexers and Consumers will be attracted to Indexers with more SQT delegated as it indicates they are reliable.', noDelegatorsInfoLink: 'Learn more about Delegators ', }, tokenApproval: { @@ -121,7 +121,7 @@ const translation = { failureCollect: 'Sorry, collect rewards transaction has failed.', }, topIndexers: { - desc: 'View the top 100 ranked Indexers and all other Indexers in the SubQuery Network and delegate your kSQT tokens to earn rewards.', + desc: 'View the top 100 ranked Indexers and all other Indexers in the SubQuery Network and delegate your SQT tokens to earn rewards.', nonData: 'There is no data to display', rank: 'indexer rank', uptime: 'uptime', @@ -151,7 +151,7 @@ const translation = { }, allIndexers: { nonData: 'There is no Indexer available.', - desc: 'View all Indexers in the SubQuery Network and delegate your kSQT tokens to earn rewards.', + desc: 'View all Indexers in the SubQuery Network and delegate your SQT tokens to earn rewards.', }, } as const; diff --git a/src/index.less b/src/index.less index 8a414eec0..5da2d45c5 100644 --- a/src/index.less +++ b/src/index.less @@ -21,7 +21,7 @@ html { :root { --gradient-from: #ff4581; - --gradient-to: #1677ff; + --gradient-to: var(--sq-blue600); --sq-gradient: linear-gradient(293.04deg, var(--gradient-from) 4.09%, var(--gradient-to) 70.16%); --sq-background-gradient: linear-gradient(293.04deg, rgba(255, 69, 129, 0.1) 4.09%, rgba(67, 136, 221, 0.1) 70.16%); --primary: #1677ff; @@ -298,4 +298,9 @@ label, .staticButton.ant-btn-primary { background-color: rgba(67, 136, 221, 0.10); color: var(--sq-blue600); -} \ No newline at end of file +} + +.ant-btn-primary.ant-btn-background-ghost { + border-color: var(--sq-blue600); + color: var(--sq-blue600); +} diff --git a/src/models.tsx b/src/models.tsx index 200200dcb..1fd7ebc61 100644 --- a/src/models.tsx +++ b/src/models.tsx @@ -12,7 +12,6 @@ export enum ProjectType { export const projectMetadataSchema = yup.object({ name: yup.string().defined(), - type: yup.string().oneOf(['SUBQUERY', 'RPC'], 'Only RPC and SUBQUERY are allowed.').required(), image: yup.string().optional(), description: yup.string().default('').optional(), websiteUrl: yup.string().optional().url(), @@ -39,6 +38,7 @@ export type ProjectDetails = { metadata: ProjectMetadata; version: string; deploymentId: string; + type: ProjectType; }; export const newDeploymentSchema = yup.object({ diff --git a/src/pages/consumer/MyOffers/CreateOffer/Summary/Summary.module.css b/src/pages/consumer/MyOffers/CreateOffer/Summary/Summary.module.css index 3eb49e26f..d5b5d8d86 100644 --- a/src/pages/consumer/MyOffers/CreateOffer/Summary/Summary.module.css +++ b/src/pages/consumer/MyOffers/CreateOffer/Summary/Summary.module.css @@ -5,10 +5,6 @@ padding: 1rem; } -.steps { - margin: 1rem 0; -} - .tabs { display: flex; justify-content: space-between; diff --git a/src/pages/consumer/MyOffers/CreateOffer/Summary/Summary.tsx b/src/pages/consumer/MyOffers/CreateOffer/Summary/Summary.tsx index 0d4abc069..c19b95add 100644 --- a/src/pages/consumer/MyOffers/CreateOffer/Summary/Summary.tsx +++ b/src/pages/consumer/MyOffers/CreateOffer/Summary/Summary.tsx @@ -144,7 +144,7 @@ export const Summary: React.FC = () => { }, { label: t('myOffers.step_2.expireDate'), - value: offer?.expireDate ? dayjs(offer.expireDate).format() : dayjs(), + value: offer?.expireDate ? dayjs(offer.expireDate).format() : dayjs().format(), }, ]; diff --git a/src/pages/delegator/DoDelegate/DelegateFrom.tsx b/src/pages/delegator/DoDelegate/DelegateFrom.tsx index e2d7763e6..0314a4664 100644 --- a/src/pages/delegator/DoDelegate/DelegateFrom.tsx +++ b/src/pages/delegator/DoDelegate/DelegateFrom.tsx @@ -5,6 +5,7 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { BsExclamationCircle } from 'react-icons/bs'; import { useNavigate } from 'react-router'; +import TokenTooltip from '@components/TokenTooltip/TokenTooltip'; import { useFetchMetadata } from '@hooks/useFetchMetadata'; import { Typography } from '@subql/components'; import { useGetDelegationQuery, useGetDelegationsLazyQuery } from '@subql/react-hooks'; @@ -123,10 +124,15 @@ export const DelegateForm: React.FC = ({ const maxAmountText = React.useMemo(() => { if (isYourself) { - return t('delegate.walletBalance', { - balance: formatEther(balance.data, 4), - token: TOKEN, - }); + return ( + <> + {t('delegate.walletBalance', { + balance: formatEther(balance.data, 4), + token: TOKEN, + })}{' '} + + + ); } return t('delegate.amountAvailable', { balance: sortedMaxAmount, diff --git a/src/pages/indexer/MyPlans/Create/Create.tsx b/src/pages/indexer/MyPlans/Create/Create.tsx index 919d2def9..2cc8c5e12 100644 --- a/src/pages/indexer/MyPlans/Create/Create.tsx +++ b/src/pages/indexer/MyPlans/Create/Create.tsx @@ -216,6 +216,7 @@ const PlanForm: React.FC = ({ templates, onSubmit, onCancel, curStep, value: ` ${template.rateLimit} queries/sec`, }, ]; + return ( = ({ templates, onSubmit, onCancel, curStep, + = ({ data, onRefresh, title }) => { return 'inactive'; }; return ( - + {text()} ); diff --git a/src/pages/indexer/MyProjects/OwnDeployments/OwnDeployments.tsx b/src/pages/indexer/MyProjects/OwnDeployments/OwnDeployments.tsx index 5df45cfb2..010e126a8 100644 --- a/src/pages/indexer/MyProjects/OwnDeployments/OwnDeployments.tsx +++ b/src/pages/indexer/MyProjects/OwnDeployments/OwnDeployments.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; -import { ProgressBar, Spinner } from '@subql/components'; +import { Spinner, SubqlProgress } from '@subql/components'; import { TableTitle } from '@subql/components'; import { getDeploymentStatus } from '@utils/getIndexerStatus'; import { Table, TableProps, Typography } from 'antd'; @@ -51,7 +51,9 @@ export const OwnDeployments: React.FC = ({ indexer, emptyList, desc }) => {indexingProgressErr}
); - return ; + return ( + + ); }, }, { diff --git a/src/pages/studio/Create/Create.tsx b/src/pages/studio/Create/Create.tsx index 74bf2aaef..c38bef720 100644 --- a/src/pages/studio/Create/Create.tsx +++ b/src/pages/studio/Create/Create.tsx @@ -14,7 +14,7 @@ import { t } from 'i18next'; import { FTextInput, ImageInput } from '../../../components'; import { useCreateProject, useProject, useRouteQuery, useUpdateProjectMetadata } from '../../../hooks'; -import { FormCreateProjectMetadata, newDeploymentSchema, projectMetadataSchema } from '../../../models'; +import { FormCreateProjectMetadata, newDeploymentSchema, projectMetadataSchema, ProjectType } from '../../../models'; import { categoriesOptions, parseError, ROUTES } from '../../../utils'; import { ProjectDeploymentsDetail } from '../Project/Project'; import styles from './Create.module.less'; @@ -33,7 +33,7 @@ const Create: React.FC = () => { const { getIfUnsafeAndWarn } = useGetIfUnsafeDeployment(); const handleSubmit = React.useCallback( - async (project: FormCreateProjectMetadata & { versionDescription: string }) => { + async (project: FormCreateProjectMetadata & { versionDescription: string; type: ProjectType }) => { try { let resultId = query.get('id'); if (isEdit) { @@ -117,7 +117,7 @@ const Create: React.FC = () => { { versionDescription: '', deploymentId: query.get('deploymentId') ?? '', categories: [], - ...(isEdit ? asyncProject.data?.metadata : {}), + ...(isEdit + ? { + ...asyncProject.data?.metadata, + type: asyncProject.data?.type, + } + : {}), }} validationSchema={ isEdit @@ -234,8 +239,8 @@ const Create: React.FC = () => { }} disabled={isEdit ? true : false} > - SubQuery - RPC + SubQuery + RPC ); }} diff --git a/src/pages/swap/Swap.module.less b/src/pages/swap/Swap.module.less deleted file mode 100644 index 87c7cc7f2..000000000 --- a/src/pages/swap/Swap.module.less +++ /dev/null @@ -1,42 +0,0 @@ -.swap { - display: flex; - background-color: var(--gray100); - flex-direction: column; - width: 100%; -} - -.container { - margin: auto; - width: 60%; - display: flex; -} - -.content { - min-height: 60vh; - width: 100%; - background: white; - border-radius: 8px; - padding: 2rem; -} - -.tabs { - display: flex; - justify-content: center; - align-items: center; - - :global { - .tabButtons { - width: auto; - } - } -} - -.dataUpdateText { - font-size: 16px; - margin: 1rem; -} - -.footer { - position: relative; - bottom: -2rem; -} diff --git a/src/pages/swap/Swap.tsx b/src/pages/swap/Swap.tsx deleted file mode 100644 index 721530ec1..000000000 --- a/src/pages/swap/Swap.tsx +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright 2020-2022 SubQuery Pte Ltd authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import * as React from 'react'; -import { useTranslation } from 'react-i18next'; -import { BsExclamationTriangle } from 'react-icons/bs'; -import { Navigate, Route, Routes } from 'react-router'; -import { ApproveContract, EmptyList, Spinner, TabButtons, WalletRoute } from '@components'; -import { useSQToken, useWeb3 } from '@containers'; -import { SQT_TOKEN_ADDRESS } from '@containers/Web3'; -import { useAirdropKyc } from '@hooks/useAirdropKyc'; -import { useAUSDAllowance, useAUSDBalance, useAUSDContract, useAUSDTotalSupply } from '@hooks/useASUDContract'; -import { - useSellSQTQuota, - useSwapOrderId, - useSwapPool, - useSwapRate, - useSwapToken, - useSwapTradeLimitation, -} from '@hooks/useSwapData'; -import { Footer, openNotification, Typography as SubqlTypography } from '@subql/components'; -import { - formatEther, - mergeAsync, - parseError, - renderAsyncArray, - ROUTES, - STABLE_TOKEN, - STABLE_TOKEN_ADDRESS, - STABLE_TOKEN_DECIMAL, - TOKEN, -} from '@utils'; -import { limitContract } from '@utils/limitation'; -import { makeCacheKey } from '@utils/limitation'; -import { Typography } from 'antd'; -import { BigNumber, BigNumberish } from 'ethers'; -import { formatUnits } from 'ethers/lib/utils'; -import i18next, { TFunction } from 'i18next'; - -import { useWeb3Store } from 'src/stores'; - -import styles from './Swap.module.less'; -import { SwapForm } from './SwapForm'; - -const { SWAP, SELL, BUY } = ROUTES; - -const buttonLinks = [ - { label: i18next.t('swap.buykSQT'), link: `${SWAP}/${BUY}` }, - { label: i18next.t('swap.sellkSQT'), link: `${SWAP}/${SELL}` }, -]; - -const getStats = ({ - sqtPoolSize, - swappableBalance, - sqtAUSDRate, - tokenGet, - tokenGive, - t, -}: { - sqtPoolSize?: BigNumberish; - swappableBalance?: BigNumberish; - tokenGet: string; - tokenGive: string; - sqtAUSDRate: number; - t: TFunction; -}) => { - const curRateStats = { - title: t('swap.curRate'), - value: sqtAUSDRate > 0 ? `1 ${tokenGet} = ${sqtAUSDRate} ${tokenGive}` : '-', - tooltip: t('swap.curRateTooltip'), - }; - - if (sqtPoolSize) { - return [ - { - title: t('swap.poolSize'), - value: `${sqtPoolSize} ${TOKEN}`, - tooltip: t('swap.poolSizeTooltip'), - }, - curRateStats, - ]; - } - - return [ - { - title: t('swap.swappableBalance'), - value: `${swappableBalance ?? 0} ${TOKEN}`, - tooltip: t('swap.swappableBalanceTooltip'), - }, - curRateStats, - ]; -}; - -const useGetUSDCTradeLimitation = () => { - const { account } = useWeb3(); - const { contracts } = useWeb3Store(); - const aUSDBalance = useAUSDBalance(); - const totalLimitation = useSwapTradeLimitation(); - const [maxTradelimitation, setMaxTradeLimitation] = React.useState(0); - const [leftTradeAmount, setLeftTradeAmount] = React.useState(0); - const [totalTradeAmountUser, setTotalTradeAmountUser] = React.useState(0); - const initMaxTradeLimitation = async () => { - try { - if (!aUSDBalance.data || !contracts || !account) return 0; - // returned values are USDC - const tradeLimitationPerAccount = - (await limitContract( - () => contracts.permissionedExchange.tradeLimitationPerAccount(), - makeCacheKey(account, { type: 'perAccountTrade' }), - )) || BigNumber.from(0); - const accumulatedTrades = - (await limitContract( - () => contracts.permissionedExchange.accumulatedTrades(account), - makeCacheKey(account, { type: 'accumulated' }), - )) || BigNumber.from(0); - - const tradeLimitation = totalLimitation.data || BigNumber.from(0); - - const formatedTradeLimitation = formatUnits(tradeLimitation, STABLE_TOKEN_DECIMAL); - const formtedTradelimitationPerAccount = formatUnits(tradeLimitationPerAccount, STABLE_TOKEN_DECIMAL); - const formatedAccumulatedTrades = formatUnits(accumulatedTrades, STABLE_TOKEN_DECIMAL); - setLeftTradeAmount(+formtedTradelimitationPerAccount - +formatedAccumulatedTrades); - setTotalTradeAmountUser(+formtedTradelimitationPerAccount); - return Math.min( - +aUSDBalance.data, - +formatedTradeLimitation, - +formtedTradelimitationPerAccount - +formatedAccumulatedTrades, - ); - } catch (e) { - console.warn(e); - openNotification({ - type: 'error', - description: 'Fetch trade limitation failed, please change your RPC Endpoint and try again.', - }); - - return 0; - } - }; - - const init = async () => { - const res = await initMaxTradeLimitation(); - - setMaxTradeLimitation(res); - }; - - React.useEffect(() => { - init(); - }, [account, aUSDBalance.data, totalLimitation.data, contracts]); - - return { maxTradelimitation, leftTradeAmount, totalTradeAmountUser, refresh: init }; -}; - -const USDCToSqt = () => { - const { t } = useTranslation(); - const { account } = useWeb3(); - const [kycStatus, setKycStatus] = React.useState(false); - const { maxTradelimitation, leftTradeAmount, totalTradeAmountUser, refresh } = useGetUSDCTradeLimitation(); - const aUSDContract = useAUSDContract(); - const aUSDAllowance = useAUSDAllowance(); - const requireTokenApproval = aUSDAllowance?.data?.isZero(); - const { orderId, loading: fetchingOrderId } = useSwapOrderId(SQT_TOKEN_ADDRESS ?? ''); - - const swapRate = useSwapRate(orderId); - const swapPool = useSwapPool(orderId); - const swapTokens = useSwapToken(orderId); - const aUSDBalance = useAUSDBalance(); - const aUSDTotalSupply = useAUSDTotalSupply(); - const usdcToSqtLimitation = useSwapTradeLimitation(); - const { getKycStatus } = useAirdropKyc(); - - const initKyc = async (address: string) => { - const res = await getKycStatus(address); - setKycStatus(res); - }; - - React.useEffect(() => { - setKycStatus(false); - if (account) { - initKyc(account); - } - }, [account, aUSDBalance.data]); - - if (fetchingOrderId) return ; - - if (!orderId) return ; - - return renderAsyncArray( - mergeAsync(swapRate, swapPool, swapTokens, aUSDBalance, aUSDTotalSupply, usdcToSqtLimitation), - { - error: (error) => { - return {`Failed to load info: ${parseError(error) || ''}`}; - }, - empty: () => {`There is no data available`}, - data: (data) => { - const [sqtAUSDRate, sqtPoolSize, tokens, _, aUSDSupply, usdcToSqtLimitation] = data; - if (sqtAUSDRate === undefined || sqtPoolSize === undefined || fetchingOrderId) return ; - - // const sortedAUSDBalance = aUSDAmount ?? '0'; - const sortedRate = sqtAUSDRate ?? 0; - const sortedPoolSize = sqtPoolSize ?? '0'; - const pair = { - from: STABLE_TOKEN, - fromMax: maxTradelimitation.toString(), - to: TOKEN, - toMax: formatEther(sortedPoolSize), - }; - - const stats = getStats({ - sqtPoolSize: formatEther(sortedPoolSize, 4), - sqtAUSDRate: sortedRate, - tokenGet: tokens?.tokenGet ?? '', - tokenGive: tokens?.tokenGive ?? '', - t, - }); - - return ( - <> - {!kycStatus && ( -
- - -
- - Only participants that have been KYC’d and whitelisted can participate in the SubQuery Kepler Swap. - You can see a list of all existing whitelisted participants{' '} - - here - - . - - - If you’re new here, check out{' '} - - this article - {' '} - and follow the instructions on how to join. - - - If you need any help, you can reach out on the #kepler-swap-support channel in our{' '} - - Discord - - . - -
-
- )} - aUSDAllowance?.refetch()} - increaseAllowanceAmount={aUSDSupply} - onUpdateSwapData={() => { - swapTokens.refetch(true); - swapPool.refetch(true); - aUSDBalance.refetch(true); - refresh(); - }} - kycStatus={kycStatus} - lifetimeLimitationInfo={{ - isOut: leftTradeAmount === 0, - limitation: totalTradeAmountUser, - }} - /> - - ); - }, - }, - ); -}; - -// TODO: Improve useSwapToken function: as current use TOKEN in util / useSwapToken two places -const SqtToUSDC = () => { - const { t } = useTranslation(); - const { account } = useWeb3(); - const { permissionExchangeAllowance } = useSQToken(); - const requireTokenApproval = permissionExchangeAllowance?.data?.isZero(); - - const { orderId, loading: fetchingOrderId } = useSwapOrderId(STABLE_TOKEN_ADDRESS ?? ''); - - const swapRate = useSwapRate(orderId); - const swapTokens = useSwapToken(orderId); - const tradableQuota = useSellSQTQuota(account ?? ''); - const { balance } = useSQToken(); - const aUSDBalance = useAUSDBalance(); - - if (fetchingOrderId) return ; - - if (!orderId) return ; - - return renderAsyncArray(mergeAsync(swapRate, tradableQuota, swapTokens, balance, aUSDBalance), { - error: (error) => ( - - {t('errors.failedToGetIndexerInfo', { message: parseError(error) || '' })} - - ), - empty: () => {`There is no data available`}, - data: (data) => { - const [swapRate, tradeQuota, tokens, sqtBalance, aUSDAmount] = data; - - if (aUSDAmount === undefined || swapRate === undefined || fetchingOrderId) return ; - - const sortedBalance = sqtBalance ?? BigNumber.from('0'); - const sortedRate = swapRate ?? 0; - const sortedPoolSize = tradeQuota ?? BigNumber.from('0'); - - const fromMax = sortedBalance.gt(sortedPoolSize) ? tradeQuota : sortedBalance; - const toMax = aUSDAmount ?? '0'; - - const pair = { - from: TOKEN, - fromMax: formatEther(fromMax), - to: STABLE_TOKEN, - toMax, - }; - - const stats = getStats({ - swappableBalance: formatEther(tradeQuota, 4), - sqtAUSDRate: sortedRate, - tokenGet: tokens?.tokenGet ?? '', - tokenGive: tokens?.tokenGive ?? '', - t, - }); - - const leftOrderAmount = tokens?.leftTokenGiveBalance; - - return ( - requireTokenApproval && permissionExchangeAllowance.refetch()} - contract={ApproveContract.PermissionedExchange} - onUpdateSwapData={() => { - swapTokens.refetch(true); - balance.refetch(true); - aUSDBalance.refetch(true); - tradableQuota.refetch(true); - }} - leftOrdersAmountInfo={{ - isOut: !!leftOrderAmount?.eq(0), - leftOrderAmount: leftOrderAmount || BigNumber.from(0), - }} - kycStatus - /> - ); - }, - }); -}; - -const Swap: React.FC = () => { - return ( - -
-
-
- -
- - } /> - } /> - } /> - -
-
- -
-
- } - > - ); -}; - -export default Swap; diff --git a/src/pages/swap/SwapForm.module.less b/src/pages/swap/SwapForm.module.less deleted file mode 100644 index 989cc81cf..000000000 --- a/src/pages/swap/SwapForm.module.less +++ /dev/null @@ -1,58 +0,0 @@ -.container { - margin: 1rem 0; -} - -.statsContainer { - display: flex; - column-gap: 1rem; -} - -.stat { - width: 100%; - overflow: auto; -} - -.swapAction { - width: 100%; - display: flex; - justify-content: center; -} - -.swapButton { - width: 20%; - background: var(--sq-primary-blue); - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -.statsContainer { - display: flex; - justify-content: space-between; - margin-bottom: 2rem; - - :global { - .subql-typography { - display: inline-flex; - } - } -} - -.stats { - width: 48%; -} - -.dataUpdateText { - font-size: 16px; -} - -.errorAlert { - display: flex; - justify-content: center; - margin: 1rem; -} - -.alert { - max-width: 50%; -} diff --git a/src/pages/swap/SwapForm.tsx b/src/pages/swap/SwapForm.tsx deleted file mode 100644 index 6b300913b..000000000 --- a/src/pages/swap/SwapForm.tsx +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2020-2022 SubQuery Pte Ltd authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import * as React from 'react'; -import { useTranslation } from 'react-i18next'; -import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined'; -import { Card } from '@subql/components'; -import { limitContract, makeCacheKey } from '@utils/limitation'; -import { Alert, Button } from 'antd'; -import assert from 'assert'; -import BigNumberJs from 'bignumber.js'; -import { BigNumber, ContractTransaction } from 'ethers'; -import { formatUnits, parseUnits } from 'ethers/lib/utils'; -import { Form, Formik } from 'formik'; -import * as Yup from 'yup'; - -import { useWeb3Store } from 'src/stores'; - -import { - ApproveContract, - getTokenApprovalModalText, - ModalApproveToken, - NumberInput, - SummaryList, -} from '../../components'; -import TransactionModal from '../../components/TransactionModal'; -import { STABLE_TOKEN, STABLE_TOKEN_DECIMAL, TOKEN, tokenDecimals, truncFormatEtherStr } from '../../utils'; -import styles from './SwapForm.module.less'; - -interface Stats { - title: string; - value: string; - tooltip?: string; -} - -interface SwapPair { - from: string; - fromMax: string; - to: string; - toMax: string; -} - -interface ISwapForm { - stats: Array; - pair: SwapPair; - fromRate?: number; - orderId: string | undefined; - requireTokenApproval?: boolean; - contract?: ApproveContract; - onApproveAllowance?: () => void; - increaseAllowanceAmount?: BigNumber; - usdcLimitation?: BigNumber; - onIncreaseAllowance?: (address: string, allowance: BigNumber) => Promise; - onUpdateSwapData?: () => void; - lifetimeLimitationInfo?: { - isOut: boolean; - limitation: number; - }; - leftOrdersAmountInfo?: { - isOut: boolean; - leftOrderAmount: BigNumber; - }; - kycStatus: boolean; -} - -interface PairFrom { - from: string; - to: string; -} - -const FROM_INPUT_ID = 'from'; -const TO_INPUT_ID = 'to'; - -// TODO: confirm error msg with design/business -// TODO: confirm alert component and move as component -export const SwapForm: React.FC = ({ - stats, - pair, - fromRate = 1, - orderId, - requireTokenApproval, - contract, - onApproveAllowance, - increaseAllowanceAmount, - onIncreaseAllowance, - onUpdateSwapData, - usdcLimitation, - kycStatus, - lifetimeLimitationInfo, - leftOrdersAmountInfo, -}) => { - const { t } = useTranslation(); - const { contracts } = useWeb3Store(); - const [tokenGet, setTokenGet] = React.useState(''); - const calWithRate = (value: string | number, reverseCal = false): string => { - if (fromRate === 0) return '0'; - const strValue = value.toString(); - const calValue = BigNumberJs(reverseCal ? parseFloat(strValue) / fromRate : parseFloat(strValue) * fromRate); - - return truncFormatEtherStr(calValue.toFixed(), STABLE_TOKEN_DECIMAL); - }; - - const initialPairValues: PairFrom = { - from: '', - to: '', - }; - - const updateFieldVal = ( - fileKey: typeof FROM_INPUT_ID | typeof TO_INPUT_ID, - value: string | number | null, - setValues: (props: any) => void, - setErrors: (props: any) => void, - ) => { - if (!value) return null; - const isReversedCal = fileKey === TO_INPUT_ID; - const autoUpdateField = isReversedCal ? FROM_INPUT_ID : TO_INPUT_ID; - setErrors({ [fileKey]: undefined }); - const autoUpdateFieldVal = calWithRate(value, isReversedCal); - setValues({ [fileKey]: value, [autoUpdateField]: autoUpdateFieldVal }); - }; - - const SwapFormSchema = Yup.object().shape({ - from: Yup.string() - .required() - .test( - 'isOutLifetimeLimitation', - `You have exceeded your lifetime swap limit of ${lifetimeLimitationInfo?.limitation.toLocaleString()} ${ - pair.from - } (${((lifetimeLimitationInfo?.limitation || 0) * fromRate).toLocaleString()} kSQT)`, - (from) => { - if (lifetimeLimitationInfo) { - return !lifetimeLimitationInfo.isOut; - } - - return true; - }, - ) - .test( - 'isOutOfLimatation', - t('swap.usdcLimitation', { - limitation: formatUnits(usdcLimitation || BigNumber.from(0), tokenDecimals[tokenGet]), - }), - (from) => { - if (from && usdcLimitation) { - if (parseUnits(from, tokenDecimals[tokenGet]).gt(usdcLimitation)) { - return false; - } - } - return true; - }, - ) - .test('isMin', 'From should be greater than 0.', (from) => (from ? parseFloat(from) > 0 : false)) - .test('isMax', `There is not enough ${pair.from} to swap.`, (from) => - from ? parseFloat(from) <= parseFloat(pair.fromMax) : false, - ) - .typeError('Please input valid from amount.'), - to: Yup.string() - .required() - .test( - 'hasLeft', - `Out of this order's total amount, left amount is ${formatUnits( - leftOrdersAmountInfo?.leftOrderAmount || '0', - STABLE_TOKEN_DECIMAL, - )}`, - (to) => { - if (leftOrdersAmountInfo) { - if (!leftOrdersAmountInfo.isOut) { - return leftOrdersAmountInfo.leftOrderAmount.gte( - BigNumber.from(parseUnits(BigNumberJs(to || '0').toFixed(6), STABLE_TOKEN_DECIMAL)), - ); - } - - if (leftOrdersAmountInfo.isOut) return false; - } - - return true; - }, - ) - .test('isMin', 'To should be greater than 0.', (to) => (to ? parseFloat(to) > 0 : false)) - .test('isValid', `There is not enough ${pair.to} to swap.`, (to) => { - if (pair.to === STABLE_TOKEN) { - return true; - } - - return to ? parseFloat(to) <= parseFloat(pair.toMax) : false; - }) - .typeError('Please input valid from amount.'), - }); - - const modalText = requireTokenApproval - ? getTokenApprovalModalText(pair.from) - : { - title: t('swap.confirmSwap'), - steps: [t('swap.reviewSwap'), t('indexer.confirmOnMetamask')], - submitText: t('general.confirm'), - failureText: t('swap.swapFailure'), - successText: t('swap.swapSuccess'), - }; - - const getTokenGet = async () => { - if (!contracts || !orderId) return; - - const { tokenGet } = await limitContract( - () => contracts.permissionedExchange.orders(orderId), - makeCacheKey(orderId, { type: 'swapOrders' }), - ); - - setTokenGet(tokenGet); - }; - - const onTradeOrder = async (amount: string) => { - assert(contracts, 'Contracts not available'); - assert(orderId, 'There is no orderId available.'); - - return contracts.permissionedExchange.trade(orderId, parseUnits(amount, tokenDecimals[tokenGet])); - }; - - React.useEffect(() => { - getTokenGet(); - }, [contracts, orderId]); - - return ( -
- {!orderId && ( -
- -
- )} - -
- {stats.map((statsItem) => ( - } - titleTooltip={statsItem.tooltip} - className={styles.stat} - /> - ))} -
- -
- { - actions.setSubmitting(false); - }} - enableReinitialize - > - {({ isValid, isSubmitting, setErrors, values, errors, setValues, resetForm }) => { - const isActionDisabled = - !kycStatus || !isValid || !orderId || !values[FROM_INPUT_ID] || !values[TO_INPUT_ID]; - const summaryList = [ - { - label: t('swap.from'), - value: `${values[FROM_INPUT_ID]} ${pair.from}`, - }, - { - label: t('swap.to'), - value: `${values[TO_INPUT_ID]} ${pair.to}`, - }, - ]; - - return ( -
- updateFieldVal(FROM_INPUT_ID, value, setValues, setErrors)} - errorMsg={errors[FROM_INPUT_ID]} - onClickMax={(value) => updateFieldVal(FROM_INPUT_ID, value.toString(), setValues, setErrors)} - placeholder={'1'} - /> - updateFieldVal(TO_INPUT_ID, value, setValues, setErrors)} - errorMsg={errors[TO_INPUT_ID]} - onClickMax={(value) => updateFieldVal(TO_INPUT_ID, value.toString(), setValues, setErrors)} - placeholder={calWithRate('1') ?? '0'} - /> - -
- onTradeOrder(values[FROM_INPUT_ID])} - onSuccess={() => { - resetForm(); - onUpdateSwapData && onUpdateSwapData(); - }} - renderContent={(onSubmit, onCancel, isLoading, error) => { - if (!!requireTokenApproval) { - return ( - onApproveAllowance && onApproveAllowance()} - /> - ); - } - - return ( - <> - -
- -
- - ); - }} - variant={isActionDisabled ? 'disabledButton' : 'button'} - /> -
- - ); - }} -
-
-
- ); -}; diff --git a/src/router/routes.ts b/src/router/routes.ts index 755c9d3e2..d3a8879d0 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -225,6 +225,5 @@ export const routers: BasicRouteType[] = [ }, { path: '/consumer/*', component: React.lazy(() => import('../pages/consumer/index')) }, - { path: '/swap/*', component: React.lazy(() => import('../pages/swap/Swap')) }, { path: '/studio/*', component: React.lazy(() => import('../pages/studio/index')) }, ]; diff --git a/src/stores/web3Account.ts b/src/stores/web3Account.ts index 52657c85e..b968558d8 100644 --- a/src/stores/web3Account.ts +++ b/src/stores/web3Account.ts @@ -1,7 +1,7 @@ // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { ContractSDK } from '@subql/contract-sdk'; +import { ContractSDK, SQToken } from '@subql/contract-sdk'; import { ContractClient } from '@subql/network-clients'; import { ethers } from 'ethers'; import { create } from 'zustand'; @@ -12,6 +12,10 @@ import { create } from 'zustand'; * */ +interface rootContracts { + sqToken: SQToken; +} + interface Web3Store { error?: any; setError: (error: any) => void; @@ -26,6 +30,10 @@ interface Web3Store { contracts?: ContractSDK; setContracts: (contracts: ContractSDK) => void; + + rootContracts?: rootContracts; + setRootContracts: (rootContracts: rootContracts) => void; + contractClient?: ContractClient; setContractClient: (contracts: ContractClient) => void; } @@ -34,6 +42,8 @@ export const useWeb3Store = create()((set) => ({ ethWindowObj: window?.ethereum, contracts: undefined, isInitialAccount: false, + rootContracts: undefined, + setRootContracts: (rootContracts: rootContracts | undefined) => set((state) => ({ ...state, rootContracts })), ethProvider: () => { const providers = [ new ethers.providers.JsonRpcProvider('https://eth.llamarpc.com'), diff --git a/src/utils/constants.ts b/src/utils/constants.ts index a1d35136a..a8e15dab1 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -15,7 +15,7 @@ export enum LOCK_STATUS { export const STABLE_TOKEN = import.meta.env.VITE_STABLE_TOKEN ?? 'USDC'; -export const TOKEN = import.meta.env.VITE_TOKEN ?? 'kSQT'; +export const TOKEN = import.meta.env.VITE_TOKEN ?? 'SQT'; export const tokenDecimals: { [key: string]: number } = { [STABLE_TOKEN_ADDRESS]: 6, diff --git a/src/utils/links.ts b/src/utils/links.ts index 97174c337..c1072c36e 100644 --- a/src/utils/links.ts +++ b/src/utils/links.ts @@ -45,10 +45,6 @@ export const entryLinks = [ link: ROUTES.DELEGATOR, label: t('delegator'), }, - { - link: ROUTES.SWAP, - label: t('header.swap'), - }, { link: 'https://academy.subquery.network/subquery_network/kepler/welcome.html', label: t('header.documentation'), diff --git a/yarn.lock b/yarn.lock index f57fbd221..424049abe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4667,10 +4667,10 @@ jwt-decode "^3.1.2" lru-cache "^10.0.1" -"@subql/components@1.0.3-22": - version "1.0.3-22" - resolved "https://registry.npmjs.org/@subql/components/-/components-1.0.3-22.tgz#0a1623a972971b0c2e2056b077ab6e62c6a5f723" - integrity sha512-LUaRWB+iSu7PXUdy2i0N4GNadIjXjNMKb067M0dCq+L9vCFKnRzpWNOslXPiqs41bqUXwgL0kHlNFge0ooZQMg== +"@subql/components@1.0.3-25": + version "1.0.3-25" + resolved "https://registry.npmjs.org/@subql/components/-/components-1.0.3-25.tgz#3be52c0560d72b2c781f8af430569b0450a89d21" + integrity sha512-RJvw0ihejEzpybtph2mmyNeBU3m0MPW5i+jdYeAux7XhmcRMh/vODB2RpsAh69Uudf4WdN5r/OagmTZ6ijuA6A== dependencies: "@graphiql/plugin-explorer" "^0.3.4" "@graphiql/toolkit" "^0.9.1" @@ -4681,6 +4681,7 @@ graphql "^16.7.0" graphql-ws "^5.13.1" jdenticon "^3.2.0" + lodash-es "^4.17.21" ra-data-graphql "^4.11.3" react-icons "^4.8.0" react-jazzicon "^0.1.3"