diff --git a/.github/workflows/build_deploy.yml b/.github/workflows/build_deploy.yml
index aa4340f82..7aa666831 100644
--- a/.github/workflows/build_deploy.yml
+++ b/.github/workflows/build_deploy.yml
@@ -41,8 +41,8 @@ jobs:
\"insufficientBalanceUrl\": \"https://faucet.stage.ssv.network\",
\"googleTagSecret\": \"${{ secrets.STAGE_GOOGLE_TAG_SECRET }}\",
\"tokenAddress\": \"0x746c33ccc28b1363c35c09badaf41b2ffa7e6d56\",
- \"setterContractAddress\": \"0x0aaace4e8affc47c6834171c88d342a4abd8f105\",
- \"getterContractAddress\": \"0x9143b8c25efa53f28de4cbefd0b6dfd66d43fea6\"
+ \"setterContractAddress\": \"0x384AC2c8AF4Df1faD7E20F15064B2C2917fAa7a3\",
+ \"getterContractAddress\": \"0x92c71f0A9823789f72BAEBB2BFE39e897bDd26bd\"
}
]
PROD_SSV_NETWORKS: >
@@ -87,6 +87,9 @@ jobs:
- name: Run lint
run: pnpm lint
+ - name: Run tests
+ run: pnpm run test:run
+
- name: Run semantic-release
if: github.event_name == 'push' && (github.ref == 'refs/heads/stage' || github.ref == 'refs/heads/main')
env:
@@ -94,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 }}
@@ -121,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 }}"
@@ -141,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/.husky/pre-commit b/.husky/pre-commit
index 79fde575c..374e37fdd 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,4 +1,5 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
+pnpm run test:run
pnpm run lint-staged
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/initial-funding.tsx b/src/app/routes/create-cluster/initial-funding.tsx
index 81b5150b3..32169b404 100644
--- a/src/app/routes/create-cluster/initial-funding.tsx
+++ b/src/app/routes/create-cluster/initial-funding.tsx
@@ -1,6 +1,6 @@
import {
useComputeFundingCost,
- useFundingCost,
+ useFundingCostETH,
} from "@/hooks/use-compute-funding-cost";
import type { ComponentPropsWithoutRef, FC } from "react";
import { Alert, AlertDescription } from "@/components/ui/alert";
@@ -88,19 +88,19 @@ export const InitialFunding: FCProps = ({ ...props }) => {
days && days < globals.CLUSTER_VALIDITY_PERIOD_MINIMUM,
);
- const customFundingCost = useFundingCost({
+ const customFundingCost = useFundingCostETH({
fundingDays: values.custom,
operators: operators.data ?? [],
effectiveBalance,
});
- const yearFundingCost = useFundingCost({
+ const yearFundingCost = useFundingCostETH({
fundingDays: periods.year,
operators: operators.data ?? [],
effectiveBalance,
});
- const halfYearFundingCost = useFundingCost({
+ const halfYearFundingCost = useFundingCostETH({
fundingDays: periods["half-year"],
operators: operators.data ?? [],
effectiveBalance,
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/create-cluster/reactivate.tsx b/src/app/routes/create-cluster/reactivate.tsx
index 46421a2a2..557ab4d40 100644
--- a/src/app/routes/create-cluster/reactivate.tsx
+++ b/src/app/routes/create-cluster/reactivate.tsx
@@ -27,7 +27,7 @@ import { useClusterPageParams } from "@/hooks/cluster/use-cluster-page-params";
import { useOperators } from "@/hooks/operator/use-operators";
import {
useComputeFundingCost,
- useFundingCost,
+ useFundingCostETH,
} from "@/hooks/use-compute-funding-cost";
import { withTransactionModal } from "@/lib/contract-interactions/utils/useWaitForTransactionReceipt";
import { useReactivate } from "@/lib/contract-interactions/write/use-reactivate";
@@ -104,19 +104,19 @@ export const ReactivateCluster: FCProps = ({ ...props }) => {
days && days < globals.CLUSTER_VALIDITY_PERIOD_MINIMUM,
);
- const customFundingCost = useFundingCost({
+ const customFundingCost = useFundingCostETH({
fundingDays: values.custom,
operators: operators.data ?? [],
effectiveBalance,
});
- const yearFundingCost = useFundingCost({
+ const yearFundingCost = useFundingCostETH({
fundingDays: periods.year,
operators: operators.data ?? [],
effectiveBalance,
});
- const halfYearFundingCost = useFundingCost({
+ const halfYearFundingCost = useFundingCostETH({
fundingDays: periods["half-year"],
operators: operators.data ?? [],
effectiveBalance,
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) => (
> = ({ ...props }) => {
const params = useOperatorPageParams();
const operatorId = BigInt(params.operatorId!);
const operator = useOperator(operatorId!);
-
const { feeEth, yearlyFeeEth, yearlyFeeSSV, balanceEth, balanceSSV } =
useOperatorEarningsAndFees(operatorId);
@@ -104,7 +103,7 @@ export const Operator: FC> = ({ ...props }) => {
-
+ {yearlyFeeSSV !== 0n && !operator.data.migrated && }
> = () => {
- const navigate = useNavigate();
const { isPrivate } = useRegisterOperatorContext();
+
+ const navigate = useNavigate();
const rates = useRates();
+
+ const { data: minFee = 0n } = useGetMinimumOperatorEthFee({
+ staleTime: ms(1, "weeks"),
+ });
+ const { data: maxFee = 13900000000n /* value from the contract */ } =
+ useGetMaximumOperatorFee({
+ staleTime: ms(1, "weeks"),
+ });
+
+ const minYearlyFee = getYearlyFee(minFee);
+ const minYearlyFeeFormatted = getYearlyFee(minFee, { format: true });
+
+ const maxYearlyFee = getYearlyFee(maxFee);
+ const maxYearlyFeeFormatted = getYearlyFee(maxFee, { format: true });
+
const ethRate = rates.data?.eth ?? 0;
const form = useForm({
@@ -43,10 +60,10 @@ export const SetOperatorFee: FC> = () => {
resolver: zodResolver(
z.object({
yearlyFee: z.bigint().superRefine((value, ctx) => {
- if (value > parseEther("200")) {
+ if (value > maxYearlyFee) {
return ctx.addIssue({
code: z.ZodIssueCode.custom,
- message: "Fee must be lower than 200 ETH",
+ message: `Fee must be lower than ${maxYearlyFeeFormatted}`,
});
}
if (isPrivate && value === parseEther("0")) return;
@@ -57,10 +74,10 @@ export const SetOperatorFee: FC> = () => {
message: `Fee cannot be set to 0 while operator status is set to public. To set the fee to 0, switch the operator status to private in the previous step.`,
});
- if (value >= parseEther("0") && value < minimumFee)
+ if (value >= parseEther("0") && value < minYearlyFee)
return ctx.addIssue({
code: z.ZodIssueCode.custom,
- message: `Fee must be greater than ${formatUnits(minimumFee, 18)} ETH`,
+ message: `Fee must be greater than ${minYearlyFeeFormatted}`,
});
}),
}),
@@ -137,7 +154,7 @@ export const SetOperatorFee: FC> = () => {
id="register-operator-fee"
value={field.value}
onChange={field.onChange}
- max={parseEther("200")}
+ max={maxYearlyFee}
rightSlot={
![]()
{
// const isBulk = validatorsAmount > 1;
- const cost = useFundingCost({
+ const cost = useFundingCostETH({
operators,
fundingDays,
effectiveBalance,
diff --git a/src/components/cluster/estimated-operational-runway.tsx b/src/components/cluster/estimated-operational-runway.tsx
index ee14a6cfe..fe8386712 100644
--- a/src/components/cluster/estimated-operational-runway.tsx
+++ b/src/components/cluster/estimated-operational-runway.tsx
@@ -1,4 +1,4 @@
-import type { FC, ComponentPropsWithoutRef } from "react";
+import type { ComponentPropsWithoutRef, FC } from "react";
import { cn } from "@/lib/utils/tw";
import { Span, Text } from "@/components/ui/text";
import { FaCircleInfo } from "react-icons/fa6";
@@ -13,7 +13,7 @@ import { humanizeFundingDuration } from "@/lib/utils/date";
export type EstimatedOperationalRunwayProps = {
clusterHash?: string;
deltaBalance?: bigint;
- deltaValidators?: bigint;
+ deltaEffectiveBalance?: bigint;
withAlerts?: boolean;
};
@@ -25,7 +25,7 @@ type EstimatedOperationalRunwayFC = FC<
export const EstimatedOperationalRunway: EstimatedOperationalRunwayFC = ({
className,
clusterHash,
- deltaValidators = 0n,
+ deltaEffectiveBalance = 0n,
deltaBalance = 0n,
withAlerts = true,
...props
@@ -37,7 +37,7 @@ export const EstimatedOperationalRunway: EstimatedOperationalRunwayFC = ({
const { data: clusterRunway } = useClusterRunway(hash!, {
deltaBalance,
- deltaValidators,
+ deltaEffectiveBalance,
watch: true,
});
@@ -81,10 +81,12 @@ export const EstimatedOperationalRunway: EstimatedOperationalRunwayFC = ({
{withAlerts && (
)}
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 (