Stake ETH using Figment and Ledger Enterprise APIs

In this tutorial, we present how you can implement a fully automated and secure Ethereum staking workflow using Figment and Ledger Enterprise APIs.

For this tutorial, we will run these operations on the Goerli test network.

Requirements

You need to have access to both Figment and Ledger Enterprise APIs, especially you need to have API keys for both.

On Figment side:

  • Your workspace must be configured so that you can create staking intent via Figment API.
  • You need an API key to be able to send requests. If you don't have a Figment API key, or your workspace is not properly configured, please contact your Figment account manager.

On Ledger Enterprise side:

  • You need to have access to your workspace's Ledger Authentication Module (LAM).
  • You need to have the necessary credentials, i.e. an API user account and corresponding API key.
  • The Ethereum account from which you will stake must have Smart Contract Interaction enabled.
  • Your API user must have authorization to create a smart contract interaction from this particular account.
  • For maximum security, you also want to whitelist the Figment staking contract address 0x94a2da805867c962148d3832d9afc512034a62c5 (Goerli contract) on the corresponding Ledger Enterprise Ethereum account.
Figment Batch Contract addresses

You can find the up-to-date addresses (on mainnet & Goerli) of Figment's Batch Contract in Figment's official documentation. Should you have any doubts, please ask your contact at Figment for confirmation.

Below, we define the LAM address and headers we will use to send API requests:

Copy
Copied
LAM = '<your_LAM_address>'

headers_ledger = {'X-Ledger-API-User': '<your_ledger_api_username>', 'X-Ledger-API-Key': '<your_ledger_api_key>'}
headers_figment = {'Authorization': 'Bearer <your_figment_api_key>'}

Process

The staking process here is twofold:

  • The user must first initiate a staking intent using the Figment API which will return the unsigned staking data to be signed by the user wallet to fund the corresponding validator.
  • The user must then sign the staking transaction using the Ledger Enterprise API to fund the validator and complete the staking operation.

Provision validators via Figment's API

Before starting the staking flow, you need to provision validators by calling the following POST endpoint: https://hubble.figment.io/api/v1/prime/eth2_staking/provision on Figment's API.

You need to specify the number of validators that you wish to stake on (max. 250 per request), as well as the withdrawal credentials (your Ledger Enteprise ETH address to which the initial deposit and rewards will be sent to during withdrawals) and the network (goerli for tests,mainnet).

Get staking intent data from Figment API

To create a staking intent, we can query the endpoint /prime/eth2_staking/funding_transactions from the Figment API.

The staking intent request must include:

  • the funding_address : the address of the wallet signing the staking transaction and funding the validator.
  • the withdrawal_address : the withdrawal authority address. An execution layer address to which the initial deposit and rewards will be sent to during withdrawals.
    • For maximum security, you want this address to be one of your Ledger Enterprise Ethereum addresses, so that your withdrawal authority is always protected by Ledger's security and governance.
  • the validators_count : the number of validator to fund. In our case we will fund a single validator with 32 ETH.
  • the eth2_network_name : the network on which the staking will take place. In this case we will stake on Goerli testnet.

For more details and more options, please refer to the official Figment documentation.

In this tutorial, we will use the following Ledger Enterprise Ethereum account/address as both our funding address and withdrawal authority:

Copy
Copied
ledger_eth_account = 'ETH-Goerli-Staking'
ledger_eth_account_address = '0x128075552e4C6dC64Bca2Cf9ca46ee688629e4CD'

Let's first check that the account holds at least 32 ETH, and enough funds to pay for the staking transaction gas fees:

Copy
Copied
import requests

r = requests.get(LAM + '/accounts', headers=headers_ledger, params={'name': ledger_eth_account})

balance = int(r.json()['edges'][0]['node']['balance']) / 1e18
print(f'balance: {balance}')

which in this case returns:

balance: 33.16732738779311.

In this example, the account holds more than 33 ETH, which is more than enough for our staking transaction.

In the example below, we create a staking intent for a single block of 32 ETH, and with withdrawal_address set as our Ledger Enterprise Ethereum account address:

Copy
Copied
staking_request = {
  'funding_address': ledger_eth_account_address,
  'withdrawal_address': ledger_eth_account_address,
  'validators_count': 1,
  'eth2_network_name': 'goerli'
}

r = requests.post('https://hubble.figment.io/api/v1/prime/eth2_staking/funding_transactions',
                  headers=headers_figment,
                  json=staking_request)

staking_intent = r.json()
print(staking_intent)

which returns the following response:

Copy
Copied
{'data': [{'id': '50485',
   'type': 'validator',
   'attributes': {'id': 50485,
    'pubkey': '956dfb1ce263163eeefc67d9aa4e7d4957a1e807a97b128a35ce68ccded88c61b7f6a7214cd4301285e83d6bdb2466b1',
    'name': None,
    'withdrawal_credentials': '010000000000000000000000128075552e4c6dc64bca2cf9ca46ee688629e4cd',
    'amount': 32000000000,
    'signature': 'aa641438bd72c2a0adb6f0762e2149c2f3ef4e4e4d4e0813494d5c032144646feb421feb574f35e769e2e4f67a0e98150285d45043c63d467c2c7c76526e8ffae21c5fa8be6ed5520e14e220652715d9d10d94f9146b3f37e180148e59cc7993',
    'deposit_data_root': 'fdd510180769ce959fe7c38a0b606577cb9b17f497a4cc722080526447fa2860',
    'deposit_message_root': '93ef83e3594dfdd1fcf5885e33485e3614245da0d34246a8a4286716b367d305',
    'deposit_cli_version': '2.5.0',
    'fork_version': '00001020',
    'eth2_network_name': 'goerli',
    'status': 'funding_requested',
    'status_synced_at': '2023-04-17T03:20:59.540Z',
    'fee_recipient_address': '0x128075552e4C6dC64Bca2Cf9ca46ee688629e4CD'},
   'relationships': {'position': {'data': {'id': '2764',
      'type': 'position'}}}}],
 'meta': {'raw_transaction': {'from': '0x128075552e4C6dC64Bca2Cf9ca46ee688629e4CD',
   'to': '0x94a2da805867c962148d3832d9afc512034a62c5',
   'value': '32000000000000000000',
   'data': '0x4f498c730000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030956dfb1ce263163eeefc67d9aa4e7d4957a1e807a97b128a35ce68ccded88c61b7f6a7214cd4301285e83d6bdb2466b100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020010000000000000000000000128075552e4c6dc64bca2cf9ca46ee688629e4cd000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060aa641438bd72c2a0adb6f0762e2149c2f3ef4e4e4d4e0813494d5c032144646feb421feb574f35e769e2e4f67a0e98150285d45043c63d467c2c7c76526e8ffae21c5fa8be6ed5520e14e220652715d9d10d94f9146b3f37e180148e59cc79930000000000000000000000000000000000000000000000000000000000000001fdd510180769ce959fe7c38a0b606577cb9b17f497a4cc722080526447fa2860'}},
 'links': {'self': 'https://hubble.figment.io/api/v1/prime/eth2_staking/funding_transactions',
  'current': 'https://hubble.figment.io/api/v1/prime/eth2_staking/funding_transactions?eth2_network_name=goerli&funding_address=0x128075552e4C6dC64Bca2Cf9ca46ee688629e4CD&funding_transaction[eth2_network_name]=goerli&funding_transaction[funding_address]=0x128075552e4C6dC64Bca2Cf9ca46ee688629e4CD&funding_transaction[validators_count]=1&funding_transaction[withdrawal_address]=0x128075552e4C6dC64Bca2Cf9ca46ee688629e4CD&page[number]=1&validators_count=1&withdrawal_address=0x128075552e4C6dC64Bca2Cf9ca46ee688629e4CD'}}

Figment API returns the staking intent data corresponding to our request.

Especially, it returns the raw_transaction data which must be signed by the funding wallet.

Sign staking transaction with Ledger Enterprise API

Second step is to sign the previously generated staking transaction data using the Ledger Enterprise API.

We first prepare the transaction to be signed, which includes the staking intent unsigned data from above:

Copy
Copied
import eth_utils

contract_address = staking_intent['meta']['raw_transaction']['to']  # Staking contract
data_to_sign = staking_intent['meta']['raw_transaction']['data'][2:]  # Staking data to be signed

tx_data = {
    'account_name': ledger_eth_account,
    'amount': int(32 * 1e18),  # 32 ETH (expressed in wei)
    'coin_fields': {
        'contract_interaction': {
            'contract_data': data_to_sign
        }
    },
    'recipient': eth_utils.to_checksum_address(contract_address),
    'speed': 'NORMAL'
}

Before creating the transaction, we can estimate the transaction fees for that particular smart contract interaction using the /transactions/fees endpoint:

Copy
Copied
r = requests.post(LAM + '/transactions/fees', headers=headers_ledger, json=tx_data)

The max_fees estimated by the previous request can be attached to the transaction object before being sent to the LAM:

Copy
Copied
tx_data['max_fees'] = r.json()['max_fees']

We then execute a POST /transactions request to sign the staking transaction and fund the validator. The response will include the corresponding transaction id from the Vault.

Copy
Copied
tx = requests.post(LAM + '/transactions', headers=headers_ledger, json=tx_data)

tx_id = tx.json()['id']

If the account governance requires multiple approvals, the transaction will need to be approved by the right quorum before it is signed and broadcasted.

In this example, the account governance is set so that the transaction does not require additional approvals to be signed and broadcasted.

Once the transaction is signed, we can get the transaction confirmation and hash from the Vault:

Copy
Copied
tx_confirmation = requests.get(LAM + f'/transactions/{tx_id}', headers=headers_ledger)

print(tx_confirmation.json())

which returns the following response:

Copy
Copied
{'account_id': 61,
 'account_index': 7,
 'amount': '32000000000000000000',
 'block': None,
 'broadcast_on': None,
 'coin_fields': {'gas_limit': '121431',
  'gas_price': '2282039876',
  'contract_interaction': {'contract_data': '4f498c730000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030956dfb1ce263163eeefc67d9aa4e7d4957a1e807a97b128a35ce68ccded88c61b7f6a7214cd4301285e83d6bdb2466b100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020010000000000000000000000128075552e4c6dc64bca2cf9ca46ee688629e4cd000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060aa641438bd72c2a0adb6f0762e2149c2f3ef4e4e4d4e0813494d5c032144646feb421feb574f35e769e2e4f67a0e98150285d45043c63d467c2c7c76526e8ffae21c5fa8be6ed5520e14e220652715d9d10d94f9146b3f37e180148e59cc79930000000000000000000000000000000000000000000000000000000000000001fdd510180769ce959fe7c38a0b606577cb9b17f497a4cc722080526447fa2860',
   'contract_name': None,
   'dapp': None,
   'function_arguments': {'deposit_data_roots': ['fdd510180769ce959fe7c38a0b606577cb9b17f497a4cc722080526447fa2860'],
    'pubkeys': ['956dfb1ce263163eeefc67d9aa4e7d4957a1e807a97b128a35ce68ccded88c61b7f6a7214cd4301285e83d6bdb2466b1'],
    'signatures': ['aa641438bd72c2a0adb6f0762e2149c2f3ef4e4e4d4e0813494d5c032144646feb421feb574f35e769e2e4f67a0e98150285d45043c63d467c2c7c76526e8ffae21c5fa8be6ed5520e14e220652715d9d10d94f9146b3f37e180148e59cc7993'],
    'withdrawal_credentials': ['010000000000000000000000128075552e4c6dc64bca2cf9ca46ee688629e4cd']},
   'function_name': 'deposit',
   'smart_contract_interaction_type': 'UNKNOWN'},
  'type': 'EthereumAndEvm'},
 'confirmations': 0,
 'created_by': 18,
 'created_on': '2023-04-17T03:21:31.315245+00:00',
 'currency': 'ethereum_goerli',
 'fees': None,
 'id': 1076,
 'interaction_type': 'UNKNOWN',
 'last_request': 881,
 'max_fees': '277184355333378',
 'metadata': None,
 'min_confirmations': 30,
 'notes': [{'content': '', 'title': ''}],
 'recipient': '0x94a2da805867c962148d3832d9afc512034a62c5',
 'senders': None,
 'speed': 'NORMAL',
 'status': 'APPROVED',
 'tx_hash': None,
 'type': 'SEND',
 'uid': None}

We can double check the transaction on chain using the transaction hash:

Copy
Copied
tx_hash = tx_confirmation.json()['tx_hash']

print(f'Tx on explorer: https://goerli.etherscan.io/tx/{tx_hash}')

Tx on explorer: https://goerli.etherscan.io/tx/0xfd9ec5d58ba475b899f8ad76841131a1f88cddc6011ff03b9d7856852ff24e0f

Stake via UI

Note that you can also perform your staking operations with Figment directly from your Ledger Enterprise UI. To learn more about Staking with Figment via UI, please refer to the tutorials in our Help Center.

Copyright © Ledger Enterprise Platform 2023. All right reserved.