Decoding ETH Contract Calldata

If you use our /funding_transactions endpoint to build contract call data for one or more ETH validators, you will likely want to decode the returned data before submitting it for deposit. Below we show one example in Python how to achieve this. You are free to use your own implementation in a language you are most comfortable with.

Before we get started, you will need access to an Ethereum node. You're welcome to use public nodes or deploy and manage your own infrastructure. As an example, we're signing up and using a free plan from Quicknode. Once signed in, click Create Endpoint and select the Ethereum Mainnet chain. Keep the HTTP Provider URL handy, as you'll need it in the Python script.

In Python we'll use the Web3.py library and the following code to decode the contract call data against the smart contract ABI.

from web3 import Web3
​
# The RPC URL
w3 = Web3(Web3.HTTPProvider("QUICKNODE_ETHEREUM_ENDPOINT"))
​
# Figment Mainnet contract address
contract_address = '0xF0075B3Cf8953d3E23b0eF65960913Fd97eb5227'
​
# Figment Mainnet ABI
abi = '[{"inputs":[{"internalType":"address","name":"depositContract_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"nodesAmount","type":"uint256"}],"name":"DepositEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"collateral","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"credentialsLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"pubkeys","type":"bytes[]"},{"internalType":"bytes[]","name":"withdrawal_credentials","type":"bytes[]"},{"internalType":"bytes[]","name":"signatures","type":"bytes[]"},{"internalType":"bytes32[]","name":"deposit_data_roots","type":"bytes32[]"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"depositContract","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nodesMinAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pubkeyLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"signatureLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]'
​
# The contract object
contract = w3.eth.contract(address=contract_address, abi=abi)
​
# The raw call data to be decoded
data = "0x4f498c730000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003096a25c9dbf3f04afe0d2a5685f1f569f15e291c034070795d1fe0117ae8b32a38f7d6f5d4bf868b78e9d621bbd2f619500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020010000000000000000000000b87ccbdec9de076694253c916f68d80e8952509c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060a06ff58297d30407c6ee78e71a3b7a91399c5bfd23e2dc630ea489af99504aee1153dfb0da1ae06c85d8916894216be117d36a221207615fe970b6f27f306492800ddbfc2bc0c56d8e2f2e26105efa28834c375d668c7bf76fb918822474f8960000000000000000000000000000000000000000000000000000000000000001377ce1b6174eb902fbc201e62568fbcbd9dfd46d58681fb602bed2428a3a934e"
​
# Decode the call data
decoded_data = contract.decode_function_input(data)
​
# Print the decoded call data
print("Decoded Function: deposit")
print("Decoded Arguments:")
print("pubkeys:", [item.hex() for item in decoded_data[1]['pubkeys']])
print("withdrawal_credentials:", [item.hex() for item in decoded_data[1]['withdrawal_credentials']])
print("signatures:", [item.hex() for item in decoded_data[1]['signatures']])
print("deposit_data_roots:", [item.hex() for item in decoded_data[1]['deposit_data_roots']])
​

Execute

  1. Make sure you insert the correct contract address (Figment Mainnet contract or Figment Goerli contract) with the respective smart contract ABI (you can find this under the Contract page in Etherscan)
  2. Make sure to replace the data your own contract call data returned from the /funding_transactions endpoint
  3. Run pip install web3
  4. Run python3 decode.py

For the above example, this is what the decoded data looks like:

Decoded Function: deposit
Decoded Arguments:
pubkeys: ['96a25c9dbf3f04afe0d2a5685f1f569f15e291c034070795d1fe0117ae8b32a38f7d6f5d4bf868b78e9d621bbd2f6195']
withdrawal_credentials: ['010000000000000000000000b87ccbdec9de076694253c916f68d80e8952509c']
signatures: ['a06ff58297d30407c6ee78e71a3b7a91399c5bfd23e2dc630ea489af99504aee1153dfb0da1ae06c85d8916894216be117d36a221207615fe970b6f27f306492800ddbfc2bc0c56d8e2f2e26105efa28834c375d668c7bf76fb918822474f896']
deposit_data_roots: ['377ce1b6174eb902fbc201e62568fbcbd9dfd46d58681fb602bed2428a3a934e']