Registering as a validator and making self-delegations
Similar to other Delegated Proof-of-Stake networks, Exocore requires validators to stake their own tokens and delegators to delegate their token to these validators. However, one major distinction is that Exocore supports multiple types of tokens, which may or may not live on the same chain.
Exocore is an L1 Cosmos-based chain which supports multiple tokens on Ethereum Sepolia and Holesky testnets, with additional chains to be supported in the near future. To that end, users are required to have two addresses - one on Ethereum and one on Exocore (generated previously) which may or may not be derived from the same mnemonic.
If any of the below transactions produce error code -32000: execution reverted, it likely means that you don't have enough ETH to pay for gas. This is caused by a spike in Sepolia gas prices; you can either (1) wait for the gas prices to normalize, or (2) obtain more Sepolia ETH through the faucets.
Deposit Tokens
Acquire Tokens
At the time of writing, Exocore's testnet (exocoretestnet_233-5) supports exoETH and wstETH on Sepolia.
ETH on Sepolia / Holesky can be obtained from faucets provided by Alchemy or Infura.
Purpose: ETH is used to pay for gas on the Ethereum testnet.
Purpose: exoETH is used for staking on Exocore testnet.
wstETH can be obtained by sending Sepolia ETH to the wstETH address 0xB82381A3fBD3FaFA77B3a7bE693342618240067b.
Purpose: wstETH is Lido’s liquid staking token and is used for restaking.
exo can be obtained from Exocore’s faucet after the bootstrap phase
Purpose: exo is used to pay for gas on Exocore.
Deposit Tokens
The contract which accepts deposits is currently deployed on Ethereum, which means the Ethereum private key ($ETH_PRIVATE_KEY) is needed.
## Script constantsEXO_ETH_ADDR=0x83E6850591425e3C1E263c054f4466838B9Bd9e4WST_ETH_ADDR=0xB82381A3fBD3FaFA77B3a7bE693342618240067bGATEWAY_ADDR=0x8fc4E764C2C3B0646ba572AEa958fB5724706412## User choices# Enter your Ethereum testnet private keyETH_PRIVATE_KEY=0xabcde.....# Enter the address corresponding to the keyETH_ADDRESS=0x123....# Obtain the token as described above, and choose the one you intend to deposit.TOKEN=$EXO_ETH_ADDR# Quantity of token to deposit (without the 1e18 multiplier)# Decimal values are supportedQUANTITY=0.5# Endpoint for Ethereum RPCETHEREUM_RPC_URL=https://rpc.ankr.com/eth_sepolia# Approve the deposit by VaultVAULT_ADDR=$(castcall--rpc-url $ETHEREUM_RPC_URL $GATEWAY_ADDR "tokenToVault(address) returns (address)" $TOKEN)castsend--rpc-url $ETHEREUM_RPC_URL \ $TOKEN \"approve(address,uint256)" \ $VAULT_ADDR \"$(castmaxu)" \--private-key $ETH_PRIVATE_KEYBOOTSTRAPPED=$(castcall--rpc-url $ETHEREUM_RPC_URL $GATEWAY_ADDR "bootstrapped() returns (bool)")if [ "$BOOTSTRAPPED"="true" ]; then# Generate the LZ messsage to calculate the fees DEPOSIT_PREFIX="0x00" TOKEN_B32=$(cast2b $TOKEN) ETH_ADDRESS_B32=$(cast2b $ETH_ADDRESS) QUANTITY_B32=$(printf"%064s" $(cast2h $(cast2w $QUANTITY) |cut-c3-) |tr' ''0') # left padding LZ_MESSAGE=$(castch $DEPOSIT_PREFIX $TOKEN_B32 $ETH_ADDRESS_B32 $QUANTITY_B32) VALUE=$(castcall--rpc-url $ETHEREUM_RPC_URL $GATEWAY_ADDR "quote(bytes)" $LZ_MESSAGE)else# No fees VALUE=0fi# Execute the transactioncastsend--rpc-url $ETHEREUM_RPC_URL \ $GATEWAY_ADDR \"deposit(address,uint256)" \ $TOKEN \ $(cast2w $QUANTITY) \--private-key $ETH_PRIVATE_KEY \--value $(cast2d $VALUE)
The expected output of the commands should look similar to below:
If the above transaction was executed after the network bootstrap phase, LayerZeroScan may be used to check whether the Deposit message, sent from Ethereum to Exocore, was executed on the destination chain successfully. Once it is executed, the deposited amount can be queried from Exocore.
ETH_LZ_ID=40161# Sepolia, where our $GATEWAY_ADDR livesSTAKER_ID=$(echo"${ETH_ADDRESS}"|tr'[:upper:]''[:lower:]')_$(printf'0x%x\n'"${ETH_LZ_ID}")# our public endpoint is available below, but you can use your own node tooEXOCORE_COS_GRPC_URL=https://api-cosmos-grpc.exocore-restaking.com:443exocoredqueryassetsQueStakerAssetInfos \ $STAKER_ID --node $EXOCORE_COS_GRPC_URL \--outputjson|jq
The expected output of this will represent the quantity of tokens deposited, multiplied by 1e18.
{"asset_infos": [ {// the `asset_id` will vary depending on $TOKEN chosen// the `_0x9ce1` represents that hex value of the ETH_LZ_ID, a unique ID// assigned by LayerZero to the Sepolia network"asset_id":"0xb82381a3fbd3fafa77b3a7be693342618240067b_0x9ce1","info": {// Represents a quantity of 0.5 * 1e18"total_deposit_amount":"500000000000000000","withdrawable_amount":"500000000000000000","pending_undelegation_amount":"0" } } ]}
Validator Registration
Exocore has a unique, open-ended way to kickstart its network. Instead of locking in a small group of initial validators, Exocore allows anyone to become a validator right from the start using our Bootstrap contract.
Here's how it works:
Before the Network Launches: If the network isn't live yet, you can register as a validator through the Bootstrap contract on Ethereum. However, keep in mind that this registration process closes 24 hours before the network launch to give the bootstrap validators time to prepare.
After the Network Goes Live: Once the network is up and running, you can register directly on the Exocore chain itself.
This approach ensures that the validator set isn't pre-determined by a few stakeholders. Instead, it's open to anyone who wants to participate, making the launch phase more inclusive and decentralized.
To ensure your validator is eligible to participate in consensus, it is mandatory to have a minimum self-delegation of 1000 USD of value in any supported token. This self-delegation acts as a commitment and ensures that validators have a vested interest in maintaining the integrity and security of the network.
Before Network Launch
Using the same ETH_PRIVATE_KEY that was used to make the deposit, validators may register themselves with the Bootstrap contract during this phase.
# The declaration of ETH_PRIVATE_KEY is written again for completeness but isn't needed if the steps are followed sequentially
ETH_PRIVATE_KEY=0xabcde.....# must be uniqueVALIDATOR_NAME="Dumpling Validator"## The commissions should be between 0 and 1, both inclusive# can be changed at most once during Bootstrap, and every 24 hours thereafterCOMMISSION_RATE=0.05# can never be changed once setCOMMISSION_MAX_RATE=0.7# can never be changed once setCOMMISSION_MAX_CHANGE_RATE=0.35# Endpoint for Ethereum RPCETHEREUM_RPC_URL=https://rpc.ankr.com/eth_sepolia# ConstantsBOOTSTRAP_ADDR=0x53f39D2ECd900Fb018180dB692A9fc29c8efD38E# Derived values, depending on $ACCOUNT_KEY_NAME and $HOMEDIR in prior stepsEXO_ADDRESS=$(exocored--home $HOMEDIR keysshow-a $ACCOUNT_KEY_NAME)CONSENSUS_KEY=$(exocored--home $HOMEDIR keysconsensus-pubkey-to-bytes--outputjson|jq-r.bytes32)# Actual transactioncastsend--rpc-url $ETHEREUM_RPC_URL \ $BOOTSTRAP_ADDR \"registerValidator(string,string,(uint256,uint256,uint256),bytes32)" \ $EXO_ADDRESS \"$VALIDATOR_NAME" \"($(cast2w $COMMISSION),$(cast2w $COMMISSION_MAX_RATE),$(cast2w $COMMISSION_MAX_CHANGE_RATE))" \ $CONSENSUS_KEY \--private-key $ETH_PRIVATE_KEY
After Network Launch
The Bootstrap contract is not available after the network launches; instead, validator registration happens on Exocore while the assets continue to live on Ethereum. Since Exocore is a restaking L1 supporting multiple AVSs, all validators must first register as “operators” and then opt-in to the Exocore-chain-as-an-AVS as “validators”.
Contact Exocore contributors and provide your exo1 address to obtain some exo native tokens. These tokens will be used to pay for gas on Exocore.
To check if your operator is successfully opted in, query the consensus (validator) key for the chain.
exocored--home $HOMEDIR queryoperatorget-operator-cons-key \ $(exocored--home $HOMEDIR keysshow-a $ACCOUNT_KEY_NAME) \ $CHAIN_ID \# the line below may be eliminated to query localhost:26657 (your node)--node $EXOCORE_COS_GRPC_URL \--outputjson|jq# expected output{"public_key":{"ed25519":"<sample bytes key>" },"opting_out":false}
Your validator is now registered and ready to accept (self-) delegations. As a reminder, a minimum self-delegation of 1,000 USD in token value is required for inclusion in the validator set.
Delegating Tokens
The deposited tokens can now be delegated to your validator.
## Script constants, repeated here for ease of referenceEXO_ETH_ADDR=0x83E6850591425e3C1E263c054f4466838B9Bd9e4WST_ETH_ADDR=0xB82381A3fBD3FaFA77B3a7bE693342618240067bGATEWAY_ADDR=0x8fc4E764C2C3B0646ba572AEa958fB5724706412## User choices, (some) repeated hereETH_PRIVATE_KEY=0xabcde.....ETH_ADDRESS=0x123....# The token to delegateTOKEN=$EXO_ETH_ADDR# Quantity of token to deposit (without the 1e18 multiplier)# Decimal values are supportedQUANTITY=0.5# Endpoint for Ethereum RPCETHEREUM_RPC_URL=https://rpc.ankr.com/eth_sepolia# Derived value, depending on $ACCOUNT_KEY_NAME and $HOMEDIR in prior stepsEXO_ADDRESS=$(exocored--home $HOMEDIR keysshow-a $ACCOUNT_KEY_NAME)BOOTSTRAPPED=$(castcall--rpc-url $ETHEREUM_RPC_URL $GATEWAY_ADDR "bootstrapped() returns (bool)")if [ "$BOOTSTRAPPED"="true" ]; then# Generate the LZ messsage to calculate the fees DELEGATE_PREFIX="0x03" TOKEN_B32=$(cast2b $TOKEN) ETH_ADDRESS_B32=$(cast2b $ETH_ADDRESS) EXO_ADDRESS_BYTES=$(castfu $EXO_ADDRESS) QUANTITY_B32=$(printf"%064s" $(cast2h $(cast2w $QUANTITY) |cut-c3-) |tr' ''0') LZ_MESSAGE=$(castch $DELEGATE_PREFIX $TOKEN_B32 $ETH_ADDRESS_B32 $EXO_ADDRESS_BYTES $QUANTITY_B32) VALUE=$(castcall--rpc-url $ETHEREUM_RPC_URL $GATEWAY_ADDR "quote(bytes)" $LZ_MESSAGE)else# No LZ fees, since we are in Bootstrap mode VALUE=0fi# Execute the transactioncastsend--rpc-url $ETHEREUM_RPC_URL \ $GATEWAY_ADDR \"delegateTo(string,address,uint256)" \ $EXO_ADDRESS \ $TOKEN \ $(cast2w $QUANTITY) \--private-key $ETH_PRIVATE_KEY \--value $(cast2d $VALUE)
If the above transaction was executed after the network bootstrap phase, again, LayerZeroScan may be used to query the status of the message. In addition, the withdrawable amount will change on Exocore once the message is delivered.
ETH_LZ_ID=40161# Sepolia, where our $GATEWAY_ADDR livesSTAKER_ID=$(echo"${ETH_ADDRESS}"|tr'[:upper:]''[:lower:]')_$(printf'0x%x\n'"${ETH_LZ_ID}")# our public endpoint is available below, but you can use your own node tooEXOCORE_COS_GRPC_URL=https://api-cosmos-grpc.exocore-restaking.com:443exocoredqueryassetsQueStakerAssetInfos \ $STAKER_ID --node $EXOCORE_COS_GRPC_URL \--outputjson|jq# looks like this{"asset_infos": [ {"asset_id":"0x83e6850591425e3c1e263c054f4466838b9bd9e4_0x9ce1","info":{"total_deposit_amount":"5000000000000000000","withdrawable_amount":"0",//valueof0"pending_undelegation_amount":"0" } } ]}
Marking a delegation as self-delegation (Post Bootstrap Phase)
This step only applies if the validator registration was done after the Bootstrap phase ended. Otherwise, it is assumed that the delegations made by the Ethereum address, which sent the validator creation transaction, are self-delegations for the validator.
Send the transaction to associate the two addresses: each such association indicates that the delegations made by the Ethereum address are to be considered as self-delegations for the validator.
To prevent slashing for downtime, it is recommended to send the transaction below only after the node is synced.
# User inputs, the first two of which were previously providedETH_PRIVATE_KEY=0xabcdefg...ETH_ADDRESS=0xabcd...# ConstantsEXO_ETH_RPC_URL=https://api-eth.exocore-restaking.comEXO_COS_GRPC_URL=https://api-cosmos-grpc.exocore-restaking.com:443ETH_LZ_ID=40161# Sepolia# Address of gateway derived from the params of x/assetsEXO_GATEWAY_ADDR=$(exocored query assets Params --output json --node $EXO_COS_GRPC_URL | jq -r .params.exocore_lz_app_address | cast 2a)
# First, fund the ETH_ADDRESS if it is not be the same as the EXO_ADDRESS# The validator should have gotten only their EXO_ADDRESS funded by us / faucetETH_ADDR_BALANCE_IN_EXO=$(castbalance $ETH_ADDRESS --rpc-url $EXO_ETH_RPC_URL)ONE_ETHER_IN_WEI=$(cast2w1ether)if [ "$ETH_ADDR_BALANCE_IN_EXO"-lt"$ONE_ETHER_IN_WEI" ]; then# we must execute the transfer, which is easier via cast EXO_PRIVATE_KEY=$(exocored--home $HOMEDIR \keysunsafe-export-eth-key \ $ACCOUNT_KEY_NAME)castsend--rpc-url $EXO_ETH_RPC_URL \ $ETH_ADDRESS \--value $ONE_ETHER_IN_WEI \--private-key $EXO_PRIVATE_KEYfi# Send the transaction for the associationcastsend--rpc-url $EXO_ETH_RPC_URL \ $EXO_GATEWAY_ADDR \"associateOperatorWithEVMStaker(uint32, string)" \ $ETH_LZ_ID \ $(exocored--home $HOMEDIR keysshow-a $ACCOUNT_KEY_NAME) \--private-key $ETH_PRIVATE_KEY
To check whether the above transaction went through, we can query the association.
ETH_LZ_ID=40161# Sepolia, where our $GATEWAY_ADDR livesSTAKER_ID=$(echo"${ETH_ADDRESS}"|tr'[:upper:]''[:lower:]')_$(printf'0x%x\n'"${ETH_LZ_ID}")# our public endpoint is available below, but you can use your own node tooEXOCORE_COS_GRPC_URL=https://api-cosmos-grpc.exocore-restaking.com:443exocoredquerydelegationQueryAssociatedOperatorByStaker \ $STAKER_ID --node $EXOCORE_COS_GRPC_URL \--outputjson|jq# expected output{"operator":"<your_exo_addr>"}
The validator should now be considered "eligible" to be part of the active validator set if the self-delegation exceeds 1,000 USD. The inclusion in the set happens in the beginning of the next epoch, which may be at most 1 day from now. Only the top 100 validators (by total stake = self-stake + delegated stake) are included in the validator set.
EPOCH_ID=$(exocoredquerydogfoodparams--node $EXOCORE_COS_GRPC_URL --outputjson|jq-r.params.epoch_identifier)exocoredqueryepochsepoch-infos--node $EXOCORE_COS_GRPC_URL --outputjson|jq--argEPOCH_ID"$EPOCH_ID"' . as $root | .epochs[] | select(.identifier == $EPOCH_ID) | .next_epoch_start_time = ( (.current_epoch_start_time | fromdateiso8601) + (.duration | sub("s"; "") | tonumber) ) | .time_to_go = ( (.next_epoch_start_time - ($root.block_time | sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601)) | if . < 0 then 0 else . end ) | {identifier, next_epoch_start_time: (.next_epoch_start_time | todate), time_to_go: (.time_to_go | tostring + " seconds")}
'# do include this line, which contains only a single apostrophe# it prints how much time is left for the next epoch, like the below{"identifier":"day","next_epoch_start_time":"2024-09-28T10:45:00Z","time_to_go":"25503 seconds"}
Confirm Election Status
Once the network is running you can check if you’re in the current validator set.
exocored--home $HOMEDIR qtendermint-validator-set|grep $(exocored--home $HOMEDIR tendermintshow-address)# prints your `exovalcons` address, corresponding to the consensus key. example below.-address:exovalcons18z3p42xn8pjk338upvzp794h02wh7p4t7jj9jx
If you recently registered as a validator, it takes at most 1 day (one epoch) for the validator set to update.