diff --git a/README.md b/README.md index d2fa4ab5..c01182ac 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Staking Brain is a critical logical component for Staking in DAppNode. It provides a user interface and a Launchpad API that allow manual and automatic keystore management. Designed to support not only solo stakers, but also DVT/LSD technologies, Staking Brain streamlines the staking process for all users. -Within the Dappnode environment, Staking Brain is incorporated into the Web3Signer packages (gnosis, mainnet, prater and lukso). It ensures that user configurations for validators are reliably maintained. Please note that Staking Brain does not store keystores itself, but ensures their storage in the web3signer. It also maintains consistency between the validator service and web3signer service, as the validator must recognize all the pubkeys of validators whose keystores have been imported into the signer. +Within the Dappnode environment, Staking Brain is incorporated into the Web3Signer packages (gnosis, mainnet, prater, lukso and holesky). It ensures that user configurations for validators are reliably maintained. Please note that Staking Brain does not store keystores itself, but ensures their storage in the web3signer. It also maintains consistency between the validator service and web3signer service, as the validator must recognize all the pubkeys of validators whose keystores have been imported into the signer. Each Web3Signer package includes four services: diff --git a/packages/brain/src/modules/envs/index.ts b/packages/brain/src/modules/envs/index.ts index b1182913..77a5403e 100644 --- a/packages/brain/src/modules/envs/index.ts +++ b/packages/brain/src/modules/envs/index.ts @@ -5,18 +5,22 @@ import { executionClientsPrater, executionClientsGnosis, executionClientsLukso, + executionClientsHolesky, ExecutionClientMainnet, consensusClientsMainnet, ConsensusClientMainnet, ExecutionClientGnosis, ExecutionClientLukso, + ExecutionClientHolesky, ExecutionClientPrater, consensusClientsGnosis, consensusClientsLukso, + consensusClientsHolesky, consensusClientsPrater, ConsensusClientPrater, ConsensusClientGnosis, ConsensusClientLukso, + ConsensusClientHolesky, ExecutionClient, ConsensusClient, } from "@stakingbrain/common"; @@ -342,6 +346,78 @@ export function loadStakerConfig(): { : undefined, tlsCert, }; + } else if (network === "holesky") { + const { executionClient, consensusClient, defaultFeeRecipient } = + loadEnvs("holesky"); + switch (executionClient) { + case "holesky-nethermind.dnp.dappnode.eth": + executionClientUrl = `http://holesky-nethermind.dappnode:8545`; + break; + case "holesky-besu.dnp.dappnode.eth": + executionClientUrl = `http://holesky-besu.dappnode:8545`; + break; + case "holesky-erigon.dnp.dappnode.eth": + executionClientUrl = `http://holesky-erigon.dappnode:8545`; + case "holesky-geth.dnp.dappnode.eth": + executionClientUrl = `http://holesky-geth.dappnode:8545`; + break; + default: + throw Error( + `Unknown execution client for network ${network}: ${executionClient}` + ); + } + switch (consensusClient) { + case "prysm-holesky.dnp.dappnode.eth": + token = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.MxwOozSH-TLbW_XKepjyYDHm2IT8Ki0tD3AHuajfNMg`; + beaconchainUrl = `http://beacon-chain.prysm-holesky.dappnode:3500`; + validatorUrl = `http://validator.prysm-holesky.dappnode:3500`; + break; + case "teku-holesky.dnp.dappnode.eth": + token = `cd4892ca35d2f5d3e2301a65fc7aa660`; + beaconchainUrl = `http://beacon-chain.teku-holesky.dappnode:3500`; + validatorUrl = `https://validator.teku-holesky.dappnode:3500`; + tlsCert = fs.readFileSync( + path.join(certDir, "holesky", "teku_client_keystore.p12") + ); + break; + case "lighthouse-holesky.dnp.dappnode.eth": + token = `api-token-0x0200e6ce18e26fd38caca7ae1bfb9e2bba7efb20ed2746ad17f2f6dda44603152d`; + beaconchainUrl = `http://beacon-chain.lighthouse-holesky.dappnode:3500`; + validatorUrl = `http://validator.lighthouse-holesky.dappnode:3500`; + break; + case "lodestar-holesky.dnp.dappnode.eth": + token = `api-token-0x7fd16fff6453982a5d8bf14617e7823b68cd18ade59985befe64e0a659300e7d`; + beaconchainUrl = `http://beacon-chain.lodestar-holesky.dappnode:3500`; + validatorUrl = `http://validator.lodestar-holesky.dappnode:3500`; + break; + case "nimbus-holesky.dnp.dappnode.eth": + token = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.MxwOozSH-TLbW_XKepjyYDHm2IT8Ki0tD3AHuajfNMg`; + beaconchainUrl = `http://beacon-validator.nimbus-holesky.dappnode:4500`; + validatorUrl = `http://beacon-validator.nimbus-holesky.dappnode:3500`; + break; + default: + throw Error( + `Unknown consensus client for network ${network}: ${consensusClient}` + ); + } + + return { + network, + executionClient, + consensusClient, + executionClientUrl, + validatorUrl, + beaconchainUrl, + beaconchaUrl: `https://holesky.beaconcha.in`, + signerUrl: `http://web3signer.web3signer-holesky.dappnode:9000`, + token, + host: `web3signer.web3signer-holesky.dappnode`, + defaultFeeRecipient: + defaultFeeRecipient && isValidEcdsaPubkey(defaultFeeRecipient) + ? defaultFeeRecipient + : undefined, + tlsCert, + }; } else { throw Error(`Unknown network ${network}`); } @@ -453,6 +529,28 @@ function loadEnvs( )}` ); break; + case "holesky": + if ( + !executionClientsHolesky.includes( + executionClient as ExecutionClientHolesky + ) + ) + errors.push( + `Execution client is not valid for network ${network}: ${executionClient}. Valid execution clients for ${network}: ${executionClientsHolesky.join( + ", " + )}` + ); + if ( + !consensusClientsHolesky.includes( + consensusClient as ConsensusClientHolesky + ) + ) + errors.push( + `Consensus client is not valid for network ${network}: ${consensusClient}. Valid consensus clients for ${network}: ${consensusClientsHolesky.join( + ", " + )}` + ); + break; default: errors.push( `NETWORK environment variable is not valid: ${network}. Valid NETWORK values: ${networks.join( diff --git a/packages/brain/tests/unit/modules/apiClients/fetchValidatorIndex.unit.test.ts b/packages/brain/tests/unit/modules/apiClients/fetchValidatorIndex.unit.test.ts index dd3fa7db..72fd1d6e 100644 --- a/packages/brain/tests/unit/modules/apiClients/fetchValidatorIndex.unit.test.ts +++ b/packages/brain/tests/unit/modules/apiClients/fetchValidatorIndex.unit.test.ts @@ -4,7 +4,7 @@ import { BeaconchaApi } from "../../../../src/modules/apiClients/beaconcha/index describe.skip("Test for fetching validator indexes in every available network", () => { it("should return data corresponding to every validator PK", async () => { - const networks = ["mainnet", "prater", "gnosis", "lukso"]; + const networks = ["mainnet", "prater", "gnosis", "lukso", "holesky"]; for (const network of networks) { console.log("NETWORK: ", network); @@ -74,6 +74,16 @@ const networkTestMap = new Map< indexes: [24693, 4272], }, ], + [ + "holesky", + { + pubkeys: [ + "0x800000b3884235f70b06fec68c19642fc9e81e34fbe7f1c0ae156b8b45860dfe5ac71037ae561c2a759ba83401488e18", + "0x800009f644592de8d2de0da0caca00f26fd6fb3d7f99f57101bbbfb45d4b166f8dbe5fd82b3611e6e90fe323de955bd2" + ], + indexes: [886680, 68945], + } + ], ]); // TODO: move below to common @@ -111,4 +121,12 @@ const beaconchaApiParamsMap = new Map([ apiPath: "/api/v1/", }, ], + [ + "holesky", + { + baseUrl: "https://holesky.beaconcha.in", + host: "brain.web3signer-holesky.dappnode", + apiPath: "/api/v1/", + }, + ], ]); diff --git a/packages/brain/tls/holesky/teku_client_keystore.p12 b/packages/brain/tls/holesky/teku_client_keystore.p12 new file mode 100644 index 00000000..c319e7e8 Binary files /dev/null and b/packages/brain/tls/holesky/teku_client_keystore.p12 differ diff --git a/packages/brain/tls/holesky/teku_keystore_password.txt b/packages/brain/tls/holesky/teku_keystore_password.txt new file mode 100644 index 00000000..a1a35a1b --- /dev/null +++ b/packages/brain/tls/holesky/teku_keystore_password.txt @@ -0,0 +1 @@ +dappnode \ No newline at end of file diff --git a/packages/common/src/types/network/types.ts b/packages/common/src/types/network/types.ts index 23c6cb95..3f28b70d 100644 --- a/packages/common/src/types/network/types.ts +++ b/packages/common/src/types/network/types.ts @@ -1,4 +1,4 @@ -export const networks = ["mainnet", "gnosis", "prater", "lukso"] as const; +export const networks = ["mainnet", "gnosis", "prater", "lukso", "holesky"] as const; export type Network = (typeof networks)[number]; @@ -21,6 +21,8 @@ export type ExecutionClient = T extends "mainnet" ? ExecutionClientPrater : T extends "lukso" ? ExecutionClientLukso + : T extends "holesky" + ? ExecutionClientHolesky : never; export type ConsensusClient = T extends "mainnet" @@ -31,6 +33,8 @@ export type ConsensusClient = T extends "mainnet" ? ConsensusClientPrater : T extends "lukso" ? ConsensusClientLukso + : T extends "holesky" + ? ConsensusClientHolesky : never; export type Signer = T extends "mainnet" @@ -41,6 +45,8 @@ export type Signer = T extends "mainnet" ? SignerPrater : T extends "lukso" ? SignerLukso + : T extends "holesky" + ? SignerHolesky : never; // Mainnet @@ -127,3 +133,25 @@ export const executionClientsLukso = [ "lukso-besu.dnp.dappnode.eth", ] as const; export type ExecutionClientLukso = (typeof executionClientsLukso)[number]; + +// Holesky + +export const signerHolesky = "web3signer-holesky.dnp.dappnode.eth"; +export type SignerHolesky = typeof signerHolesky; + +export const consensusClientsHolesky = [ + "prysm-holesky.dnp.dappnode.eth", + "lighthouse-holesky.dnp.dappnode.eth", + "teku-holesky.dnp.dappnode.eth", + "nimbus-holesky.dnp.dappnode.eth", + "lodestar-holesky.dnp.dappnode.eth", +] as const; +export type ConsensusClientHolesky = (typeof consensusClientsHolesky)[number]; + +export const executionClientsHolesky = [ + "holesky-geth.dnp.dappnode.eth", + "holesky-erigon.dnp.dappnode.eth", + "holesky-nethermind.dnp.dappnode.eth", + "holesky-besu.dnp.dappnode.eth", +] as const; +export type ExecutionClientHolesky = (typeof executionClientsHolesky)[number]; \ No newline at end of file diff --git a/packages/ui/public/assets/lodestar-gnosis.png b/packages/ui/public/assets/lodestar-gnosis.png new file mode 100644 index 00000000..ff26fd42 Binary files /dev/null and b/packages/ui/public/assets/lodestar-gnosis.png differ diff --git a/packages/ui/public/assets/lodestar-prater.png b/packages/ui/public/assets/lodestar-prater.png new file mode 100644 index 00000000..dd124695 Binary files /dev/null and b/packages/ui/public/assets/lodestar-prater.png differ diff --git a/packages/ui/public/assets/lodestar.png b/packages/ui/public/assets/lodestar.png new file mode 100644 index 00000000..f160f72c Binary files /dev/null and b/packages/ui/public/assets/lodestar.png differ diff --git a/packages/ui/public/assets/nethermind-goerli.png b/packages/ui/public/assets/nethermind-goerli.png new file mode 100644 index 00000000..1ccbf792 Binary files /dev/null and b/packages/ui/public/assets/nethermind-goerli.png differ diff --git a/packages/ui/public/assets/nimbus-gnosis.png b/packages/ui/public/assets/nimbus-gnosis.png deleted file mode 100644 index 8214f68c..00000000 Binary files a/packages/ui/public/assets/nimbus-gnosis.png and /dev/null differ diff --git a/packages/ui/src/ImportScreen.tsx b/packages/ui/src/ImportScreen.tsx index 3b5af54e..c7749950 100644 --- a/packages/ui/src/ImportScreen.tsx +++ b/packages/ui/src/ImportScreen.tsx @@ -154,6 +154,12 @@ export default function ImportScreen({ network ) ? [{ value: "solo", label: "Solo" }] + : ["holesky"].includes(network) + ? [ + { value: "solo", label: "Solo" }, + { value: "rocketpool", label: "Rocketpool" }, + { value: "stakehouse", label: "StakeHouse" }, + ] : [ { value: "solo", label: "Solo" }, { value: "rocketpool", label: "Rocketpool" }, diff --git a/packages/ui/src/components/StakerConfig/StakerConfig.tsx b/packages/ui/src/components/StakerConfig/StakerConfig.tsx index 9c471866..94dbe6fe 100644 --- a/packages/ui/src/components/StakerConfig/StakerConfig.tsx +++ b/packages/ui/src/components/StakerConfig/StakerConfig.tsx @@ -14,27 +14,38 @@ export default function StakerConfig({ stakerConfig: StakerConfigType; }): JSX.Element { const images = { + // Mainnet "erigon.dnp.dappnode.eth": "/assets/erigon.png", - "goerli-erigon.dnp.dappnode.eth": "/assets/erigon-goerli.png", "geth.dnp.dappnode.eth": "/assets/geth.png", "besu.public.dappnode.eth": "/assets/besu.png", "nethermind.public.dappnode.eth": "/assets/nethermind.png", - "nethermind-xdai.dnp.dappnode.eth": "/assets/nethermind-gnosis.png", - "goerli-geth.dnp.dappnode.eth": "/assets/geth-goerli.png", - "goerli-besu.dnp.dappnode.eth": "/assets/besu-goerli.png", "prysm.dnp.dappnode.eth": "/assets/prysm.png", "lighthouse.dnp.dappnode.eth": "/assets/lighthouse.png", "teku.dnp.dappnode.eth": "/assets/teku.png", "nimbus.dnp.dappnode.eth": "/assets/nimbus.png", + "lodestar.dnp.dappnode.eth": "/assets/lodestar.png", + + // Goerli/Prater + "goerli-erigon.dnp.dappnode.eth": "/assets/erigon-goerli.png", + "goerli-geth.dnp.dappnode.eth": "/assets/geth-goerli.png", + "goerli-besu.dnp.dappnode.eth": "/assets/besu-goerli.png", + "goerli-nethermind.dnp.dappnode.eth": "/assets/nethermind-goerli.png", "prysm-prater.dnp.dappnode.eth": "/assets/prysm-prater.png", "lighthouse-prater.dnp.dappnode.eth": "/assets/lighthouse-prater.png", "teku-prater.dnp.dappnode.eth": "/assets/teku-prater.png", "nimbus-prater.dnp.dappnode.eth": "/assets/nimbus-prater.png", + "lodestar-prater.dnp.dappnode.eth": "/assets/lodestar-prater.png", + + // Gnosis + "nethermind-xdai.dnp.dappnode.eth": "/assets/nethermind-gnosis.png", "gnosis-beacon-chain-prysm.dnp.dappnode.eth": "/assets/prysm-gnosis.png", "lighthouse-gnosis.dnp.dappnode.eth": "/assets/lighthouse-gnosis.png", "teku-gnosis.dnp.dappnode.eth": "/assets/teku-gnosis.png", - "nimbus-gnosis.dnp.dappnode.eth": "/assets/nimbus-gnosis.png", - // TODO: Add Lukso logos (now mainnet) + // TODO: Add Nimbus Gnosis logo (now mainnet) + "nimbus-gnosis.dnp.dappnode.eth": "/assets/nimbus.png", + "lodestar-gnosis.dnp.dappnode.eth": "/assets/lodestar-gnosis.png", + + // Lukso --> // TODO: Add Lukso logos (now mainnet) "lukso-geth.dnp.dappnode.eth": "/assets/geth.png", "lukso-erigon.dnp.dappnode.eth": "/assets/erigon.png", "lukso-besu.dnp.dappnode.eth": "/assets/besu.png", @@ -45,12 +56,19 @@ export default function StakerConfig({ "nimbus-lukso.dnp.dappnode.eth": "/assets/nimbus.png", "lodestar-lukso.dnp.dappnode.eth": "/assets/lodestar.png", + //Holesky --> // TODO: Add Holesky logos (now mainnet) + "holesky-geth.dnp.dappnode.eth": "/assets/geth.png", + "holesky-erigon.dnp.dappnode.eth": "/assets/erigon.png", + "holesky-besu.dnp.dappnode.eth": "/assets/besu.png", + "holesky-nethermind.dnp.dappnode.eth": "/assets/nethermind.png", + "prysm-holesky.dnp.dappnode.eth": "/assets/prysm.png", + "lighthouse-holesky.dnp.dappnode.eth": "/assets/lighthouse.png", + "teku-holesky.dnp.dappnode.eth": "/assets/teku.png", + "nimbus-holesky.dnp.dappnode.eth": "/assets/nimbus.png", + "lodestar-holesky.dnp.dappnode.eth": "/assets/lodestar.png", + // Default logo until we have a package for them default: "/assets/dappnode_logo_clean.png", - "goerli-nethermind.dnp.dappnode.eth": "/assets/dappnode_logo.png", - "lodestar.dnp.dappnode.eth": "/assets/dappnode_logo.png", - "lodestar-prater.dnp.dappnode.eth": "/assets/dappnode_logo.png", - "lodestar-gnosis.dnp.dappnode.eth": "/assets/dappnode_logo.png", }; return ( diff --git a/packages/ui/src/params.ts b/packages/ui/src/params.ts index c932ff82..e3d4f33a 100644 --- a/packages/ui/src/params.ts +++ b/packages/ui/src/params.ts @@ -11,6 +11,7 @@ export const beaconchaApiParamsMap = new Map>([ apiPath: "/api/v1/", }, ], + ["holesky", { baseUrl: "https://holesky.beaconcha.in", apiPath: "/api/v1/" }], ]); export interface AppParams { diff --git a/packages/ui/src/utils/dataUtils.ts b/packages/ui/src/utils/dataUtils.ts index d693f07e..0042b7ff 100644 --- a/packages/ui/src/utils/dataUtils.ts +++ b/packages/ui/src/utils/dataUtils.ts @@ -27,7 +27,8 @@ export function prettyClientDnpName(dnpName: string): string { !name.includes("goerli") && !name.includes("prater") && !name.includes("gnosis") && - !name.includes("lukso") + !name.includes("lukso") && + !name.includes("holesky") ); if (!clientName) return dnpName;