Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
To deploy a new vPool, you need to perform a transaction from the ADMIN
address of your vFactory.
The transaction should be made to the Nexus contract. This contract is the entry point of the system, it's mainly used to deploy new instances of contract, but once contracts are deployed, they do not rely on it anymore.
For this guide, the transactions are made with cast, a cli tool to make transactions and calls on Ethereum. Of course, any tool able to craft the transactions would do the job, we will provide the contract ABIs as we move forward in the guide.
We need to call the spawnPool
method on the Nexus contract.
Several parameters are required to spawn a pool:
Let's go through all of them one by one:
epochsPerFrame
: an oracle quorum will need to run and report some important consensus layer data to the vPool contract. This parameter will control at which rate the reports will be made (reports are made at the beginning of every frame). On mainnet, we suggest setting this value to 225
(24 hours), and on testnet we can use something lower like 15
(1h30).
operatorFeeBps
: Every time the oracle will report, possible rewards will be received by the pool. The operator can take a cut on these rewards in the form of shares of the vPool. These shares will be sent automatically to the vTreasury contract of the operator. This value is in basis points ( [0, 10000] )
factory
: The address of the vFactory on which this vPool will be plugged.
reportBounds
: To ensure the validity of the reported data, some reporting bounds are configured to prevent reports that would be reporting values out of bounds. The first value of the bound array if the upperBound
, this value is the maximum apy
authorized, in basis point. Setting it to 1000
means that the oracle report will make sure that the accrued rewards are not exceeding 10%
apy. The second value is the coverageUpperBound
. The vPool has the ability to pull funds from a special recipient called the vCoverageRecipient
. This recipient will be used only in the case of loss of funds due to slashing. The coverageUpperBound
is a boost applied to the upperBound
only usable for coverage funds. This would ensure that coverage funds are pulled quickly into the system. The third value is the lowerBound
. This value is relative to the total underlying supply, setting it to 100
means that the report balance shouldn't decrease by 1%
of the total underlying supply or more. You can see these bounds as additional safety nets that would prevent invalid reports from going through as exceeding these bounds should not happen based on how the consensus layer works, the rewards rate, and how penalties could be applied.
initialExtraData
: This argument is sent to the vFactory whenever a validator is purchased. This is an arbitrary metadata field that will be emitted with the event of funding on the vFactory. Feel free to use this value as you want as it has no impact on the behavior of the vPool. You can leave it empty if needed.
exitQueueImageUrl
: This argument is used for the exit queue tickets. They are represented as NFTs, and this argument configures the image field in their metadata. You can use Kiln's endpoint (wip) or build your own and serve the image that you want.
We can now perform the transaction to spawn a new vPool instance
You have now deployed your vPool alongside all its required contracts. Let's recap what each contract does:
vPool
: the main pooling contract, receives deposits of any size and creates shares of the pool in exchange.
vWithdrawalRecipient
: this contract's address is set as the withdrawal credential on the pool validators. It will be in charge of retrieving the consensus layer funds coming to the system, and will be called by the vPool contract to send the funds.
vExecLayerRecipient
: this contract's address is set as the fee recipient on the pool validators. It will be in charge of retrieving all the block proposal related fees that happen directly on the execution layer, and will be called by the vPool contract to send the funds.
vCoverageRecipient
: this contract holds funds provided by configured donors. In case of slashing losses, funds from this contract can be used to repay the loss. Pulled funds are not accounted in revenue, so no commission will be taken on these funds.
vOracleAggregator
: this contract holds a list of quorum members that have to vote on a report. Once a quorum is met for a specific report value, the report is forwarded to the vPool contract. The contract cannot work if only one member is present in the quorum, this is why Kiln runs what is called the global oracle member, a member that is present by default in every vOracleAggregator contract, and makes sure bootstrapping happens as expected. This global oracle member can be ejected if and only if the contract has 5+ oracle members registered and the ejection flag is set to true
vExitQueue
: this contract is in charge of processing the exit requests of the vPool share holders. In exchange of shares, a recipient will receive an NFT representing its future claim. Based on the demand in the exit queue contract, vPool will emit events that signal the possible need for an exit to the node operator
For both the oracle reporting and the exit queue events, Kiln provides the operatord
tool, that holds the daemons to perform the oracle reports automatically (start-oracle)
and to catch exit requests and forward them to a custom webhook endpoint (start-exit-daemon
). The configuration of both of these daemons is covered later in this guide.
To gain access to TheGraph, contact Kiln and you will receive credentials to Kiln's TheGraph instance. Feel free to also host the subgraph on your end, allowing you to run the queries locally. During this guide, we will often use TheGraph queries to verify that specific actions had the expected outcome.
The easiest way to interact with TheGraph is by visiting the web query builder.
To test it, you can try a simple query to retrieve the address of the vTreasury that was deployed alongside your vFactory:
And if everything goes well, you should have a response looking like this (you should have an empty array of pools as no vPool was deployed yet)
You're now ready to follow the guide and run the queries when needed !
The next step would be to add an oracle member, and start using the Oracle Daemon with the oracle member private key to craft and send reports to the vPool.
By default, the pool expects at least two members before it can start operating, but Kiln runs the global oracle member, so adding one regular member will do the trick.
Remember, you can remove the global oracle member from your vPool only if you have at least 5 members in your oracle quorum, and if you toggle the ejection flag.
The call is pretty simple, and should be performed by the vFactory admin
You can find the oracle daemon inside the operatord
repository. It contains all the daemons that an operator might need to run.
The oracle daemon is configured using a config.yaml
file. Let's start by creating a directory for the oracle daemon:
And then, create ~/oracle_datadir/config.yaml
, here's how it should look for one oracle member
name
is for logging purposes
private-key
is the private key of the member that you wish to run
pool
is the address of the vPool
you wish to report for
nexus
is the address of the Nexus
contract
purchase
is set to true
if the member will also participate in performing the purchaseValidators
call on the vPool
maxTxCostWei
is the maximum allowed cost of a tx in wei
It might take a bit of time to sync, but anything pulled from EL/CL that we'll need to reuse is stored inside the sqlite
database that will be created. This would greatly speedup possible restarts.
You should now ping Kiln to make sure that the global oracle member is running on their end. If that's the case, we should see oracle reports being posted
Handling exit requests
As the channels can automatically select a number of validators that should be stopped, the "Exit Daemon" is here to signal the exit requests on a given vFactory.
This utility is a REST endpoint and also a webhook for the operators to automate exit flow of the validators.
📝 You will run one Exit Daemon for one vFactory
You can find the exit daemon inside the operatord
repository.
The daemon is configured using a config.yaml
file, the same as the Oracle Daemon.
You have the option to either incorporate the following lines into a new configuration file or append them to the existing Oracle Daemon configuration file:
factory
: Address of the operator vFactory (mandatory).
hook-endpoint
: REST endpoint that will be called (POST) when a report is available (optional).
Then, we can run the exit daemon by running:
execution-node-url
is an rpc endpoint on the execution layer (geth, erigon, etc...).
consensus-node-url
is an rpc endpoint on the consensus layer (prysm, lighthouse, etc...).
config-file
is the path to the file we configured above.
At every epoch (finalized), if exits are needed, the Exit Daemon craft a report for each withdrawal channel associated with the specified vFactory.
A report follows this JSON structure:
epoch
: Epoch of the report
withdrawalChannel
: Withdrawal channel id.
lastRequestTxHash
: TX hash of the latest SetExitTotal event.
validatorCountToExit
: Delta between the requested and actual exited validators.
totalCurrentlyExited
: Total exited validators for the withdrawal channel.
suggestedValidatorExits
: Lists suggested validators, providing their index and public key, that are eligible for exit. The suggested validators for exit are those with the oldest activationEpoch
.
exitableValidators
: Complete list of index / public Keys of all the currently exitable validators of the withdrawal channel.
The last (up to date) reports can be accessed using the REST API.
Every epoch, the daemon has the capability to forward the reports to an endpoint.
As outlined in the configuration, you can specify a hook-endpoint
in the configuration file. This endpoint will be triggered with a POST request when a report becomes available.
To illustrate its operation, we've created a demo server:
The demo server accepts these reports and displays the corresponding JSON data in the terminal. It provides access to these reports via the /report
route.
Guide to deploy an integration contract and connecting it to a vPool
.
You can ask Kiln to deploy an integration contract for you or deploy it yourself.
If you want to deploy it yourself ask Kiln to give the right to deploy using the IntegrationFactory
and give us the address you want as deployer
.
Kiln or your can deploy an Integration contract using a deployer
account on the IntegrationFactory:
These function use the following structs:
Let's go through all of them one by one:
Common parameters :
name
: An ERC-20 style display name of the token.
symbol
: an ERC-20 style display symbol of the token.
admin
: The address of the admin. This account can change the fees, and commissionRecipient
as well as add pools and enable/disable them.
pools
: List of underlying pools addresses. Those are the pools to which the staked funds will flow to. You must provide at least one pool.
poolFees
: List of fee for each pool, in basis points. Must be the same length as pools
. The fee for a single pool must not exceed maxCommissionBps
.
commissionRecipients
: List of addresses to which the accumulated integrator fee will be dispatched.
commissionDistribution
: List of the share of the fee each commissionRecipient
will receive, in basis points, must add up to 10 000. Must be the the same length as commissionRecipients
.
maxCommissionBps
: A limit on the pool fee given at initialization so the stakers can be sure the fee will never go above a certain level.
poolPercentages
: The desired repartitions of ETH between the pools. In basis points, must add up to 10 000. Must be the same length as pools
. This can be changed by the admin
.
monoTicketThreshold
: A value that modify the behaviour of requestExit()
when the contract is connected to multiple pools, when exiting we want to limit the number of tickets minted for gas efficiency but we also want to limit the imbalance caused by not exiting through all the pools using the the configured poolPercentages
.
If a user requests the exit of an ETH amount below monoTicketThreshold
the code will try to exit through only one pool if possible thus minting only one ticket hence the name monoTicketThreshold
. This value can be changed later on by the admin
as the TVL grows and the pools can support bigger exit without significant imbalance.
If you are using Kiln's pool ask us to allow your contract to deposit to our pool.
If you manage your pool, allow your integration contract to deposit on the vPool
by calling this function :
This process is gas intensive and a Merkle Tree based solution is currently being worked on that would only require an update + approval of the tree root, drastically reducing the gas cost to add keys.
Now that we have the exact address of the vPool and its vWithdrawalRecipient, we can start creating validation keys for its withdrawal credentials and upload them to the vFactory.
The vFactory stores keys in separated Withdrawal Channels. Only two types of Withdrawal Channels exist:
Withdrawal Channel != 0: The withdrawal channel value is the actual withdrawal credential of the keys inside the channel. In the case of the vPool, keys are added on a withdrawal channel that is equal to the withdrawal credential of the vWithdrawalRecipient
Withdrawal Channel == 0: This special withdrawal channel holds keys with each its own withdrawal recipient, handled by the vFactory. This means that the generation of these keys is a bit different as the address of the withdrawal recipient is deterministic and based on the validator public. We won't use this channel for the vPool.
We will need both the withdrawal credential and the vWithdrawalRecipient address for the next step
For our example, we'll use the official deposit cli to generate 10 keys for the pool.
And once done, you can check the generated deposit_data
file to make sure that all keys:
have the same withdrawal credential
the value is the proper one
We can now verify our deposit data to make sure it's valid. The tool we're going to use ensures that the key has no duplicates inside the vFactory and ensures that the signature associated with the public key is valid.
You should retrieve the vsuite-utils
repository, and have golang
installed for the next step.
Clone vsuite-utils
and run:
It should directly tell you if there is any error in the deposit_data
, otherwise we're clear to advance to the next step: submitting the keys
We can now convert the generated deposit_data
into calldata for the submit transaction.
When keys are approved, authorized depositors on the withdrawal channel will be able to fund them. This means that it is a very critical action, that only the ADMIN
of the vFactory can perform. We suggest using a multisig where a quorum of members will be able to run the verification script on their end to ensure that the current keys of the channel are valid.
The approve
method of the vFactory is able to change the staking limit of several withdrawal channels at once.
The parameters are the following ones:
withdrawalChannels
: The list of withdrawal channels that we will approve
limits
: The staking limits of the withdrawal channels, at the same indexes
snapshots
: This parameter will ensure that the approval only passes if there has been no modification to the channel since the provided snapshot block number. When perform the off-chain verifications, quorum members should use the same snapshot block number to perform their verifications.
Once we know all the keys are valid, we can approve them ! In the following example we use cast but only as illustration for testnet, we strongly recommend using a strong multisig on mainnet for this purpose.
The vFactory is now ready for the vPool to purchase up to 10 validators !
The last step is deploying an integration contract and connecting it to your vPool
.
The deployment of integration contracts is covered on .
You should new be able to stake on your integration contract and purchase validator on your vPool
once 32 ETH is reached
To facilitate your usage of the integration contracts your can find the ABIs
of the integration contracts on .
Once your vPool
has accumulated 32 ETH and your oracle has reported you should be able to call :
This should not revert and a deposit should have happened.
Afterwards you should be able to monitor the growth of your position upon every oracle report.