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:
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:
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:
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:
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:
{'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:
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:
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:
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.
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:
tx_confirmation = requests.get(LAM + f'/transactions/{tx_id}', headers=headers_ledger)
print(tx_confirmation.json())
which returns the following response:
{'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:
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.