diff --git a/.gitignore b/.gitignore
index e2ac4a3fba..7d28879380 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,8 +60,8 @@ contracts/coverage.json
contracts/build/
contracts/dist/
contracts/.localKeyValueStorage
-contracts/.localKeyValueStorageMainnet
-contracts/.localKeyValueStorageHolesky
+contracts/.localKeyValueStorage.mainnet
+contracts/.localKeyValueStorage.holesky
contracts/scripts/defender-actions/dist/
todo.txt
diff --git a/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol b/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol
index 9b6455e012..2dc590f69f 100644
--- a/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol
+++ b/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol
@@ -226,12 +226,17 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
/// @notice Registers a new validator in the SSV Cluster.
/// Only the registrator can call this function.
+ /// @param publicKey The public key of the validator
+ /// @param operatorIds The operator IDs of the SSV Cluster
+ /// @param sharesData The validator shares data
+ /// @param ssvAmount The amount of SSV tokens to be deposited to the SSV cluster
+ /// @param cluster The SSV cluster details including the validator count and SSV balance
// slither-disable-start reentrancy-no-eth
function registerSsvValidator(
bytes calldata publicKey,
uint64[] calldata operatorIds,
bytes calldata sharesData,
- uint256 amount,
+ uint256 ssvAmount,
Cluster calldata cluster
) external onlyRegistrator whenNotPaused {
bytes32 pubKeyHash = keccak256(publicKey);
@@ -243,7 +248,7 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
publicKey,
operatorIds,
sharesData,
- amount,
+ ssvAmount,
cluster
);
emit SSVValidatorRegistered(pubKeyHash, publicKey, operatorIds);
@@ -256,6 +261,8 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
/// @notice Exit a validator from the Beacon chain.
/// The staked ETH will eventually swept to this native staking strategy.
/// Only the registrator can call this function.
+ /// @param publicKey The public key of the validator
+ /// @param operatorIds The operator IDs of the SSV Cluster
// slither-disable-start reentrancy-no-eth
function exitSsvValidator(
bytes calldata publicKey,
@@ -277,6 +284,9 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
/// Make sure `exitSsvValidator` is called before and the validate has exited the Beacon chain.
/// If removed before the validator has exited the beacon chain will result in the validator being slashed.
/// Only the registrator can call this function.
+ /// @param publicKey The public key of the validator
+ /// @param operatorIds The operator IDs of the SSV Cluster
+ /// @param cluster The SSV cluster details including the validator count and SSV balance
// slither-disable-start reentrancy-no-eth
function removeSsvValidator(
bytes calldata publicKey,
@@ -306,16 +316,18 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
/// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds.
/// uses "onlyStrategist" modifier so continuous front-running can't DOS our maintenance service
/// that tries to top up SSV tokens.
- /// @param cluster The SSV cluster details that must be derived from emitted events from the SSVNetwork contract.
+ /// @param operatorIds The operator IDs of the SSV Cluster
+ /// @param ssvAmount The amount of SSV tokens to be deposited to the SSV cluster
+ /// @param cluster The SSV cluster details including the validator count and SSV balance
function depositSSV(
uint64[] memory operatorIds,
- uint256 amount,
+ uint256 ssvAmount,
Cluster memory cluster
) external onlyStrategist {
ISSVNetwork(SSV_NETWORK_ADDRESS).deposit(
address(this),
operatorIds,
- amount,
+ ssvAmount,
cluster
);
}
diff --git a/contracts/docs/NativeStakingSSVStrategySquashed.svg b/contracts/docs/NativeStakingSSVStrategySquashed.svg
index a210ef9d8b..2c31395017 100644
--- a/contracts/docs/NativeStakingSSVStrategySquashed.svg
+++ b/contracts/docs/NativeStakingSSVStrategySquashed.svg
@@ -91,10 +91,10 @@
setStakeETHThreshold(_amount: uint256) <<onlyGovernor>> <<ValidatorRegistrator>>
resetStakeETHTally() <<onlyStakingMonitor>> <<ValidatorRegistrator>>
stakeEth(validators: ValidatorStakeData[]) <<onlyRegistrator, whenNotPaused>> <<ValidatorRegistrator>>
- registerSsvValidator(publicKey: bytes, operatorIds: uint64[], sharesData: bytes, amount: uint256, cluster: Cluster) <<onlyRegistrator, whenNotPaused>> <<ValidatorRegistrator>>
+ registerSsvValidator(publicKey: bytes, operatorIds: uint64[], sharesData: bytes, ssvAmount: uint256, cluster: Cluster) <<onlyRegistrator, whenNotPaused>> <<ValidatorRegistrator>>
exitSsvValidator(publicKey: bytes, operatorIds: uint64[]) <<onlyRegistrator, whenNotPaused>> <<ValidatorRegistrator>>
removeSsvValidator(publicKey: bytes, operatorIds: uint64[], cluster: Cluster) <<onlyRegistrator, whenNotPaused>> <<ValidatorRegistrator>>
- depositSSV(operatorIds: uint64[], amount: uint256, cluster: Cluster) <<onlyStrategist>> <<ValidatorRegistrator>>
+ depositSSV(operatorIds: uint64[], ssvAmount: uint256, cluster: Cluster) <<onlyStrategist>> <<ValidatorRegistrator>>
setFuseInterval(_fuseIntervalStart: uint256, _fuseIntervalEnd: uint256) <<onlyGovernor>> <<ValidatorAccountant>>
doAccounting(): (accountingValid: bool) <<onlyRegistrator, whenNotPaused>> <<ValidatorAccountant>>
manuallyFixAccounting(_validatorsDelta: int256, _consensusRewardsDelta: int256, _ethToVaultAmount: uint256) <<onlyStrategist, whenPaused>> <<ValidatorAccountant>>
diff --git a/contracts/docs/plantuml/oethProcesses-register.png b/contracts/docs/plantuml/oethProcesses-register.png
index a1f1021dba..03ac1f8f61 100644
Binary files a/contracts/docs/plantuml/oethProcesses-register.png and b/contracts/docs/plantuml/oethProcesses-register.png differ
diff --git a/contracts/docs/plantuml/oethProcesses.png b/contracts/docs/plantuml/oethProcesses.png
index 18f51ef248..4bb4928a15 100644
Binary files a/contracts/docs/plantuml/oethProcesses.png and b/contracts/docs/plantuml/oethProcesses.png differ
diff --git a/contracts/docs/plantuml/oethProcesses.puml b/contracts/docs/plantuml/oethProcesses.puml
index 6ac90f4cda..ab3747c685 100644
--- a/contracts/docs/plantuml/oethProcesses.puml
+++ b/contracts/docs/plantuml/oethProcesses.puml
@@ -83,10 +83,10 @@ api -> api: split(key)
note right : splits validator key into multiple KeyShares
return
-reg -> api: status(uuid)
+reg -> api: GET\neth/staking/ssv/request/status/uuid
activate api
-return status,\nvalidatorRegistration,\nshareData
-note right : validatorRegistration contains the pubkey, operatorIds and cluster details
+return status,\npubkey\nvalidatorRegistration,\nshareData
+note right : validatorRegistration contains the operatorIds and cluster details
reg -> nativeStrat : registerSsvValidator(\npublicKey,\noperatorIds,\nsharesData,\namount,\ncluster)
activate nativeStrat
@@ -107,6 +107,17 @@ return
return
return
+end group
+
+... 60 minutes ...
+
+group Registrator stakes to a new SSV validator
+
+reg -> api: GET\neth/staking/ssv/request/deposit-data/uuid
+activate api
+return status,\ndepositData
+note right : depositData contains the signature and depositDataRoot
+
reg -> nativeStrat : stakeEth([\npubkey,\nsignature,\ndepositDataRoot])
activate nativeStrat
nativeStrat -> nativeStrat
diff --git a/contracts/scripts/defender-actions/operateValidators.js b/contracts/scripts/defender-actions/registerValidators.js
similarity index 96%
rename from contracts/scripts/defender-actions/operateValidators.js
rename to contracts/scripts/defender-actions/registerValidators.js
index d6c1c5fe07..f635949041 100644
--- a/contracts/scripts/defender-actions/operateValidators.js
+++ b/contracts/scripts/defender-actions/registerValidators.js
@@ -6,7 +6,7 @@ const {
const {
KeyValueStoreClient,
} = require("@openzeppelin/defender-kvstore-client");
-const { operateValidators } = require("../../tasks/validator");
+const { registerValidators } = require("../../tasks/validator");
const addresses = require("../../utils/addresses");
const nativeStakingStrategyAbi = require("../../abi/native_staking_SSV_strategy.json");
@@ -83,7 +83,7 @@ const handler = async (event) => {
clear: true,
};
- await operateValidators({
+ await registerValidators({
signer,
contracts,
store,
diff --git a/contracts/scripts/defender-actions/rollup.config.cjs b/contracts/scripts/defender-actions/rollup.config.cjs
index 939c02e2e1..06704a3159 100644
--- a/contracts/scripts/defender-actions/rollup.config.cjs
+++ b/contracts/scripts/defender-actions/rollup.config.cjs
@@ -4,7 +4,11 @@ const json = require("@rollup/plugin-json");
const builtins = require("builtin-modules");
const commonConfig = {
- plugins: [resolve({ preferBuiltins: true, exportConditions: ["node"] }), commonjs(), json({ compact: true })],
+ plugins: [
+ resolve({ preferBuiltins: true, exportConditions: ["node"] }),
+ commonjs(),
+ json({ compact: true }),
+ ],
// Do not bundle these packages.
// ethers is required to be bundled even though its an Autotask package.
external: [
@@ -25,10 +29,18 @@ const commonConfig = {
module.exports = [
{
...commonConfig,
- input: "operateValidators.js",
+ input: "registerValidators.js",
+ output: {
+ file: "dist/registerValidators/index.js",
+ format: "cjs",
+ },
+ },
+ {
+ ...commonConfig,
+ input: "stakeValidators.js",
output: {
- file: "dist/operateValidators/index.js",
+ file: "dist/stakeValidators/index.js",
format: "cjs",
},
- }
+ },
];
diff --git a/contracts/tasks/tasks.js b/contracts/tasks/tasks.js
index 60f6e36621..5529744b5a 100644
--- a/contracts/tasks/tasks.js
+++ b/contracts/tasks/tasks.js
@@ -6,14 +6,8 @@ const { setActionVars } = require("./defender");
const { execute, executeOnFork, proposal, governors } = require("./governance");
const { smokeTest, smokeTestCheck } = require("./smokeTest");
const addresses = require("../utils/addresses");
-const { getDefenderSigner } = require("../utils/signers");
const { networkMap } = require("../utils/hardhat-helpers");
-const { resolveContract } = require("../utils/resolvers");
-const {
- KeyValueStoreClient,
-} = require("@openzeppelin/defender-kvstore-client");
-const { operateValidators } = require("./validator");
-const { formatUnits } = require("ethers/lib/utils");
+const { getSigner } = require("../utils/signers");
const {
storeStorageLayoutForAllContracts,
@@ -36,6 +30,7 @@ const {
tokenTransfer,
tokenTransferFrom,
} = require("./tokens");
+const { depositWETH, withdrawWETH } = require("./weth");
const {
allocate,
capital,
@@ -74,6 +69,13 @@ const {
setRewardTokenAddresses,
checkBalance,
} = require("./strategy");
+const {
+ validatorOperationsConfig,
+ registerValidators,
+ stakeValidators,
+ resetStakeETHTally,
+ setStakeETHThreshold,
+} = require("./validator");
// can not import from utils/deploy since that imports hardhat globally
const withConfirmation = async (deployOrTransactionPromise) => {
@@ -215,6 +217,37 @@ task("transferFrom").setAction(async (_, __, runSuper) => {
return runSuper();
});
+// WETH tasks
+subtask("depositWETH", "Deposit ETH into WETH")
+ .addParam("amount", "Amount of ETH to deposit", undefined, types.float)
+ .setAction(async (taskArgs) => {
+ const signer = await getSigner();
+
+ const { chainId } = await ethers.provider.getNetwork();
+ const wethAddress = addresses[networkMap[chainId]].WETH;
+ const weth = await ethers.getContractAt("IWETH9", wethAddress);
+
+ await depositWETH({ ...taskArgs, weth, signer });
+ });
+task("depositWETH").setAction(async (_, __, runSuper) => {
+ return runSuper();
+});
+
+subtask("withdrawWETH", "Withdraw ETH from WETH")
+ .addParam("amount", "Amount of ETH to withdraw", undefined, types.float)
+ .setAction(async (taskArgs) => {
+ const signer = await getSigner();
+
+ const { chainId } = await ethers.provider.getNetwork();
+ const wethAddress = addresses[networkMap[chainId]].WETH;
+ const weth = await ethers.getContractAt("IWETH9", wethAddress);
+
+ await withdrawWETH({ ...taskArgs, weth, signer });
+ });
+task("withdrawWETH").setAction(async (_, __, runSuper) => {
+ return runSuper();
+});
+
// Vault tasks.
task("allocate", "Call allocate() on the Vault")
.addOptionalParam(
@@ -280,6 +313,12 @@ subtask("mint", "Mint OTokens from the Vault using collateral assets")
types.string
)
.addOptionalParam("min", "Minimum amount of OTokens to mint", 0, types.float)
+ .addOptionalParam(
+ "approve",
+ "Approve the asset to the OETH Vault before the mint",
+ true,
+ types.boolean
+ )
.setAction(mint);
task("mint").setAction(async (_, __, runSuper) => {
return runSuper();
@@ -869,16 +908,17 @@ subtask("getClusterInfo", "Print out information regarding SSV cluster")
types.string
)
.setAction(async (taskArgs) => {
- const network = await ethers.provider.getNetwork();
- const ssvNetwork = addresses[networkMap[network.chainId]].SSVNetwork;
+ const { chainId } = await ethers.provider.getNetwork();
+ const network = networkMap[chainId];
+ const ssvNetwork = addresses[network].SSVNetwork;
log(
- `Fetching cluster info for cluster owner ${taskArgs.owner} with operator ids: ${taskArgs.operatorids} from the ${network.name} network using ssvNetworkContract ${ssvNetwork}`
+ `Fetching cluster info for cluster owner ${taskArgs.owner} with operator ids: ${taskArgs.operatorids} from the ${network} network using ssvNetworkContract ${ssvNetwork}`
);
await printClusterInfo({
...taskArgs,
ownerAddress: taskArgs.owner,
- chainId: network.chainId,
+ chainId: chainId,
ssvNetwork,
});
});
@@ -911,15 +951,13 @@ subtask(
"deployNativeStakingProxy",
"Deploy the native staking proxy via the Defender Relayer"
).setAction(async () => {
- const defenderSigner = await getDefenderSigner();
+ const signer = await getSigner();
log("Deploy NativeStakingSSVStrategyProxy");
const nativeStakingProxyFactory = await ethers.getContractFactory(
"NativeStakingSSVStrategyProxy"
);
- const contract = await nativeStakingProxyFactory
- .connect(defenderSigner)
- .deploy();
+ const contract = await nativeStakingProxyFactory.connect(signer).deploy();
await contract.deployed();
log(`Address of deployed contract is: ${contract.address}`);
});
@@ -937,16 +975,16 @@ subtask(
)
.addParam("address", "Address of the new governor", undefined, types.string)
.setAction(async (taskArgs) => {
- const defenderSigner = await getDefenderSigner();
+ const signer = await getSigner();
- log("Tranfer governance of NativeStakingSSVStrategyProxy");
+ log("Transfer governance of NativeStakingSSVStrategyProxy");
const nativeStakingProxyFactory = await ethers.getContract(
"NativeStakingSSVStrategyProxy"
);
await withConfirmation(
nativeStakingProxyFactory
- .connect(defenderSigner)
+ .connect(signer)
.transferGovernance(taskArgs.address)
);
log(
@@ -959,97 +997,83 @@ task("transferGovernanceNativeStakingProxy").setAction(
}
);
-// Defender
+// Validator Operations
+
subtask(
- "operateValidators",
+ "registerValidators",
"Creates the required amount of new SSV validators and stakes ETH"
)
- .addOptionalParam("index", "Index of Native Staking contract", 1, types.int)
- .addOptionalParam(
- "stake",
- "Stake 32 ether after registering a new SSV validator",
- true,
- types.boolean
- )
.addOptionalParam(
"days",
"SSV Cluster operational time in days",
40,
types.int
)
- .addOptionalParam("clear", "Clear storage", true, types.boolean)
+ .addOptionalParam("clear", "Clear storage", false, types.boolean)
.setAction(async (taskArgs) => {
- const network = await ethers.provider.getNetwork();
- const isMainnet = network.chainId === 1;
- const isHolesky = network.chainId === 17000;
- const addressesSet = isMainnet ? addresses.mainnet : addresses.holesky;
-
- if (!isMainnet && !isHolesky) {
- throw new Error(
- "operate validators is supported on Mainnet and Holesky only"
- );
- }
-
- const storeFilePath = require("path").join(
- __dirname,
- "..",
- `.localKeyValueStorage${isMainnet ? "Mainnet" : "Holesky"}`
- );
+ const config = await validatorOperationsConfig(taskArgs);
+ await registerValidators(config);
+ });
+task("registerValidators").setAction(async (_, __, runSuper) => {
+ return runSuper();
+});
- const store = new KeyValueStoreClient({ path: storeFilePath });
- const signer = await getDefenderSigner();
+subtask(
+ "stakeValidators",
+ "Creates the required amount of new SSV validators and stakes ETH"
+)
+ .addOptionalParam(
+ "uuid",
+ "uuid of P2P's request SSV validator API call",
+ undefined,
+ types.string
+ )
+ .setAction(async (taskArgs) => {
+ const config = await validatorOperationsConfig(taskArgs);
+ await stakeValidators(config);
+ });
+task("stakeValidators").setAction(async (_, __, runSuper) => {
+ return runSuper();
+});
- const WETH = await ethers.getContractAt("IWETH9", addressesSet.WETH);
- const SSV = await ethers.getContractAt("IERC20", addressesSet.SSV);
+subtask(
+ "resetStakeETHTally",
+ "Resets the amount of Ether staked back to zero"
+).setAction(async () => {
+ const signer = await getSigner();
- // TODO: use index to target different native staking strategies when we have more than 1
- const nativeStakingStrategy = await resolveContract(
- "NativeStakingSSVStrategyProxy",
- "NativeStakingSSVStrategy"
- );
+ const nativeStakingProxyFactory = await ethers.getContract(
+ "NativeStakingSSVStrategyProxy"
+ );
- log(
- "Balance of SSV tokens on the native staking contract: ",
- formatUnits(await SSV.balanceOf(nativeStakingStrategy.address))
+ await resetStakeETHTally({
+ signer,
+ nativeStakingProxyFactory,
+ });
+});
+task("resetStakeETHTally").setAction(async (_, __, runSuper) => {
+ return runSuper();
+});
+
+subtask(
+ "setStakeETHThreshold",
+ "Sets the amount of Ether than can be staked before needing a reset"
+)
+ .addParam("amount", "Amount in ether", undefined, types.int)
+ .setAction(async (taskArgs) => {
+ const signer = await getSigner();
+
+ const nativeStakingProxyFactory = await ethers.getContract(
+ "NativeStakingSSVStrategyProxy"
);
- const contracts = {
- nativeStakingStrategy,
- WETH,
- };
- const feeAccumulatorAddress =
- await nativeStakingStrategy.FEE_ACCUMULATOR_ADDRESS();
-
- const p2p_api_key = isMainnet
- ? process.env.P2P_MAINNET_API_KEY
- : process.env.P2P_HOLESKY_API_KEY;
- if (!p2p_api_key) {
- throw new Error(
- "P2P API key environment variable is not set. P2P_MAINNET_API_KEY or P2P_HOLESKY_API_KEY"
- );
- }
- const p2p_base_url = isMainnet ? "api.p2p.org" : "api-test-holesky.p2p.org";
-
- const config = {
- feeAccumulatorAddress,
- p2p_api_key,
- p2p_base_url,
- // how much SSV (expressed in days of runway) gets deposited into the
- // SSV Network contract on validator registration. This is calculated
- // at a Cluster level rather than a single validator.
- validatorSpawnOperationalPeriodInDays: taskArgs.days,
- stake: taskArgs.stake,
- clear: taskArgs.clear,
- };
-
- await operateValidators({
+ await setStakeETHThreshold({
+ ...taskArgs,
signer,
- contracts,
- store,
- config,
+ nativeStakingProxyFactory,
});
});
-task("operateValidators").setAction(async (_, __, runSuper) => {
+task("setStakeETHThreshold").setAction(async (_, __, runSuper) => {
return runSuper();
});
diff --git a/contracts/tasks/validator.js b/contracts/tasks/validator.js
index ee033a6162..a194040b4f 100644
--- a/contracts/tasks/validator.js
+++ b/contracts/tasks/validator.js
@@ -1,14 +1,86 @@
const fetch = require("node-fetch");
-const { defaultAbiCoder, formatUnits, hexDataSlice, parseEther } =
+const { defaultAbiCoder, formatUnits, hexDataSlice, parseEther, keccak256 } =
require("ethers").utils;
const { v4: uuidv4 } = require("uuid");
-
-//const { resolveContract } = require("../utils/resolvers");
+const {
+ KeyValueStoreClient,
+} = require("@openzeppelin/defender-kvstore-client");
+
+const { getClusterInfo } = require("./ssv");
+const addresses = require("../utils/addresses");
+const { resolveContract } = require("../utils/resolvers");
+const { getSigner } = require("../utils/signers");
const { sleep } = require("../utils/time");
-//const { getClusterInfo } = require("./ssv");
+const { logTxDetails } = require("../utils/txLogger");
+const { networkMap } = require("../utils/hardhat-helpers");
const log = require("../utils/logger")("task:p2p");
+const validatorStateEnum = {
+ 0: "NOT_REGISTERED",
+ 1: "REGISTERED",
+ 2: "STAKED",
+ 3: "EXITED",
+ 4: "EXIT_COMPLETE",
+};
+
+const validatorOperationsConfig = async (taskArgs) => {
+ const { chainId } = await ethers.provider.getNetwork();
+ const network = networkMap[chainId];
+
+ if (!network) {
+ throw new Error(
+ `registerValidators does not support chain with id ${chainId}`
+ );
+ }
+ const addressesSet = addresses[network];
+ const isMainnet = network === "mainnet";
+
+ const signer = await getSigner();
+
+ const storeFilePath = require("path").join(
+ __dirname,
+ "..",
+ `.localKeyValueStorage.${network}`
+ );
+
+ const WETH = await ethers.getContractAt("IWETH9", addressesSet.WETH);
+
+ const nativeStakingStrategy = await resolveContract(
+ "NativeStakingSSVStrategyProxy",
+ "NativeStakingSSVStrategy"
+ );
+ const feeAccumulatorAddress =
+ await nativeStakingStrategy.FEE_ACCUMULATOR_ADDRESS();
+
+ const p2p_api_key = isMainnet
+ ? process.env.P2P_MAINNET_API_KEY
+ : process.env.P2P_HOLESKY_API_KEY;
+ if (!p2p_api_key) {
+ throw new Error(
+ "P2P API key environment variable is not set. P2P_MAINNET_API_KEY or P2P_HOLESKY_API_KEY"
+ );
+ }
+ const p2p_base_url = isMainnet ? "api.p2p.org" : "api-test-holesky.p2p.org";
+
+ return {
+ store: new KeyValueStoreClient({ path: storeFilePath }),
+ signer,
+ p2p_api_key,
+ p2p_base_url,
+ nativeStakingStrategy,
+ feeAccumulatorAddress,
+ WETH,
+ // how much SSV (expressed in days of runway) gets deposited into the
+ // SSV Network contract on validator registration. This is calculated
+ // at a Cluster level rather than a single validator.
+ validatorSpawnOperationalPeriodInDays: taskArgs.days,
+ stake: taskArgs.stake,
+ clear: taskArgs.clear,
+ uuid: taskArgs.uuid,
+ };
+};
+
/* When same UUID experiences and error threshold amount of times it is
* discarded.
*/
@@ -32,16 +104,17 @@ const ERROR_THRESHOLD = 5;
* - TODO: (implement this) if fuse of the native staking strategy is blown
* stop with all the operations
*/
-const operateValidators = async ({ store, signer, contracts, config }) => {
- const {
- feeAccumulatorAddress,
- p2p_api_key,
- p2p_base_url,
- validatorSpawnOperationalPeriodInDays,
- stake,
- clear,
- } = config;
-
+const registerValidators = async ({
+ store,
+ signer,
+ p2p_api_key,
+ p2p_base_url,
+ nativeStakingStrategy,
+ feeAccumulatorAddress,
+ WETH,
+ validatorSpawnOperationalPeriodInDays,
+ clear,
+}) => {
let currentState = await getState(store);
log("currentState", currentState);
@@ -50,13 +123,15 @@ const operateValidators = async ({ store, signer, contracts, config }) => {
currentState = undefined;
}
- if (!(await stakingContractHas32ETH(contracts))) {
- log(`Native staking contract doesn't have enough ETH, exiting`);
+ if (!(await stakingContractHas32ETH(nativeStakingStrategy, WETH))) {
+ console.log(
+ `Native staking contract doesn't have enough WETH available to stake. Does depositToStrategy or resetStakeETHTally need to be called?`
+ );
return;
}
- if (await stakingContractPaused(contracts)) {
- log(`Native staking contract is paused... exiting`);
+ if (await stakingContractPaused(nativeStakingStrategy)) {
+ console.log(`Native staking contract is paused... exiting`);
return;
}
@@ -64,33 +139,36 @@ const operateValidators = async ({ store, signer, contracts, config }) => {
while (true) {
if (!currentState) {
await createValidatorRequest(
- p2p_api_key, // api key
+ store,
+ "validator_creation_issued", // next state
+ p2p_api_key,
p2p_base_url,
- contracts.nativeStakingStrategy.address, // SSV owner address & withdrawal address
+ nativeStakingStrategy.address, // SSV owner address & withdrawal address
feeAccumulatorAddress, // execution layer fee recipient
- validatorSpawnOperationalPeriodInDays,
- store
+ validatorSpawnOperationalPeriodInDays
);
currentState = await getState(store);
}
if (currentState.state === "validator_creation_issued") {
- await confirmValidatorCreatedRequest(
- p2p_api_key,
- p2p_base_url,
+ await confirmValidatorRegistered(
+ store,
currentState.uuid,
- store
+ "validator_creation_confirmed", // next state
+ p2p_api_key,
+ p2p_base_url
);
currentState = await getState(store);
}
if (currentState.state === "validator_creation_confirmed") {
await broadcastRegisterValidator(
- signer,
store,
currentState.uuid,
+ "register_transaction_broadcast", // next state
+ signer,
currentState.metadata,
- contracts.nativeStakingStrategy
+ nativeStakingStrategy
);
currentState = await getState(store);
}
@@ -99,22 +177,110 @@ const operateValidators = async ({ store, signer, contracts, config }) => {
await waitForTransactionAndUpdateStateOnSuccess(
store,
currentState.uuid,
- contracts.nativeStakingStrategy.provider,
+ "validator_registered", // next state
+ nativeStakingStrategy.provider,
currentState.metadata.validatorRegistrationTx,
- "registerSsvValidator", // name of transaction we are waiting for
- "validator_registered" // new state when transaction confirmed
+ "registerSsvValidator" // name of transaction we are waiting for
);
currentState = await getState(store);
+ break;
}
- if (!stake) break;
+ await sleep(1000);
+ }
+ };
+
+ try {
+ if ((await getErrorCount(store)) >= ERROR_THRESHOLD) {
+ await clearState(
+ currentState.uuid,
+ store,
+ `Errors have reached the threshold(${ERROR_THRESHOLD}) discarding attempt`
+ );
+ return;
+ }
+ await executeOperateLoop();
+ } catch (e) {
+ await increaseErrorCount(currentState ? currentState.uuid : "", store, e);
+ throw e;
+ }
+};
+
+const stakeValidators = async ({
+ store,
+ signer,
+ nativeStakingStrategy,
+ WETH,
+ p2p_api_key,
+ p2p_base_url,
+ uuid,
+}) => {
+ let currentState;
+ if (!uuid) {
+ let currentState = await getState(store);
+ log("currentState", currentState);
+
+ if (!currentState) {
+ console.log(`Failed to get state from local storage`);
+ return;
+ }
+ }
+
+ if (!(await stakingContractHas32ETH(nativeStakingStrategy, WETH))) {
+ console.log(`Native staking contract doesn't have enough ETH, exiting`);
+ return;
+ }
+
+ if (await stakingContractPaused(nativeStakingStrategy)) {
+ console.log(`Native staking contract is paused... exiting`);
+ return;
+ }
+
+ const executeOperateLoop = async () => {
+ while (true) {
+ if (!currentState) {
+ await confirmValidatorRegistered(
+ store,
+ uuid,
+ "validator_registered", // next state
+ p2p_api_key,
+ p2p_base_url
+ );
+ currentState = await getState(store);
+
+ // Check the validator has not already been staked
+ const hashedPubkey = keccak256(currentState.metadata.pubkey);
+ const status = await nativeStakingStrategy.validatorsStates(
+ hashedPubkey
+ );
+ if (validatorStateEnum[status] !== "REGISTERED") {
+ console.log(
+ `Validator with pubkey ${currentState.metadata.pubkey} not in REGISTERED state. Current state: ${validatorStateEnum[status]}`
+ );
+ await clearState(currentState.uuid, store);
+ break;
+ }
+ }
if (currentState.state === "validator_registered") {
+ await getDepositData(
+ store,
+ currentState.uuid,
+ "deposit_data_got", // next state
+ p2p_api_key,
+ p2p_base_url
+ );
+ currentState = await getState(store);
+ }
+
+ if (currentState.state === "deposit_data_got") {
await depositEth(
- signer,
store,
currentState.uuid,
- contracts.nativeStakingStrategy,
+ "deposit_transaction_broadcast", // next state
+ signer,
+ nativeStakingStrategy,
+ currentState.metadata.pubkey,
currentState.metadata.depositData
);
currentState = await getState(store);
@@ -124,10 +290,10 @@ const operateValidators = async ({ store, signer, contracts, config }) => {
await waitForTransactionAndUpdateStateOnSuccess(
store,
currentState.uuid,
- contracts.nativeStakingStrategy.provider,
+ "deposit_confirmed", // next state
+ nativeStakingStrategy.provider,
currentState.metadata.depositTx,
- "stakeEth", // name of transaction we are waiting for
- "deposit_confirmed" // new state when transaction confirmed
+ "stakeEth" // name of transaction we are waiting for
);
currentState = await getState(store);
@@ -137,6 +303,7 @@ const operateValidators = async ({ store, signer, contracts, config }) => {
await clearState(currentState.uuid, store);
break;
}
+
await sleep(1000);
}
};
@@ -206,6 +373,7 @@ const updateState = async (requestUUID, state, store, metadata = {}) => {
"validator_creation_confirmed",
"register_transaction_broadcast",
"validator_registered",
+ "deposit_data_got",
"deposit_transaction_broadcast",
"deposit_confirmed",
].includes(state)
@@ -254,46 +422,46 @@ const getState = async (store) => {
return JSON.parse(await store.get("currentRequest"));
};
-const stakingContractPaused = async (contracts) => {
- const paused = await contracts.nativeStakingStrategy.paused();
+const stakingContractPaused = async (nativeStakingStrategy) => {
+ const paused = await nativeStakingStrategy.paused();
log(`Native staking contract is ${paused ? "" : "not "}paused`);
return paused;
};
-const stakingContractHas32ETH = async (contracts) => {
- const address = contracts.nativeStakingStrategy.address;
- const wethBalance = await contracts.WETH.balanceOf(address);
+const stakingContractHas32ETH = async (nativeStakingStrategy, WETH) => {
+ const address = nativeStakingStrategy.address;
+ const wethBalance = await WETH.balanceOf(address);
log(
`Native Staking Strategy has ${formatUnits(wethBalance, 18)} WETH in total`
);
- const stakeETHThreshold = contracts.nativeStakingStrategy.stakeETHThreshold();
- const stakeETHTally = contracts.nativeStakingStrategy.stakeETHTally();
- const remainingETH = stakeETHThreshold.sub(stakeETHTally);
+ const stakeETHThreshold = await nativeStakingStrategy.stakeETHThreshold();
+ const stakeETHTally = await nativeStakingStrategy.stakeETHTally();
+ const remainingWETH = stakeETHThreshold.sub(stakeETHTally);
log(
`Native Staking Strategy has staked ${formatUnits(
stakeETHTally
)} of ${formatUnits(stakeETHThreshold)} ETH with ${formatUnits(
- remainingETH
- )} ETH remaining`
+ remainingWETH
+ )} WETH remaining`
);
// Take the minimum of the remainingETH and the WETH balance
- const availableETH = wethBalance.gt(remainingETH)
- ? remainingETH
+ const availableETH = wethBalance.gt(remainingWETH)
+ ? remainingWETH
: wethBalance;
log(
`Native Staking Strategy has ${formatUnits(
availableETH
- )} ETH available to stake`
+ )} WETH available to stake`
);
return availableETH.gte(parseEther("32"));
};
-/* Make a GET or POST request to P2P service
- * @param api_key: p2p service api key
+/* Make a GET or POST request to P2P API
+ * @param api_key: P2P API key
* @param method: http method that can either be POST or GET
* @param body: body object in case of a POST request
*/
@@ -309,7 +477,7 @@ const p2pRequest = async (url, api_key, method, body) => {
const bodyString = JSON.stringify(body);
log(
- `Creating a P2P ${method} request with ${url} `,
+ `About to call P2P API: ${method} ${url} `,
body != undefined ? ` and body: ${bodyString}` : ""
);
@@ -321,24 +489,26 @@ const p2pRequest = async (url, api_key, method, body) => {
const response = await rawResponse.json();
if (response.error != null) {
- log("Request to P2P service failed with an error:", response);
+ log("Call to P2P API failed with response:", response);
throw new Error(
- `Call to P2P has failed: ${JSON.stringify(response.error)}`
+ `Failed to call to P2P API. Error: ${JSON.stringify(response.error)}`
);
} else {
- log("Request to P2P service succeeded: ", response);
+ log(`${method} request to P2P API succeeded:`);
+ log(response);
}
return response;
};
const createValidatorRequest = async (
+ store,
+ nextState,
p2p_api_key,
p2p_base_url,
nativeStakingStrategy,
feeAccumulatorAddress,
- validatorSpawnOperationalPeriodInDays,
- store
+ validatorSpawnOperationalPeriodInDays
) => {
const uuid = uuidv4();
await p2pRequest(
@@ -357,19 +527,19 @@ const createValidatorRequest = async (
}
);
- await updateState(uuid, "validator_creation_issued", store);
+ await updateState(uuid, nextState, store);
};
const waitForTransactionAndUpdateStateOnSuccess = async (
store,
uuid,
+ nextState,
provider,
txHash,
- methodName,
- newState
+ methodName
) => {
log(
- `Waiting for transaction with hash "${txHash}" method "${methodName}" and uuid "${uuid}" to be mined...`
+ `Waiting for transaction with hash "${txHash}", method "${methodName}" and uuid "${uuid}" to be mined...`
);
const tx = await provider.waitForTransaction(txHash);
if (!tx) {
@@ -377,17 +547,22 @@ const waitForTransactionAndUpdateStateOnSuccess = async (
`Transaction with hash "${txHash}" not found for method "${methodName}" and uuid "${uuid}"`
);
}
- await updateState(uuid, newState, store);
+ log(
+ `Transaction with hash "${txHash}", method "${methodName}" and uuid "${uuid}" has been mined`
+ );
+ await updateState(uuid, nextState, store);
};
const depositEth = async (
- signer,
store,
uuid,
+ nextState,
+ signer,
nativeStakingStrategy,
+ pubkey,
depositData
) => {
- const { pubkey, signature, depositDataRoot } = depositData;
+ const { signature, depositDataRoot } = depositData;
try {
log(`About to stake ETH with:`);
log(`pubkey: ${pubkey}`);
@@ -403,7 +578,7 @@ const depositEth = async (
log(`Transaction to stake ETH has been broadcast with hash: ${tx.hash}`);
- await updateState(uuid, "deposit_transaction_broadcast", store, {
+ await updateState(uuid, nextState, store, {
depositTx: tx.hash,
});
} catch (e) {
@@ -414,9 +589,10 @@ const depositEth = async (
};
const broadcastRegisterValidator = async (
- signer,
store,
uuid,
+ nextState,
+ signer,
metadata,
nativeStakingStrategy
) => {
@@ -433,11 +609,9 @@ const broadcastRegisterValidator = async (
// the publicKey and sharesData params are not encoded correctly by P2P so we will ignore them
const [, operatorIds, , amount, cluster] = registerTransactionParams;
// get publicKey and sharesData state storage
- const publicKey = metadata.depositData.pubkey;
+ const publicKey = metadata.pubkey;
if (!publicKey) {
- throw Error(
- `pubkey not found in metadata.depositData: ${metadata?.depositData}`
- );
+ throw Error(`pubkey not found in metadata: ${metadata}`);
}
const { sharesData } = metadata;
if (!sharesData) {
@@ -466,7 +640,7 @@ const broadcastRegisterValidator = async (
`Transaction to register SSV Validator has been broadcast with hash: ${tx.hash}`
);
- await updateState(uuid, "register_transaction_broadcast", store, {
+ await updateState(uuid, nextState, store, {
validatorRegistrationTx: tx.hash,
});
} catch (e) {
@@ -476,11 +650,12 @@ const broadcastRegisterValidator = async (
}
};
-const confirmValidatorCreatedRequest = async (
- p2p_api_key,
- p2p_base_url,
+const confirmValidatorRegistered = async (
+ store,
uuid,
- store
+ nextState,
+ p2p_api_key,
+ p2p_base_url
) => {
const doConfirmation = async () => {
const response = await p2pRequest(
@@ -488,44 +663,93 @@ const confirmValidatorCreatedRequest = async (
p2p_api_key,
"GET"
);
+ const isReady =
+ response.result?.status === "ready" ||
+ response.result?.status === "validator-ready";
if (response.error != null) {
- log(`Error processing request uuid: ${uuid} error: ${response}`);
- } else if (response.result.status === "ready") {
+ log(
+ `Error getting validator status with uuid ${uuid}: ${response.error}`
+ );
+ log(response);
+ return false;
+ } else if (!isReady) {
+ log(
+ `Validators with request uuid ${uuid} are not ready yet. Status: ${response?.result?.status}`
+ );
+ return false;
+ } else {
+ log(`Validators requested with uuid ${uuid} are ready`);
+
+ const pubkey = response.result.encryptedShares[0].publicKey;
const registerValidatorData =
response.result.validatorRegistrationTxs[0].data;
- const depositData = response.result.depositData[0];
const sharesData = response.result.encryptedShares[0].sharesData;
- await updateState(uuid, "validator_creation_confirmed", store, {
+ await updateState(uuid, nextState, store, {
+ pubkey,
registerValidatorData,
- depositData,
sharesData,
});
- log(`Validator created using uuid: ${uuid} is ready`);
- log(`Primary key: ${depositData.pubkey}`);
- log(`signature: ${depositData.signature}`);
- log(`depositDataRoot: ${depositData.depositDataRoot}`);
+ log(`Public key: ${pubkey}`);
log(`sharesData: ${sharesData}`);
return true;
- } else {
+ }
+ };
+
+ await retry(doConfirmation, uuid, store);
+};
+
+const getDepositData = async (
+ store,
+ uuid,
+ nextState,
+ p2p_api_key,
+ p2p_base_url
+) => {
+ const doConfirmation = async () => {
+ const response = await p2pRequest(
+ `https://${p2p_base_url}/api/v1/eth/staking/ssv/request/deposit-data/${uuid}`,
+ p2p_api_key,
+ "GET"
+ );
+ if (response.error != null) {
+ log(`Error getting deposit data with uuid ${uuid}: ${response.error}`);
+ log(response);
+ return false;
+ } else if (response.result?.status != "validator-ready") {
log(
- `Validator created using uuid: ${uuid} not yet ready. State: ${response.result.status}`
+ `Deposit data with request uuid ${uuid} are not ready yet. Status: ${response.result?.status}`
);
return false;
+ } else if (response.result?.status === "validator-ready") {
+ log(`Deposit data with request uuid ${uuid} is ready`);
+
+ const depositData = response.result.depositData[0];
+ await updateState(uuid, nextState, store, {
+ depositData,
+ });
+ log(`signature: ${depositData.signature}`);
+ log(`depositDataRoot: ${depositData.depositDataRoot}`);
+ return true;
+ } else {
+ log(`Error getting deposit data with uuid ${uuid}: ${response.error}`);
+ log(response);
+ throw Error(`Failed to get deposit data with uuid ${uuid}.`);
}
};
+ await retry(doConfirmation, uuid, store);
+};
+
+const retry = async (apiCall, uuid, store, attempts = 20) => {
let counter = 0;
- const attempts = 20;
while (true) {
- if (await doConfirmation()) {
+ if (await apiCall()) {
break;
}
counter++;
if (counter > attempts) {
- log(
- `Tried validating the validator formation with ${attempts} but failed`
- );
+ log(`Failed P2P API call after ${attempts} attempts.`);
await clearState(
uuid,
store,
@@ -537,42 +761,70 @@ const confirmValidatorCreatedRequest = async (
}
};
-// async function exitValidator({ publicKey, signer, operatorIds }) {
-// const strategy = await resolveContract(
-// "NativeStakingSSVStrategyProxy",
-// "NativeStakingSSVStrategy"
-// );
-
-// log(`About to exit validator`);
-// const tx = await strategy
-// .connect(signer)
-// .exitSsvValidator(publicKey, operatorIds);
-// await logTxDetails(tx, "exitSsvValidator");
-// }
-
-// async function removeValidator({ publicKey, signer, operatorIds }) {
-// const strategy = await resolveContract(
-// "NativeStakingSSVStrategyProxy",
-// "NativeStakingSSVStrategy"
-// );
-
-// // Cluster details
-// const { cluster } = await getClusterInfo({
-// chainId: hre.network.config.chainId,
-// ssvNetwork: hre.network.name.toUpperCase(),
-// operatorIds,
-// ownerAddress: strategy.address,
-// });
-
-// log(`About to exit validator`);
-// const tx = await strategy
-// .connect(signer)
-// .removeSsvValidator(publicKey, operatorIds, cluster);
-// await logTxDetails(tx, "removeSsvValidator");
-// }
+async function exitValidator({ publicKey, signer, operatorIds }) {
+ const strategy = await resolveContract(
+ "NativeStakingSSVStrategyProxy",
+ "NativeStakingSSVStrategy"
+ );
+
+ log(`About to exit validator`);
+ const tx = await strategy
+ .connect(signer)
+ .exitSsvValidator(publicKey, operatorIds);
+ await logTxDetails(tx, "exitSsvValidator");
+}
+
+async function removeValidator({ publicKey, signer, operatorIds }) {
+ const strategy = await resolveContract(
+ "NativeStakingSSVStrategyProxy",
+ "NativeStakingSSVStrategy"
+ );
+
+ // Cluster details
+ const { cluster } = await getClusterInfo({
+ chainId: hre.network.config.chainId,
+ ssvNetwork: hre.network.name.toUpperCase(),
+ operatorIds,
+ ownerAddress: strategy.address,
+ });
+
+ log(`About to exit validator`);
+ const tx = await strategy
+ .connect(signer)
+ .removeSsvValidator(publicKey, operatorIds, cluster);
+ await logTxDetails(tx, "removeSsvValidator");
+}
+
+async function resetStakeETHTally({ signer }) {
+ const strategy = await resolveContract(
+ "NativeStakingSSVStrategyProxy",
+ "NativeStakingSSVStrategy"
+ );
+
+ log(`About to resetStakeETHTally`);
+ const tx = await strategy.connect(signer).resetStakeETHTally();
+ await logTxDetails(tx, "resetStakeETHTally");
+}
+
+async function setStakeETHThreshold({ signer, amount }) {
+ const strategy = await resolveContract(
+ "NativeStakingSSVStrategyProxy",
+ "NativeStakingSSVStrategy"
+ );
+
+ const threshold = parseEther(amount.toString());
+
+ log(`About to setStakeETHThreshold`);
+ const tx = await strategy.connect(signer).setStakeETHThreshold(threshold);
+ await logTxDetails(tx, "setStakeETHThreshold");
+}
module.exports = {
- operateValidators,
- //removeValidator,
- //exitValidator,
+ validatorOperationsConfig,
+ registerValidators,
+ stakeValidators,
+ removeValidator,
+ exitValidator,
+ resetStakeETHTally,
+ setStakeETHThreshold,
};
diff --git a/contracts/tasks/vault.js b/contracts/tasks/vault.js
index f31db84369..ba49d4259b 100644
--- a/contracts/tasks/vault.js
+++ b/contracts/tasks/vault.js
@@ -133,7 +133,7 @@ async function capital({ symbol, pause }, hre) {
}
}
-async function mint({ amount, asset, symbol, min }, hre) {
+async function mint({ amount, asset, symbol, min, approve }, hre) {
const signer = await getSigner();
const { vault } = await getContract(hre, symbol);
@@ -142,7 +142,12 @@ async function mint({ amount, asset, symbol, min }, hre) {
const assetUnits = parseUnits(amount.toString(), await cAsset.decimals());
const minUnits = parseUnits(min.toString());
- await cAsset.connect(signer).approve(vault.address, assetUnits);
+ if (approve) {
+ const approveTx = await cAsset
+ .connect(signer)
+ .approve(vault.address, assetUnits);
+ await logTxDetails(approveTx, "approve");
+ }
log(`About to mint ${symbol} using ${amount} ${asset}`);
const tx = await vault
diff --git a/contracts/tasks/weth.js b/contracts/tasks/weth.js
new file mode 100644
index 0000000000..b9f35396f6
--- /dev/null
+++ b/contracts/tasks/weth.js
@@ -0,0 +1,23 @@
+const { parseUnits } = require("ethers").utils;
+
+const { logTxDetails } = require("../utils/txLogger");
+
+const log = require("../utils/logger")("task:weth");
+
+const depositWETH = async ({ weth, amount, signer }) => {
+ const etherAmount = parseUnits(amount.toString());
+
+ log(`About to deposit ${amount} ETH for WETH`);
+ const tx = await weth.connect(signer).deposit({ value: etherAmount });
+ await logTxDetails(tx, "deposit");
+};
+
+const withdrawWETH = async ({ weth, amount, signer }) => {
+ const etherAmount = parseUnits(amount.toString());
+
+ log(`About to withdraw ${amount} ETH from WETH`);
+ const tx = await weth.connect(signer).withdraw(etherAmount);
+ await logTxDetails(tx, "withdraw");
+};
+
+module.exports = { depositWETH, withdrawWETH };
diff --git a/contracts/utils/resolvers.js b/contracts/utils/resolvers.js
index b47f5b229e..498a1b50fa 100644
--- a/contracts/utils/resolvers.js
+++ b/contracts/utils/resolvers.js
@@ -1,5 +1,6 @@
const addresses = require("./addresses");
const { ethereumAddress } = require("./regex");
+const { networkMap } = require("./hardhat-helpers");
const log = require("./logger")("task:assets");
@@ -15,12 +16,8 @@ const resolveAsset = async (symbol) => {
// Not using helpers here as they import hardhat which won't work for Hardhat tasks
if (process.env.FORK === "true" || hre.network.name != "hardhat") {
- const network =
- hre.network.name != "hardhat"
- ? hre.network.name != "hardhat"
- : hre.network.config.chainId == 17000
- ? "holesky"
- : "mainnet";
+ const { chainId } = await hre.ethers.provider.getNetwork();
+ const network = networkMap[chainId] || "mainnet";
const assetAddr =
addresses[network][symbol + "Proxy"] || addresses[network][symbol];
diff --git a/contracts/utils/signers.js b/contracts/utils/signers.js
index 439d6ee07f..520688f3e9 100644
--- a/contracts/utils/signers.js
+++ b/contracts/utils/signers.js
@@ -1,5 +1,8 @@
const { Wallet } = require("ethers");
-const { Defender } = require("@openzeppelin/defender-sdk");
+const {
+ DefenderRelaySigner,
+ DefenderRelayProvider,
+} = require("@openzeppelin/defender-relay-client/lib/ethers");
const { ethereumAddress, privateKey } = require("./regex");
const { hardhatSetBalance } = require("../test/_fund");
@@ -59,7 +62,7 @@ async function getSigner(address = undefined) {
}
const getDefenderSigner = async () => {
- const speed = process.env.SPEED || "fast";
+ const speed = process.env.SPEED || "fastest";
if (!["safeLow", "average", "fast", "fastest"].includes(speed)) {
console.error(
`Defender Relay Speed param must be either 'safeLow', 'average', 'fast' or 'fastest'. Not "${speed}"`
@@ -67,28 +70,26 @@ const getDefenderSigner = async () => {
process.exit(2);
}
- const network = await ethers.provider.getNetwork();
- const isMainnet = network.chainId === 1;
- const isHolesky = network.chainId === 17000;
+ const { chainId } = await ethers.provider.getNetwork();
+ const isMainnet = chainId === 1;
- const apiKeyName = isMainnet
- ? "DEFENDER_API_KEY"
- : "HOLESKY_DEFENDER_API_KEY";
- const apiKeySecret = isMainnet
- ? "DEFENDER_API_SECRET"
- : "HOLESKY_DEFENDER_API_SECRET";
+ const apiKey = isMainnet
+ ? process.env.DEFENDER_API_KEY
+ : process.env.HOLESKY_DEFENDER_API_KEY || process.env.DEFENDER_API_KEY;
+ const apiSecret = isMainnet
+ ? process.env.DEFENDER_API_SECRET
+ : process.env.HOLESKY_DEFENDER_API_SECRET ||
+ process.env.DEFENDER_API_SECRET;
- const credentials = {
- relayerApiKey: process.env[apiKeyName],
- relayerApiSecret: process.env[apiKeySecret],
- };
+ const credentials = { apiKey, apiSecret };
- const client = new Defender(credentials);
- const provider = client.relaySigner.getProvider();
+ const provider = new DefenderRelayProvider(credentials);
+ const signer = new DefenderRelaySigner(credentials, provider, {
+ speed,
+ });
- const signer = client.relaySigner.getSigner(provider, { speed });
log(
- `Using Defender Relayer account ${await signer.getAddress()} from env vars ${apiKeyName} and ${apiKeySecret}`
+ `Using Defender Relayer account ${await signer.getAddress()} with key ${apiKey} and speed ${speed}`
);
return signer;
};