diff --git a/.github/workflows/build_deploy.yml b/.github/workflows/build_deploy.yml
index 1a63406bc..7aa666831 100644
--- a/.github/workflows/build_deploy.yml
+++ b/.github/workflows/build_deploy.yml
@@ -97,7 +97,7 @@ jobs:
run: npx semantic-release
- name: Configure AWS credentials
- if: github.ref == 'refs/heads/pre-stage' || github.ref == 'refs/heads/stage' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/bapps-prod'
+ if: github.ref == 'refs/heads/pre-stage' || github.ref == 'refs/heads/stage' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/bapps-prod' || github.ref == 'refs/heads/temporary-hoodi-deploy'
uses: aws-actions/configure-aws-credentials@50ac8dd1e1b10d09dac7b8727528b91bed831ac0 # v3
with:
role-to-assume: ${{ secrets.SSV_WEB_AWS_IAM_ROLE }}
@@ -124,10 +124,9 @@ jobs:
run: |
aws s3 cp ./build s3://${{ secrets.SSV_WEB_STAGE_AWS_S3_BUCKET }} --recursive
#
-
#
- name: Run prod webapp build
- if: github.ref == 'refs/heads/main'
+ if: github.ref == 'refs/heads/temporary-hoodi-deploy'
run: >
GAS_PRICE="${{ env.GAS_PRICE }}"
GAS_LIMIT="${{ env.GAS_LIMIT }}"
@@ -144,7 +143,7 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: Upload files to S3
- if: github.ref == 'refs/heads/main'
+ if: github.ref == 'refs/heads/temporary-hoodi-deploy'
run: |
aws s3 cp ./build s3://${{ secrets.SSV_WEB_PROD_HOODI_AWS_S3_BUCKET }} --recursive
- #
\ No newline at end of file
+ #
diff --git a/scripts/generate-hook-exports.cjs b/scripts/generate-hook-exports.cjs
index 2fff5fa53..d9124c735 100644
--- a/scripts/generate-hook-exports.cjs
+++ b/scripts/generate-hook-exports.cjs
@@ -197,8 +197,14 @@ function generate() {
const outputPath = path.join(HOOKS_DIR, `${name}.ts`);
const content = generateExportFile(name, hookNames);
- fs.writeFileSync(outputPath, content, "utf-8");
- console.log(` Generated: ${outputPath}`);
+ const existing = fs.existsSync(outputPath) ? fs.readFileSync(outputPath, "utf-8") : "";
+ const normalize = (s) => s.replace(/\s/g, "");
+ if (normalize(existing) === normalize(content)) {
+ console.log(` Unchanged: ${outputPath}`);
+ } else {
+ fs.writeFileSync(outputPath, content, "utf-8");
+ console.log(` Generated: ${outputPath}`);
+ }
}
console.log("Done!");
diff --git a/src/app/layouts/dashboard/navbar-dvt.tsx b/src/app/layouts/dashboard/navbar-dvt.tsx
index 46c5a22ae..5e66b289d 100644
--- a/src/app/layouts/dashboard/navbar-dvt.tsx
+++ b/src/app/layouts/dashboard/navbar-dvt.tsx
@@ -6,7 +6,6 @@ import { HiOutlineExternalLink, HiOutlineGlobeAlt } from "react-icons/hi";
import { TbDots } from "react-icons/tb";
import { ConnectWalletBtn } from "@/components/connect-wallet/connect-wallet-btn";
-import { NetworkSwitchBtn } from "@/components/connect-wallet/network-switch-btn";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
@@ -23,6 +22,7 @@ import { ThemeSwitcher } from "@/components/ui/theme-switcher";
import { Link } from "react-router-dom";
import { useLinks } from "@/hooks/use-links";
import { useAccountState } from "@/hooks/account/use-account-state";
+import { NetworkSwitcher } from "@/components/connect-wallet/network-switcher-hotfix";
export type NavbarProps = {
// TODO: Add props or remove this type
@@ -130,7 +130,8 @@ export const NavbarDVT: FCProps = ({ className, ...props }) => {
-
+ {/* */}
+
diff --git a/src/app/layouts/dashboard/navbar.tsx b/src/app/layouts/dashboard/navbar.tsx
index 2a7c57172..8a9e09267 100644
--- a/src/app/layouts/dashboard/navbar.tsx
+++ b/src/app/layouts/dashboard/navbar.tsx
@@ -180,6 +180,20 @@ export const Navbar: FCProps = ({ className, ...props }) => {
+
diff --git a/src/app/routes/create-cluster/additional-funding.tsx b/src/app/routes/create-cluster/additional-funding.tsx
index 43f54d16a..2a7cfc369 100644
--- a/src/app/routes/create-cluster/additional-funding.tsx
+++ b/src/app/routes/create-cluster/additional-funding.tsx
@@ -42,7 +42,8 @@ export const AdditionalFunding: FC = () => {
});
const context = useRegisterValidatorContext();
- const deltaValidators = BigInt(context.shares.length);
+
+ const deltaEffectiveBalance = context.effectiveBalance;
const form = useForm({
defaultValues: { depositAmount: context.depositAmount, topUp: true },
@@ -53,7 +54,7 @@ export const AdditionalFunding: FC = () => {
const { data: clusterRunway } = useClusterRunway(params.clusterHash!, {
deltaBalance: topUp ? depositAmount : 0n,
- deltaValidators,
+ deltaEffectiveBalance,
});
const submit = form.handleSubmit((data) => {
@@ -80,7 +81,7 @@ export const AdditionalFunding: FC = () => {
diff --git a/src/app/routes/create-cluster/preparation.tsx b/src/app/routes/create-cluster/preparation.tsx
index aec2b0bce..56ddd7680 100644
--- a/src/app/routes/create-cluster/preparation.tsx
+++ b/src/app/routes/create-cluster/preparation.tsx
@@ -59,9 +59,7 @@ export const Preparation: FCProps = ({ className, ...props }) => {
-
- SSV tokens to cover operational fees
-
+ ETH to cover operational fees
diff --git a/src/app/routes/dashboard/clusters/cluster/cluster.tsx b/src/app/routes/dashboard/clusters/cluster/cluster.tsx
index 5a2ca4468..d0cbc2e13 100644
--- a/src/app/routes/dashboard/clusters/cluster/cluster.tsx
+++ b/src/app/routes/dashboard/clusters/cluster/cluster.tsx
@@ -71,6 +71,7 @@ export const Cluster: FC = () => {
{cluster.data?.operators.map((operatorId) => (
)}
diff --git a/src/components/connect-wallet/connect-wallet-btn.tsx b/src/components/connect-wallet/connect-wallet-btn.tsx
index fd19c7c11..39e4fb5af 100644
--- a/src/components/connect-wallet/connect-wallet-btn.tsx
+++ b/src/components/connect-wallet/connect-wallet-btn.tsx
@@ -4,7 +4,6 @@ import { textVariants } from "@/components/ui/text";
import { useAccount } from "@/hooks/account/use-account";
import { shortenAddress } from "@/lib/utils/strings";
import { ConnectButton } from "@rainbow-me/rainbowkit";
-import { ChevronDown } from "lucide-react";
import type { FC } from "react";
type WalletType = "ledger" | "trezor" | "walletconnect" | "metamask";
@@ -40,15 +39,9 @@ export const ConnectWalletBtn: FC
= (props) => {
return (
- {({
- chain,
- openAccountModal,
- openChainModal,
- openConnectModal,
- mounted,
- }) => {
+ {({ chain, openAccountModal, openConnectModal, mounted }) => {
const connected = mounted && account && chain;
- if (!mounted) return null;
+ if (!mounted || chain?.unsupported) return null;
if (!connected) {
return (
@@ -64,25 +57,6 @@ export const ConnectWalletBtn: FC = (props) => {
);
}
- if (chain.unsupported) {
- return (
-
- );
- }
-
return (
diff --git a/src/components/validator/clusters-table/clusters-table-row.tsx b/src/components/validator/clusters-table/clusters-table-row.tsx
index 1678f33bc..e532a164a 100644
--- a/src/components/validator/clusters-table/clusters-table-row.tsx
+++ b/src/components/validator/clusters-table/clusters-table-row.tsx
@@ -42,6 +42,12 @@ export const ClustersTableRow: FCProps = ({ cluster, className, ...props }) => {
const isLoadingRunway = !isLiquidated && runway.isLoading;
const resolvedCluster = merge({}, cluster, apiCluster);
+
+ const effectiveBalance = Math.max(
+ Number(resolvedCluster.effectiveBalance),
+ resolvedCluster.validatorCount * 32,
+ );
+
const isMigrated = resolvedCluster.migrated;
const isSsvCluster = !isMigrated;
return (
@@ -95,7 +101,7 @@ export const ClustersTableRow: FCProps = ({ cluster, className, ...props }) => {

{" "}
- {formatEffectiveBalance(BigInt(resolvedCluster.effectiveBalance))}
+ {formatEffectiveBalance(BigInt(effectiveBalance))}
diff --git a/src/hooks/cluster/use-cluster-runway.ts b/src/hooks/cluster/use-cluster-runway.ts
index ef0327a2c..2daf10c2b 100644
--- a/src/hooks/cluster/use-cluster-runway.ts
+++ b/src/hooks/cluster/use-cluster-runway.ts
@@ -1,17 +1,17 @@
import { useCluster } from "@/hooks/cluster/use-cluster";
import { useClusterBalance } from "@/hooks/cluster/use-cluster-balance";
import { useClusterPageParams } from "@/hooks/cluster/use-cluster-page-params";
-import { useRegisterValidatorContext } from "@/guard/register-validator-guard.tsx";
import { bigintMax } from "@/lib/utils/bigint";
import { calculateRunway } from "@/lib/utils/cluster";
import { useNetworkFee, useNetworkFeeSSV } from "@/hooks/use-ssv-network-fee";
import { sumOperatorsFee } from "@/lib/utils/operator";
import { useOperators } from "@/hooks/operator/use-operators";
-const getDeltaValidators = (options: Options) => {
- if ("deltaValidators" in options) return options.deltaValidators ?? 0n;
+const getDeltaEffectiveBalance = (options: Options) => {
+ if ("deltaValidators" in options)
+ return (options.deltaValidators ?? 0n) * 32n;
if ("deltaEffectiveBalance" in options)
- return BigInt(options.deltaEffectiveBalance ?? 0) / 32n;
+ return BigInt(options.deltaEffectiveBalance ?? 0);
return 0n;
};
@@ -30,12 +30,10 @@ export const useClusterRunway = (
watch: false,
},
) => {
- const { state } = useRegisterValidatorContext;
-
const params = useClusterPageParams();
const clusterHash = hash ?? params.clusterHash;
- const deltaValidators = getDeltaValidators(opts);
+ const deltaEffectiveBalance = getDeltaEffectiveBalance(opts);
const cluster = useCluster(clusterHash, { watch: opts.watch });
const balance = useClusterBalance(clusterHash!, { watch: opts.watch });
@@ -60,16 +58,12 @@ export const useClusterRunway = (
const feesPerBlock = operatorFees + networkFee;
- const clusterEffectiveBalance = BigInt(cluster.data?.effectiveBalance ?? 0);
- const minClusterEffectiveBalance =
- BigInt(cluster.data?.validatorCount ?? 1) * 32n;
-
- const effectiveBalance = bigintMax(
- clusterEffectiveBalance,
- minClusterEffectiveBalance,
- );
-
- const validators = (effectiveBalance + state.effectiveBalance) / 32n;
+ const effectiveBalance = isETH
+ ? bigintMax(
+ BigInt(cluster.data?.effectiveBalance ?? 0),
+ BigInt(cluster.data?.validatorCount ?? 1) * 32n,
+ )
+ : BigInt(cluster.data?.validatorCount ?? 1) * 32n;
const isLoading =
cluster.isLoading ||
@@ -79,10 +73,10 @@ export const useClusterRunway = (
ssvNetworkFee.isLoading;
const runway = calculateRunway({
- balance: balance.data.eth || balance.data.ssv || 0n,
+ balance: (isETH ? balance.data.eth : balance.data.ssv) || 0n,
feesPerBlock,
- validators,
- deltaValidators: deltaValidators,
+ effectiveBalance,
+ deltaEffectiveBalance,
deltaBalance: opts.deltaBalance ?? 0n,
liquidationThresholdBlocks,
minimumLiquidationCollateral,
diff --git a/src/hooks/use-links.ts b/src/hooks/use-links.ts
index 914e8788b..8a5a23b53 100644
--- a/src/hooks/use-links.ts
+++ b/src/hooks/use-links.ts
@@ -1,19 +1,20 @@
import { useMemo } from "react";
import { useAccount } from "@/hooks/account/use-account";
-// const isProduction = location.hostname === "app.ssv.network"; // TODO: determine production through build.yaml environment variable
+const isProduction = location.hostname === "app.ssv.network"; // TODO: determine production through build.yaml environment variable
export const useLinks = () => {
const { chain } = useAccount();
return useMemo(() => {
const chainPrefix = chain?.testnet ? `${chain.name.toLowerCase()}.` : "";
+ const envPrefix = isProduction ? "" : `stage.`;
return {
beaconcha: `https://${chainPrefix}beaconcha.in`,
launchpad: `https://${chainPrefix}launchpad.ethereum.org`,
etherscan: `https://${chainPrefix}etherscan.io`,
ssv: {
explorer: `https://explorer.${chainPrefix}ssv.network/`,
- stake: `https://stake.${chainPrefix}ssv.network`,
+ stake: `https://stake.${envPrefix}ssv.network`,
docs: `https://docs.ssv.network`,
forum: `https://forum.ssv.network/`,
governanceForum: `https://forum.ssv.network/`,
diff --git a/src/hooks/use-ssv-network-details.ts b/src/hooks/use-ssv-network-details.ts
index 3cef3867a..ce039e192 100644
--- a/src/hooks/use-ssv-network-details.ts
+++ b/src/hooks/use-ssv-network-details.ts
@@ -1,12 +1,13 @@
+import { isMainnetEnvironment } from "@/lib/utils/env-checker";
import { isAddress } from "viem";
-import { useChainId } from "wagmi";
import { z } from "zod";
-import { config, hoodi } from "@/wagmi/config";
-import { getAccount, getChainId } from "@wagmi/core";
-import { useAccount } from "@/hooks/account/use-account";
-
-const networks = import.meta.env.VITE_SSV_NETWORKS;
+// Get the network that matches the current environment for app.ssv.network or app.hoodi.ssv.network
+// NETWORKS will be an array with only one network -> hoodi or mainnet
+export const NETWORKS = import.meta.env.VITE_SSV_NETWORKS.filter(
+ (network) =>
+ network.apiNetwork === (isMainnetEnvironment ? "mainnet" : "hoodi"),
+);
const networkSchema = z
.array(
@@ -25,13 +26,13 @@ const networkSchema = z
)
.min(1);
-if (!networks) {
+if (!NETWORKS) {
throw new Error(
"VITE_SSV_NETWORKS is not defined in the environment variables",
);
}
-const parsed = networkSchema.safeParse(networks);
+const parsed = networkSchema.safeParse(NETWORKS);
if (!parsed.success) {
throw new Error(
@@ -44,19 +45,10 @@ Invalid network schema in VITE_SSV_NETWORKS environment variable:
);
}
-export const getSSVNetworkDetails = (chainId?: number) => {
- const _chainId = chainId ?? getChainId(config);
- const { isConnected } = getAccount(config);
- return networks.find(
- (network) => network.networkId === (isConnected ? _chainId : hoodi.id),
- )!;
+export const getSSVNetworkDetails = () => {
+ return NETWORKS[0];
};
export const useSSVNetworkDetails = () => {
- const { isConnected } = useAccount();
- const chainId = useChainId();
-
- return import.meta.env.VITE_SSV_NETWORKS.find(
- (network) => network.networkId === (isConnected ? chainId : hoodi.id),
- )!;
+ return NETWORKS[0];
};
diff --git a/src/lib/utils/cluster.ts b/src/lib/utils/cluster.ts
index 46f93d5fa..cc41cdf5b 100644
--- a/src/lib/utils/cluster.ts
+++ b/src/lib/utils/cluster.ts
@@ -100,24 +100,29 @@ export const filterOutRemovedValidators = (
type CalculateRunwayParams = {
balance: bigint;
feesPerBlock: bigint;
- validators: bigint;
+ effectiveBalance: bigint;
deltaBalance?: bigint;
- deltaValidators?: bigint;
+ deltaEffectiveBalance?: bigint;
liquidationThresholdBlocks: bigint;
minimumLiquidationCollateral: bigint;
};
+const EB_PER_VALIDATOR = 32n;
+
export const calculateRunway = ({
balance,
feesPerBlock,
- validators,
+ effectiveBalance,
deltaBalance = 0n,
- deltaValidators = 0n,
+ deltaEffectiveBalance = 0n,
liquidationThresholdBlocks,
minimumLiquidationCollateral,
}: CalculateRunwayParams) => {
- const burnRateSnapshot = feesPerBlock * (validators || 1n);
- const burnRateWithDelta = feesPerBlock * (validators + deltaValidators);
+ const snapshotEB = effectiveBalance || EB_PER_VALIDATOR;
+ const totalEB = effectiveBalance + deltaEffectiveBalance;
+ const burnRateSnapshot = (feesPerBlock * snapshotEB) / EB_PER_VALIDATOR;
+ const burnRateWithDelta =
+ (feesPerBlock * (totalEB || EB_PER_VALIDATOR)) / EB_PER_VALIDATOR;
const collateralSnapshot = bigintMax(
burnRateSnapshot * liquidationThresholdBlocks,
diff --git a/src/lib/utils/env-checker.ts b/src/lib/utils/env-checker.ts
new file mode 100644
index 000000000..7507ce921
--- /dev/null
+++ b/src/lib/utils/env-checker.ts
@@ -0,0 +1,8 @@
+const isDev = import.meta.env.DEV;
+export const MAINNET_HOST = isDev ? "app.localhost:3000" : "app.ssv.network";
+export const HOODI_HOST = isDev
+ ? "app.hoodi.localhost:3000"
+ : "app.hoodi.ssv.network";
+
+export const isMainnetEnvironment = document.location.host === MAINNET_HOST;
+export const isHoodiEnvironment = document.location.host === HOODI_HOST;
diff --git a/src/wagmi/config.ts b/src/wagmi/config.ts
index b66fe2385..d28c7dbdb 100644
--- a/src/wagmi/config.ts
+++ b/src/wagmi/config.ts
@@ -1,7 +1,8 @@
-import { connectorsForWallets, type Chain } from "@rainbow-me/rainbowkit";
+import { isMainnetEnvironment } from "@/lib/utils/env-checker";
+import { type Chain, connectorsForWallets } from "@rainbow-me/rainbowkit";
import {
- walletConnectWallet,
coinbaseWallet,
+ walletConnectWallet,
} from "@rainbow-me/rainbowkit/wallets";
import { createPublicClient, http } from "viem";
@@ -17,7 +18,6 @@ const mainnet: Chain = {
export const hoodi = {
id: 560048,
name: "Hoodi",
- network: "hoodi",
nativeCurrency: {
name: "Ethereum",
symbol: "ETH",
@@ -44,15 +44,13 @@ export const hoodi = {
iconBackground: "none",
iconUrl: "/images/networks/light.svg",
testnet: true,
-};
-
-const chains = import.meta.env.VITE_SSV_NETWORKS.map((network) =>
- [mainnet, hoodi].find((chain) => chain.id === network.networkId),
-).filter(Boolean) as [Chain, ...Chain[]];
-export const isChainSupported = (chainId: number) => {
- return chains.some((chain) => chain.id === chainId);
-};
+} satisfies Chain;
+const chains = (isMainnetEnvironment ? [mainnet] : [hoodi]) satisfies [
+ Chain,
+ ...Chain[],
+];
+export type Chains = typeof chains;
const connectors = connectorsForWallets(
[
{