2. Deploying vPool

To deploy a new vPool, you need to perform a transaction from the ADMIN address of your vFactory.

TheGraph: Retrieving your admin address
{
  vFactories(where:{address:"YOUR_VFACTORY_ADDRESS"}) {
    admin
  }
}

{
  "data": {
    "vFactory": {
      "admin": "YOUR_ADMIN_ADDRESS"
    }
  }
}

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.

TheGraph: Retrieving the nexus address
{
    nexuses {
        address
    }
}

{
  "data": {
    "nexuses": [
      {
        "address": "THE_NEXUS_ADDRESS"
      }
    ]
  }
}

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.

Spawning a vPool instance

We need to call the spawnPool method on the Nexus contract.

Several parameters are required to spawn a pool:


struct PoolConstructionArguments {
  uint256 epochsPerFrame;
  uint256 operatorFeeBps;
  address factory;
  uint64[3] reportBounds;
  string initialExtraData;
  string exitQueueImageUrl;
}

function spawnPool(PoolConstructionArguments calldata pca) external returns (address[6] memory spawned);

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

Cast: Spawn a new vPool instance

from=ADMIN


cast send $NEXUS_ADDRESS \
    "spawnPool((uint256,uint256,address,uint64[3],string,string))(address[6])" \
    "(15,500,$YOUR_FACTORY_ADDRESS,[1000,200,500],'','')" \
    --rpc-url $YOUR_RPC_ENDPOINT
  • You can add the -i flag to interactively provide the private key for the transaction

  • You can add --ledger --from YOUR_ADDRESS to use a Ledger device to perform the transaction

TheGraph: Retrieve the addresses of the newly deployed contracts
{
  vFactories(where:{address:"YOUR_VFACTORY_ADDRESS"}) {
    pools {
      address
      withdrawalRecipient {
        address
        withdrawalCredentials
      }
      execLayerRecipient {
        address
      }
      coverageRecipient {
        address
      }
      oracleAggregator {
        address
      }
      exitQueue {
        address
      }
    }
  }
}
{
  "data": {
    "vFactory": {
      "pools": [
        {
          "address": "YOUR_NEW_VPOOL_ADDRESS",
          "withdrawalRecipient": {
            "address": "ITS_WITHDRAWAL_RECIPIENT",
            "withdrawalCredentials": "THE_WITHDRAWAL_CREDENTIALS"
          },
          "execLayerRecipient": {
            "address": "ITS_EXEC_LAYER_RECIPIENT"
          },
          "coverageRecipient": {
            "address": "ITS_COVERAGE_RECIPIENT"
          },
          "oracleAggregator": {
            "address": "ITS_ORACLE_AGGREGATOR"
          },
          "exitQueue": {
            "address": "ITS_EXIT_QUEUE"
          }
        }
      ]
    }
  }
}

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.

Last updated