diff --git a/.env.test b/.env.test new file mode 100644 index 00000000..a37d6dce --- /dev/null +++ b/.env.test @@ -0,0 +1,10 @@ +NEXT_PUBLIC_WALLETCONNECT_KEY=test_walletconnect_key +NEXT_PUBLIC_WEB3AUTH_NETWORK=testnet +NEXT_PUBLIC_WEB3AUTH_CLIENT_ID=test_client_id +NEXT_PUBLIC_CHAIN=manifest +NEXT_PUBLIC_CHAIN_ID=test_chain_id +NEXT_PUBLIC_CHAIN_TIER=testnet +NEXT_PUBLIC_RPC_URL=https://test.rpc.url +NEXT_PUBLIC_API_URL=https://test.api.url +NEXT_PUBLIC_EXPLORER_URL=https://test.explorer.url +NEXT_PUBLIC_INDEXER_URL=https://test.indexer.url \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b2966b71..55413639 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -32,18 +32,14 @@ jobs: touch .env echo "NEXT_PUBLIC_CHAIN=${{ vars.NEXT_PUBLIC_CHAIN }}" >> .env echo "NEXT_PUBLIC_CHAIN_ID=${{ vars.NEXT_PUBLIC_CHAIN_ID }}" >> .env - echo "NEXT_PUBLIC_TESTNET_CHAIN_ID=${{ vars.NEXT_PUBLIC_TESTNET_CHAIN_ID }}" >> .env - echo "NEXT_PUBLIC_MAINNET_RPC_URL=${{ vars.NEXT_PUBLIC_MAINNET_RPC_URL }}" >> .env - echo "NEXT_PUBLIC_TESTNET_RPC_URL=${{ vars.NEXT_PUBLIC_TESTNET_RPC_URL }}" >> .env - echo "NEXT_PUBLIC_MAINNET_API_URL=${{ vars.NEXT_PUBLIC_MAINNET_API_URL }}" >> .env - echo "NEXT_PUBLIC_TESTNET_API_URL=${{ vars.NEXT_PUBLIC_TESTNET_API_URL }}" >> .env - echo "NEXT_PUBLIC_ABLY_API_KEY=${{ secrets.NEXT_PUBLIC_ABLY_API_KEY }}" >> .env + echo "NEXT_PUBLIC_CHAIN_TIER=${{ vars.NEXT_PUBLIC_CHAIN_TIER }}" >> .env + echo "NEXT_PUBLIC_RPC_URL=${{ vars.NEXT_PUBLIC_RPC_URL }}" >> .env + echo "NEXT_PUBLIC_API_URL=${{ vars.NEXT_PUBLIC_API_URL }}" >> .env echo "NEXT_PUBLIC_WALLETCONNECT_KEY=${{ secrets.NEXT_PUBLIC_WALLETCONNECT_KEY }}" >> .env - echo "NEXT_PUBLIC_WEB3_CLIENT_ID=${{ secrets.NEXT_PUBLIC_WEB3_CLIENT_ID }}" >> .env - echo "NEXT_PUBLIC_TESTNET_EXPLORER_URL=${{ vars.NEXT_PUBLIC_TESTNET_EXPLORER_URL }}" >> .env - echo "NEXT_PUBLIC_MAINNET_EXPLORER_URL=${{ vars.NEXT_PUBLIC_MAINNET_EXPLORER_URL }}" >> .env - echo "NEXT_PUBLIC_TESTNET_INDEXER_URL=${{ vars.NEXT_PUBLIC_TESTNET_INDEXER_URL }}" >> .env - echo "NEXT_PUBLIC_MAINNET_INDEXER_URL=${{ vars.NEXT_PUBLIC_MAINNET_INDEXER_URL }}" >> .env + echo "NEXT_PUBLIC_WEB3AUTH_NETWORK=${{ vars.NEXT_PUBLIC_WEB3AUTH_NETWORK }}" >> .env + echo "NEXT_PUBLIC_WEB3AUTH_CLIENT_ID=${{ secrets.NEXT_PUBLIC_WEB3AUTH_CLIENT_ID }}" >> .env + echo "NEXT_PUBLIC_EXPLORER_URL=${{ vars.NEXT_PUBLIC_EXPLORER_URL }}" >> .env + echo "NEXT_PUBLIC_INDEXER_URL=${{ vars.NEXT_PUBLIC_INDEXER_URL }}" >> .env cat .env - name: Get the Git tag diff --git a/.gitignore b/.gitignore index 9a929d57..0add2f24 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ yarn-error.log* # local env files .env* +!.env.test # vercel .vercel diff --git a/README.md b/README.md index 24774171..b06d1539 100644 --- a/README.md +++ b/README.md @@ -22,20 +22,30 @@ For more information on the Manifest Network and its modules, please visit the [ ``` NEXT_PUBLIC_WALLETCONNECT_KEY= -NEXT_PUBLIC_WEB3_CLIENT_ID= -NEXT_PUBLIC_CHAIN=manifest -NEXT_PUBLIC_CHAIN_ID=manifest-1 -NEXT_PUBLIC_TESTNET_CHAIN_ID=manifest-ledger-beta -NEXT_PUBLIC_MAINNET_RPC_URL=https://nodes.chandrastation.com/rpc/manifest/ -NEXT_PUBLIC_TESTNET_RPC_URL=https://manifest-beta-rpc.liftedinit.tech/ -NEXT_PUBLIC_MAINNET_API_URL=https://nodes.chandrastation.com/api/manifest/ -NEXT_PUBLIC_TESTNET_API_URL=https://manifest-beta-rest.liftedinit.tech/ -NEXT_PUBLIC_TESTNET_EXPLORER_URL=https://testnet.manifest.explorers.guru -NEXT_PUBLIC_MAINNET_EXPLORER_URL=https://manifest.explorers.guru -NEXT_PUBLIC_TESTNET_INDEXER_URL=https://testnet-indexer.liftedinit.tech -NEXT_PUBLIC_MAINNET_INDEXER_URL=https://indexer.liftedinit.app +NEXT_PUBLIC_WEB3AUTH_NETWORK= +NEXT_PUBLIC_WEB3AUTH_CLIENT_ID= +NEXT_PUBLIC_CHAIN= +NEXT_PUBLIC_CHAIN_ID= +NEXT_PUBLIC_CHAIN_TIER= +NEXT_PUBLIC_RPC_URL= +NEXT_PUBLIC_API_URL= +NEXT_PUBLIC_EXPLORER_URL= +NEXT_PUBLIC_INDEXER_URL= ``` +where + +- `NEXT_PUBLIC_WALLETCONNECT_KEY` is the WalletConnect key +- `NEXT_PUBLIC_WEB3AUTH_NETWORK` is the Web3Auth network to use for social login +- `NEXT_PUBLIC_WEB3AUTH_CLIENT_ID` is the Web3Auth client ID to use for social login +- `NEXT_PUBLIC_CHAIN` is the chain name +- `NEXT_PUBLIC_CHAIN_ID` is the chain ID +- `NEXT_PUBLIC_CHAIN_TIER` is the chain tier (e.g., `testnet`, `mainnet`) +- `NEXT_PUBLIC_RPC_URL` is the chain RPC URL +- `NEXT_PUBLIC_API_URL` is the chain API URL +- `NEXT_PUBLIC_EXPLORER_URL` is the block explorer URL +- `NEXT_PUBLIC_INDEXER_URL` is the YACI indexer URL + ### Development 1. Start the server diff --git a/bun.lockb b/bun.lockb index d6a0cea1..07aa674c 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/bunfig.toml b/bunfig.toml index c5764ab6..a1a29b5e 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -1,4 +1,4 @@ [test] -preload = "./happydom.ts" +preload = "./setup.ts" coverageSkipTestFiles = true coverageReporter = ["text", "lcov"] \ No newline at end of file diff --git a/components/admins/modals/cancelUpgradeModal.tsx b/components/admins/modals/cancelUpgradeModal.tsx index b3f93bbe..05304099 100644 --- a/components/admins/modals/cancelUpgradeModal.tsx +++ b/components/admins/modals/cancelUpgradeModal.tsx @@ -3,9 +3,9 @@ import { createPortal } from 'react-dom'; import { cosmos } from '@liftedinit/manifestjs'; import { useFeeEstimation, useTx } from '@/hooks'; import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any'; -import { chainName } from '@/config'; import { MsgCancelUpgrade } from '@liftedinit/manifestjs/dist/codegen/cosmos/upgrade/v1beta1/tx'; import { PlanSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/upgrade/v1beta1/upgrade'; +import env from '@/config/env'; interface BaseModalProps { isOpen: boolean; @@ -37,8 +37,8 @@ export function CancelUpgradeModal({ }: BaseModalProps) { const { cancelUpgrade } = cosmos.upgrade.v1beta1.MessageComposer.withTypeUrl; const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; - const { tx, isSigning, setIsSigning } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); + const { tx, isSigning, setIsSigning } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); useEffect(() => { const handleEscape = (e: KeyboardEvent) => { diff --git a/components/admins/modals/multiMfxBurnModal.tsx b/components/admins/modals/multiMfxBurnModal.tsx index 18bbcd1c..a7881fb9 100644 --- a/components/admins/modals/multiMfxBurnModal.tsx +++ b/components/admins/modals/multiMfxBurnModal.tsx @@ -8,13 +8,13 @@ import { NumberInput, TextInput } from '@/components/react/inputs'; import { PlusIcon, MinusIcon } from '@/components/icons'; import { MdContacts } from 'react-icons/md'; import { useTx, useFeeEstimation } from '@/hooks'; -import { chainName } from '@/config'; import { cosmos, liftedinit } from '@liftedinit/manifestjs'; import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any'; import { parseNumberToBigInt } from '@/utils'; import { MetadataSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/bank/v1beta1/bank'; import { TailwindModal } from '@/components/react'; +import env from '@/config/env'; interface BurnPair { address: string; @@ -48,8 +48,8 @@ const MultiBurnSchema = Yup.object().shape({ export function MultiBurnModal({ isOpen, onClose, admin, address, denom }: MultiBurnModalProps) { const [burnPairs, setBurnPairs] = useState([{ address: admin, amount: '' }]); - const { tx, isSigning, setIsSigning } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); + const { tx, isSigning, setIsSigning } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); const { burnHeldBalance } = liftedinit.manifest.v1.MessageComposer.withTypeUrl; const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; const [isContactsOpen, setIsContactsOpen] = useState(false); diff --git a/components/admins/modals/multiMfxMintModal.tsx b/components/admins/modals/multiMfxMintModal.tsx index d396f26a..e4c00c75 100644 --- a/components/admins/modals/multiMfxMintModal.tsx +++ b/components/admins/modals/multiMfxMintModal.tsx @@ -8,12 +8,12 @@ import { createPortal } from 'react-dom'; import { MdContacts } from 'react-icons/md'; import { PlusIcon, MinusIcon } from '@/components/icons'; import { useTx, useFeeEstimation } from '@/hooks'; -import { chainName } from '@/config'; import { cosmos, liftedinit } from '@liftedinit/manifestjs'; import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any'; import { MsgPayout } from '@liftedinit/manifestjs/dist/codegen/liftedinit/manifest/v1/tx'; import { parseNumberToBigInt, shiftDigits } from '@/utils'; import { MetadataSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/bank/v1beta1/bank'; +import env from '@/config/env'; //TODO: find max mint amount from team for mfx. Find tx size limit for max payout pairs interface PayoutPair { address: string; @@ -48,8 +48,8 @@ const MultiMintSchema = Yup.object().shape({ export function MultiMintModal({ isOpen, onClose, admin, address, denom }: MultiMintModalProps) { const [payoutPairs, setPayoutPairs] = useState([{ address: '', amount: '' }]); - const { tx, isSigning, setIsSigning } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); + const { tx, isSigning, setIsSigning } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); const { payout } = liftedinit.manifest.v1.MessageComposer.withTypeUrl; const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; const [isContactsOpen, setIsContactsOpen] = useState(false); diff --git a/components/admins/modals/upgradeModal.tsx b/components/admins/modals/upgradeModal.tsx index 8cb0f569..b4f36ec2 100644 --- a/components/admins/modals/upgradeModal.tsx +++ b/components/admins/modals/upgradeModal.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState, useMemo } from 'react'; import { createPortal } from 'react-dom'; import { cosmos } from '@liftedinit/manifestjs'; import { useTx, useFeeEstimation, useGitHubReleases, GitHubRelease } from '@/hooks'; -import { chainName } from '@/config'; import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any'; import { MsgSoftwareUpgrade } from '@liftedinit/manifestjs/dist/codegen/cosmos/upgrade/v1beta1/tx'; import { Formik, Form } from 'formik'; @@ -10,6 +9,7 @@ import Yup from '@/utils/yupExtensions'; import { TextInput } from '@/components/react/inputs'; import { PiCaretDownBold } from 'react-icons/pi'; import { SearchIcon } from '@/components/icons'; +import env from '@/config/env'; interface BaseModalProps { isOpen: boolean; @@ -82,8 +82,8 @@ export function UpgradeModal({ isOpen, onClose, admin, address, refetchPlan }: B const { softwareUpgrade } = cosmos.upgrade.v1beta1.MessageComposer.withTypeUrl; const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; - const { tx, isSigning, setIsSigning } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); + const { tx, isSigning, setIsSigning } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); const handleUpgrade = async (values: { name: string; height: string; info: string }) => { setIsSigning(true); diff --git a/components/admins/modals/validatorModal.tsx b/components/admins/modals/validatorModal.tsx index c2363f4d..3b2ad92c 100644 --- a/components/admins/modals/validatorModal.tsx +++ b/components/admins/modals/validatorModal.tsx @@ -1,20 +1,19 @@ import React, { useState, useEffect } from 'react'; import { TruncatedAddressWithCopy } from '@/components/react/addressCopy'; -import { ExtendedValidatorSDKType } from '../components'; +import { ExtendedValidatorSDKType } from '@/components'; import ProfileAvatar from '@/utils/identicon'; import { BsThreeDots } from 'react-icons/bs'; import { DescriptionModal } from './descriptionModal'; -import { chainName } from '@/config'; import { useTx, useFeeEstimation } from '@/hooks'; -import { strangelove_ventures } from '@liftedinit/manifestjs'; +import { strangelove_ventures, cosmos } from '@liftedinit/manifestjs'; import { useChain } from '@cosmos-kit/react'; -import { cosmos } from '@liftedinit/manifestjs'; import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any'; import { MsgSetPower } from '@liftedinit/manifestjs/dist/codegen/strangelove_ventures/poa/v1/tx'; -import { Formik, Form, Field, ErrorMessage, FieldProps } from 'formik'; +import { Formik, Field, FieldProps } from 'formik'; import * as Yup from 'yup'; import { calculateIsUnsafe } from '@/utils/maths'; import { TextInput } from '@/components/react'; +import env from '@/config/env'; import { createPortal } from 'react-dom'; const PowerUpdateSchema = Yup.object().shape({ @@ -55,9 +54,9 @@ export function ValidatorDetailsModal({ const [power, setPowerInput] = useState(validator?.consensus_power?.toString() || ''); - const { tx, isSigning, setIsSigning } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); - const { address: userAddress } = useChain(chainName); + const { tx, isSigning, setIsSigning } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); + const { address: userAddress } = useChain(env.chain); const { setPower } = strangelove_ventures.poa.v1.MessageComposer.withTypeUrl; const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; diff --git a/components/admins/modals/warningModal.tsx b/components/admins/modals/warningModal.tsx index a6907fd5..dc42c2a3 100644 --- a/components/admins/modals/warningModal.tsx +++ b/components/admins/modals/warningModal.tsx @@ -1,4 +1,3 @@ -import { chainName } from '@/config'; import { useFeeEstimation, useTx } from '@/hooks'; import { cosmos, strangelove_ventures } from '@liftedinit/manifestjs'; import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any'; @@ -6,6 +5,7 @@ import { MsgRemoveValidator } from '@liftedinit/manifestjs/dist/codegen/strangel import { useChain } from '@cosmos-kit/react'; import React, { useEffect } from 'react'; import { PiWarning } from 'react-icons/pi'; +import env from '@/config/env'; import { createPortal } from 'react-dom'; interface WarningModalProps { @@ -38,9 +38,9 @@ export function WarningModal({ return () => document.removeEventListener('keydown', handleEscape); }, [openWarningModal]); - const { tx, isSigning, setIsSigning } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); - const { address: userAddress } = useChain(chainName); + const { tx, isSigning, setIsSigning } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); + const { address: userAddress } = useChain(env.chain); const { removePending, removeValidator } = strangelove_ventures.poa.v1.MessageComposer.withTypeUrl; const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; diff --git a/components/bank/forms/ibcSendForm.tsx b/components/bank/forms/ibcSendForm.tsx index ac11ea7e..9e71ef76 100644 --- a/components/bank/forms/ibcSendForm.tsx +++ b/components/bank/forms/ibcSendForm.tsx @@ -1,8 +1,13 @@ import React, { useState, useMemo } from 'react'; -import { chainName } from '@/config'; import { useFeeEstimation, useTx } from '@/hooks'; import { ibc } from '@liftedinit/manifestjs'; -import { getIbcInfo, parseNumberToBigInt } from '@/utils'; +import { + getIbcInfo, + parseNumberToBigInt, + shiftDigits, + truncateString, + formatTokenDisplayName, +} from '@/utils'; import { PiCaretDownBold } from 'react-icons/pi'; import { MdContacts } from 'react-icons/md'; import { CombinedBalanceInfo } from '@/utils/types'; @@ -10,13 +15,12 @@ import { DenomImage } from '@/components/factory'; import { Formik, Form } from 'formik'; import Yup from '@/utils/yupExtensions'; import { TextInput } from '@/components/react/inputs'; -import { IbcChain } from '../components/sendBox'; +import { IbcChain } from '@/components'; import Image from 'next/image'; -import { shiftDigits, truncateString } from '@/utils'; import { SearchIcon } from '@/components/icons'; import { TailwindModal } from '@/components/react/modal'; -import { formatTokenDisplayName } from '@/utils'; +import env from '@/config/env'; //TODO: use formatTokenDisplayName instead of repeating format export default function IbcSendForm({ @@ -49,8 +53,8 @@ export default function IbcSendForm({ const [isSending, setIsSending] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const [feeWarning, setFeeWarning] = useState(''); - const { tx } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); + const { tx } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); const { transfer } = ibc.applications.transfer.v1.MessageComposer.withTypeUrl; const [isContactsOpen, setIsContactsOpen] = useState(false); @@ -114,7 +118,7 @@ export default function IbcSendForm({ const exponent = values.selectedToken.metadata?.denom_units[1]?.exponent ?? 6; const amountInBaseUnits = parseNumberToBigInt(values.amount, exponent).toString(); - const { source_port, source_channel } = getIbcInfo(chainName ?? '', destinationChain ?? ''); + const { source_port, source_channel } = getIbcInfo(env.chain, destinationChain ?? ''); const token = { denom: values.selectedToken.coreDenom, diff --git a/components/bank/forms/sendForm.tsx b/components/bank/forms/sendForm.tsx index ab8e4857..a634990c 100644 --- a/components/bank/forms/sendForm.tsx +++ b/components/bank/forms/sendForm.tsx @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -import { chainName } from '@/config'; import { useFeeEstimation, useTx } from '@/hooks'; import { cosmos } from '@liftedinit/manifestjs'; import { PiCaretDownBold } from 'react-icons/pi'; @@ -12,6 +11,7 @@ import { TextInput } from '@/components/react/inputs'; import { SearchIcon } from '@/components/icons'; import { TailwindModal } from '@/components/react/modal'; import { MdContacts } from 'react-icons/md'; +import env from '@/config/env'; import { Any } from 'cosmjs-types/google/protobuf/any'; import { MsgSend } from '@liftedinit/manifestjs/dist/codegen/cosmos/bank/v1beta1/tx'; @@ -39,8 +39,8 @@ export default function SendForm({ const [isSending, setIsSending] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const [feeWarning, setFeeWarning] = useState(''); - const { tx } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); + const { tx } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); const { send } = cosmos.bank.v1beta1.MessageComposer.withTypeUrl; const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; const [isContactsOpen, setIsContactsOpen] = useState(false); diff --git a/components/bank/modals/txInfo.tsx b/components/bank/modals/txInfo.tsx index 63a2f2df..a1b9cbad 100644 --- a/components/bank/modals/txInfo.tsx +++ b/components/bank/modals/txInfo.tsx @@ -3,7 +3,7 @@ import { TruncatedAddressWithCopy } from '@/components/react/addressCopy'; import { formatDenom, TransactionGroup } from '@/components'; import { FaExternalLinkAlt } from 'react-icons/fa'; import { shiftDigits } from '@/utils'; -import { useEndpointStore } from '@/store/endpointStore'; +import env from '@/config/env'; interface TxInfoModalProps { tx: TransactionGroup; @@ -12,9 +12,6 @@ interface TxInfoModalProps { } export default function TxInfoModal({ tx, modalId }: TxInfoModalProps) { - const { selectedEndpoint } = useEndpointStore(); - const explorerUrl = selectedEndpoint?.explorer || ''; - function formatDate(dateString: string): string { const date = new Date(dateString); return date.toLocaleString('en-US', { @@ -45,31 +42,31 @@ export default function TxInfoModal({ tx, modalId }: TxInfoModalProps) {
@@ -91,7 +88,7 @@ export default function TxInfoModal({ tx, modalId }: TxInfoModalProps) {
- +
@@ -120,7 +117,7 @@ function InfoItem({
({ poaAdmin: '', isPoaAdminLoading: false, }), + useDenomAuthorityMetadata: jest.fn().mockReturnValue({ + denomAuthority: '', + isDenomAuthorityLoading: false, + }), })); const renderWithProps = (props = {}) => { diff --git a/components/factory/forms/BurnForm.tsx b/components/factory/forms/BurnForm.tsx index aa2e45f7..8cc21196 100644 --- a/components/factory/forms/BurnForm.tsx +++ b/components/factory/forms/BurnForm.tsx @@ -1,10 +1,9 @@ import React, { useMemo, useState } from 'react'; -import { chainName } from '@/config'; import { useTokenFactoryBalance, useFeeEstimation, useTx } from '@/hooks'; import { cosmos, osmosis, liftedinit } from '@liftedinit/manifestjs'; import { MdContacts } from 'react-icons/md'; -import { parseNumberToBigInt, shiftDigits } from '@/utils'; +import { parseNumberToBigInt, shiftDigits, ExtendedMetadataSDKType, truncateString } from '@/utils'; import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any'; import { MsgBurnHeldBalance } from '@liftedinit/manifestjs/dist/codegen/liftedinit/manifest/v1/tx'; @@ -12,8 +11,8 @@ import { useToast } from '@/contexts'; import { Formik, Form } from 'formik'; import Yup from '@/utils/yupExtensions'; import { NumberInput, TextInput } from '@/components/react/inputs'; -import { ExtendedMetadataSDKType, truncateString } from '@/utils'; import { TailwindModal } from '@/components/react/modal'; +import env from '@/config/env'; interface BurnPair { address: string; @@ -49,8 +48,8 @@ export default function BurnForm({ const [isContactsOpen, setIsContactsOpen] = useState(false); - const { tx, isSigning, setIsSigning } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); + const { tx, isSigning, setIsSigning } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); const { burn } = osmosis.tokenfactory.v1beta1.MessageComposer.withTypeUrl; const { burnHeldBalance } = liftedinit.manifest.v1.MessageComposer.withTypeUrl; const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; diff --git a/components/factory/forms/ConfirmationForm.tsx b/components/factory/forms/ConfirmationForm.tsx index a03ee28c..0400b4a8 100644 --- a/components/factory/forms/ConfirmationForm.tsx +++ b/components/factory/forms/ConfirmationForm.tsx @@ -1,9 +1,8 @@ -import { useState } from 'react'; +import env from '@/config/env'; import { TokenFormData } from '@/helpers/formReducer'; import { useFeeEstimation } from '@/hooks/useFeeEstimation'; import { useTx } from '@/hooks/useTx'; import { osmosis } from '@liftedinit/manifestjs'; -import { chainName } from '@/config'; export default function ConfirmationForm({ nextStep, @@ -16,8 +15,8 @@ export default function ConfirmationForm({ formData: TokenFormData; address: string; }>) { - const { tx, isSigning, setIsSigning } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); + const { tx, isSigning, setIsSigning } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); const { setDenomMetadata, createDenom } = osmosis.tokenfactory.v1beta1.MessageComposer.withTypeUrl; diff --git a/components/factory/forms/MintForm.tsx b/components/factory/forms/MintForm.tsx index bb82dd17..e957425e 100644 --- a/components/factory/forms/MintForm.tsx +++ b/components/factory/forms/MintForm.tsx @@ -1,16 +1,15 @@ import React, { useState } from 'react'; -import { chainName } from '@/config'; import { useFeeEstimation, useTx } from '@/hooks'; import { osmosis } from '@liftedinit/manifestjs'; -import { parseNumberToBigInt, shiftDigits } from '@/utils'; +import { parseNumberToBigInt, shiftDigits, ExtendedMetadataSDKType, truncateString } from '@/utils'; import { MdContacts } from 'react-icons/md'; import { Formik, Form } from 'formik'; import Yup from '@/utils/yupExtensions'; import { NumberInput, TextInput } from '@/components/react/inputs'; -import { ExtendedMetadataSDKType, truncateString } from '@/utils'; import { TailwindModal } from '@/components/react/modal'; +import env from '@/config/env'; export default function MintForm({ isAdmin, @@ -33,8 +32,8 @@ export default function MintForm({ const [recipient, setRecipient] = useState(address); const [isContactsOpen, setIsContactsOpen] = useState(false); - const { tx, isSigning, setIsSigning } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); + const { tx, isSigning, setIsSigning } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); const { mint } = osmosis.tokenfactory.v1beta1.MessageComposer.withTypeUrl; const exponent = diff --git a/components/factory/modals/TransferModal.tsx b/components/factory/modals/TransferModal.tsx index 8b2a4b58..53e02056 100644 --- a/components/factory/modals/TransferModal.tsx +++ b/components/factory/modals/TransferModal.tsx @@ -1,13 +1,13 @@ import { useEffect } from 'react'; import { ExtendedMetadataSDKType, truncateString } from '@/utils'; import { useDenomAuthorityMetadata, useFeeEstimation, useTx } from '@/hooks'; -import { chainName } from '@/config'; import { osmosis } from '@liftedinit/manifestjs'; import { createPortal } from 'react-dom'; import Yup from '@/utils/yupExtensions'; import { Form, Formik, FormikValues } from 'formik'; import { TextInput } from '@/components'; import { useToast } from '@/contexts'; +import env from '@/config/env'; const TokenOwnershipSchema = Yup.object().shape({ newAdmin: Yup.string().required('New admin address is required').manifestAddress(), @@ -56,8 +56,8 @@ export default function TransferModal({ newAdmin: '', }; - const { tx, isSigning, setIsSigning } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); + const { tx, isSigning, setIsSigning } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); const { changeAdmin } = osmosis.tokenfactory.v1beta1.MessageComposer.withTypeUrl; const handleTransfer = async (values: FormikValues, resetForm: () => void) => { diff --git a/components/factory/modals/denomInfo.tsx b/components/factory/modals/denomInfo.tsx index 52535c0c..540e2384 100644 --- a/components/factory/modals/denomInfo.tsx +++ b/components/factory/modals/denomInfo.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from 'react'; import { TruncatedAddressWithCopy } from '@/components/react/addressCopy'; import { FaExternalLinkAlt } from 'react-icons/fa'; import { MetadataSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/bank/v1beta1/bank'; -import { useEndpointStore } from '@/store/endpointStore'; +import env from '@/config/env'; export const DenomInfoModal: React.FC<{ openDenomInfoModal: boolean; @@ -25,9 +25,6 @@ export const DenomInfoModal: React.FC<{ nameIsAddress = true; } - const { selectedEndpoint } = useEndpointStore(); - const explorerUrl = selectedEndpoint?.explorer || ''; - const handleClose = () => { setOpenDenomInfoModal(false); }; @@ -55,18 +52,18 @@ export const DenomInfoModal: React.FC<{
@@ -88,13 +85,13 @@ export const DenomInfoModal: React.FC<{ })() : '' } - explorerUrl={explorerUrl} + explorerUrl={env.explorerUrl} isAddress={true} /> diff --git a/components/factory/modals/updateDenomMetadata.tsx b/components/factory/modals/updateDenomMetadata.tsx index 0dfed590..eda5187c 100644 --- a/components/factory/modals/updateDenomMetadata.tsx +++ b/components/factory/modals/updateDenomMetadata.tsx @@ -2,12 +2,12 @@ import { TokenFormData } from '@/helpers/formReducer'; import { useFeeEstimation } from '@/hooks/useFeeEstimation'; import { useTx } from '@/hooks/useTx'; import { osmosis } from '@liftedinit/manifestjs'; -import { chainName } from '@/config'; import { Formik, Form } from 'formik'; import Yup from '@/utils/yupExtensions'; import { TextInput, TextArea } from '@/components/react/inputs'; import { truncateString, ExtendedMetadataSDKType } from '@/utils'; import { useEffect } from 'react'; +import env from '@/config/env'; import { createPortal } from 'react-dom'; const TokenDetailsSchema = (context: { subdenom: string }) => @@ -80,8 +80,8 @@ export function UpdateDenomMetadataModal({ exponent: '6', label: fullDenom, }; - const { tx, isSigning, setIsSigning } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); + const { tx, isSigning, setIsSigning } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); const { setDenomMetadata } = osmosis.tokenfactory.v1beta1.MessageComposer.withTypeUrl; const handleUpdate = async (values: TokenFormData, resetForm: () => void) => { diff --git a/components/groups/components/__tests__/myGroups.test.tsx b/components/groups/components/__tests__/myGroups.test.tsx index 2b94b379..74f8f814 100644 --- a/components/groups/components/__tests__/myGroups.test.tsx +++ b/components/groups/components/__tests__/myGroups.test.tsx @@ -29,6 +29,13 @@ mock.module('@/hooks/useQueries', () => ({ isBalanceLoading: false, isBalanceError: false, }), + useGetFilteredTxAndSuccessfulProposals: jest.fn().mockReturnValue({ + sendTxs: [], + totalPages: 1, + isLoading: false, + isError: false, + refetch: jest.fn(), + }), })); mock.module('@/hooks/useIsMobile', () => ({ diff --git a/components/groups/components/myGroups.tsx b/components/groups/components/myGroups.tsx index 1fdd1c51..6e584ef3 100644 --- a/components/groups/components/myGroups.tsx +++ b/components/groups/components/myGroups.tsx @@ -26,8 +26,7 @@ import { GroupInfo } from '../modals/groupInfo'; import { MemberManagementModal } from '../modals/memberManagementModal'; import { useChain } from '@cosmos-kit/react'; import useIsMobile from '@/hooks/useIsMobile'; -import { chainName } from '@/config'; -import { useEndpointStore } from '@/store/endpointStore'; +import env from '@/config/env'; export function YourGroups({ groups, @@ -110,9 +109,6 @@ export function YourGroups({ refetchBalances: resolveRefetch, } = useTokenBalancesResolved(address ?? ''); - const { selectedEndpoint } = useEndpointStore(); - const indexerUrl = selectedEndpoint?.indexer || ''; - const { metadatas, isMetadatasLoading } = useTokenFactoryDenomsMetadata(); const [currentPageGroupInfo, setCurrentPageGroupInfo] = useState(1); @@ -128,7 +124,7 @@ export function YourGroups({ isError, refetch: refetchHistory, } = useGetFilteredTxAndSuccessfulProposals( - indexerUrl, + env.indexerUrl, selectedGroup?.policyAddress ?? '', currentPageGroupInfo, pageSizeHistory diff --git a/components/groups/modals/groupInfo.tsx b/components/groups/modals/groupInfo.tsx index f962ff5d..a228d9e1 100644 --- a/components/groups/modals/groupInfo.tsx +++ b/components/groups/modals/groupInfo.tsx @@ -1,11 +1,11 @@ import { TruncatedAddressWithCopy } from '@/components/react/addressCopy'; import ProfileAvatar from '@/utils/identicon'; import { ExtendedGroupType } from '@/hooks/useQueries'; -import { UpdateGroupModal } from './updateGroupModal'; +import { UpdateGroupModal } from '@/components'; import { ThresholdDecisionPolicySDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/types'; import { useFeeEstimation, useTx } from '@/hooks'; -import { chainName } from '@/config'; import { cosmos } from '@liftedinit/manifestjs'; +import env from '@/config/env'; import { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; @@ -39,9 +39,9 @@ export function GroupInfo({ return () => document.removeEventListener('keydown', handleEscape); }, [showInfoModal, setShowInfoModal]); const [showUpdateModal, setShowUpdateModal] = useState(false); - const { tx, isSigning, setIsSigning } = useTx(chainName); + const { tx, isSigning, setIsSigning } = useTx(env.chain); const { leaveGroup } = cosmos.group.v1.MessageComposer.withTypeUrl; - const { estimateFee } = useFeeEstimation(chainName); + const { estimateFee } = useFeeEstimation(env.chain); if (!group || !group.policies || group.policies.length === 0) return null; const policy = group.policies[0]; diff --git a/components/groups/modals/memberManagementModal.tsx b/components/groups/modals/memberManagementModal.tsx index 3b073173..7502075d 100644 --- a/components/groups/modals/memberManagementModal.tsx +++ b/components/groups/modals/memberManagementModal.tsx @@ -5,12 +5,12 @@ import { createPortal } from 'react-dom'; import * as Yup from 'yup'; import { cosmos } from '@liftedinit/manifestjs'; import { useTx, useFeeEstimation } from '@/hooks'; -import { chainName } from '@/config'; import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any'; import { MemberSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/types'; import { CopyIcon, TrashIcon } from '@/components/icons'; import { MdContacts } from 'react-icons/md'; import { TailwindModal } from '@/components/react/modal'; +import env from '@/config/env'; interface ExtendedMember extends MemberSDKType { isNew: boolean; @@ -50,8 +50,8 @@ export function MemberManagementModal({ document.addEventListener('keydown', handleEscape); return () => document.removeEventListener('keydown', handleEscape); }, [showMemberManagementModal]); - const { tx, isSigning, setIsSigning } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); + const { tx, isSigning, setIsSigning } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); const validationSchema = Yup.object().shape({ members: Yup.array().of( diff --git a/components/groups/modals/updateGroupModal.tsx b/components/groups/modals/updateGroupModal.tsx index 77d5c7bb..51e0b810 100644 --- a/components/groups/modals/updateGroupModal.tsx +++ b/components/groups/modals/updateGroupModal.tsx @@ -3,15 +3,14 @@ import { Formik, Form } from 'formik'; import Yup from '@/utils/yupExtensions'; import { TextInput, TextArea, NumberInput } from '@/components/react/inputs'; -import { useTx, useFeeEstimation } from '@/hooks'; -import { chainName } from '@/config'; +import { useTx, useFeeEstimation, ExtendedGroupType } from '@/hooks'; import { ThresholdDecisionPolicy, ThresholdDecisionPolicySDKType, } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/types'; import { cosmos } from '@liftedinit/manifestjs'; import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any'; -import { ExtendedGroupType } from '@/hooks'; +import env from '@/config/env'; import { createPortal } from 'react-dom'; export function UpdateGroupModal({ @@ -29,8 +28,8 @@ export function UpdateGroupModal({ showUpdateModal: boolean; setShowUpdateModal: (show: boolean) => void; }) { - const { tx, isSigning, setIsSigning } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); + const { tx, isSigning, setIsSigning } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); const maybeIpfsMetadata = group?.ipfsMetadata; const maybeTitle = maybeIpfsMetadata?.title ?? ''; diff --git a/components/groups/modals/voteDetailsModal.tsx b/components/groups/modals/voteDetailsModal.tsx index 00076284..93e12b80 100644 --- a/components/groups/modals/voteDetailsModal.tsx +++ b/components/groups/modals/voteDetailsModal.tsx @@ -16,7 +16,6 @@ import VotingPopup from './voteModal'; import { ApexOptions } from 'apexcharts'; import { useChain } from '@cosmos-kit/react'; -import { chainName } from '@/config'; import { useTx } from '@/hooks/useTx'; import { cosmos } from '@liftedinit/manifestjs'; import { useTheme } from '@/contexts/theme'; @@ -25,6 +24,7 @@ import { useFeeEstimation } from '@/hooks'; import { TrashIcon, CheckIcon } from '@heroicons/react/24/outline'; import { ArrowUpIcon, CopyIcon } from '@/components/icons'; +import env from '@/config/env'; const Chart = dynamic(() => import('react-apexcharts'), { ssr: false, }) as any; @@ -68,8 +68,7 @@ function VoteDetailsModal({ [votes] ); - const { address } = useChain(chainName); - + const { address } = useChain(env.chain); const { theme } = useTheme(); const textColor = theme === 'dark' ? '#FFFFFF' : '#161616'; @@ -196,8 +195,8 @@ function VoteDetailsModal({ enabled: false, }, }; - const { tx, isSigning, setIsSigning } = useTx(chainName); - const { estimateFee } = useFeeEstimation(chainName); + const { tx, isSigning, setIsSigning } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); const { exec } = cosmos.group.v1.MessageComposer.withTypeUrl; const { withdrawProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; diff --git a/components/react/endpointSelector.tsx b/components/react/endpointSelector.tsx deleted file mode 100644 index c2273413..00000000 --- a/components/react/endpointSelector.tsx +++ /dev/null @@ -1,512 +0,0 @@ -import React, { useState } from 'react'; -import { useQuery } from '@tanstack/react-query'; -import { Formik, Form } from 'formik'; -import dynamic from 'next/dynamic'; - -import { useToast } from '@/contexts'; -import { useEndpointStore } from '@/store/endpointStore'; -import { TextInput } from './inputs'; -import Yup from '@/utils/yupExtensions'; - -export interface Endpoint { - rpc: string; - api: string; - provider: string; - isHealthy: boolean; - network: 'mainnet' | 'testnet'; - custom: boolean; -} - -const validateRPCEndpoint = async (url: string) => { - console.log('Validating RPC endpoint:', url); - if (!url) return false; - try { - const endpoint = url.startsWith('http') ? url : `https://${url}`; - console.log('Making RPC request to:', endpoint); - - const response = await fetch(endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 1, - method: 'status', - params: [], - }), - }); - - console.log('RPC Response status:', response.status); - if (!response.ok) { - console.log('RPC Response not ok'); - return false; - } - - const data = await response.json(); - console.log('RPC Response data:', data); - - return data?.result?.sync_info?.catching_up === false; - } catch (error) { - console.error('RPC Validation error:', error); - return false; - } -}; - -const validateAPIEndpoint = async (url: string) => { - console.log('Validating API endpoint:', url); - if (!url) return false; - try { - const endpoint = url.startsWith('http') ? url : `https://${url}`; - const baseUrl = endpoint.endsWith('/') ? endpoint.slice(0, -1) : endpoint; - const apiUrl = `${baseUrl}/cosmos/base/tendermint/v1beta1/syncing`; - - console.log('Making API request to:', apiUrl); - - const response = await fetch(apiUrl); - console.log('API Response status:', response.status); - - if (!response.ok) { - console.log('API Response not ok'); - return false; - } - - return response.ok; - } catch (error) { - console.error('API Validation error:', error); - return false; - } -}; - -const validateIndexerEndpoint = async (url: string) => { - console.log('Validating Indexer endpoint:', url); - if (!url) return false; - try { - const endpoint = url.startsWith('http') ? url : `https://${url}`; - const baseUrl = endpoint.endsWith('/') ? endpoint.slice(0, -1) : endpoint; - const indexerUrl = `${baseUrl}`; - - console.log('Making Indexer request to:', indexerUrl); - - const response = await fetch(indexerUrl); - console.log('Indexer Response status:', response.status); - - if (!response.ok) { - console.log('Indexer Response not ok'); - return false; - } - - return response.ok; - } catch (error) { - console.error('Indexer Validation error:', error); - return false; - } -}; - -const validateExplorerEndpoint = async (url: string) => { - console.log('Validating Explorer endpoint:', url); - if (!url) return false; - try { - // const endpoint = url.startsWith('http') ? url : `https://${url}`; - // const baseUrl = endpoint.endsWith('/') ? endpoint.slice(0, -1) : endpoint; - // const explorerUrl = `${baseUrl}`; - - // console.log('Making Explorer request to:', explorerUrl); - // - // const response = await fetch(explorerUrl); - // console.log('Explorer Response status:', response.status); - // - // if (!response.ok) { - // console.log('Explorer Response not ok'); - // return false; - // } - // - // return response.ok; - return true; - } catch (error) { - console.error('Explorer Validation error:', error); - return false; - } -}; - -const EndpointSchema = Yup.object().shape({ - rpc: Yup.string().required('RPC endpoint is required').test({ - name: 'rpc-validation', - message: 'RPC endpoint is not responding', - test: validateRPCEndpoint, - }), - api: Yup.string().required('API endpoint is required').test({ - name: 'api-validation', - message: 'API endpoint is not responding', - test: validateAPIEndpoint, - }), - indexer: Yup.string().required('Indexer endpoint is required').test({ - name: 'indexer-validation', - message: 'Indexer endpoint is not responding', - test: validateIndexerEndpoint, - }), - explorer: Yup.string().required('Explorer endpoint is required').test({ - name: 'explorer-validation', - message: 'Explorer endpoint is not responding', - test: validateExplorerEndpoint, - }), -}); - -function SSREndpointSelector() { - const { - endpoints, - selectedEndpointKey, - setSelectedEndpointKey, - addEndpoint, - removeEndpoint, - updateEndpointHealth, - } = useEndpointStore(state => ({ - endpoints: state.endpoints, - selectedEndpointKey: state.selectedEndpointKey, - setSelectedEndpointKey: state.setSelectedEndpointKey, - addEndpoint: state.addEndpoint, - removeEndpoint: state.removeEndpoint, - updateEndpointHealth: state.updateEndpointHealth, - })); - - const { setToastMessage } = useToast(); - const [isModalOpen, setIsModalOpen] = useState(false); - const [endpointToRemove, setEndpointToRemove] = useState(null); - - const { isLoading, error } = useQuery({ - queryKey: ['checkEndpoints', endpoints], - queryFn: updateEndpointHealth, - refetchInterval: 30000, - enabled: true, - }); - - const handleCustomEndpointSubmit = async (values: { - rpc: string; - api: string; - indexer: string; - explorer: string; - }) => { - const rpcUrl = values.rpc.startsWith('http') ? values.rpc : `https://${values.rpc}`; - const apiUrl = values.api.startsWith('http') ? values.api : `https://${values.api}`; - const indexerUrl = values.indexer.startsWith('http') - ? values.indexer - : `https://${values.indexer}`; - const explorerUrl = values.explorer.startsWith('http') - ? values.explorer - : `https://${values.explorer}`; - - try { - const [isRPCValid, isAPIValid] = await Promise.all([ - validateRPCEndpoint(rpcUrl), - validateAPIEndpoint(apiUrl), - ]); - - if (!isRPCValid || !isAPIValid) { - throw new Error('Endpoint validation failed'); - } - - await addEndpoint(rpcUrl, apiUrl, indexerUrl, explorerUrl); - setToastMessage({ - type: 'alert-success', - title: 'Custom endpoint added', - description: 'The new endpoint has been successfully added.', - bgColor: '#2ecc71', - }); - } catch (error) { - console.error('Error adding custom endpoint:', error); - let errorMessage = 'An unknown error occurred while adding the endpoint.'; - - if (error instanceof Error) { - if (error.message.includes('Invalid URL')) { - errorMessage = 'Invalid URL format. Please check all URLs.'; - } else if (error.message.includes('Network error')) { - errorMessage = 'Network error. Please check your internet connection and try again.'; - } else if (error.message.includes('Timeout')) { - errorMessage = 'Connection timeout. The endpoint might be unreachable.'; - } else { - errorMessage = error.message; - } - } - - setToastMessage({ - type: 'alert-error', - title: 'Error adding custom endpoint', - description: errorMessage, - bgColor: '#e74c3c', - }); - throw error; - } - }; - - const truncateUrl = (url: string) => { - try { - const ipRegex = /^(?:\d{1,3}\.){3}\d{1,3}(?::\d+)?$/; - if (!url.startsWith('http://') && !url.startsWith('https://')) { - url = ipRegex.test(url) ? `http://${url}` : `https://${url}`; - } - const parsedUrl = new URL(url); - return `${parsedUrl.host}`; - } catch (error) { - console.error('Invalid URL:', url); - return url; - } - }; - - const isCustomEndpoint = (endpoint: Endpoint) => endpoint.custom; - - return ( - -
-

- Available Endpoints -

- -
- {isLoading ? ( -

Checking endpoints...

- ) : error ? ( -

Error checking endpoints

- ) : ( -
    - {endpoints.map((endpoint: Endpoint, index: number) => ( -
  • setSelectedEndpointKey(endpoint.provider)} - > -
    -
    -
    -
    -

    - {endpoint.custom ? `Custom (${endpoint.network})` : endpoint.provider} -

    -
    -

    - {truncateUrl(endpoint.rpc)} -

    -
    - - {isCustomEndpoint(endpoint) && ( - - )} -
    -
  • - ))} -
- )} -
- - -
- - - - - - {isModalOpen && ( -
-
-

- Add Custom Endpoint -

- -
- { - try { - await handleCustomEndpointSubmit(values); - resetForm(); - setIsModalOpen(false); - } catch (error) { - console.error(error); - } finally { - setSubmitting(false); - } - }} - > - {({ isSubmitting, isValid }) => ( -
-
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- - -
-
- )} -
-
-
-
- )} -
- ); -} - -const EndpointSelector = dynamic(() => Promise.resolve(SSREndpointSelector), { - ssr: false, -}); - -export default EndpointSelector; diff --git a/components/react/mobileNav.tsx b/components/react/mobileNav.tsx index a5e3872d..7fa269f4 100644 --- a/components/react/mobileNav.tsx +++ b/components/react/mobileNav.tsx @@ -16,6 +16,7 @@ import { WalletSection } from '../wallet'; import { RiMenuUnfoldFill } from 'react-icons/ri'; import { useState } from 'react'; import { MdOutlineNetworkPing, MdContacts } from 'react-icons/md'; +import env from '@/config/env'; export default function MobileNav() { const closeDrawer = () => { @@ -60,7 +61,12 @@ export default function MobileNav() {
logo - Alberto +
+

Alberto

+ {env.chainTier === 'mainnet' ? null : ( +

{env.chainTier}

+ )} +
{/* Updated Theme Toggle */} @@ -87,21 +93,7 @@ export default function MobileNav() {
- {/* Added Endpoint Selector and Contacts buttons */} -
  • - -
  • + {/* Added Contacts button */}
    • @@ -156,17 +150,6 @@ export default function SideNav({ isDrawerVisible, setDrawerVisible }: SideNavPr
    - - {/* Browser and Social sections - browaer hidden on mobile/tablet */} -
    +
    {browser.map(({ walletInfo: { name, prettyName, logo } }) => (
    @@ -250,8 +215,6 @@ function ManifestApp({ Component, pageProps }: ManifestAppProps) {
    - - {/* Web3auth signing modal */} {isBrowser && createPortal( @@ -267,7 +230,7 @@ function ManifestApp({ Component, pageProps }: ManifestAppProps) { - )} + } ); diff --git a/pages/bank.tsx b/pages/bank.tsx index b7391304..7fe5adbe 100644 --- a/pages/bank.tsx +++ b/pages/bank.tsx @@ -1,5 +1,5 @@ -import { WalletNotConnected } from '@/components'; -import { chainName } from '@/config'; +import { WalletNotConnected, HistoryBox } from '@/components'; +import { TokenList } from '@/components/bank/components/tokenList'; import { useGetFilteredTxAndSuccessfulProposals, useIsMobile, @@ -7,18 +7,16 @@ import { useTokenBalancesResolved, useTokenFactoryDenomsMetadata, } from '@/hooks'; - import { useChain } from '@cosmos-kit/react'; import Head from 'next/head'; import React, { useMemo, useState } from 'react'; -import { HistoryBox, TokenList } from '@/components'; import { BankIcon } from '@/components/icons'; import { CombinedBalanceInfo } from '@/utils/types'; import { MFX_TOKEN_DATA } from '@/utils/constants'; -import { useEndpointStore } from '@/store/endpointStore'; // Import MFX_TOKEN_DATA +import env from '@/config/env'; export default function Bank() { - const { address, isWalletConnected } = useChain(chainName); + const { address, isWalletConnected } = useChain(env.chain); const { balances, isBalancesLoading, refetchBalances } = useTokenBalances(address ?? ''); const { balances: resolvedBalances, @@ -26,9 +24,6 @@ export default function Bank() { refetchBalances: resolveRefetch, } = useTokenBalancesResolved(address ?? ''); - const { selectedEndpoint } = useEndpointStore(); - const indexerUrl = selectedEndpoint?.indexer || ''; - const { metadatas, isMetadatasLoading } = useTokenFactoryDenomsMetadata(); const [currentPage, setCurrentPage] = useState(1); @@ -45,7 +40,7 @@ export default function Bank() { isLoading: txLoading, isError, refetch: refetchHistory, - } = useGetFilteredTxAndSuccessfulProposals(indexerUrl, address ?? '', currentPage, pageSize); + } = useGetFilteredTxAndSuccessfulProposals(env.indexerUrl, address ?? '', currentPage, pageSize); const combinedBalances = useMemo(() => { if (!balances || !resolvedBalances || !metadatas) return []; diff --git a/pages/factory/create.tsx b/pages/factory/create.tsx index 01fc215e..13587d98 100644 --- a/pages/factory/create.tsx +++ b/pages/factory/create.tsx @@ -2,15 +2,14 @@ import React, { useState, useReducer } from 'react'; import { tokenFormDataReducer, TokenFormData } from '@/helpers/formReducer'; import ConfirmationForm from '@/components/factory/forms/ConfirmationForm'; import TokenDetails from '@/components/factory/forms/TokenDetailsForm'; -import { Duration } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/duration'; import StepIndicator from '@/components/react/StepIndicator'; import { useChain } from '@cosmos-kit/react'; -import { chainName } from '@/config'; -import { WalletNotConnected, WalletSection } from '@/components'; +import { WalletNotConnected } from '@/components'; import Success from '@/components/factory/forms/Success'; import Head from 'next/head'; import CreateDenom from '@/components/factory/forms/CreateDenom'; import { FactoryIcon } from '@/components/icons'; +import env from '@/config/env'; const initialFormData: TokenFormData = { subdenom: '', @@ -30,7 +29,7 @@ export default function CreateToken() { const [currentStep, setCurrentStep] = useState(1); const [formData, dispatch] = useReducer(tokenFormDataReducer, initialFormData); - const { address } = useChain(chainName); + const { address, isWalletConnected } = useChain(env.chain); const nextStep = () => { if (currentStep < 4) { setCurrentStep(currentStep + 1); @@ -43,8 +42,6 @@ export default function CreateToken() { } }; - const { isWalletConnected } = useChain(chainName); - const steps = [ { label: 'Create Denom', mobileLabel: 'Denom', step: 1 }, { label: 'Token Metadata', mobileLabel: 'Metadata', step: 2 }, diff --git a/pages/factory/index.tsx b/pages/factory/index.tsx index 9a9b0628..9110b723 100644 --- a/pages/factory/index.tsx +++ b/pages/factory/index.tsx @@ -9,14 +9,12 @@ import { import { useChain } from '@cosmos-kit/react'; import Head from 'next/head'; - import React, { useMemo } from 'react'; -import { chainName } from '@/config'; - import { ExtendedMetadataSDKType } from '@/utils'; +import env from '@/config/env'; export default function Factory() { - const { address, isWalletConnected } = useChain(chainName); + const { address, isWalletConnected } = useChain(env.chain); const { denoms, isDenomsLoading, isDenomsError, refetchDenoms } = useTokenFactoryDenomsFromAdmin( address ?? '' ); diff --git a/pages/groups/create.tsx b/pages/groups/create.tsx index dc3e4ca4..e924a796 100644 --- a/pages/groups/create.tsx +++ b/pages/groups/create.tsx @@ -7,11 +7,10 @@ import MemberInfoForm from '@/components/groups/forms/groups/MemberInfoForm'; import { Duration } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/duration'; import StepIndicator from '@/components/react/StepIndicator'; import { useChain } from '@cosmos-kit/react'; -import { chainName } from '@/config'; -import { WalletNotConnected, WalletSection } from '@/components'; +import { WalletNotConnected, GroupsIcon } from '@/components'; import Success from '@/components/groups/forms/groups/Success'; import Head from 'next/head'; -import { GroupsIcon } from '@/components'; +import env from '@/config/env'; const initialFormData: FormData = { title: '', @@ -25,8 +24,7 @@ const initialFormData: FormData = { export default function CreateGroup() { const [currentStep, setCurrentStep] = useState(1); const [formData, dispatch] = useReducer(formDataReducer, initialFormData); - const { address } = useChain(chainName); - console.log(formData); + const { address, isWalletConnected } = useChain(env.chain); const nextStep = () => { if (currentStep < 5) { setCurrentStep(currentStep + 1); @@ -39,8 +37,6 @@ export default function CreateGroup() { } }; - const { isWalletConnected } = useChain(chainName); - const steps = [ { label: 'Details', step: 1 }, { label: 'Members', step: 2 }, diff --git a/pages/groups/index.tsx b/pages/groups/index.tsx index da155366..15316371 100644 --- a/pages/groups/index.tsx +++ b/pages/groups/index.tsx @@ -1,16 +1,15 @@ -import { WalletNotConnected } from '@/components'; +import { WalletNotConnected, GroupsIcon } from '@/components'; import { YourGroups } from '@/components/groups/components/myGroups'; import { GroupInfo } from '@/components/groups/modals/groupInfo'; import { useChain } from '@cosmos-kit/react'; import Head from 'next/head'; import Link from 'next/link'; import React, { useState } from 'react'; -import { chainName } from '../../config'; -import { useGroupsByMember, useProposalsByPolicyAccountAll } from '../../hooks/useQueries'; -import { GroupsIcon } from '@/components'; +import { useGroupsByMember, useProposalsByPolicyAccountAll } from '@/hooks'; +import env from '@/config/env'; export default function Groups() { - const { address, isWalletConnected } = useChain(chainName); + const { address, isWalletConnected } = useChain(env.chain); const { groupByMemberData, isGroupByMemberLoading, isGroupByMemberError, refetchGroupByMember } = useGroupsByMember(address ?? ''); diff --git a/happydom.ts b/setup.ts similarity index 60% rename from happydom.ts rename to setup.ts index 1512a7e6..7aecc0d2 100644 --- a/happydom.ts +++ b/setup.ts @@ -1,3 +1,5 @@ import { GlobalRegistrator } from '@happy-dom/global-registrator'; +import { config } from 'dotenv'; GlobalRegistrator.register(); +config({ path: '.env.test' }); diff --git a/store/endpointStore.ts b/store/endpointStore.ts deleted file mode 100644 index fba5b542..00000000 --- a/store/endpointStore.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { create } from 'zustand'; -import { persist } from 'zustand/middleware'; - -interface Endpoint { - rpc: string; - api: string; - provider: string; - isHealthy: boolean; - network: 'mainnet' | 'testnet'; - custom: boolean; - indexer: string; - explorer: string; -} - -interface EndpointState { - endpoints: Endpoint[]; - selectedEndpointKey: string; - selectedEndpoint: Endpoint | null; - setEndpoints: (endpoints: Endpoint[]) => void; - setSelectedEndpointKey: (key: string) => void; - addEndpoint: (rpc: string, api: string, indexer: string, explorer: string) => Promise; - removeEndpoint: (provider: string) => void; - updateEndpointHealth: () => Promise; -} - -const defaultEndpoints: Endpoint[] = [ - { - rpc: process.env.NEXT_PUBLIC_MAINNET_RPC_URL || '', - api: process.env.NEXT_PUBLIC_MAINNET_API_URL || '', - provider: 'Mainnet', - isHealthy: true, - network: 'mainnet', - custom: false, - indexer: process.env.NEXT_PUBLIC_MAINNET_INDEXER_URL || '', - explorer: process.env.NEXT_PUBLIC_MAINNET_EXPLORER_URL || '', - }, - { - rpc: process.env.NEXT_PUBLIC_TESTNET_RPC_URL || '', - api: process.env.NEXT_PUBLIC_TESTNET_API_URL || '', - provider: 'Testnet', - isHealthy: true, - network: 'testnet', - custom: false, - indexer: process.env.NEXT_PUBLIC_TESTNET_INDEXER_URL || '', - explorer: process.env.NEXT_PUBLIC_TESTNET_EXPLORER_URL || '', - }, -]; - -export const useEndpointStore = create( - persist( - (set, get) => ({ - endpoints: defaultEndpoints, - selectedEndpointKey: 'Mainnet', - selectedEndpoint: defaultEndpoints[0], - setEndpoints: endpoints => set({ endpoints }), - setSelectedEndpointKey: key => { - const endpoint = get().endpoints.find(e => e.provider === key); - set({ selectedEndpointKey: key, selectedEndpoint: endpoint || null }); - }, - addEndpoint: async (rpc: string, api: string, indexer: string, explorer: string) => { - try { - const rpcResponse = await fetch(`${rpc.trim()}/status`); - const rpcData = await rpcResponse.json(); - - const network = - rpcData.result.node_info.network === - (process.env.NEXT_PUBLIC_CHAIN_ID || process.env.NEXT_PUBLIC_TESTNET_CHAIN_ID) - ? 'mainnet' - : 'testnet'; - - const newEndpoint: Endpoint = { - rpc: rpc.trim(), - api: api.trim(), - provider: `Custom (${network})`, - isHealthy: true, - network, - custom: true, - indexer: indexer.trim(), - explorer: explorer.trim(), - }; - - const { endpoints } = get(); - set({ - endpoints: [...endpoints, newEndpoint], - }); - } catch (error) { - console.error('Error in addEndpoint:', error); - throw error; - } - }, - removeEndpoint: provider => { - const { endpoints, selectedEndpointKey } = get(); - const newEndpoints = endpoints.filter(e => e.provider !== provider); - set({ endpoints: newEndpoints }); - if (selectedEndpointKey === provider) { - const newSelectedEndpoint = newEndpoints[0]; - set({ - selectedEndpointKey: newSelectedEndpoint.provider, - selectedEndpoint: newSelectedEndpoint, - }); - } - }, - updateEndpointHealth: async () => { - const { endpoints } = get(); - const updatedEndpoints = await Promise.all( - endpoints.map(async endpoint => { - try { - const rpcResponse = await fetch(endpoint.rpc.trim(), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: 1, - method: 'status', - params: [], - }), - }); - - const baseApiUrl = endpoint.api.trim().endsWith('/') - ? endpoint.api.trim().slice(0, -1) - : endpoint.api.trim(); - const apiResponse = await fetch( - `${baseApiUrl}/cosmos/base/tendermint/v1beta1/syncing` - ); - - const rpcData = await rpcResponse.json(); - const isHealthy = - rpcResponse.ok && - apiResponse.ok && - rpcData?.result?.sync_info?.catching_up === false; - - return { - ...endpoint, - isHealthy, - }; - } catch (error) { - console.error('Error checking endpoint health:', error); - return { - ...endpoint, - isHealthy: false, - }; - } - }) - ); - set({ endpoints: updatedEndpoints }); - return updatedEndpoints; - }, - }), - { - name: 'endpoint-storage', - getStorage: () => localStorage, - } - ) -); diff --git a/utils/logos.ts b/utils/logos.ts index fe4d39e4..29d0f3d8 100644 --- a/utils/logos.ts +++ b/utils/logos.ts @@ -1,45 +1,5 @@ import { ExtendedValidatorSDKType } from '@/components'; -type ImageSource = { - imageSource: 'cosmostation' | 'keybase'; -}; - -export const splitIntoChunks = (arr: any[], chunkSize: number) => { - const res = []; - for (let i = 0; i < arr.length; i += chunkSize) { - const chunk = arr.slice(i, i + chunkSize); - res.push(chunk); - } - return res; -}; - -export const convertChainName = (chainName: string) => { - if (chainName.endsWith('testnet')) { - return chainName.replace('testnet', '-testnet'); - } - - switch (chainName) { - case 'cosmoshub': - return 'cosmos'; - case 'assetmantle': - return 'asset-mantle'; - case 'cryptoorgchain': - return 'crypto-org'; - case 'dig': - return 'dig-chain'; - case 'gravitybridge': - return 'gravity-bridge'; - case 'kichain': - return 'ki-chain'; - case 'oraichain': - return 'orai-chain'; - case 'terra': - return 'terra-classic'; - default: - return chainName; - } -}; - export const getRealLogo = (logo: string, flip?: boolean) => { const isDarkMode = document.documentElement.classList.contains('dark'); const localAndHasExtension = /^\/(?!.*\.[0-9a-z]+$)/i.test(logo); @@ -49,38 +9,9 @@ export const getRealLogo = (logo: string, flip?: boolean) => { : logo?.toString() + '_dark.svg' : logo; }; - -export const isUrlValid = async (url: string) => { - const res = await fetch(url, { - method: 'HEAD', - }); - const contentType = res?.headers?.get('Content-Type') || ''; - return contentType.startsWith('image'); -}; - -export const getCosmostationUrl = (chainName: string, validatorAddr: string) => { - const env = process.env.NEXT_PUBLIC_CHAIN_ENV; - const convertedChainName = convertChainName(chainName); - const dynamicChainName = env === 'testnet' ? `${convertedChainName}-testnet` : convertedChainName; - return `https://raw.githubusercontent.com/cosmostation/chainlist/main/chain/${dynamicChainName}/moniker/${validatorAddr}.png`; -}; - export const getKeybaseUrl = (identity: string) => { return `https://keybase.io/_/api/1.0/user/lookup.json?key_suffix=${identity}&fields=pictures`; }; - -export const addLogoUrlSource = async ( - validator: ExtendedValidatorSDKType, - chainName: string -): Promise => { - const url = getCosmostationUrl(chainName, validator.operator_address); - const isValid = await isUrlValid(url); - return { - ...validator, - imageSource: isValid ? 'cosmostation' : 'keybase', - }; -}; - export const getLogoUrlForValidator = async ( validator: ExtendedValidatorSDKType ): Promise<{ url: string }> => { diff --git a/utils/voting.ts b/utils/voting.ts index d4694b57..c0c5525a 100644 --- a/utils/voting.ts +++ b/utils/voting.ts @@ -1,61 +1,15 @@ -import { AssetList, Asset } from '@chain-registry/types'; -import BigNumber from 'bignumber.js'; +import { AssetList } from '@chain-registry/types'; import { assets } from 'chain-registry'; -import dayjs from 'dayjs'; -import { Proposal, ProposalStatus } from '@liftedinit/manifestjs/dist/codegen/cosmos/gov/v1/gov'; - -export const parseProposals = (proposals: Proposal[]) => { - const sortedProposal = proposals.sort((a, b) => Number(b.id) - Number(a.id)); - const votingProposals = sortedProposal.filter( - ({ status }) => status === ProposalStatus.PROPOSAL_STATUS_VOTING_PERIOD - ); - const nonVotingProposals = sortedProposal.filter( - ({ status }) => status !== ProposalStatus.PROPOSAL_STATUS_VOTING_PERIOD - ); - - return [...votingProposals, ...nonVotingProposals]; -}; export const decodeUint8Arr = (uint8array: Uint8Array | undefined) => { if (!uint8array) return ''; return new TextDecoder('utf-8').decode(uint8array); }; - -export const parseQuorum = (quorum: Uint8Array | undefined) => { - const quorumStr = decodeUint8Arr(quorum); - return new BigNumber(quorumStr).shiftedBy(-quorumStr.length).toNumber(); -}; - export const getChainAssets = (chainName: string) => { return assets.find(chain => chain.chain_name === chainName) as AssetList; }; export const getCoin = (chainName: string) => { const chainAssets = getChainAssets(chainName); - return chainAssets?.assets[0] as Asset; -}; - -export const getExponent = (chainName: string) => { - return getCoin(chainName).denom_units.find(unit => unit.denom === getCoin(chainName).display) - ?.exponent as number; -}; - -export const exponentiate = (num: number | string | undefined, exp: number) => { - if (!num) return 0; - return new BigNumber(num).multipliedBy(new BigNumber(10).exponentiatedBy(exp)).toNumber(); -}; - -export const formatDate = (date: Date | undefined) => { - if (!date) return 'not specified'; - return dayjs(date).format('YYYY-MM-DD hh:mm:ss'); -}; - -export const getTitleFromDecoded = (decodedStr: string) => { - return decodedStr.slice(0, 250).match(/[A-Z][A-Za-z].*(?=\u0012)/)?.[0]; -}; - -export const getPercentage = (option: string | undefined, total: number) => { - if (!total) return '0.00%'; - const voted = option ? Number(option) : 0; - return ((voted / total) * 100).toFixed(2) + '%'; + return chainAssets?.assets[0]; };