diff --git a/.commitlintrc.json b/.commitlintrc.json index cc3116586..08b772fed 100644 --- a/.commitlintrc.json +++ b/.commitlintrc.json @@ -15,6 +15,7 @@ "deployment", "certificate", "dx", + "config", "stats" ] ] diff --git a/.dockerignore b/.dockerignore index 9c820219e..68d7baa79 100644 --- a/.dockerignore +++ b/.dockerignore @@ -12,7 +12,6 @@ apps/indexer/.env* apps/landing/.env* apps/provider-console/.env* apps/provider-proxy/.env* -apps/stats-web/.env* **/.next *.md diff --git a/.github/workflows/docker-build-stats-web.yml b/.github/workflows/docker-build-stats-web.yml index 9d822b542..13c13133e 100644 --- a/.github/workflows/docker-build-stats-web.yml +++ b/.github/workflows/docker-build-stats-web.yml @@ -24,4 +24,4 @@ jobs: - name: Build the Docker image if: steps.filter.outputs.stats-web == 'true' - run: npm run dc:build -- stats-web + run: npm run dc:build -- --build-arg DEPLOYMENT_ENV=production stats-web diff --git a/apps/api/deploy.yml b/apps/api/deploy.yml index 9067ad4c4..0f632041c 100644 --- a/apps/api/deploy.yml +++ b/apps/api/deploy.yml @@ -6,19 +6,8 @@ services: image: : depends-on: cloud-sql-proxy env: - - GITHUB_PAT= - - AKASH_SANDBOX_DATABASE_CS= - - USER_DATABASE_CS= - - SECRET_TOKEN= - - NETWORK= - - MASTER_WALLET_MNEMONIC= - - POSTGRES_DB_URI= - - ANONYMOUS_USER_TOKEN_SECRET= - - SENTRY_DSN= + - DOPPLER_TOKEN= - SENTRY_SERVER_NAME= - - DEPLOYMENT_ENV= - - STRIPE_SECRET_KEY= - - STRIPE_WEBHOOK_SECRET= expose: - port: 3080 as: 80 diff --git a/apps/api/package.json b/apps/api/package.json index f6e2f54bf..66f481867 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -19,6 +19,7 @@ "lint": "eslint .", "migrate": "node-pg-migrate", "migration:gen": "drizzle-kit generate", + "prod": "doppler run -- node dist/server.js", "start": "webpack --config webpack.dev.js --watch", "test": "jest --selectProjects unit functional", "test:cov": "jest --selectProjects unit functional --coverage", diff --git a/apps/deploy-web/package.json b/apps/deploy-web/package.json index 3872f06f2..e854ac4aa 100644 --- a/apps/deploy-web/package.json +++ b/apps/deploy-web/package.json @@ -26,6 +26,7 @@ "@cosmjs/encoding": "^0.32.4", "@cosmjs/proto-signing": "^0.32.4", "@cosmjs/stargate": "^0.32.4", + "@cosmos-kit/cosmos-extension-metamask": "0.10.0", "@cosmos-kit/cosmostation-extension": "^2.12.2", "@cosmos-kit/keplr": "^2.12.2", "@cosmos-kit/leap": "^2.12.2", diff --git a/apps/deploy-web/src/components/deployments/DeploymentLeaseShell.tsx b/apps/deploy-web/src/components/deployments/DeploymentLeaseShell.tsx index d78e77baf..7c92d7dcc 100644 --- a/apps/deploy-web/src/components/deployments/DeploymentLeaseShell.tsx +++ b/apps/deploy-web/src/components/deployments/DeploymentLeaseShell.tsx @@ -283,12 +283,7 @@ export const DeploymentLeaseShell: React.FunctionComponent = ({ leases }) {isConnectionClosed && ( - The connection to your Akash Console Shell was lost. ( - - More Info - - - ) + The connection to your Akash Console Shell was not established or lost. )} diff --git a/apps/deploy-web/src/components/layout/WalletStatus.tsx b/apps/deploy-web/src/components/layout/WalletStatus.tsx index f25c0b2f1..d62748973 100644 --- a/apps/deploy-web/src/components/layout/WalletStatus.tsx +++ b/apps/deploy-web/src/components/layout/WalletStatus.tsx @@ -23,6 +23,7 @@ import { browserEnvConfig } from "@src/config/browser-env.config"; import { useWallet } from "@src/context/WalletProvider"; import { useLoginRequiredEventHandler } from "@src/hooks/useLoginRequiredEventHandler"; import { useManagedEscrowFaqModal } from "@src/hooks/useManagedEscrowFaqModal"; +import { getSplitText } from "@src/hooks/useShortText"; import { useWalletBalance } from "@src/hooks/useWalletBalance"; import { udenomToDenom } from "@src/utils/mathHelpers"; import { uaktToAKT } from "@src/utils/priceUtils"; @@ -104,10 +105,15 @@ export function WalletStatus() { - {walletName} + {walletName?.length > 20 ? ( + {getSplitText(walletName, 4, 4)} + ) : ( + {walletName} + )} -
+
{walletName}
+
diff --git a/apps/deploy-web/src/components/remote-deploy/RemoteRepositoryDeployManager.tsx b/apps/deploy-web/src/components/remote-deploy/RemoteRepositoryDeployManager.tsx index 368fdce0f..75eae49c2 100644 --- a/apps/deploy-web/src/components/remote-deploy/RemoteRepositoryDeployManager.tsx +++ b/apps/deploy-web/src/components/remote-deploy/RemoteRepositoryDeployManager.tsx @@ -1,4 +1,4 @@ -import { Dispatch, useEffect, useState } from "react"; +import { Dispatch, useEffect, useMemo, useState } from "react"; import { Control, UseFormSetValue } from "react-hook-form"; import { Button, Spinner, Tabs, TabsContent, TabsList, TabsTrigger } from "@akashnetwork/ui/components"; import { Bitbucket, Github as GitIcon, GitlabFull } from "iconoir-react"; @@ -53,7 +53,7 @@ const RemoteRepositoryDeployManager = ({ const shouldResetValue = isRepoUrlDefault(services?.[0]?.env || []); - const envVarUpdater = new EnvVarUpdater(services); + const envVarUpdater = useMemo(() => new EnvVarUpdater(services), [services]); const { reLoginWithGithub, loginWithGithub } = new GitHubService(); diff --git a/apps/deploy-web/src/components/remote-deploy/Repos.tsx b/apps/deploy-web/src/components/remote-deploy/Repos.tsx index 5c50684b7..8894c304e 100644 --- a/apps/deploy-web/src/components/remote-deploy/Repos.tsx +++ b/apps/deploy-web/src/components/remote-deploy/Repos.tsx @@ -1,4 +1,4 @@ -import { Dispatch, useEffect, useState } from "react"; +import { Dispatch, useEffect, useMemo, useState } from "react"; import { UseFormSetValue } from "react-hook-form"; import { Button, @@ -64,10 +64,8 @@ const Repos = ({ const [directory, setDirectory] = useState(null); const [open, setOpen] = useState(false); const [accounts, setAccounts] = useState([]); - const envVarUpdater = new EnvVarUpdater(services); const repo = repos?.find(r => r.html_url === currentRepoUrl); const currentFolder = currentServiceEnv?.find(e => e.key === protectedEnvironmentVariables.FRONTEND_FOLDER); - const { currentFramework, isLoading: frameworkLoading } = useRemoteDeployFramework({ currentRepoUrl, currentBranchName, @@ -75,20 +73,18 @@ const Repos = ({ subFolder: currentFolder?.value, setCpus: (cpus: number) => setValue("services.0.profile.cpu", +cpus > 2 ? +cpus : 2) }); - const { isLoading: isGettingDirectory, isFetching: isGithubLoading } = useSrcFolders(setFolders, formatUrlWithoutInitialPath(currentRepoUrl)); const { isLoading: isGettingDirectoryBit, isFetching: isBitLoading } = useBitSrcFolders( setFolders, formatUrlWithoutInitialPath(currentRepoUrl), currentBranchName ); - const { isLoading: isGettingDirectoryGitlab, isFetching: isGitlabLoading } = useGitlabSrcFolders( setFolders, currentServiceEnv?.find(e => e.key === protectedEnvironmentVariables.GITLAB_PROJECT_ID)?.value ); - const isLoadingDirectories = isGithubLoading || isGitlabLoading || isBitLoading || isGettingDirectory || isGettingDirectoryBit || isGettingDirectoryGitlab; + const envVarUpdater = useMemo(() => new EnvVarUpdater(services), [services]); useEffect(() => { if (type === "github") { diff --git a/apps/deploy-web/src/components/remote-deploy/deployment-configurations/RemoteBuildInstallConfig.tsx b/apps/deploy-web/src/components/remote-deploy/deployment-configurations/RemoteBuildInstallConfig.tsx index 4136dfe5b..e3385987c 100644 --- a/apps/deploy-web/src/components/remote-deploy/deployment-configurations/RemoteBuildInstallConfig.tsx +++ b/apps/deploy-web/src/components/remote-deploy/deployment-configurations/RemoteBuildInstallConfig.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useMemo, useState } from "react"; import { UseFormSetValue } from "react-hook-form"; import { Card, CardContent, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, Label, Separator } from "@akashnetwork/ui/components"; import { cn } from "@akashnetwork/ui/utils"; @@ -12,7 +12,7 @@ import BoxTextInput from "../BoxTextInput"; const RemoteBuildInstallConfig = ({ services, setValue }: { services: ServiceType[]; setValue: UseFormSetValue }) => { const [expanded, setExpanded] = useState(false); const currentService = services[0]; - const envVarUpdater = new EnvVarUpdater(services); + const envVarUpdater = useMemo(() => new EnvVarUpdater(services), [services]); return ( (false); const { control, watch, setValue } = useForm({ defaultValues: { services: [defaultService] } }); const { fields: services } = useFieldArray({ control, name: "services", keyName: "id" }); - const envVarUpdater = new EnvVarUpdater(services); const { getTemplateById } = useTemplates(); const remoteDeployTemplate = getTemplateById(CI_CD_TEMPLATE_ID); + const envVarUpdater = useMemo(() => new EnvVarUpdater(services), [services]); useEffect(() => { const { unsubscribe }: any = watch(data => { diff --git a/apps/deploy-web/src/components/settings/CertificateList.tsx b/apps/deploy-web/src/components/settings/CertificateList.tsx index 10cd555b2..cb4259960 100644 --- a/apps/deploy-web/src/components/settings/CertificateList.tsx +++ b/apps/deploy-web/src/components/settings/CertificateList.tsx @@ -1,7 +1,7 @@ "use client"; -import { useState, useEffect } from "react"; +import { useEffect,useState } from "react"; import { FormattedDate } from "react-intl"; -import { Button, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, CustomPagination } from "@akashnetwork/ui/components"; +import { Button, CustomPagination,Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@akashnetwork/ui/components"; import { Check } from "iconoir-react"; import { ConnectWallet } from "@src/components/shared/ConnectWallet"; diff --git a/apps/deploy-web/src/context/CertificateProvider/CertificateProviderContext.tsx b/apps/deploy-web/src/context/CertificateProvider/CertificateProviderContext.tsx index deddee560..1bdc6a47e 100644 --- a/apps/deploy-web/src/context/CertificateProvider/CertificateProviderContext.tsx +++ b/apps/deploy-web/src/context/CertificateProvider/CertificateProviderContext.tsx @@ -7,11 +7,11 @@ import { useSnackbar } from "notistack"; import { RestApiCertificate } from "@src/types/certificate"; import { AnalyticsEvents } from "@src/utils/analytics"; +import { ApiUrlService, loadWithPagination } from "@src/utils/apiUtils"; import { TransactionMessageData } from "@src/utils/TransactionMessageData"; import { getStorageWallets, updateWallet } from "@src/utils/walletUtils"; import { useSettings } from "../SettingsProvider"; import { useWallet } from "../WalletProvider"; -import { ApiUrlService, loadWithPagination } from "@src/utils/apiUtils"; export type LocalCert = { certPem: string; diff --git a/apps/deploy-web/src/context/CustomChainProvider/CustomChainProvider.tsx b/apps/deploy-web/src/context/CustomChainProvider/CustomChainProvider.tsx index 4586935aa..35e3f207d 100644 --- a/apps/deploy-web/src/context/CustomChainProvider/CustomChainProvider.tsx +++ b/apps/deploy-web/src/context/CustomChainProvider/CustomChainProvider.tsx @@ -3,6 +3,7 @@ import "@interchain-ui/react/styles"; import "@interchain-ui/react/globalStyles"; import { GasPrice } from "@cosmjs/stargate"; +import { wallets as metamask } from "@cosmos-kit/cosmos-extension-metamask"; import { wallets as cosmostation } from "@cosmos-kit/cosmostation-extension"; import { wallets as keplr } from "@cosmos-kit/keplr"; import { wallets as leap } from "@cosmos-kit/leap"; @@ -22,7 +23,7 @@ export function CustomChainProvider({ children }: Props) { { diff --git a/apps/deploy-web/src/pages/faq/index.tsx b/apps/deploy-web/src/pages/faq/index.tsx index f7c06cb97..f8a6f618d 100644 --- a/apps/deploy-web/src/pages/faq/index.tsx +++ b/apps/deploy-web/src/pages/faq/index.tsx @@ -6,6 +6,34 @@ import Layout from "@src/components/layout/Layout"; import { Title } from "@src/components/shared/Title"; const FaqEntries = [ + { + anchor: "cpu-support", + title: "Which CPUs are officially supported?", + content: ( + <> +

+ + Only x86_64 processors + {" "} + are officially supported for Akash deployments. This may change in the future and when ARM processors are supported it will be announced and + documented. +

+

+ If you're on MacOS or linux, you can specify the{" "} + + target platform + {" "} + when building your docker image. For example, if you're using a Dockerfile you can use the following command: +

+

+ docker build -t my-image --platform linux/amd64 . +

+ + ) + }, { anchor: "lease-closed", title: "My lease is closed, but the deployment isn't.", @@ -34,36 +62,6 @@ const FaqEntries = [ ) }, - { - anchor: "shell-lost", - title: "Can't access shell: 'The connection to your Akash Console Shell was lost.'", - content: ( - <> -

- There is a{" "} - - known issue - {" "} - where the shell access will stop working if the provider pod gets restarted. Here's two workarounds you can try: -

-
    -
  • - You can try the "UPDATE DEPLOYMENT" button in the "UPDATE" tab of your deployment. Even without changing your SDL, this should temporarily restore - the shell access. -
    - Update Deployment -
  • -
  • - A permanent solution would be to add your own ssh access to your deployment, here is an{" "} - - example SDL - {" "} - with ssh. -
  • -
- - ) - }, { anchor: "shell-arrows-and-completion", title: "Shell: UP arrow and TAB autocompletion does not work", @@ -130,23 +128,25 @@ export default function FaqPage() { Frequently Asked Questions -
    - {FaqEntries.map(entry => ( -
  • - {entry.title} -
  • - ))} -
+
+
    + {FaqEntries.map(entry => ( +
  • + {entry.title} +
  • + ))} +
-
- {FaqEntries.map(entry => ( -
- - {entry.title} - - {entry.content} -
- ))} +
+ {FaqEntries.map(entry => ( +
+ + {entry.title} + + {entry.content} +
+ ))} +
); diff --git a/apps/indexer/package.json b/apps/indexer/package.json index 6b23c4c92..4808c0737 100644 --- a/apps/indexer/package.json +++ b/apps/indexer/package.json @@ -18,6 +18,7 @@ "dev": "npm run start", "format": "prettier --write ./*.{js,json} **/*.{ts,js,json}", "lint": "eslint .", + "prod": "node dist/server.js", "start": "webpack --mode development --config webpack.dev.js --watch", "test": "jest" }, diff --git a/apps/stats-web/.gitignore b/apps/stats-web/.gitignore index fd3dbb571..47fa2a149 100644 --- a/apps/stats-web/.gitignore +++ b/apps/stats-web/.gitignore @@ -15,6 +15,7 @@ # production /build +env-config.schema.js # misc .DS_Store diff --git a/apps/stats-web/env/.env.production b/apps/stats-web/env/.env.production new file mode 100644 index 000000000..dd649e84e --- /dev/null +++ b/apps/stats-web/env/.env.production @@ -0,0 +1,9 @@ +NEXT_PUBLIC_NODE_ENV=$NODE_ENV +NEXT_PUBLIC_BASE_API_MAINNET_URL=https://console-api.akash.network +NEXT_PUBLIC_BASE_API_SANDBOX_URL=https://console-api-sandbox.akash.network +NEXT_PUBLIC_BASE_API_TESTNET_URL=https://console-api-testnet.akash.network +NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_BASE_API_MAINNET_URL + +BASE_API_MAINNET_URL=$NEXT_PUBLIC_BASE_API_MAINNET_URL +BASE_API_TESTNET_URL=$NEXT_PUBLIC_BASE_API_TESTNET_URL +BASE_API_SANDBOX_URL=$NEXT_PUBLIC_BASE_API_SANDBOX_URL \ No newline at end of file diff --git a/apps/stats-web/env/.env.sample b/apps/stats-web/env/.env.sample new file mode 100644 index 000000000..0e09e65e8 --- /dev/null +++ b/apps/stats-web/env/.env.sample @@ -0,0 +1 @@ +NEXT_PUBLIC_API_BASE_URL= \ No newline at end of file diff --git a/apps/stats-web/env/.env.staging b/apps/stats-web/env/.env.staging new file mode 100644 index 000000000..3778aa132 --- /dev/null +++ b/apps/stats-web/env/.env.staging @@ -0,0 +1,9 @@ +NEXT_PUBLIC_NODE_ENV=$NODE_ENV +NEXT_PUBLIC_BASE_API_MAINNET_URL=https://console-api-mainnet-staging.akash.network +NEXT_PUBLIC_BASE_API_SANDBOX_URL=https://console-api-sandbox-staging.akash.network +NEXT_PUBLIC_BASE_API_TESTNET_URL=$NEXT_PUBLIC_BASE_API_MAINNET_URL +NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_BASE_API_MAINNET_URL + +BASE_API_MAINNET_URL=$NEXT_PUBLIC_BASE_API_MAINNET_URL +BASE_API_TESTNET_URL=$NEXT_PUBLIC_BASE_API_TESTNET_URL +BASE_API_SANDBOX_URL=$NEXT_PUBLIC_BASE_API_SANDBOX_URL \ No newline at end of file diff --git a/apps/stats-web/next.config.js b/apps/stats-web/next.config.js index 35ad76b7c..273c77080 100644 --- a/apps/stats-web/next.config.js +++ b/apps/stats-web/next.config.js @@ -1,5 +1,16 @@ +require("@akashnetwork/env-loader"); const { version } = require("./package.json"); +try { + const { browserEnvSchema } = require("./env-config.schema"); + + browserEnvSchema.parse(process.env); +} catch (error) { + if (error.message.includes("Cannot find module")) { + console.warn("No env-config.schema.js found, skipping env validation"); + } +} + /** @type {import('next').NextConfig} */ const nextConfig = { output: "standalone", diff --git a/apps/stats-web/package.json b/apps/stats-web/package.json index 6584bc329..2616ebbb7 100644 --- a/apps/stats-web/package.json +++ b/apps/stats-web/package.json @@ -3,14 +3,17 @@ "version": "0.20.0", "private": true, "scripts": { - "build": "next build", + "build": "npm run build-env-schemas && next build", + "build-env-schemas": "tsc src/config/env-config.schema.ts --outDir . --skipLibCheck", "dev": "next dev", "format": "prettier --write ./*.{ts,js,json} **/*.{ts,tsx,js,json}", "lint": "eslint .", "start": "next start" }, "dependencies": { + "@akashnetwork/network-store": "*", "@akashnetwork/ui": "*", + "@akashnetwork/network-store": "*", "@cosmjs/encoding": "^0.32.4", "@json2csv/plainjs": "^7.0.4", "@nivo/line": "^0.87.0", diff --git a/apps/stats-web/src/app/(home)/DashboardContainer.tsx b/apps/stats-web/src/app/(home)/DashboardContainer.tsx index 9d374e93d..0e5d985ea 100644 --- a/apps/stats-web/src/app/(home)/DashboardContainer.tsx +++ b/apps/stats-web/src/app/(home)/DashboardContainer.tsx @@ -5,14 +5,14 @@ import { Spinner } from "@akashnetwork/ui/components"; import { Dashboard } from "./Dashboard"; import { Title } from "@/components/Title"; -import { useSelectedNetwork } from "@/hooks/useSelectedNetwork"; import { useMarketData } from "@/queries"; import { useDashboardData } from "@/queries/useDashboardData"; +import { networkStore } from "@/store/network.store"; export const DashboardContainer: React.FunctionComponent = () => { const { data: dashboardData, isLoading: isLoadingDashboardData } = useDashboardData(); const { data: marketData, isLoading: isLoadingMarketData } = useMarketData(); - const selectedNetwork = useSelectedNetwork(); + const selectedNetwork = networkStore.useSelectedNetwork(); const isLoading = isLoadingMarketData || isLoadingDashboardData; return ( diff --git a/apps/stats-web/src/app/addresses/[address]/deployments/[dseq]/page.tsx b/apps/stats-web/src/app/addresses/[address]/deployments/[dseq]/page.tsx index 71f6cf4bf..0dac31a87 100644 --- a/apps/stats-web/src/app/addresses/[address]/deployments/[dseq]/page.tsx +++ b/apps/stats-web/src/app/addresses/[address]/deployments/[dseq]/page.tsx @@ -1,19 +1,28 @@ +import type { Network } from "@akashnetwork/network-store"; import { Metadata } from "next"; +import { z } from "zod"; import { DeploymentInfo } from "./DeploymentInfo"; import PageContainer from "@/components/PageContainer"; import { Title } from "@/components/Title"; -import { getNetworkBaseApiUrl } from "@/lib/constants"; +import { networkId } from "@/config/env-config.schema"; import { UrlService } from "@/lib/urlUtils"; +import { serverApiUrlService } from "@/services/api-url/server-api-url.service"; import { DeploymentDetail } from "@/types"; -interface IProps { - params: { address: string; dseq: string }; - searchParams: { [key: string]: string | string[] | undefined }; -} +const DeploymentDetailPageSchema = z.object({ + params: z.object({ + address: z.string(), + dseq: z.string() + }), + searchParams: z.object({ + network: networkId + }) +}); +type DeploymentDetailPageProps = z.infer; -export async function generateMetadata({ params: { address, dseq } }: IProps): Promise { +export async function generateMetadata({ params: { address, dseq } }: DeploymentDetailPageProps): Promise { const url = `https://stats.akash.network${UrlService.deployment(address, dseq)}`; return { @@ -27,8 +36,8 @@ export async function generateMetadata({ params: { address, dseq } }: IProps): P }; } -async function fetchDeploymentData(address: string, dseq: string, network: string): Promise { - const apiUrl = getNetworkBaseApiUrl(network); +async function fetchDeploymentData(address: string, dseq: string, network: Network["id"]): Promise { + const apiUrl = serverApiUrlService.getBaseApiUrlFor(network); const response = await fetch(`${apiUrl}/v1/deployment/${address}/${dseq}`); if (!response.ok) { @@ -39,8 +48,12 @@ async function fetchDeploymentData(address: string, dseq: string, network: strin return response.json(); } -export default async function DeploymentDetailPage({ params: { address, dseq }, searchParams: { network } }: IProps) { - const deployment = await fetchDeploymentData(address, dseq, network as string); +export default async function DeploymentDetailPage(props: DeploymentDetailPageProps) { + const { + params: { address, dseq }, + searchParams: { network } + } = DeploymentDetailPageSchema.parse(props); + const deployment = await fetchDeploymentData(address, dseq, network); return ( diff --git a/apps/stats-web/src/app/addresses/[address]/page.tsx b/apps/stats-web/src/app/addresses/[address]/page.tsx index ba0464822..2f26909cb 100644 --- a/apps/stats-web/src/app/addresses/[address]/page.tsx +++ b/apps/stats-web/src/app/addresses/[address]/page.tsx @@ -1,4 +1,6 @@ -import { Metadata } from "next"; +import type { Network } from "@akashnetwork/network-store"; +import type { Metadata } from "next"; +import { z } from "zod"; import { AddressInfo } from "./AddressInfo"; import AddressLayout from "./AddressLayout"; @@ -7,16 +9,23 @@ import { AssetList } from "./AssetList"; import { LatestTransactions } from "./LatestTransactions"; import { Title } from "@/components/Title"; -import { getNetworkBaseApiUrl } from "@/lib/constants"; +import { networkId } from "@/config/env-config.schema"; import { UrlService } from "@/lib/urlUtils"; +import { serverApiUrlService } from "@/services/api-url/server-api-url.service"; import { AddressDetail } from "@/types"; -interface IProps { - params: { address: string }; - searchParams: { [key: string]: string | string[] | undefined }; -} +const AddressDetailPageSchema = z.object({ + params: z.object({ + address: z.string(), + dseq: z.string() + }), + searchParams: z.object({ + network: networkId + }) +}); +type AddressDetailPageProps = z.infer; -export async function generateMetadata({ params: { address } }: IProps): Promise { +export async function generateMetadata({ params: { address } }: AddressDetailPageProps): Promise { const url = `https://stats.akash.network${UrlService.address(address)}`; return { @@ -30,8 +39,8 @@ export async function generateMetadata({ params: { address } }: IProps): Promise }; } -async function fetchAddressData(address: string, network: string): Promise { - const apiUrl = getNetworkBaseApiUrl(network); +async function fetchAddressData(address: string, network: Network["id"]): Promise { + const apiUrl = serverApiUrlService.getBaseApiUrlFor(network); const response = await fetch(`${apiUrl}/v1/addresses/${address}`); if (!response.ok) { @@ -42,8 +51,12 @@ async function fetchAddressData(address: string, network: string): Promise diff --git a/apps/stats-web/src/app/blocks/[height]/page.tsx b/apps/stats-web/src/app/blocks/[height]/page.tsx index 7f36b23d9..16955584d 100644 --- a/apps/stats-web/src/app/blocks/[height]/page.tsx +++ b/apps/stats-web/src/app/blocks/[height]/page.tsx @@ -1,28 +1,36 @@ +import type { Network } from "@akashnetwork/network-store"; import { Card, CardContent, Table, TableBody, TableHead, TableHeader, TableRow } from "@akashnetwork/ui/components"; import { SearchX } from "lucide-react"; -import { Metadata } from "next"; +import type { Metadata } from "next"; +import { z } from "zod"; import { BlockInfo } from "./BlockInfo"; import { TransactionRow } from "@/components/blockchain/TransactionRow"; import PageContainer from "@/components/PageContainer"; import { Title } from "@/components/Title"; -import { getNetworkBaseApiUrl } from "@/lib/constants"; +import { networkId } from "@/config/env-config.schema"; +import { serverApiUrlService } from "@/services/api-url/server-api-url.service"; import { BlockDetail } from "@/types"; -interface IProps { - params: { height: string }; - searchParams: { [key: string]: string | string[] | undefined }; -} +const BlockDetailPageSchema = z.object({ + params: z.object({ + height: z.string() + }), + searchParams: z.object({ + network: networkId + }) +}); +type BlockDetailPageProps = z.infer; -export async function generateMetadata({ params: { height } }: IProps): Promise { +export async function generateMetadata({ params: { height } }: BlockDetailPageProps): Promise { return { title: `Block #${height}` }; } -async function fetchBlockData(height: string, network: string): Promise { - const apiUrl = getNetworkBaseApiUrl(network); +async function fetchBlockData(height: string, network: Network["id"]): Promise { + const apiUrl = serverApiUrlService.getBaseApiUrlFor(network); const response = await fetch(`${apiUrl}/v1/blocks/${height}`); if (!response.ok) { @@ -33,8 +41,12 @@ async function fetchBlockData(height: string, network: string): Promise diff --git a/apps/stats-web/src/app/graph/[snapshot]/GraphContainer.tsx b/apps/stats-web/src/app/graph/[snapshot]/GraphContainer.tsx index bf73b139b..72e1c7ad0 100644 --- a/apps/stats-web/src/app/graph/[snapshot]/GraphContainer.tsx +++ b/apps/stats-web/src/app/graph/[snapshot]/GraphContainer.tsx @@ -9,7 +9,7 @@ import dynamic from "next/dynamic"; import { DiffNumber } from "@/components/DiffNumber"; import { DiffPercentageChip } from "@/components/DiffPercentageChip"; import { TimeRange } from "@/components/graph/TimeRange"; -import { selectedRangeValues } from "@/lib/constants"; +import { SELECTED_RANGE_VALUES } from "@/config/date.config"; import { percIncrease, udenomToDenom } from "@/lib/mathHelpers"; import { SNAPSHOT_NOT_FOUND } from "@/lib/snapshotsUrlHelpers"; import { bytesToShrink } from "@/lib/unitUtils"; @@ -25,7 +25,7 @@ export interface IGraphProps { } export default function GraphContainer({ snapshot }: IGraphProps) { - const [selectedRange, setSelectedRange] = useState(selectedRangeValues["7D"]); + const [selectedRange, setSelectedRange] = useState(SELECTED_RANGE_VALUES["7D"]); const { data: snapshotData, status } = useGraphSnapshot(snapshot); const snapshotMetadata = snapshotData && getSnapshotMetadata(snapshot as Snapshots); const rangedData = snapshotData && snapshotData.snapshots.slice(Math.max(snapshotData.snapshots.length - selectedRange, 0), snapshotData.snapshots.length); @@ -50,7 +50,7 @@ export default function GraphContainer({ snapshot }: IGraphProps) { const csvContent = parser.parse(rangedData.map(d => ({ date: d.date, value: snapshotMetadata.unitFn(d.value).value }))); const datePart = new Date().toISOString().substring(0, 10).replaceAll("-", ""); - const rangePart = Object.keys(selectedRangeValues).find(key => selectedRangeValues[key] === selectedRange); + const rangePart = Object.keys(SELECTED_RANGE_VALUES).find(key => SELECTED_RANGE_VALUES[key] === selectedRange); const fileName = `${snapshot}-${datePart}-${rangePart}.csv`; const encodedUri = encodeURI("data:text/csv;charset=utf-8," + csvContent); diff --git a/apps/stats-web/src/app/provider-graph/[snapshot]/GraphContainer.tsx b/apps/stats-web/src/app/provider-graph/[snapshot]/GraphContainer.tsx index ecec6a618..4d4234525 100644 --- a/apps/stats-web/src/app/provider-graph/[snapshot]/GraphContainer.tsx +++ b/apps/stats-web/src/app/provider-graph/[snapshot]/GraphContainer.tsx @@ -9,7 +9,7 @@ import dynamic from "next/dynamic"; import { DiffNumber } from "@/components/DiffNumber"; import { DiffPercentageChip } from "@/components/DiffPercentageChip"; import { TimeRange } from "@/components/graph/TimeRange"; -import { selectedRangeValues } from "@/lib/constants"; +import { SELECTED_RANGE_VALUES } from "@/config/date.config"; import { percIncrease } from "@/lib/mathHelpers"; import { getProviderSnapshotMetadata } from "@/lib/providerUtils"; import { SNAPSHOT_NOT_FOUND } from "@/lib/snapshotsUrlHelpers"; @@ -25,7 +25,7 @@ export interface IGraphProps { } export default function GraphContainer({ snapshot }: IGraphProps) { - const [selectedRange, setSelectedRange] = useState(selectedRangeValues["7D"]); + const [selectedRange, setSelectedRange] = useState(SELECTED_RANGE_VALUES["7D"]); const { data: snapshotData, status } = useProviderGraphSnapshot(snapshot); const snapshotMetadata = snapshotData && getProviderSnapshotMetadata(snapshot as ProviderSnapshots); const rangedData = snapshotData && snapshotData.snapshots.slice(Math.max(snapshotData.snapshots.length - selectedRange, 0), snapshotData.snapshots.length); @@ -50,7 +50,7 @@ export default function GraphContainer({ snapshot }: IGraphProps) { const csvContent = parser.parse(rangedData.map(d => ({ date: d.date, value: snapshotMetadata.unitFn(d.value).value }))); const datePart = new Date().toISOString().substring(0, 10).replaceAll("-", ""); - const rangePart = Object.keys(selectedRangeValues).find(key => selectedRangeValues[key] === selectedRange); + const rangePart = Object.keys(SELECTED_RANGE_VALUES).find(key => SELECTED_RANGE_VALUES[key] === selectedRange); const fileName = `${snapshot}-${datePart}-${rangePart}.csv`; const encodedUri = encodeURI("data:text/csv;charset=utf-8," + csvContent); diff --git a/apps/stats-web/src/app/transactions/[hash]/page.tsx b/apps/stats-web/src/app/transactions/[hash]/page.tsx index f63fd61ec..76bc605b6 100644 --- a/apps/stats-web/src/app/transactions/[hash]/page.tsx +++ b/apps/stats-web/src/app/transactions/[hash]/page.tsx @@ -1,35 +1,44 @@ import React from "react"; +import type { Network } from "@akashnetwork/network-store"; import { Alert, Card, CardContent } from "@akashnetwork/ui/components"; -import { Metadata } from "next"; +import type { Metadata } from "next"; +import { z } from "zod"; import { TransactionInfo } from "./TransactionInfo"; import PageContainer from "@/components/PageContainer"; import { Title } from "@/components/Title"; import { TxMessageRow } from "@/components/transactions/TxMessageRow"; +import { networkId } from "@/config/env-config.schema"; import { getSplitText } from "@/hooks/useShortText"; -import { getNetworkBaseApiUrl } from "@/lib/constants"; +import { serverApiUrlService } from "@/services/api-url/server-api-url.service"; import { TransactionDetail } from "@/types"; -interface IProps { - params: { hash: string }; - searchParams: { [key: string]: string | string[] | undefined }; -} +const TransactionDetailPageSchema = z.object({ + params: z.object({ + hash: z.string() + }), + searchParams: z.object({ + network: networkId + }) +}); +type TransactionDetailPageProps = z.infer; -export async function generateMetadata({ params: { hash } }: IProps): Promise { - const splittedTxHash = getSplitText(hash, 6, 6); +export async function generateMetadata({ params: { hash } }: TransactionDetailPageProps): Promise { + const splitTxHash = getSplitText(hash, 6, 6); return { - title: `Tx ${splittedTxHash}` + title: `Tx ${splitTxHash}` }; } -async function fetchTransactionData(hash: string, network: string): Promise { - const apiUrl = getNetworkBaseApiUrl(network); +async function fetchTransactionData(hash: string, network: Network["id"]): Promise { + const apiUrl = serverApiUrlService.getBaseApiUrlFor(network); + console.log("DEBUG apiUrl", apiUrl); const response = await fetch(`${apiUrl}/v1/transactions/${hash}`); if (!response.ok && response.status !== 404) { // This will activate the closest `error.js` Error Boundary - throw new Error("Error fetching transction data"); + throw new Error("Error fetching transaction data"); } else if (response.status === 404) { return null; } @@ -37,8 +46,12 @@ async function fetchTransactionData(hash: string, network: string): Promise diff --git a/apps/stats-web/src/app/validators/[address]/page.tsx b/apps/stats-web/src/app/validators/[address]/page.tsx index 079793141..4a0f68b38 100644 --- a/apps/stats-web/src/app/validators/[address]/page.tsx +++ b/apps/stats-web/src/app/validators/[address]/page.tsx @@ -1,21 +1,29 @@ -import { Metadata } from "next"; +import type { Network } from "@akashnetwork/network-store"; +import type { Metadata } from "next"; +import { z } from "zod"; import { ValidatorsInfo } from "./ValidatorInfo"; import PageContainer from "@/components/PageContainer"; import { Title } from "@/components/Title"; -import { getNetworkBaseApiUrl } from "@/lib/constants"; +import { networkId } from "@/config/env-config.schema"; import { UrlService } from "@/lib/urlUtils"; +import { serverApiUrlService } from "@/services/api-url/server-api-url.service"; import { ValidatorDetail } from "@/types"; -interface IProps { - params: { address: string }; - searchParams: { [key: string]: string | string[] | undefined }; -} +const ValidatorDetailPageSchema = z.object({ + params: z.object({ + address: z.string() + }), + searchParams: z.object({ + network: networkId + }) +}); +type ValidatorDetailPageProps = z.infer; -export async function generateMetadata({ params: { address }, searchParams: { network } }: IProps): Promise { +export async function generateMetadata({ params: { address }, searchParams: { network } }: ValidatorDetailPageProps): Promise { const url = `https://stats.akash.network${UrlService.validator(address)}`; - const apiUrl = getNetworkBaseApiUrl(network as string); + const apiUrl = serverApiUrlService.getBaseApiUrlFor(network); const response = await fetch(`${apiUrl}/v1/validators/${address}`); const data = (await response.json()) as ValidatorDetail; @@ -30,8 +38,8 @@ export async function generateMetadata({ params: { address }, searchParams: { ne }; } -async function fetchValidatorData(address: string, network: string): Promise { - const apiUrl = getNetworkBaseApiUrl(network); +async function fetchValidatorData(address: string, network: Network["id"]): Promise { + const apiUrl = serverApiUrlService.getBaseApiUrlFor(network); const response = await fetch(`${apiUrl}/v1/validators/${address}`); if (!response.ok) { @@ -42,8 +50,12 @@ async function fetchValidatorData(address: string, network: string): Promise diff --git a/apps/stats-web/src/components/graph/TimeRange.tsx b/apps/stats-web/src/components/graph/TimeRange.tsx index f1bd6ec76..aae2d5a4b 100644 --- a/apps/stats-web/src/components/graph/TimeRange.tsx +++ b/apps/stats-web/src/components/graph/TimeRange.tsx @@ -2,7 +2,7 @@ import { ReactNode } from "react"; import { ToggleGroup, ToggleGroupItem } from "@akashnetwork/ui/components"; -import { selectedRangeValues } from "@/lib/constants"; +import { SELECTED_RANGE_VALUES } from "@/config/date.config"; import { cn } from "@/lib/utils"; type Props = { @@ -20,24 +20,24 @@ export const TimeRange: React.FunctionComponent = ({ selectedRange, onRan _onRangeChange(selectedRangeValues["7D"])} + className={cn({ ["!bg-primary font-bold !text-white"]: selectedRange === SELECTED_RANGE_VALUES["7D"] })} + onClick={() => _onRangeChange(SELECTED_RANGE_VALUES["7D"])} size="sm" > 7D _onRangeChange(selectedRangeValues["1M"])} + className={cn({ ["!bg-primary font-bold !text-white"]: selectedRange === SELECTED_RANGE_VALUES["1M"] })} + onClick={() => _onRangeChange(SELECTED_RANGE_VALUES["1M"])} size="sm" > 1M _onRangeChange(selectedRangeValues["ALL"])} + className={cn({ ["!bg-primary font-bold !text-white"]: selectedRange === SELECTED_RANGE_VALUES["ALL"] })} + onClick={() => _onRangeChange(SELECTED_RANGE_VALUES["ALL"])} size="sm" > ALL diff --git a/apps/stats-web/src/components/layout/CustomGoogleAnalytics.tsx b/apps/stats-web/src/components/layout/CustomGoogleAnalytics.tsx index 18299f77f..e9c65409c 100644 --- a/apps/stats-web/src/components/layout/CustomGoogleAnalytics.tsx +++ b/apps/stats-web/src/components/layout/CustomGoogleAnalytics.tsx @@ -3,7 +3,7 @@ import { useReportWebVitals } from "next/web-vitals"; import { event, GoogleAnalytics as GAnalytics } from "nextjs-google-analytics"; -import { isProd } from "@/lib/constants"; +import { browserEnvConfig } from "@/config/browser-env.config"; export default function GoogleAnalytics() { useReportWebVitals(({ id, name, label, value }) => { @@ -15,5 +15,5 @@ export default function GoogleAnalytics() { }); }); - return <>{isProd && }; + return <>{browserEnvConfig.NEXT_PUBLIC_NODE_ENV === "production" && }; } diff --git a/apps/stats-web/src/components/layout/CustomProviders.tsx b/apps/stats-web/src/components/layout/CustomProviders.tsx index 6bd86fe45..a330a52c8 100644 --- a/apps/stats-web/src/components/layout/CustomProviders.tsx +++ b/apps/stats-web/src/components/layout/CustomProviders.tsx @@ -12,12 +12,13 @@ import { CustomIntlProvider } from "./CustomIntlProvider"; import { PricingProvider } from "@/context/PricingProvider"; import { customColors } from "@/lib/colors"; import { queryClient } from "@/queries"; +import { store } from "@/store/global.store"; function Providers({ children }: React.PropsWithChildren) { return ( - + diff --git a/apps/stats-web/src/components/layout/NetworkSelect.tsx b/apps/stats-web/src/components/layout/NetworkSelect.tsx index 9e81c351a..c2f57fe03 100644 --- a/apps/stats-web/src/components/layout/NetworkSelect.tsx +++ b/apps/stats-web/src/components/layout/NetworkSelect.tsx @@ -1,49 +1,22 @@ "use client"; -import React, { useEffect, useState } from "react"; +import React from "react"; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, Spinner } from "@akashnetwork/ui/components"; -import { mainnetId, setNetworkVersion } from "@/lib/constants"; import { cn } from "@/lib/utils"; -import { initiateNetworkData, networks } from "@/store/networkStore"; +import { networkStore } from "@/store/network.store"; interface NetworkSelectProps { className?: string; } const NetworkSelect: React.FC = ({ className }) => { - const [isLoadingSettings, setIsLoadingSettings] = useState(true); - const [selectedNetworkId, setSelectedNetworkId] = useState(mainnetId); - - useEffect(() => { - async function init() { - await initiateNetworkData(); - setNetworkVersion(); - - const selectedNetworkId = localStorage.getItem("selectedNetworkId"); - if (selectedNetworkId) { - setSelectedNetworkId(selectedNetworkId); - } - - setIsLoadingSettings(false); - } - - init(); - }, []); - - const onSelectNetworkChange = (networkId: string) => { - setSelectedNetworkId(networkId); - - // Set in the settings and local storage - localStorage.setItem("selectedNetworkId", networkId); - // Reset the ui to reload the settings for the currently selected network - - location.reload(); - }; + const [{ isLoading: isLoadingNetworks, data: networks }] = networkStore.useNetworksStore(); + const [selectedNetworkId, setSelectedNetworkId] = networkStore.useSelectedNetworkIdStore({ reloadOnChange: true }); return ( - - {isLoadingSettings && } + {isLoadingNetworks && } diff --git a/apps/stats-web/src/config/browser-env.config.ts b/apps/stats-web/src/config/browser-env.config.ts new file mode 100644 index 000000000..903fec82c --- /dev/null +++ b/apps/stats-web/src/config/browser-env.config.ts @@ -0,0 +1,10 @@ +import { validateStaticEnvVars } from "./env-config.schema"; + +export const browserEnvConfig = validateStaticEnvVars({ + NEXT_PUBLIC_DEFAULT_NETWORK_ID: process.env.NEXT_PUBLIC_DEFAULT_NETWORK_ID, + NEXT_PUBLIC_API_BASE_URL: process.env.NEXT_PUBLIC_API_BASE_URL, + NEXT_PUBLIC_NODE_ENV: process.env.NEXT_PUBLIC_NODE_ENV, + NEXT_PUBLIC_BASE_API_TESTNET_URL: process.env.NEXT_PUBLIC_BASE_API_TESTNET_URL, + NEXT_PUBLIC_BASE_API_SANDBOX_URL: process.env.NEXT_PUBLIC_BASE_API_SANDBOX_URL, + NEXT_PUBLIC_BASE_API_MAINNET_URL: process.env.NEXT_PUBLIC_BASE_API_MAINNET_URL +}); diff --git a/apps/stats-web/src/config/date.config.ts b/apps/stats-web/src/config/date.config.ts new file mode 100644 index 000000000..de6acce2e --- /dev/null +++ b/apps/stats-web/src/config/date.config.ts @@ -0,0 +1,5 @@ +export const SELECTED_RANGE_VALUES: Record = { + "7D": 7, + "1M": 30, + ALL: Number.MAX_SAFE_INTEGER +}; diff --git a/apps/stats-web/src/config/denom.config.ts b/apps/stats-web/src/config/denom.config.ts new file mode 100644 index 000000000..eb079e6ba --- /dev/null +++ b/apps/stats-web/src/config/denom.config.ts @@ -0,0 +1,8 @@ +import type { MainnetNetworkId, SandboxNetworkId } from "@akashnetwork/akashjs/build/types/network"; +import { MAINNET_ID, SANDBOX_ID } from "@akashnetwork/network-store"; + +export const UAKT_DENOM = "uakt"; +export const USDC_IBC_DENOMS: Record = { + [MAINNET_ID]: "ibc/170C677610AC31DF0904FFE09CD3B5C657492170E7E52372E48756B71E56F2F1", + [SANDBOX_ID]: "ibc/12C6A0C374171B595A0A9E18B83FA09D295FB1F2D8C6DAA3AC28683471752D84" +}; diff --git a/apps/stats-web/src/config/env-config.schema.ts b/apps/stats-web/src/config/env-config.schema.ts new file mode 100644 index 000000000..1cb77e54f --- /dev/null +++ b/apps/stats-web/src/config/env-config.schema.ts @@ -0,0 +1,33 @@ +import { z } from "zod"; + +export const networkId = z.enum(["mainnet", "sandbox", "testnet"]); +const coercedBoolean = () => z.enum(["true", "false"]).transform(val => val === "true"); + +export const browserEnvSchema = z.object({ + NEXT_PUBLIC_DEFAULT_NETWORK_ID: networkId.optional().default("mainnet"), + NEXT_PUBLIC_API_BASE_URL: z.string().url(), + NEXT_PUBLIC_NODE_ENV: z.enum(["development", "production", "test"]).optional().default("development"), + NEXT_PUBLIC_BASE_API_TESTNET_URL: z.string().url(), + NEXT_PUBLIC_BASE_API_SANDBOX_URL: z.string().url(), + NEXT_PUBLIC_BASE_API_MAINNET_URL: z.string().url() +}); + +export const serverEnvSchema = browserEnvSchema.extend({ + MAINTENANCE_MODE: coercedBoolean().optional().default("false"), + BASE_API_MAINNET_URL: z.string().url(), + BASE_API_TESTNET_URL: z.string().url(), + BASE_API_SANDBOX_URL: z.string().url() +}); + +export type BrowserEnvConfig = z.infer; +export type ServerEnvConfig = z.infer; + +export const validateStaticEnvVars = (config: Record) => browserEnvSchema.parse(config); +export const validateRuntimeEnvVars = (config: Record) => { + if (process.env.NEXT_PHASE === "phase-production-build") { + console.log("Skipping validation of serverEnvConfig during build"); + return config as ServerEnvConfig; + } else { + return serverEnvSchema.parse(config); + } +}; diff --git a/apps/stats-web/src/config/server-env.config.ts b/apps/stats-web/src/config/server-env.config.ts new file mode 100644 index 000000000..1b85264cc --- /dev/null +++ b/apps/stats-web/src/config/server-env.config.ts @@ -0,0 +1,5 @@ +import "@akashnetwork/env-loader"; + +import { validateRuntimeEnvVars } from "./env-config.schema"; + +export const serverEnvConfig = validateRuntimeEnvVars(process.env); diff --git a/apps/stats-web/src/context/PricingProvider/PricingProvider.tsx b/apps/stats-web/src/context/PricingProvider/PricingProvider.tsx index bfc47ef9a..78347b0a3 100644 --- a/apps/stats-web/src/context/PricingProvider/PricingProvider.tsx +++ b/apps/stats-web/src/context/PricingProvider/PricingProvider.tsx @@ -2,8 +2,8 @@ import React from "react"; +import { UAKT_DENOM } from "@/config/denom.config"; import { useUsdcDenom } from "@/hooks/useDenom"; -import { uAktDenom } from "@/lib/constants"; import { roundDecimal } from "@/lib/mathHelpers"; import { useMarketData } from "@/queries"; @@ -42,7 +42,7 @@ export const PricingProvider: React.FC = ({ children }) => { const getPriceForDenom = (denom: string): number => { switch (denom) { - case uAktDenom: + case UAKT_DENOM: return marketData?.price || 0; case usdcIbcDenom: return 1; // TODO Get price from API diff --git a/apps/stats-web/src/hooks/useDenom.ts b/apps/stats-web/src/hooks/useDenom.ts index dde453160..d53706f8f 100644 --- a/apps/stats-web/src/hooks/useDenom.ts +++ b/apps/stats-web/src/hooks/useDenom.ts @@ -1,22 +1,7 @@ -import { getSelectedNetwork, useSelectedNetwork } from "./useSelectedNetwork"; - -import { usdcIbcDenoms } from "@/lib/constants"; +import { USDC_IBC_DENOMS } from "@/config/denom.config"; +import { networkStore } from "@/store/network.store"; export const useUsdcDenom = () => { - const selectedNetwork = useSelectedNetwork(); - return usdcIbcDenoms[selectedNetwork.id]; -}; - -export const getUsdcDenom = () => { - const selectedNetwork = getSelectedNetwork(); - return usdcIbcDenoms[selectedNetwork.id as any]; -}; - -export const useSdlDenoms = () => { - const usdcDenom = useUsdcDenom(); - - return [ - { id: "uakt", label: "uAKT", tokenLabel: "AKT", value: "uakt" }, - { id: "uusdc", label: "uUSDC", tokenLabel: "USDC", value: usdcDenom } - ]; + const selectedNetworkId = networkStore.useSelectedNetworkId(); + return USDC_IBC_DENOMS[selectedNetworkId]; }; diff --git a/apps/stats-web/src/hooks/useSelectedNetwork.ts b/apps/stats-web/src/hooks/useSelectedNetwork.ts index 44c1f4e42..e69de29bb 100644 --- a/apps/stats-web/src/hooks/useSelectedNetwork.ts +++ b/apps/stats-web/src/hooks/useSelectedNetwork.ts @@ -1,24 +0,0 @@ -import { useAtom } from "jotai"; -import { useEffectOnce } from "usehooks-ts"; - -import { mainnetId } from "@/lib/constants"; -import networkStore, { networks } from "@/store/networkStore"; - -export const getSelectedNetwork = () => { - const selectedNetworkId = localStorage.getItem("selectedNetworkId") ?? mainnetId; - const selectedNetwork = networks.find(n => n.id === selectedNetworkId); - - // return mainnet if selected network is not found - return selectedNetwork ?? networks[0]; -}; - -export const useSelectedNetwork = () => { - const [selectedNetwork, setSelectedNetwork] = useAtom(networkStore.selectedNetwork); - - useEffectOnce(() => { - const selectedNetworkId = localStorage.getItem("selectedNetworkId") ?? mainnetId; - setSelectedNetwork(networks.find(n => n.id === selectedNetworkId) || networks[0]); - }); - - return selectedNetwork ?? networks[0]; -}; diff --git a/apps/stats-web/src/lib/apiUtils.ts b/apps/stats-web/src/lib/apiUtils.ts index 87c6a5739..484ee4585 100644 --- a/apps/stats-web/src/lib/apiUtils.ts +++ b/apps/stats-web/src/lib/apiUtils.ts @@ -1,87 +1,57 @@ -import axios from "axios"; - -import { BASE_API_URL } from "./constants"; import { appendSearchParams } from "./urlUtils"; +import { browserApiUrlService } from "@/services/api-url/browser-api-url.service"; +import { networkStore } from "@/store/network.store"; + export class ApiUrlService { static dashboardData() { - return `${BASE_API_URL}/v1/dashboard-data`; + return `${this.baseApiUrl}/v1/dashboard-data`; } static marketData() { - return `${BASE_API_URL}/v1/market-data`; + return `${this.baseApiUrl}/v1/market-data`; } static proposals() { - return `${BASE_API_URL}/v1/proposals`; + return `${this.baseApiUrl}/v1/proposals`; } static validators() { - return `${BASE_API_URL}/v1/validators`; + return `${this.baseApiUrl}/v1/validators`; } static transactions(limit: number) { - return `${BASE_API_URL}/v1/transactions${appendSearchParams({ limit })}`; + return `${this.baseApiUrl}/v1/transactions${appendSearchParams({ limit })}`; } static addressTransactions(address: string, skip: number, limit: number) { - return `${BASE_API_URL}/v1/addresses/${address}/transactions/${skip}/${limit}`; + return `${this.baseApiUrl}/v1/addresses/${address}/transactions/${skip}/${limit}`; } static addressDeployments(address: string, skip: number, limit: number, reverseSorting: boolean, filters: { [key: string]: string }) { - return `${BASE_API_URL}/v1/addresses/${address}/deployments/${skip}/${limit}${appendSearchParams({ reverseSorting, ...filters })}`; + return `${this.baseApiUrl}/v1/addresses/${address}/deployments/${skip}/${limit}${appendSearchParams({ reverseSorting, ...filters })}`; } static graphData(snapshot: string) { - return `${BASE_API_URL}/v1/graph-data/${snapshot}`; + return `${this.baseApiUrl}/v1/graph-data/${snapshot}`; } static providerGraphData(snapshot: string) { - return `${BASE_API_URL}/v1/provider-graph-data/${snapshot}`; + return `${this.baseApiUrl}/v1/provider-graph-data/${snapshot}`; } static blocks(limit: number) { - return `${BASE_API_URL}/v1/blocks${appendSearchParams({ limit })}`; + return `${this.baseApiUrl}/v1/blocks${appendSearchParams({ limit })}`; } static providerAttributesSchema() { - return `${BASE_API_URL}/v1/provider-attributes-schema`; + return `${this.baseApiUrl}/v1/provider-attributes-schema`; } static networkCapacity() { - return `${BASE_API_URL}/v1/network-capacity`; + return `${this.baseApiUrl}/v1/network-capacity`; } static mainnetVersion() { - return `${BASE_API_URL}/v1/version/mainnet`; + return `${this.baseApiUrl}/v1/version/mainnet`; } static testnetVersion() { - return `${BASE_API_URL}/v1/version/testnet`; + return `${this.baseApiUrl}/v1/version/testnet`; } static sandboxVersion() { - return `${BASE_API_URL}/v1/version/sandbox`; + return `${this.baseApiUrl}/v1/version/sandbox`; } -} - -export async function loadWithPagination(baseUrl: string, dataKey: string, limit: number) { - let items: any[] = []; - let nextKey = null; - // let callCount = 1; - // let totalCount = null; - - do { - const _hasQueryParam = hasQueryParam(baseUrl); - let queryUrl = `${baseUrl}${_hasQueryParam ? "&" : "?"}pagination.limit=${limit}&pagination.count_total=true`; - if (nextKey) { - queryUrl += "&pagination.key=" + encodeURIComponent(nextKey); - } - // console.log(`Querying ${dataKey} [${callCount}] from : ${queryUrl}`); - const response = await axios.get(queryUrl); - const data = response.data; - - // if (!nextKey) { - // totalCount = data.pagination.total; - // } - items = items.concat(data[dataKey]); - nextKey = data.pagination.next_key; - // callCount++; - - // console.log(`Got ${items.length} of ${totalCount}`); - } while (nextKey); - - return items.filter(item => item); -} - -function hasQueryParam(url: string) { - return /[?&]/gm.test(url); + static get baseApiUrl() { + return browserApiUrlService.getBaseApiUrlFor(networkStore.selectedNetworkId); + } } diff --git a/apps/stats-web/src/lib/constants.ts b/apps/stats-web/src/lib/constants.ts index 3e2a46ed8..e69de29bb 100644 --- a/apps/stats-web/src/lib/constants.ts +++ b/apps/stats-web/src/lib/constants.ts @@ -1,102 +0,0 @@ -export const mainnetId = "mainnet"; -export const testnetId = "testnet"; -export const sandboxId = "sandbox"; - -export const selectedRangeValues: { [key: string]: number } = { - "7D": 7, - "1M": 30, - ALL: Number.MAX_SAFE_INTEGER -}; - -const productionMainnetApiUrl = "https://console-api.akash.network"; -const productionTestnetApiUrl = "https://console-api-testnet.akash.network"; -const productionSandboxApiUrl = "https://console-api-sandbox.akash.network"; -const productionHostnames = ["stats.akash.network"]; - -export const isProd = process.env.NODE_ENV === "production"; -export const isMaintenanceMode = process.env.MAINTENANCE_MODE === "true"; -export const BASE_API_MAINNET_URL = getApiMainnetUrl(); -export const BASE_API_TESTNET_URL = getApiTestnetUrl(); -export const BASE_API_SANDBOX_URL = getApiSandboxUrl(); - -export const BASE_API_URL = getApiUrl(); - -export function getNetworkBaseApiUrl(network: string | null) { - switch (network) { - case testnetId: - return BASE_API_TESTNET_URL; - case sandboxId: - return BASE_API_SANDBOX_URL; - default: - return BASE_API_MAINNET_URL; - } -} - -export const uAktDenom = "uakt"; -export const usdcIbcDenoms: { [key: string]: string } = { - [mainnetId]: "ibc/170C677610AC31DF0904FFE09CD3B5C657492170E7E52372E48756B71E56F2F1", - [sandboxId]: "ibc/12C6A0C374171B595A0A9E18B83FA09D295FB1F2D8C6DAA3AC28683471752D84" -}; - -function getApiMainnetUrl() { - if (process.env.API_MAINNET_BASE_URL) return process.env.API_MAINNET_BASE_URL; - if (typeof window === "undefined") return "http://localhost:3080"; - if (productionHostnames.includes(window.location?.hostname)) return productionMainnetApiUrl; - return "http://localhost:3080"; -} - -function getApiTestnetUrl() { - if (process.env.API_TESTNET_BASE_URL) return process.env.API_TESTNET_BASE_URL; - if (typeof window === "undefined") return "http://localhost:3080"; - if (productionHostnames.includes(window.location?.hostname)) return productionTestnetApiUrl; - return "http://localhost:3080"; -} - -function getApiSandboxUrl() { - if (process.env.API_SANDBOX_BASE_URL) return process.env.API_SANDBOX_BASE_URL; - if (typeof window === "undefined") return "http://localhost:3080"; - if (productionHostnames.includes(window.location?.hostname)) return productionSandboxApiUrl; - return "http://localhost:3080"; -} - -function getApiUrl() { - if (process.env.API_BASE_URL) return process.env.API_BASE_URL; - if (typeof window === "undefined") return "http://localhost:3080"; - if (productionHostnames.includes(window.location?.hostname)) { - try { - const _selectedNetworkId = localStorage.getItem("selectedNetworkId"); - return getNetworkBaseApiUrl(_selectedNetworkId); - } catch (e) { - console.error(e); - return productionMainnetApiUrl; - } - } - return "http://localhost:3080"; -} - -export let selectedNetworkId = ""; -export let networkVersion: "v1beta2" | "v1beta3"; - -export function setNetworkVersion() { - const _selectedNetworkId = localStorage.getItem("selectedNetworkId"); - - switch (_selectedNetworkId) { - case mainnetId: - networkVersion = "v1beta3"; - selectedNetworkId = mainnetId; - break; - case testnetId: - networkVersion = "v1beta3"; - selectedNetworkId = testnetId; - break; - case sandboxId: - networkVersion = "v1beta3"; - selectedNetworkId = sandboxId; - break; - - default: - networkVersion = "v1beta3"; - selectedNetworkId = mainnetId; - break; - } -} diff --git a/apps/stats-web/src/lib/urlUtils.ts b/apps/stats-web/src/lib/urlUtils.ts index d57940e4f..d6e070404 100644 --- a/apps/stats-web/src/lib/urlUtils.ts +++ b/apps/stats-web/src/lib/urlUtils.ts @@ -1,30 +1,20 @@ -import { selectedNetworkId } from "./constants"; - -function getSelectedNetworkQueryParam() { - if (selectedNetworkId) { - return selectedNetworkId; - } else if (typeof window !== "undefined") { - return new URLSearchParams(window.location.search).get("network"); - } - - return undefined; -} +import { networkStore } from "@/store/network.store"; export class UrlService { static home = () => "/"; static graph = (snapshot: string) => `/graph/${snapshot}`; static providerGraph = (snapshot: string) => `/provider-graph/${snapshot}`; static blocks = () => `/blocks`; - static block = (height: number) => `/blocks/${height}${appendSearchParams({ network: getSelectedNetworkQueryParam() as string })}`; + static block = (height: number) => `/blocks/${height}${appendSearchParams({ network: networkStore.selectedNetworkId })}`; static transactions = () => `/transactions`; - static transaction = (hash: string) => `/transactions/${hash}${appendSearchParams({ network: getSelectedNetworkQueryParam() as string })}`; - static address = (address: string) => `/addresses/${address}${appendSearchParams({ network: getSelectedNetworkQueryParam() as string })}`; + static transaction = (hash: string) => `/transactions/${hash}${appendSearchParams({ network: networkStore.selectedNetworkId })}`; + static address = (address: string) => `/addresses/${address}${appendSearchParams({ network: networkStore.selectedNetworkId })}`; static addressTransactions = (address: string) => `/addresses/${address}/transactions`; static addressDeployments = (address: string) => `/addresses/${address}/deployments`; static deployment = (owner: string, dseq: string) => - `/addresses/${owner}/deployments/${dseq}${appendSearchParams({ network: getSelectedNetworkQueryParam() as string })}`; + `/addresses/${owner}/deployments/${dseq}${appendSearchParams({ network: networkStore.selectedNetworkId })}`; static validators = () => "/validators"; - static validator = (address: string) => `/validators/${address}${appendSearchParams({ network: getSelectedNetworkQueryParam() as string })}`; + static validator = (address: string) => `/validators/${address}${appendSearchParams({ network: networkStore.selectedNetworkId })}`; static proposals = () => "/proposals"; static proposal = (id: number) => `/proposals/${id}`; } @@ -64,9 +54,3 @@ export function isValidHttpUrl(str: string): boolean { return url.protocol === "http:" || url.protocol === "https:"; } - -export function handleDocClick(ev: Event, url: string) { - ev.preventDefault(); - - window.open(url, "_blank"); -} diff --git a/apps/stats-web/src/services/api-url/api-url.service.ts b/apps/stats-web/src/services/api-url/api-url.service.ts new file mode 100644 index 000000000..5c13fb9cd --- /dev/null +++ b/apps/stats-web/src/services/api-url/api-url.service.ts @@ -0,0 +1,36 @@ +import type { NetworkId } from "@akashnetwork/akashjs/build/types/network"; +import { SANDBOX_ID, TESTNET_ID } from "@akashnetwork/network-store"; + +import type { BrowserEnvConfig, ServerEnvConfig } from "@/config/env-config.schema"; + +export class ApiUrlService { + constructor( + private readonly config: + | Pick + | Pick + ) {} + + getBaseApiUrlFor(network: NetworkId) { + if ("BASE_API_MAINNET_URL" in this.config) { + switch (network) { + case TESTNET_ID: + return this.config.BASE_API_TESTNET_URL; + case SANDBOX_ID: + return this.config.BASE_API_SANDBOX_URL; + default: + return this.config.BASE_API_MAINNET_URL; + } + } + + if ("NEXT_PUBLIC_BASE_API_MAINNET_URL" in this.config) { + switch (network) { + case TESTNET_ID: + return this.config.NEXT_PUBLIC_BASE_API_TESTNET_URL; + case SANDBOX_ID: + return this.config.NEXT_PUBLIC_BASE_API_SANDBOX_URL; + default: + return this.config.NEXT_PUBLIC_BASE_API_MAINNET_URL; + } + } + } +} diff --git a/apps/stats-web/src/services/api-url/browser-api-url.service.ts b/apps/stats-web/src/services/api-url/browser-api-url.service.ts new file mode 100644 index 000000000..ca1a02faf --- /dev/null +++ b/apps/stats-web/src/services/api-url/browser-api-url.service.ts @@ -0,0 +1,4 @@ +import { browserEnvConfig } from "@/config/browser-env.config"; +import { ApiUrlService } from "@/services/api-url/api-url.service"; + +export const browserApiUrlService = new ApiUrlService(browserEnvConfig); diff --git a/apps/stats-web/src/services/api-url/server-api-url.service.ts b/apps/stats-web/src/services/api-url/server-api-url.service.ts new file mode 100644 index 000000000..5b17432a7 --- /dev/null +++ b/apps/stats-web/src/services/api-url/server-api-url.service.ts @@ -0,0 +1,4 @@ +import { serverEnvConfig } from "@/config/server-env.config"; +import { ApiUrlService } from "@/services/api-url/api-url.service"; + +export const serverApiUrlService = new ApiUrlService(serverEnvConfig); diff --git a/apps/stats-web/src/store/global.store.ts b/apps/stats-web/src/store/global.store.ts new file mode 100644 index 000000000..2ff6e1b41 --- /dev/null +++ b/apps/stats-web/src/store/global.store.ts @@ -0,0 +1,3 @@ +import { createStore } from "jotai"; + +export const store = createStore(); diff --git a/apps/stats-web/src/store/network.store.ts b/apps/stats-web/src/store/network.store.ts new file mode 100644 index 000000000..da9ef9579 --- /dev/null +++ b/apps/stats-web/src/store/network.store.ts @@ -0,0 +1,10 @@ +import { NetworkStore } from "@akashnetwork/network-store"; + +import { browserEnvConfig } from "@/config/browser-env.config"; +import { store } from "@/store/global.store"; + +export const networkStore = NetworkStore.create({ + defaultNetworkId: browserEnvConfig.NEXT_PUBLIC_DEFAULT_NETWORK_ID, + apiBaseUrl: browserEnvConfig.NEXT_PUBLIC_API_BASE_URL, + store +}); diff --git a/apps/stats-web/src/store/networkStore.ts b/apps/stats-web/src/store/networkStore.ts index 7eba30159..e69de29bb 100644 --- a/apps/stats-web/src/store/networkStore.ts +++ b/apps/stats-web/src/store/networkStore.ts @@ -1,67 +0,0 @@ -import axios from "axios"; -import { atom } from "jotai"; - -import { ApiUrlService } from "@/lib/apiUtils"; -import { mainnetId, sandboxId, testnetId } from "@/lib/constants"; -import { Network } from "@/types/network"; - -export let networks: Network[] = [ - { - id: mainnetId, - title: "Mainnet", - description: "Akash Network mainnet network.", - chainId: "akashnet-2", - versionUrl: ApiUrlService.mainnetVersion(), - rpcEndpoint: "https://rpc.cosmos.directory/akash", - enabled: true, - version: null // Set asynchronously - }, - { - id: testnetId, - title: "GPU Testnet", - description: "Testnet of the new GPU features.", - chainId: "testnet-02", - versionUrl: ApiUrlService.testnetVersion(), - enabled: false, - version: null // Set asynchronously - }, - { - id: sandboxId, - title: "Sandbox", - description: "Sandbox of the mainnet version.", - chainId: "sandbox-01", - versionUrl: ApiUrlService.sandboxVersion(), - version: null, // Set asynchronously - enabled: true - } -]; - -/** - * Get the actual versions and metadata of the available networks - */ -export const initiateNetworkData = async () => { - networks = await Promise.all( - networks.map(async network => { - let version = null; - try { - const response = await axios.get(network.versionUrl, { timeout: 10000 }); - version = response.data; - } catch (error) { - console.log(error); - } - - return { - ...network, - version - }; - }) - ); -}; - -const selectedNetwork = atom(networks[0]); - -const networkStore = { - selectedNetwork -}; - -export default networkStore; diff --git a/docker-compose.yml b/docker-compose.yml index e9f993d5e..73023682c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -42,3 +42,4 @@ services: - /app/node_modules - /app/apps/stats-web/node_modules - /app/apps/stats-web/.next + - ./packages:/app/packages diff --git a/docker/Dockerfile.node b/docker/Dockerfile.node index 630ccb489..6c5e38b9d 100644 --- a/docker/Dockerfile.node +++ b/docker/Dockerfile.node @@ -44,11 +44,15 @@ RUN npm ci --workspace $WORKSPACE --omit=dev RUN apk add --no-cache libcap; \ setcap cap_net_bind_service=+ep `readlink -f \`which node\`` +RUN wget -q -t3 'https://packages.doppler.com/public/cli/rsa.8004D9FF50437357.key' -O /etc/apk/keys/cli@doppler-8004D9FF50437357.rsa.pub && \ + echo 'https://packages.doppler.com/public/cli/alpine/any-version/main' | tee -a /etc/apk/repositories && \ + apk add doppler + USER $APP_USER WORKDIR /app/$WORKSPACE -CMD ["node", "dist/server.js"] +CMD ["npm", "run", "prod"] FROM production AS production-nginx @@ -62,4 +66,7 @@ RUN apk add --no-cache libcap nginx openssl \ COPY $WORKSPACE/nginx.conf /etc/nginx/nginx.conf -CMD sed -i "s/127.0.0.1/$(hostname -i)/" /etc/nginx/nginx.conf && sed -i "s/:3000/:$PORT/" /etc/nginx/nginx.conf && nginx && node dist/server.js \ No newline at end of file +ARG PORT=3000 +ENV PORT=${PORT} + +CMD sed -i "s/127.0.0.1/$(hostname -i)/" /etc/nginx/nginx.conf && sed -i "s/:3000/:$PORT/" /etc/nginx/nginx.conf && nginx && npm run prod \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 06d4d9f09..82d6d8e20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -277,6 +277,7 @@ "@cosmjs/encoding": "^0.32.4", "@cosmjs/proto-signing": "^0.32.4", "@cosmjs/stargate": "^0.32.4", + "@cosmos-kit/cosmos-extension-metamask": "0.10.0", "@cosmos-kit/cosmostation-extension": "^2.12.2", "@cosmos-kit/keplr": "^2.12.2", "@cosmos-kit/leap": "^2.12.2", @@ -746,6 +747,7 @@ "apps/stats-web": { "version": "0.20.0", "dependencies": { + "@akashnetwork/network-store": "*", "@akashnetwork/ui": "*", "@cosmjs/encoding": "^0.32.4", "@json2csv/plainjs": "^7.0.4", @@ -2848,20 +2850,22 @@ "integrity": "sha512-HPyeRCroJo04xJVHPvT05eskXn4EbII7LyJBsP2ol5jr0wseBBj94ISheB/Xr/moLY5PjZWW4G0foSD+4RiWsA==" }, "node_modules/@chain-registry/keplr": { - "version": "1.68.71", - "resolved": "https://registry.npmjs.org/@chain-registry/keplr/-/keplr-1.68.71.tgz", - "integrity": "sha512-QLRDj3thHh0FNCI42gZa2v1EAn/d7TqdhCc2TcoFQinz/Ziwym7tC++vzsQr+zAruEwMXru6vSGM7owgJIDBmg==", + "version": "1.68.2", + "resolved": "https://registry.npmjs.org/@chain-registry/keplr/-/keplr-1.68.2.tgz", + "integrity": "sha512-H3rdf/cLx7bNyyKo+1nI9HpLTlLzyeqi0Rmt+ggwtFRC63ZmDaMg/3vPY4rHvu38OdcaOid4Nyfc+7h3EEPW8Q==", + "license": "SEE LICENSE IN LICENSE", "dependencies": { - "@chain-registry/types": "^0.45.57", + "@chain-registry/types": "^0.45.1", "@keplr-wallet/cosmos": "0.12.28", "@keplr-wallet/crypto": "0.12.28", "semver": "^7.5.0" } }, "node_modules/@chain-registry/keplr/node_modules/@chain-registry/types": { - "version": "0.45.57", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.57.tgz", - "integrity": "sha512-HPyeRCroJo04xJVHPvT05eskXn4EbII7LyJBsP2ol5jr0wseBBj94ISheB/Xr/moLY5PjZWW4G0foSD+4RiWsA==" + "version": "0.45.55", + "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.55.tgz", + "integrity": "sha512-uy7DQzLgxLOoVL5EyNUUUvftcoJznij06sXVUavdO/4w8HPFTpuAibY9aB7u/ILpIvirQsa6czMRIuWkRQdHEw==", + "license": "SEE LICENSE IN LICENSE" }, "node_modules/@chain-registry/types": { "version": "0.41.4", @@ -4151,6 +4155,22 @@ "xstream": "^11.14.0" } }, + "node_modules/@cosmos-kit/cosmos-extension-metamask": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@cosmos-kit/cosmos-extension-metamask/-/cosmos-extension-metamask-0.10.0.tgz", + "integrity": "sha512-Ii+1MnVDXECjlLH3djer0GORa/R23dgv6fyKPiXhoE0tynzLTlWcwC4OGHZUxgQIpRdTATshivuZEGVvdV+ctA==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@chain-registry/keplr": "1.68.2", + "@cosmos-kit/core": "^2.13.1", + "@cosmsnap/snapper": "^0.2.5", + "cosmjs-types": ">=0.9.0" + }, + "peerDependencies": { + "@cosmjs/amino": ">=0.32.3", + "@cosmjs/proto-signing": ">=0.32.3" + } + }, "node_modules/@cosmos-kit/cosmostation-extension": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/@cosmos-kit/cosmostation-extension/-/cosmostation-extension-2.13.0.tgz", @@ -4219,22 +4239,6 @@ "@cosmjs/proto-signing": ">=0.32.3" } }, - "node_modules/@cosmos-kit/keplr-mobile/node_modules/@chain-registry/keplr": { - "version": "1.68.2", - "resolved": "https://registry.npmjs.org/@chain-registry/keplr/-/keplr-1.68.2.tgz", - "integrity": "sha512-H3rdf/cLx7bNyyKo+1nI9HpLTlLzyeqi0Rmt+ggwtFRC63ZmDaMg/3vPY4rHvu38OdcaOid4Nyfc+7h3EEPW8Q==", - "dependencies": { - "@chain-registry/types": "^0.45.1", - "@keplr-wallet/cosmos": "0.12.28", - "@keplr-wallet/crypto": "0.12.28", - "semver": "^7.5.0" - } - }, - "node_modules/@cosmos-kit/keplr-mobile/node_modules/@chain-registry/types": { - "version": "0.45.57", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.57.tgz", - "integrity": "sha512-HPyeRCroJo04xJVHPvT05eskXn4EbII7LyJBsP2ol5jr0wseBBj94ISheB/Xr/moLY5PjZWW4G0foSD+4RiWsA==" - }, "node_modules/@cosmos-kit/leap": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/@cosmos-kit/leap/-/leap-2.12.2.tgz", @@ -4259,22 +4263,6 @@ "@cosmjs/proto-signing": ">=0.32.3" } }, - "node_modules/@cosmos-kit/leap-extension/node_modules/@chain-registry/keplr": { - "version": "1.68.2", - "resolved": "https://registry.npmjs.org/@chain-registry/keplr/-/keplr-1.68.2.tgz", - "integrity": "sha512-H3rdf/cLx7bNyyKo+1nI9HpLTlLzyeqi0Rmt+ggwtFRC63ZmDaMg/3vPY4rHvu38OdcaOid4Nyfc+7h3EEPW8Q==", - "dependencies": { - "@chain-registry/types": "^0.45.1", - "@keplr-wallet/cosmos": "0.12.28", - "@keplr-wallet/crypto": "0.12.28", - "semver": "^7.5.0" - } - }, - "node_modules/@cosmos-kit/leap-extension/node_modules/@chain-registry/types": { - "version": "0.45.57", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.57.tgz", - "integrity": "sha512-HPyeRCroJo04xJVHPvT05eskXn4EbII7LyJBsP2ol5jr0wseBBj94ISheB/Xr/moLY5PjZWW4G0foSD+4RiWsA==" - }, "node_modules/@cosmos-kit/leap-metamask-cosmos-snap": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@cosmos-kit/leap-metamask-cosmos-snap/-/leap-metamask-cosmos-snap-0.12.2.tgz", @@ -4292,46 +4280,6 @@ "cosmjs-types": ">=0.9.0" } }, - "node_modules/@cosmos-kit/leap-metamask-cosmos-snap/node_modules/@chain-registry/keplr": { - "version": "1.68.2", - "resolved": "https://registry.npmjs.org/@chain-registry/keplr/-/keplr-1.68.2.tgz", - "integrity": "sha512-H3rdf/cLx7bNyyKo+1nI9HpLTlLzyeqi0Rmt+ggwtFRC63ZmDaMg/3vPY4rHvu38OdcaOid4Nyfc+7h3EEPW8Q==", - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "@chain-registry/types": "^0.45.1", - "@keplr-wallet/cosmos": "0.12.28", - "@keplr-wallet/crypto": "0.12.28", - "semver": "^7.5.0" - } - }, - "node_modules/@cosmos-kit/leap-metamask-cosmos-snap/node_modules/@chain-registry/types": { - "version": "0.45.61", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.61.tgz", - "integrity": "sha512-ScSUGG+0FJ+hI19c344ixDRdxdYr5YtU6zCY5Jhb/CJiPxjuoIv2US7a9qBysKGdIYK7iNrp8K5IoSGTRusKfg==", - "license": "SEE LICENSE IN LICENSE" - }, - "node_modules/@cosmos-kit/leap-metamask-cosmos-snap/node_modules/@metamask/providers": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@metamask/providers/-/providers-11.1.2.tgz", - "integrity": "sha512-xjE4cKrGpKZjripkMKMStc0H4LXrWJPijfbaj1kKeDLVhRH2Yu3ZecV3iIhf1EIJePeA+Kx6Pcm7d0IVJ+ea7g==", - "license": "MIT", - "dependencies": { - "@metamask/object-multiplex": "^1.1.0", - "@metamask/safe-event-emitter": "^3.0.0", - "detect-browser": "^5.2.0", - "eth-rpc-errors": "^4.0.2", - "extension-port-stream": "^2.1.1", - "fast-deep-equal": "^3.1.3", - "is-stream": "^2.0.0", - "json-rpc-engine": "^6.1.0", - "json-rpc-middleware-stream": "^4.2.1", - "pump": "^3.0.0", - "webextension-polyfill": "^0.10.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@cosmos-kit/leap-mobile": { "version": "2.11.2", "resolved": "https://registry.npmjs.org/@cosmos-kit/leap-mobile/-/leap-mobile-2.11.2.tgz", @@ -4343,24 +4291,6 @@ "@cosmos-kit/walletconnect": "^2.10.1" } }, - "node_modules/@cosmos-kit/leap-mobile/node_modules/@chain-registry/keplr": { - "version": "1.68.2", - "resolved": "https://registry.npmjs.org/@chain-registry/keplr/-/keplr-1.68.2.tgz", - "integrity": "sha512-H3rdf/cLx7bNyyKo+1nI9HpLTlLzyeqi0Rmt+ggwtFRC63ZmDaMg/3vPY4rHvu38OdcaOid4Nyfc+7h3EEPW8Q==", - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "@chain-registry/types": "^0.45.1", - "@keplr-wallet/cosmos": "0.12.28", - "@keplr-wallet/crypto": "0.12.28", - "semver": "^7.5.0" - } - }, - "node_modules/@cosmos-kit/leap-mobile/node_modules/@chain-registry/types": { - "version": "0.45.61", - "resolved": "https://registry.npmjs.org/@chain-registry/types/-/types-0.45.61.tgz", - "integrity": "sha512-ScSUGG+0FJ+hI19c344ixDRdxdYr5YtU6zCY5Jhb/CJiPxjuoIv2US7a9qBysKGdIYK7iNrp8K5IoSGTRusKfg==", - "license": "SEE LICENSE IN LICENSE" - }, "node_modules/@cosmos-kit/react": { "version": "2.18.0", "resolved": "https://registry.npmjs.org/@cosmos-kit/react/-/react-2.18.0.tgz", @@ -4426,6 +4356,92 @@ "resolved": "https://registry.npmjs.org/@cosmostation/extension-client/-/extension-client-0.1.15.tgz", "integrity": "sha512-HlXYJjFrNpjiV/GUKhri1UL8/bhlOIFFLpRF78YDSqq16x0+plIqx5CAvEusFcKTDpVfpeD5sfUHiKvP7euNFg==" }, + "node_modules/@cosmsnap/snapper": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@cosmsnap/snapper/-/snapper-0.2.7.tgz", + "integrity": "sha512-APdNxu6b761pNL9LTk4uxQr+cE88TdW6abtkVKxOgJcOtRsheI5mj7d5/hcIsm1dSiHv6WXefYdcTG9sOU/K9A==", + "license": "MIT", + "dependencies": { + "@cosmjs/amino": "^0.31.3", + "@keplr-wallet/proto-types": "0.12.12", + "@keplr-wallet/types": "0.12.12", + "appwrite": "^14.0.0", + "node-appwrite": "^14.0.0", + "ses": "^0.18.4" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@cosmsnap/snapper/node_modules/@cosmjs/amino": { + "version": "0.31.3", + "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.31.3.tgz", + "integrity": "sha512-36emtUq895sPRX8PTSOnG+lhJDCVyIcE0Tr5ct59sUbgQiI14y43vj/4WAlJ/utSOxy+Zhj9wxcs4AZfu0BHsw==", + "license": "Apache-2.0", + "dependencies": { + "@cosmjs/crypto": "^0.31.3", + "@cosmjs/encoding": "^0.31.3", + "@cosmjs/math": "^0.31.3", + "@cosmjs/utils": "^0.31.3" + } + }, + "node_modules/@cosmsnap/snapper/node_modules/@cosmjs/crypto": { + "version": "0.31.3", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.31.3.tgz", + "integrity": "sha512-vRbvM9ZKR2017TO73dtJ50KxoGcFzKtKI7C8iO302BQ5p+DuB+AirUg1952UpSoLfv5ki9O416MFANNg8UN/EQ==", + "license": "Apache-2.0", + "dependencies": { + "@cosmjs/encoding": "^0.31.3", + "@cosmjs/math": "^0.31.3", + "@cosmjs/utils": "^0.31.3", + "@noble/hashes": "^1", + "bn.js": "^5.2.0", + "elliptic": "^6.5.4", + "libsodium-wrappers-sumo": "^0.7.11" + } + }, + "node_modules/@cosmsnap/snapper/node_modules/@cosmjs/encoding": { + "version": "0.31.3", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.31.3.tgz", + "integrity": "sha512-6IRtG0fiVYwyP7n+8e54uTx2pLYijO48V3t9TLiROERm5aUAIzIlz6Wp0NYaI5he9nh1lcEGJ1lkquVKFw3sUg==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "bech32": "^1.1.4", + "readonly-date": "^1.0.0" + } + }, + "node_modules/@cosmsnap/snapper/node_modules/@cosmjs/math": { + "version": "0.31.3", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.31.3.tgz", + "integrity": "sha512-kZ2C6glA5HDb9hLz1WrftAjqdTBb3fWQsRR+Us2HsjAYdeE6M3VdXMsYCP5M3yiihal1WDwAY2U7HmfJw7Uh4A==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.2.0" + } + }, + "node_modules/@cosmsnap/snapper/node_modules/@cosmjs/utils": { + "version": "0.31.3", + "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.31.3.tgz", + "integrity": "sha512-VBhAgzrrYdIe0O5IbKRqwszbQa7ZyQLx9nEQuHQ3HUplQW7P44COG/ye2n6AzCudtqxmwdX7nyX8ta1J07GoqA==", + "license": "Apache-2.0" + }, + "node_modules/@cosmsnap/snapper/node_modules/@keplr-wallet/proto-types": { + "version": "0.12.12", + "resolved": "https://registry.npmjs.org/@keplr-wallet/proto-types/-/proto-types-0.12.12.tgz", + "integrity": "sha512-iAqqNlJpxu/8j+SwOXEH2ymM4W0anfxn+eNeWuqz2c/0JxGTWeLURioxQmCtewtllfHdDHHcoQ7/S+NmXiaEgQ==", + "license": "Apache-2.0", + "dependencies": { + "long": "^4.0.0", + "protobufjs": "^6.11.2" + } + }, + "node_modules/@cosmsnap/snapper/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "license": "MIT", @@ -6558,6 +6574,21 @@ "resolved": "https://registry.npmjs.org/@keplr-wallet/simple-fetch/-/simple-fetch-0.12.28.tgz", "integrity": "sha512-T2CiKS2B5n0ZA7CWw0CA6qIAH0XYI1siE50MP+i+V0ZniCGBeL+BMcDw64vFJUcEH+1L5X4sDAzV37fQxGwllA==" }, + "node_modules/@keplr-wallet/types": { + "version": "0.12.12", + "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.12.tgz", + "integrity": "sha512-fo6b8j9EXnJukGvZorifJWEm1BPIrvaTLuu5PqaU5k1ANDasm/FL1NaUuaTBVvhRjINtvVXqYpW/rVUinA9MBA==", + "license": "Apache-2.0", + "dependencies": { + "long": "^4.0.0" + } + }, + "node_modules/@keplr-wallet/types/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "license": "Apache-2.0" + }, "node_modules/@keplr-wallet/unit": { "version": "0.12.28", "resolved": "https://registry.npmjs.org/@keplr-wallet/unit/-/unit-0.12.28.tgz", @@ -6667,6 +6698,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@metamask/object-multiplex/-/object-multiplex-1.3.0.tgz", "integrity": "sha512-czcQeVYdSNtabd+NcYQnrM69MciiJyd1qvKH8WM2Id3C0ZiUUX5Xa/MK+/VUk633DBhVOwdNzAKIQ33lGyA+eQ==", + "license": "ISC", "dependencies": { "end-of-stream": "^1.4.4", "once": "^1.4.0", @@ -6676,10 +6708,33 @@ "node": ">=12.0.0" } }, + "node_modules/@metamask/providers": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@metamask/providers/-/providers-11.1.2.tgz", + "integrity": "sha512-xjE4cKrGpKZjripkMKMStc0H4LXrWJPijfbaj1kKeDLVhRH2Yu3ZecV3iIhf1EIJePeA+Kx6Pcm7d0IVJ+ea7g==", + "license": "MIT", + "dependencies": { + "@metamask/object-multiplex": "^1.1.0", + "@metamask/safe-event-emitter": "^3.0.0", + "detect-browser": "^5.2.0", + "eth-rpc-errors": "^4.0.2", + "extension-port-stream": "^2.1.1", + "fast-deep-equal": "^3.1.3", + "is-stream": "^2.0.0", + "json-rpc-engine": "^6.1.0", + "json-rpc-middleware-stream": "^4.2.1", + "pump": "^3.0.0", + "webextension-polyfill": "^0.10.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@metamask/safe-event-emitter": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-3.1.1.tgz", "integrity": "sha512-ihb3B0T/wJm1eUuArYP4lCTSEoZsClHhuWyfo/kMX3m/odpqNcPfsz5O2A3NT7dXCAgWPGDQGPqygCpgeniKMw==", + "license": "ISC", "engines": { "node": ">=12.0.0" } @@ -7021,9 +7076,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.4.tgz", - "integrity": "sha512-AH3mO4JlFUqsYcwFUHb1wAKlebHU/Hv2u2kb1pAuRanDZ7pD/A/KPD98RHZmwsJpdHQwfEc/06mgpSzwrJYnNg==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.6.tgz", + "integrity": "sha512-BtJZb+hYXGaVJJivpnDoi3JFVn80SHKCiiRUW3kk1SY6UCUy5dWFFSbh+tGi5lHAughzeduMyxbLt3pspvXNSg==", "cpu": [ "arm64" ], @@ -7036,9 +7091,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.4.tgz", - "integrity": "sha512-QVadW73sWIO6E2VroyUjuAxhWLZWEpiFqHdZdoQ/AMpN9YWGuHV8t2rChr0ahy+irKX5mlDU7OY68k3n4tAZTg==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.6.tgz", + "integrity": "sha512-ZHRbGpH6KHarzm6qEeXKSElSXh8dS2DtDPjQt3IMwY8QVk7GbdDYjvV4NgSnDA9huGpGgnyy3tH8i5yHCqVkiQ==", "cpu": [ "x64" ], @@ -7051,9 +7106,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.4.tgz", - "integrity": "sha512-KT6GUrb3oyCfcfJ+WliXuJnD6pCpZiosx2X3k66HLR+DMoilRb76LpWPGb4tZprawTtcnyrv75ElD6VncVamUQ==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.6.tgz", + "integrity": "sha512-O4HqUEe3ZvKshXHcDUXn1OybN4cSZg7ZdwHJMGCXSUEVUqGTJVsOh17smqilIjooP/sIJksgl+1kcf2IWMZWHg==", "cpu": [ "arm64" ], @@ -7066,9 +7121,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.4.tgz", - "integrity": "sha512-Alv8/XGSs/ytwQcbCHwze1HmiIkIVhDHYLjczSVrf0Wi2MvKn/blt7+S6FJitj3yTlMwMxII1gIJ9WepI4aZ/A==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.6.tgz", + "integrity": "sha512-xUcdhr2hfalG8RDDGSFxQ75yOG894UlmFS4K2M0jLrUhauRBGOtUOxoDVwiIIuZQwZ3Y5hDsazNjdYGB0cQ9yQ==", "cpu": [ "arm64" ], @@ -7081,9 +7136,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.4.tgz", - "integrity": "sha512-ze0ShQDBPCqxLImzw4sCdfnB3lRmN3qGMB2GWDRlq5Wqy4G36pxtNOo2usu/Nm9+V2Rh/QQnrRc2l94kYFXO6Q==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.6.tgz", + "integrity": "sha512-InosKxw8UMcA/wEib5n2QttwHSKHZHNSbGcMepBM0CTcNwpxWzX32KETmwbhKod3zrS8n1vJ+DuJKbL9ZAB0Ag==", "cpu": [ "x64" ], @@ -7096,9 +7151,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.4.tgz", - "integrity": "sha512-8dwC0UJoc6fC7PX70csdaznVMNr16hQrTDAMPvLPloazlcaWfdPogq+UpZX6Drqb1OBlwowz8iG7WR0Tzk/diQ==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.6.tgz", + "integrity": "sha512-d4QXfJmt5pGJ7cG8qwxKSBnO5AXuKAFYxV7qyDRHnUNvY/dgDh+oX292gATpB2AAHgjdHd5ks1wXxIEj6muLUQ==", "cpu": [ "x64" ], @@ -7111,9 +7166,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.4.tgz", - "integrity": "sha512-jxyg67NbEWkDyvM+O8UDbPAyYRZqGLQDTPwvrBBeOSyVWW/jFQkQKQ70JDqDSYg1ZDdl+E3nkbFbq8xM8E9x8A==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.6.tgz", + "integrity": "sha512-AlgIhk4/G+PzOG1qdF1b05uKTMsuRatFlFzAi5G8RZ9h67CVSSuZSbqGHbJDlcV1tZPxq/d4G0q6qcHDKWf4aQ==", "cpu": [ "arm64" ], @@ -7126,9 +7181,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.4.tgz", - "integrity": "sha512-twrmN753hjXRdcrZmZttb/m5xaCBFa48Dt3FbeEItpJArxriYDunWxJn+QFXdJ3hPkm4u7CKxncVvnmgQMY1ag==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.6.tgz", + "integrity": "sha512-hNukAxq7hu4o5/UjPp5jqoBEtrpCbOmnUqZSKNJG8GrUVzfq0ucdhQFVrHcLRMvQcwqqDh1a5AJN9ORnNDpgBQ==", "cpu": [ "ia32" ], @@ -7141,9 +7196,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.4.tgz", - "integrity": "sha512-tkLrjBzqFTP8DVrAAQmZelEahfR9OxWpFR++vAI9FBhCiIxtwHwBHC23SBHCTURBtwB4kc/x44imVOnkKGNVGg==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.6.tgz", + "integrity": "sha512-NANtw+ead1rSDK1jxmzq3TYkl03UNK2KHqUYf1nIhNci6NkeqBD4s1njSzYGIlSHxCK+wSaL8RXZm4v+NF/pMw==", "cpu": [ "x64" ], @@ -15352,6 +15407,45 @@ "node": ">= 8" } }, + "node_modules/appwrite": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/appwrite/-/appwrite-14.0.1.tgz", + "integrity": "sha512-ORlvfqVif/2K3qKGgGiGfMP33Zwm+xxB1fIC4Lm3sojOkDd8u8YvgKQO0Meq5UXb8Dc0Rl66Z7qlGBAfRQ04bA==", + "license": "BSD-3-Clause", + "dependencies": { + "cross-fetch": "3.1.5", + "isomorphic-form-data": "2.0.0" + } + }, + "node_modules/appwrite/node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "license": "MIT", + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/appwrite/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/arg": { "version": "5.0.2", "license": "MIT" @@ -19925,6 +20019,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz", "integrity": "sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg==", + "license": "MIT", "dependencies": { "fast-safe-stringify": "^2.0.6" } @@ -20154,6 +20249,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/extension-port-stream/-/extension-port-stream-2.1.1.tgz", "integrity": "sha512-qknp5o5rj2J9CRKfVB8KJr+uXQlrojNZzdESUPhKYLXf97TPcGf6qWWKmpsNNtUyOdzFhab1ON0jzouNxHHvow==", + "license": "ISC", "dependencies": { "webextension-polyfill": ">=0.10.0 <1.0" }, @@ -22726,6 +22822,29 @@ "node": ">=0.10.0" } }, + "node_modules/isomorphic-form-data": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-form-data/-/isomorphic-form-data-2.0.0.tgz", + "integrity": "sha512-TYgVnXWeESVmQSg4GLVbalmQ+B4NPi/H4eWxqALKj63KsUrcu301YDjBqaOw3h+cbak7Na4Xyps3BiptHtxTfg==", + "license": "MIT", + "dependencies": { + "form-data": "^2.3.2" + } + }, + "node_modules/isomorphic-form-data/node_modules/form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/isomorphic-ws": { "version": "4.0.1", "license": "MIT", @@ -24740,6 +24859,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-6.1.0.tgz", "integrity": "sha512-NEdLrtrq1jUZyfjkr9OCz9EzCNhnRyWtt1PAnvnhwy6e8XETS0Dtc+ZNCO2gvuAoKsIn2+vCSowXTYE4CkgnAQ==", + "license": "ISC", "dependencies": { "@metamask/safe-event-emitter": "^2.0.0", "eth-rpc-errors": "^4.0.2" @@ -24751,12 +24871,14 @@ "node_modules/json-rpc-engine/node_modules/@metamask/safe-event-emitter": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz", - "integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==" + "integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==", + "license": "ISC" }, "node_modules/json-rpc-middleware-stream": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/json-rpc-middleware-stream/-/json-rpc-middleware-stream-4.2.3.tgz", "integrity": "sha512-4iFb0yffm5vo3eFKDbQgke9o17XBcLQ2c3sONrXSbcOLzP8LTojqo8hRGVgtJShhm5q4ZDSNq039fAx9o65E1w==", + "license": "ISC", "dependencies": { "@metamask/safe-event-emitter": "^3.0.0", "json-rpc-engine": "^6.1.0", @@ -28249,6 +28371,15 @@ "version": "4.3.0", "license": "MIT" }, + "node_modules/node-appwrite": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-14.1.0.tgz", + "integrity": "sha512-kuKAZrdaAcGYOMUXtxNb1j+uIy+FIMiiU1dFkgwTXLsMLeLvC6HJ8/FH/kN9JyrWR2a2zcGN7gWfyQgWYoLMTA==", + "license": "BSD-3-Clause", + "dependencies": { + "node-fetch-native-with-agent": "1.7.2" + } + }, "node_modules/node-dependency-injection": { "version": "3.1.2", "license": "MIT", @@ -28440,6 +28571,12 @@ "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.4.tgz", "integrity": "sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==" }, + "node_modules/node-fetch-native-with-agent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-fetch-native-with-agent/-/node-fetch-native-with-agent-1.7.2.tgz", + "integrity": "sha512-5MaOOCuJEvcckoz7/tjdx1M6OusOY6Xc5f459IaruGStWnKzlI1qpNgaAwmn4LmFYcsSlj+jBMk84wmmRxfk5g==", + "license": "MIT" + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -32074,6 +32211,12 @@ "node": ">= 0.8.0" } }, + "node_modules/ses": { + "version": "0.18.4", + "resolved": "https://registry.npmjs.org/ses/-/ses-0.18.4.tgz", + "integrity": "sha512-Ph0PC38Q7uutHmMM9XPqA7rp/2taiRwW6pIZJwTr4gz90DtrBvy/x7AmNPH2uqNPhKriZpYKvPi1xKWjM9xJuQ==", + "license": "Apache-2.0" + }, "node_modules/set-blocking": { "version": "2.0.0", "license": "ISC" @@ -35212,7 +35355,8 @@ "node_modules/webextension-polyfill": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz", - "integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==" + "integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==", + "license": "MPL-2.0" }, "node_modules/webidl-conversions": { "version": "3.0.1", @@ -36310,7 +36454,8 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "axios": "^1.7.2" + "axios": "^1.7.2", + "lodash": "^4.17.21" }, "devDependencies": { "@akashnetwork/akashjs": "^0.10.0" diff --git a/packages/dev-config/.eslintrc.base.js b/packages/dev-config/.eslintrc.base.js index a8d461aa5..0b6ca3f6f 100644 --- a/packages/dev-config/.eslintrc.base.js +++ b/packages/dev-config/.eslintrc.base.js @@ -5,7 +5,7 @@ module.exports = { }, extends: ["eslint:recommended"], plugins: ["simple-import-sort"], - ignorePatterns: ["node_modules", "dist", "build", "public"], + ignorePatterns: ["node_modules", "dist", "build", "public", "Leap"], rules: { "@typescript-eslint/no-unused-vars": ["error", { ignoreRestSiblings: true }], "simple-import-sort/imports": [ diff --git a/packages/network-store/package.json b/packages/network-store/package.json index 4959ea4b8..b7ea9e61b 100644 --- a/packages/network-store/package.json +++ b/packages/network-store/package.json @@ -11,7 +11,8 @@ "lint": "eslint ." }, "dependencies": { - "axios": "^1.7.2" + "axios": "^1.7.2", + "lodash": "^4.17.21" }, "devDependencies": { "@akashnetwork/akashjs": "^0.10.0" diff --git a/packages/network-store/src/network.store.ts b/packages/network-store/src/network.store.ts index f756f8069..2d65b61b2 100644 --- a/packages/network-store/src/network.store.ts +++ b/packages/network-store/src/network.store.ts @@ -2,6 +2,7 @@ import axios from "axios"; import { atom } from "jotai"; import { getDefaultStore, useAtom } from "jotai"; import { atomWithStorage } from "jotai/utils"; +import cloneDeep from "lodash/cloneDeep"; import { INITIAL_NETWORKS_CONFIG } from "./network.config"; import type { Network } from "./network.type"; @@ -31,9 +32,11 @@ export class NetworkStore { return new NetworkStore(options); } - readonly networksStore = atom({ isLoading: true, error: undefined, data: INITIAL_NETWORKS_CONFIG }); + private readonly STORAGE_KEY = "selectedNetworkId"; - private readonly selectedNetworkIdStore = atomWithStorage("selectedNetworkId", this.options.defaultNetworkId, undefined, { getOnInit: true }); + readonly networksStore = atom({ isLoading: true, error: undefined, data: cloneDeep(INITIAL_NETWORKS_CONFIG) }); + + private readonly selectedNetworkIdStore = atomWithStorage(this.STORAGE_KEY, this.options.defaultNetworkId); private readonly selectedNetworkStore = atom( get => { @@ -42,8 +45,8 @@ export class NetworkStore { return networks.find(n => n.id === networkId) ?? networks[0]; }, - async (get, set, next) => { - await set(this.selectedNetworkIdStore, next.id); + (get, set, next) => { + set(this.selectedNetworkIdStore, next.id); } ); @@ -71,13 +74,14 @@ export class NetworkStore { constructor(private readonly options: NetworkStoreOptions) { this.store = options.store || getDefaultStore(); + this.initiateNetworkFromUrlQuery(); this.initiateNetworks(); } private async initiateNetworks() { const errors: { network: Network; error: Error }[] = []; const networks = await Promise.all( - INITIAL_NETWORKS_CONFIG.map(async network => { + cloneDeep(INITIAL_NETWORKS_CONFIG).map(async network => { try { network.versionUrl = this.options.apiBaseUrl + network.versionUrl; network.nodesUrl = this.options.apiBaseUrl + network.nodesUrl; @@ -101,6 +105,24 @@ export class NetworkStore { } } + private initiateNetworkFromUrlQuery(): void { + if (typeof window === "undefined") { + return; + } + + const url = new URL(window.location.href); + + if (!url.searchParams.has("network")) { + return; + } + + const raw = url.searchParams.get("network"); + + if (INITIAL_NETWORKS_CONFIG.some(({ id }) => id === raw)) { + window.localStorage.setItem(this.STORAGE_KEY, JSON.stringify(raw)); + } + } + useNetworksStore() { return useAtom(this.networksStore); } diff --git a/packages/ui/components/custom/address.tsx b/packages/ui/components/custom/address.tsx index add9479f2..ae70ac7b5 100644 --- a/packages/ui/components/custom/address.tsx +++ b/packages/ui/components/custom/address.tsx @@ -43,7 +43,7 @@ export const Address: React.FunctionComponent = ({ address, isCopyable, d {...rest} > {formattedAddress} - {isCopyable && } + {isCopyable && } );