Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/types/stakers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,41 @@ export const executionClientsLukso = Object.freeze([
export type SignerLukso = "web3signer-lukso.dnp.dappnode.eth";
export const signerLukso: SignerLukso = "web3signer-lukso.dnp.dappnode.eth";

// COMMON
export type ConsensusClient = (typeof consensusClients)[number];
export const consensusClients = Object.freeze([
...consensusClientsMainnet,
...consensusClientsPrater,
...consensusClientsHolesky,
...consensusClientsGnosis,
...consensusClientsLukso,
] as const);

export type ExecutionClient = (typeof executionClients)[number];
export const executionClients = Object.freeze([
...executionClientsMainnet,
...executionClientsPrater,
...executionClientsHolesky,
...executionClientsGnosis,
...executionClientsLukso,
] as const);

export type Signer = (typeof signers)[number];
export const signers = Object.freeze([
signerMainnet,
signerPrater,
signerHolesky,
signerGnosis,
signerLukso,
] as const);

export type MevBoost = (typeof mevBoosts)[number];
export const mevBoosts = Object.freeze([
mevBoostMainnet,
mevBoostPrater,
//mevBoostGnosis,
] as const);

// STAKERS
export const stakerPkgs = Object.freeze([
...executionClientsMainnet,
Expand Down
14 changes: 14 additions & 0 deletions src/utils/ens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,17 @@ export function isEnsDomain(ensDomain: string): boolean {

return regex.test(ensDomain);
}

/**
* Checks if a domain name is a valid DNP name.
* @param dnpName - The domain name to check.
* @returns - True if the domain name is valid, false otherwise.
*/
export function isValidDnpName(dnpName: string): boolean {
// Regular expression to check for a valid DNP name.
// It should have only one subdomain before `.dnp.dappnode.eth` or `.public.dappnode.eth`.
const regex = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.(dnp|public)\.dappnode\.eth$/i;

return regex.test(dnpName);
}

36 changes: 36 additions & 0 deletions src/utils/stakers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import {
ConsensusClientGnosis,
ExecutionClientLukso,
ConsensusClientLukso,
executionClients,
ExecutionClient,
consensusClients,
ConsensusClient,
} from "../types/index.js";
import { isValidDnpName } from "./ens.js";

/**
* Retrieves the URLs of the JSON RPC APIs associated with all execution and consensus clients based on dappnode package names.
Expand Down Expand Up @@ -204,3 +209,34 @@ export function getUrlFromDnpName(): {
consensusClientLuksoUrl,
};
}

export function getJsonRpcApiFromDnpName(dnpName: string): string {

if (!isValidDnpName(dnpName)) throw new Error("Invalid DNP name format.");

let subdomain: string, port: string;

const [pkgShortName, repoType] = dnpName.split('.');

if (executionClients.includes(dnpName as ExecutionClient)) {
subdomain = pkgShortName;
port = '8545';
} else if (consensusClients.includes(dnpName as ConsensusClient)) {

if (pkgShortName.startsWith('nimbus')) {
subdomain = `beacon-validator.${pkgShortName}`;
port = '4500';
} else {
subdomain = `beacon-chain.${pkgShortName}`;
port = '3500';
}

} else {
throw new Error(`The DNP ${dnpName} does not correspond to an execution or consensus client.`);
}

const baseDomain = repoType === 'dnp' ? 'dappnode' : 'public.dappnode';

return `http://${subdomain}.${baseDomain}:${port}`;

}
67 changes: 67 additions & 0 deletions test/stakers.utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { expect } from "chai";
import { getJsonRpcApiFromDnpName } from "../src/utils/index.js";

describe('DNP Name to URL Conversion', function () {
const testCases = [
// Mainnet Execution Clients
{ dnpName: "geth.dnp.dappnode.eth", expectedUrl: "http://geth.dappnode:8545" },
{ dnpName: "besu.public.dappnode.eth", expectedUrl: "http://besu.public.dappnode:8545" },
{ dnpName: "nethermind.public.dappnode.eth", expectedUrl: "http://nethermind.public.dappnode:8545" },
{ dnpName: "erigon.dnp.dappnode.eth", expectedUrl: "http://erigon.dappnode:8545" },

// Mainnet Consensus Clients
{ dnpName: "prysm.dnp.dappnode.eth", expectedUrl: "http://beacon-chain.prysm.dappnode:3500" },
{ dnpName: "lighthouse.dnp.dappnode.eth", expectedUrl: "http://beacon-chain.lighthouse.dappnode:3500" },
{ dnpName: "teku.dnp.dappnode.eth", expectedUrl: "http://beacon-chain.teku.dappnode:3500" },
{ dnpName: "nimbus.dnp.dappnode.eth", expectedUrl: "http://beacon-validator.nimbus.dappnode:4500" },
{ dnpName: "lodestar.dnp.dappnode.eth", expectedUrl: "http://beacon-chain.lodestar.dappnode:3500" },

// Prater Testnet Execution Clients
{ dnpName: "goerli-geth.dnp.dappnode.eth", expectedUrl: "http://goerli-geth.dappnode:8545" },
{ dnpName: "goerli-besu.dnp.dappnode.eth", expectedUrl: "http://goerli-besu.dappnode:8545" },
{ dnpName: "goerli-nethermind.dnp.dappnode.eth", expectedUrl: "http://goerli-nethermind.dappnode:8545" },
{ dnpName: "goerli-erigon.dnp.dappnode.eth", expectedUrl: "http://goerli-erigon.dappnode:8545" },

// Prater Testnet Consensus Clients
{ dnpName: "prysm-prater.dnp.dappnode.eth", expectedUrl: "http://beacon-chain.prysm-prater.dappnode:3500" },
{ dnpName: "lighthouse-prater.dnp.dappnode.eth", expectedUrl: "http://beacon-chain.lighthouse-prater.dappnode:3500" },
{ dnpName: "teku-prater.dnp.dappnode.eth", expectedUrl: "http://beacon-chain.teku-prater.dappnode:3500" },
{ dnpName: "nimbus-prater.dnp.dappnode.eth", expectedUrl: "http://beacon-validator.nimbus-prater.dappnode:4500" },
{ dnpName: "lodestar-prater.dnp.dappnode.eth", expectedUrl: "http://beacon-chain.lodestar-prater.dappnode:3500" },

// Gnosis Chain Execution Clients
{ dnpName: "nethermind-xdai.dnp.dappnode.eth", expectedUrl: "http://nethermind-xdai.dappnode:8545" },

// Gnosis Chain Consensus Clients
{ dnpName: "lighthouse-gnosis.dnp.dappnode.eth", expectedUrl: "http://beacon-chain.lighthouse-gnosis.dappnode:3500" },
{ dnpName: "teku-gnosis.dnp.dappnode.eth", expectedUrl: "http://beacon-chain.teku-gnosis.dappnode:3500" },
{ dnpName: "lodestar-gnosis.dnp.dappnode.eth", expectedUrl: "http://beacon-chain.lodestar-gnosis.dappnode:3500" },

// Holesky Testnet Execution Clients
{ dnpName: "holesky-geth.dnp.dappnode.eth", expectedUrl: "http://holesky-geth.dappnode:8545" },
{ dnpName: "holesky-besu.dnp.dappnode.eth", expectedUrl: "http://holesky-besu.dappnode:8545" },
{ dnpName: "holesky-nethermind.dnp.dappnode.eth", expectedUrl: "http://holesky-nethermind.dappnode:8545" },
{ dnpName: "holesky-erigon.dnp.dappnode.eth", expectedUrl: "http://holesky-erigon.dappnode:8545" },

// Holesky Testnet Consensus Clients
{ dnpName: "prysm-holesky.dnp.dappnode.eth", expectedUrl: "http://beacon-chain.prysm-holesky.dappnode:3500" },
{ dnpName: "lighthouse-holesky.dnp.dappnode.eth", expectedUrl: "http://beacon-chain.lighthouse-holesky.dappnode:3500" },
{ dnpName: "teku-holesky.dnp.dappnode.eth", expectedUrl: "http://beacon-chain.teku-holesky.dappnode:3500" },
{ dnpName: "nimbus-holesky.dnp.dappnode.eth", expectedUrl: "http://beacon-validator.nimbus-holesky.dappnode:4500" },
{ dnpName: "lodestar-holesky.dnp.dappnode.eth", expectedUrl: "http://beacon-chain.lodestar-holesky.dappnode:3500" },

// Lukso Execution Clients
{ dnpName: "lukso-geth.dnp.dappnode.eth", expectedUrl: "http://lukso-geth.dappnode:8545" },
];

testCases.forEach(({ dnpName, expectedUrl }) => {
it(`should return the correct URL for ${dnpName}`, function () {
expect(getJsonRpcApiFromDnpName(dnpName)).to.equal(expectedUrl);
});
});

// Add a test case for invalid DNP name
it('should throw an error for an invalid DNP name', function () {
expect(() => getJsonRpcApiFromDnpName("invalid.dnp.name")).to.throw("Invalid DNP name format.");
});
});