diff --git a/eth2util/deposit/deposit_data.go b/eth2util/deposit/deposit_data.go
new file mode 100644
index 000000000..333eb4701
--- /dev/null
+++ b/eth2util/deposit/deposit_data.go
@@ -0,0 +1,159 @@
+// Copyright © 2022 Obol Labs Inc.
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see .
+
+package deposit
+
+import (
+ "encoding/hex"
+ "encoding/json"
+
+ eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
+ ssz "github.com/ferranbt/fastssz"
+
+ "github.com/obolnetwork/charon/app/errors"
+)
+
+const depositAmt = 32000000000
+
+// depositData contains all the information required for activating validators on the Ethereum Network.
+// Ref: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#depositdata
+type depositData struct {
+ // PubKey is the group public key for a Distributed Validator.
+ PubKey eth2p0.BLSPubKey
+
+ // Amount is the amount of Eth needed to activate a validator.
+ Amount eth2p0.Gwei
+
+ // Eth1WithdrawalAddress is the Ethereum withdrawal address.
+ Eth1WithdrawalAddress string
+
+ // DepositMessageRoot is the hash tree root of DepositMessage.
+ DepositMessageRoot eth2p0.Root
+
+ // Signature is constructed from DepositMessageRoot combined with DOMAIN_DEPOSIT.
+ Signature eth2p0.BLSSignature
+
+ // ForkVersion identifies the network/chainID.
+ ForkVersion eth2p0.Version
+}
+
+func (d depositData) HashTreeRoot() ([32]byte, error) {
+ b, err := ssz.HashWithDefaultHasher(d)
+ if err != nil {
+ return [32]byte{}, errors.Wrap(err, "hash deposit data")
+ }
+
+ return b, nil
+}
+
+func (d depositData) HashTreeRootWith(hh *ssz.Hasher) error {
+ idx := hh.Index()
+
+ // Field 0 'PubKey`
+ hh.PutBytes(d.PubKey[:])
+
+ // Field 1 'WithdrawalCredentials
+ creds, err := withdrawalCredsFromAddr(d.Eth1WithdrawalAddress)
+ if err != nil {
+ return errors.Wrap(err, "withdrawal credentials")
+ }
+ hh.PutBytes(creds[:])
+
+ // Field 2 'Amount'
+ hh.PutUint64(uint64(d.Amount))
+
+ // Field 4 'Signature'
+ hh.PutBytes(d.Signature[:])
+
+ hh.Merkleize(idx)
+
+ return nil
+}
+
+// MarshalDepositData returns the json serialised deposit data bytes to be written to disk.
+func MarshalDepositData(pubkey eth2p0.BLSPubKey, msgRoot eth2p0.Root, sig eth2p0.BLSSignature, withdrawalAddr, forkVersion string) ([]byte, error) {
+ creds, err := withdrawalCredsFromAddr(withdrawalAddr)
+ if err != nil {
+ return nil, err
+ }
+
+ var version eth2p0.Version
+ forkVersionBytes, err := hex.DecodeString(forkVersion)
+ if err != nil {
+ return nil, errors.Wrap(err, "decode fork version")
+ }
+ copy(version[:], forkVersionBytes)
+
+ // construct DepositData and then calculate the hash.
+ d := depositData{
+ PubKey: pubkey,
+ Amount: depositAmt,
+ Eth1WithdrawalAddress: withdrawalAddr,
+ DepositMessageRoot: msgRoot,
+ Signature: sig,
+ ForkVersion: version,
+ }
+ hash, err := d.HashTreeRoot()
+ if err != nil {
+ return nil, err
+ }
+
+ // Marshal json version of deposit data.
+ resp, err := json.Marshal(ddJSON{
+ PubKey: hex.EncodeToString(pubkey[:]),
+ Amount: uint64(d.Amount),
+ WithdrawalCredentials: hex.EncodeToString(creds[:]),
+ DepositDataRoot: hex.EncodeToString(hash[:]),
+ DepositMessageRoot: hex.EncodeToString(msgRoot[:]),
+ Signature: hex.EncodeToString(sig[:]),
+ ForkVersion: forkVersion,
+ NetworkName: forkVersionToNetwork(forkVersion),
+ })
+ if err != nil {
+ return nil, errors.Wrap(err, "marshal deposit data")
+ }
+
+ return resp, nil
+}
+
+// forkVersionToNetwork returns the name of the ethereum network corresponding to a given fork version.
+func forkVersionToNetwork(forkVersion string) string {
+ switch forkVersion {
+ case "00000000":
+ return "mainnet"
+ case "00001020":
+ return "prater"
+ case "60000069":
+ return "kintsugi"
+ case "70000069":
+ return "kiln"
+ case "00000064":
+ return "gnosis"
+ default:
+ return "mainnet"
+ }
+}
+
+// ddJSON is the json formatter for depositData.
+type ddJSON struct {
+ PubKey string `json:"pubkey"`
+ Amount uint64 `json:"amount"`
+ WithdrawalCredentials string `json:"withdrawal_credentials"`
+ DepositDataRoot string `json:"deposit_data_root"`
+ DepositMessageRoot string `json:"deposit_message_root"`
+ Signature string `json:"signature"`
+ ForkVersion string `json:"fork_version"`
+ NetworkName string `json:"network_name"`
+}
diff --git a/eth2util/deposit/deposit_message.go b/eth2util/deposit/deposit_message.go
new file mode 100644
index 000000000..d3ca3ed63
--- /dev/null
+++ b/eth2util/deposit/deposit_message.go
@@ -0,0 +1,123 @@
+// Copyright © 2022 Obol Labs Inc.
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see .
+
+package deposit
+
+import (
+ "encoding/hex"
+ "strings"
+
+ eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
+ "github.com/ethereum/go-ethereum/common"
+ ssz "github.com/ferranbt/fastssz"
+
+ "github.com/obolnetwork/charon/app/errors"
+ "github.com/obolnetwork/charon/app/z"
+)
+
+var (
+ eth1AddressWithdrawalPrefix = byte(0x01)
+ elevenZeroes = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+)
+
+// depositMessage contains all the basic information necessary to activate a validator. The fields are
+// hashed to get the DepositMessageRoot. This root is signed and then the signature is added to DepositData.
+// Ref: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#depositmessage
+type depositMessage struct {
+ pubKey eth2p0.BLSPubKey
+
+ // WithdrawalCredentials is the 0x01 withdrawal credentials. See spec:
+ // https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/validator.md#withdrawal-credentials
+ withdrawalCredentials [32]byte
+
+ amount eth2p0.Gwei
+}
+
+func (d depositMessage) HashTreeRoot() ([32]byte, error) {
+ b, err := ssz.HashWithDefaultHasher(d)
+ if err != nil {
+ return [32]byte{}, errors.Wrap(err, "hash deposit message")
+ }
+
+ return b, nil
+}
+
+func (d depositMessage) HashTreeRootWith(hh *ssz.Hasher) error {
+ idx := hh.Index()
+
+ // Field 0 'pubKey`
+ hh.PutBytes(d.pubKey[:])
+
+ // Field 1 'withdrawalCredentials'
+ hh.PutBytes(d.withdrawalCredentials[:])
+
+ // Field 2 'amount'
+ hh.PutUint64(uint64(d.amount))
+
+ hh.Merkleize(idx)
+
+ return nil
+}
+
+// MessageRoot returns the hash tree root of the deposit message.
+func MessageRoot(pubkey eth2p0.BLSPubKey, withdrawalAddr string) (eth2p0.Root, error) {
+ creds, err := withdrawalCredsFromAddr(withdrawalAddr)
+ if err != nil {
+ return eth2p0.Root{}, err
+ }
+
+ depositMessage := depositMessage{
+ pubKey: pubkey,
+ amount: depositAmt,
+ withdrawalCredentials: creds,
+ }
+
+ root, err := depositMessage.HashTreeRoot()
+ if err != nil {
+ return eth2p0.Root{}, err
+ }
+
+ return root, nil
+}
+
+// withdrawalCredsFromAddr returns the Withdrawal Credentials corresponding to a '0x01' Ethereum withdrawal address.
+func withdrawalCredsFromAddr(addr string) ([32]byte, error) {
+ // Check for validity of address.
+ if !common.IsHexAddress(addr) {
+ return [32]byte{}, errors.New("invalid withdrawal address", z.Str("address", addr))
+ }
+
+ var creds []byte
+
+ // Append the single byte ETH1_ADDRESS_WITHDRAWAL_PREFIX as prefix.
+ creds = append(creds, eth1AddressWithdrawalPrefix)
+
+ // Append 11 bytes of 0.
+ creds = append(creds, elevenZeroes...)
+
+ addr = strings.TrimPrefix(addr, "0x")
+ addrBytes, err := hex.DecodeString(addr)
+ if err != nil {
+ return [32]byte{}, errors.Wrap(err, "decode address")
+ }
+
+ // Finally, append 20 bytes of ethereum address.
+ creds = append(creds, addrBytes...)
+
+ var resp [32]byte
+ copy(resp[:], creds)
+
+ return resp, nil
+}