Common Addresses

  • BurnerRouterFactory: 0x99F2B89fB3C363fBafD8d826E5AA77b28bAB70a0
  • wstETH: 0xC329400492c6ff2438472D4651Ad17389fCb843a
  • wstETH global burner: 0xdCaC890b14121FD5D925E2589017Be68C2B5B324
  • VaultConfigurator: 0x29300b1d3150B4E2b12fE80BE72f365E200441EC
  • Network: 0x8560C667Ae72F28D09465B342A480daB28821f6b
  • NetworkMiddleware:
  • DefaultStakerRewardsFactory: 0xFEB871581C2ab2e1EEe6f7dDC7e6246cFa087A23
  • NetworkOptInService: 0x7133415b33B438843D581013f98A08704316633c
  • VaultOptInService: 0xb361894bC06cbBA7Ea8098BF0e32EB1906A5F891
  • OperatorRegistry: 0xAd817a6Bc954F678451A71363f04150FDD81Af9F

Setup

The following setup steps will reference various Symbiotic core contracts. Refer to their deployments page and core source code.

Prerequisites

For technical users who’re comfortable running Foundry scripts, we’ve provided example code for every step in the setup process. → Setup Scripts
Alternatively the Symbiotic CLI can assist in many of these steps. Refer to their docs.

Docs:

Tooling:

  • git clone - Gitinstall
  • forge script, cast - Foundryinstall
  • python3 symb.py - Symbiotic CLIinstall

Vault Configuration

The Vault configuration process consists of the following substeps:

1

Deploy Burner Router

The Burner Router defines where slashed collateral is sent (burn address, treasury, etc.).
Every Vault must point to a Burner Router, so we deploy this first.

For the official Symbiotic approach, see: Symbiotic Burner Router Deployment Guide

# Clone ditto-contracts repository
git clone https://github.com/dittonetwork/ditto-contracts
cd ditto-contracts
# Define parameters
OWNER=<owner address>
BURNER_ROUTER_FACTORY=0x99F2B89fB3C363fBafD8d826E5AA77b28bAB70a0
COLLATERAL=0xC329400492c6ff2438472D4651Ad17389fCb843a  # wstETH
DELAY=$((2*24*60*60))  # 2 days
GLOBAL_RECEIVER=<treasury address>
# For the array parameters, format exactly as shown
NETWORK_RECEIVERS="[(<network address>,<receiver address>)]"
OPERATOR_NETWORK_RECEIVERS="[(<network address>,<operator address>,<receiver address>)]"

# Execute the script
forge script script/operator/01_BurnerRouterSetup.s.sol:BurnerRouterSetup \
  --sig "run(address,address,address,uint48,address,(address,address)[],(address,address,address)[])" \
  $OWNER $BURNER_ROUTER_FACTORY $COLLATERAL $DELAY $GLOBAL_RECEIVER \
  "$NETWORK_RECEIVERS" "$OPERATOR_NETWORK_RECEIVERS" \
  --rpc-url https://ethereum-rpc.publicnode.com \
  --chain mainnet \
  --private-key <your-private-key> \
  --etherscan-api-key <etherscan api key> \
  --verify --broadcast
2

Deploy Vault Delegator + Slasher

A Vault is the core Symbiotic primitive—an ERC-20 pool that can be restaked across networks and operators.
We use NetworkRestakeDelegator (type 0) for Ditto.
The Veto Slasher lets a resolver veto or approve each slash event.

For the official Symbiotic approach, see: Symbiotic Core Modules Deployment Guide

# Define parameters
VAULT_CONFIGURATOR=0x29300b1d3150B4E2b12fE80BE72f365E200441EC
OWNER=<owner address>
COLLATERAL=0xC329400492c6ff2438472D4651Ad17389fCb843a  # wstETH
BURNER=<burner router address from previous step>
EPOCH_DURATION=$((7*24*60*60))  # 7 days
WHITELISTED_DEPOSITORS="[]"  # Empty array or specific addresses
DEPOSIT_LIMIT=0  # No limit, or set a specific limit
DELEGATOR_INDEX=0  # 0=NetworkRestakeDelegator, 1=FullRestakeDelegator, 2=OperatorSpecificDelegator, 3=OperatorNetworkSpecificDelegator
HOOK=0x0000000000000000000000000000000000000000  # Contract with onSlash() or zero address
NETWORK=0x8560C667Ae72F28D09465B342A480daB28821f6b  # Network address
WITH_SLASHER=true  # Enable slasher
SLASHER_INDEX=1  # 0=Slasher, 1=VetoSlasher
VETO_DURATION=$((24*60*60))  # 1 day

# Execute the script
forge script script/operator/02_CoreSetup.s.sol:CoreSetup \
  --sig "run(address,address,address,address,uint48,address[],uint256,uint64,address,address,bool,uint64,uint48)" \
  $VAULT_CONFIGURATOR $OWNER $COLLATERAL $BURNER $EPOCH_DURATION \
  "$WHITELISTED_DEPOSITORS" $DEPOSIT_LIMIT $DELEGATOR_INDEX \
  $HOOK $NETWORK $WITH_SLASHER $SLASHER_INDEX $VETO_DURATION \
  --rpc-url https://ethereum-rpc.publicnode.com \
  --chain mainnet \
  --private-key <your-private-key> \
  --etherscan-api-key <etherscan api key> \
  --verify --broadcast

Deploy Default Staker Rewards

DefaultStakerRewards harvests rewards earned by the Vault and redistributes them to depositors, taking an optional admin fee.

For the official Symbiotic approach, see: Symbiotic Staker Rewards Deployment Guide

# Define parameters
VAULT=<vault address from previous step>
ADMIN_FEE=1000  # 10% (basis points, 10000 = 100%)
DEFAULT_ADMIN_ROLE_HOLDER=<admin address>
ADMIN_FEE_CLAIM_ROLE_HOLDER=<admin address>
ADMIN_FEE_SET_ROLE_HOLDER=<admin address>
DEFAULT_STAKER_REWARDS_FACTORY=0xFEB871581C2ab2e1EEe6f7dDC7e6246cFa087A23

# Execute the script
forge script script/operator/03_DefaultStakerRewardsSetup.s.sol:DefaultStakerRewardsSetup \
  --sig "run(address,uint256,address,address,address,address)" \
  $VAULT $ADMIN_FEE $DEFAULT_ADMIN_ROLE_HOLDER $ADMIN_FEE_CLAIM_ROLE_HOLDER \
  $ADMIN_FEE_SET_ROLE_HOLDER $DEFAULT_STAKER_REWARDS_FACTORY \
  --rpc-url https://ethereum-rpc.publicnode.com \
  --chain mainnet \
  --private-key <your-private-key> \
  --etherscan-api-key <etherscan api key> \
  --broadcast

Register Operator & Opt-In

Your EOA (or node runner contract) must be registered as an Operator inside Symbiotic. Opt-in links that operator to a specific Network and Vault.

For the official Symbiotic approach, see: Symbiotic Operator Registration Guide

1

Register in Symbiotic and opt-in to vault and network

# Define parameters
NETWORK_OPT_IN_SERVICE=0x7133415b33B438843D581013f98A08704316633c
NETWORK=0x8560C667Ae72F28D09465B342A480daB28821f6b
VAULT_OPT_IN_SERVICE=0xb361894bC06cbBA7Ea8098BF0e32EB1906A5F891
VAULT=<vault address from previous steps>
OPERATOR_REGISTRY=0xAd817a6Bc954F678451A71363f04150FDD81Af9F

# Execute the script
forge script script/operator/04_OperatorOptInSetup.s.sol:SetupOperatorOptIn \
  --sig "run(address,address,address,address,address)" \
  $NETWORK_OPT_IN_SERVICE $NETWORK $VAULT_OPT_IN_SERVICE $VAULT $OPERATOR_REGISTRY \
  --rpc-url https://ethereum-rpc.publicnode.com \
  --chain mainnet \
  --private-key <your-private-key> \
  --etherscan-api-key <etherscan api key> \
  --broadcast
2

Register in Ditto network middleware

To Register in Ditto network, you have to contact us and send this info:

Vault Address: <vault> 
Operator Address: <operator>
Operator Public Key: <key>

If you have access to register by yourself, you can run this scripts:

# --- env vars ---
MIDDLEWARE=<middleware address>      # Contact Ditto for this address
OPERATOR=<operator address>          # Your operator address
KEY=<key>                            # Your operator's public key
VAULT=<vault address>                # Optional: ONLY FOR OperatorSpecificDelegator and OperatorNetworkSpecificDelegator - Your vault address from previous steps 
# ----------------

forge script script/middleware/OperatorMiddlewareScript.s.sol:OperatorMiddlewareScript \
  --sig "registerOperator(address,address,address,bytes)" \
  $MIDDLEWARE $OPERATOR $KEY $VAULT \
  --rpc-url https://ethereum-rpc.publicnode.com \
  --chain mainnet \
  --private-key <your-private-key> \
  --etherscan-api-key <etherscan api key> \
  --broadcast

Deposit Collateral

After the vault, burner router, delegator, and slasher are deployed in the previous step, the vault must obtain deposits of the appropriate ERC20 collateral type. These deposits can come from a variety of sources depending on the vault. For the official Symbiotic approach, refer to the Symbiotic CLI Guide

Before depositing, ensure you have approved the vault to spend your tokens:

# Approve tokens using cast (replace with your addresses)
cast send <COLLATERAL_TOKEN> "approve(address,uint256)" <VAULT_ADDRESS> <AMOUNT> \
  --rpc-url <your-rpc-url> \
  --private-key <your-private-key>

Vault Actions

Finally, the vault curator address must make some calls to allocate collateral to the Ditto network and operators.

For Ditto, the subnetwork ID is always 1. The subnetwork bytes32 value is computed by concatenating the network address with the subnetwork ID.

For Mainnet, the subnetwork bytes32 value is:

0x8560C667Ae72F28D09465B342A480daB28821f6b000000000000000000000001

For Sepolia:

0x9403942D5B9EbC1D2cF9Ff0960b688d64588B094000000000000000000000001
1

Set Network Limit

First, call setNetworkLimit(bytes32 subnetwork, uint256 amount) on the delegator module of the vault. This sets the total amount of collateral the vault would like to restake to the Ditto network.

# Using cast (replace with your addresses and values)
DELEGATOR=<delegator address>  # The delegator module of your vault
SUBNETWORK=0x8560C667Ae72F28D09465B342A480daB28821f6b000000000000000000000001  # For mainnet
LIMIT=<amount>  # Amount in wei (e.g., 1000000000000000000000 for 1000 tokens)

cast send $DELEGATOR "setNetworkLimit(bytes32,uint256)" $SUBNETWORK $LIMIT \
  --rpc-url https://ethereum-rpc.publicnode.com \
  --private-key <your-private-key>
2

Set Operator Network Shares (for NetworkRestakeDelegator)

NetworkRestakeDelegator (type 0)

You must also call setOperatorNetworkShares(subnetwork, operator, shares) to allocate stake to specific operators.

OperatorSpecificDelegator (type 2)

This step is not required as 100% of shares are automatically allocated to the single operator.

# Using cast (replace with your addresses and values)
DELEGATOR=<delegator address>  # The delegator module of your vault
SUBNETWORK=0x8560C667Ae72F28D09465B342A480daB28821f6b000000000000000000000001  # For mainnet
OPERATOR=<operator address>  # Your operator address
SHARES=<shares>  # Amount in basis points (e.g., 10000 for 100%)

cast send $DELEGATOR "setOperatorNetworkShares(bytes32,address,uint256)" $SUBNETWORK $OPERATOR $SHARES \
  --rpc-url https://ethereum-rpc.publicnode.com \
  --private-key <your-private-key>

Here is an example of how the vault curator would complete this step using a Foundry script:

// SaveToFile: VaultActions.s.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {Script} from "forge-std/Script.sol";
import {console2} from "forge-std/console2.sol";
import {IOperatorSpecificDelegator} from "symbiotic-core/src/interfaces/delegator/IOperatorSpecificDelegator.sol";

contract VaultActions is Script {
  function run() external {
    vm.startBroadcast();

    // Replace with your delegator address
    IOperatorSpecificDelegator delegator = IOperatorSpecificDelegator(0x75b131De299A5D343b9408081DD6A9891b8c);
    
    // Set network limit (1,000,000 tokens with 18 decimals)
    delegator.setNetworkLimit(0x8560C667Ae72F28D09465B342A480daB28821f6b000000000000000000000001, 1000000 ether);

    // Check stake allocation for the operator
    uint256 stake = delegator.stake(0x8560C667Ae72F28D09465B342A480daB28821f6b000000000000000000000001, msg.sender);
    console2.log("Stake toward operator:", stake);

    vm.stopBroadcast();
  }
}

To run this script:

# Create the script file and paste the content above

# Execute the script
forge script VaultActions.s.sol:VaultActions \
  --rpc-url https://ethereum-rpc.publicnode.com \
  --private-key <your-private-key> \
  --etherscan-api-key <etherscan api key> \
  --broadcast

Troubleshooting

If you encounter any issues during the setup process, check our common troubleshooting solutions:

For further assistance, contact Ditto support.