Trustless ratio
Ethereum
For the transparency, we decided to apply a “proof-of-reserves” solution to the Ethereum Liquid Staking ratio. All the data is taken from the consensus layer (Beacon) and execution layer (Ethereum) at the same time. The moment of data fixation' is recorded as a slot (for Beacon; the latest at the time of calculation) and a block (from the slot’s payload).
Ratio — the proportion of ankrETH shares supply in relation to total staked ETH & new ETH that has been earned by validators (block & attestation rewards, MEV, tips).
Formula:
Given tvl = (fee_recipient + staking_pool + withdrawal_pool + clBalance - pending_withdrawal - collectable_fee), ratio = shares_supply / tvl.
Calculation example:
Given shares_supply = 27215630108443761510111 and tvl = 31789243852435000000000 - 1334272127500000000 + 279331299124130160 - 957321814817038989347 + 3273569179535509862 + 98868842000000000, ratio = shares_supply * 1e18 / tvl = 882643143435334753.
Get ratio
RESTful API endpoint that returns the historical ratio calculation.
Host
https://api.staking.ankr.com (opens in a new tab)
Endpoint
GET /v1alpha/beacon/eth/ratio/history
Request
curl https://api.staking.ankr.com/v1alpha/beacon/eth/ratio/history?page=0&size=10
Request params
page
— page number.size
— page size.
Response
200
{"content":
[
{
"ratio":"882718285663503849",
"block":"18283573",
"clBalance":"31786654231688000000000",
"collectableFee":"1073665184800000000",
"feeRecipient":"315854633781247534",
"pendingWithdrawal":"922323106280285134548",
"sharesSupply":"27246524108443761510111",
"txHash":"0x04ae51b4f38bf61c744170ce598c1da6c7b1574a42f11289c29a0d74266048bb",
"slot":7472998,
"stakingPool":"2957714545754262328",
"withdrawalPool":"82420169000000000"
}
...
Response params
content — array of data.
- ratio (wei) — the current ratio; ratio = shares_supply / (fee_recipient + staking_pool + withdrawal_pool + clBalance - pending_withdrawal - collectable_fee).
- block (uint64) — block that store the calculations.
- clBalance (wei) — sum of all the Ankr ETH validator balances on the consensus layer (Beacon Chain); see "Get deposits of validators” to understand how clBalance is calculated.
- collectableFee (wei) — total Ankr_fee across all Ankr validators.
- feeRecipient (wei) — total collected Ankr_fee.
- pendingWithdrawal (wei) — total amount of pending unstakes.
- sharesSupply (wei) — total amount of minted ankrETH.
- txHash — hash of the transaction where the ratio was published.
- slot — slot (block) to get the clBalance value from.
- stakingPool — staking pool balance.
- withdrawalPool — withdrawal pool balance.
How ratio gets validated
Data from Beacon Chain: (clBalance, collectable_fee)
The examples are using web3.py.
- Get the Ankr ETH validatorindex from Beaconcha.
You can get the validators addresses from the validator index page (opens in a new tab) and then use them in the API.
For example, for Ankr ETH Liquid Staking validators:https://beaconcha.in/api/v1/validator/eth1/0x4069d8a3de3a72eca86ca5e0a4b94619085e7362
andhttps://beaconcha.in/api/v1/validator/eth1/0x4069d8a3de3a72eca86ca5e0a4b94619085e7362
. - Get the Beacon ETH balances of the validators on a specific slot:
balance = beacon.get_validator_balances(state_id=slot)
, using the addresses of the validators from API results at Step 1.
To calculate clBalance, sum up the retrieved balance: clBalance = sum(balance). To calculate the fee: collectable_fee = sum (balance (if balance>321e18) - 321e18).
For more details, refer to the Beacon API docs (opens in a new tab) and Beaconcha API interactive docs in Swagger UI (opens in a new tab).
Data from Ethereum RPC
The examples are using web3.py.
You can obtain the parts of the ratio formula:
- Resulting ratio:
contract.functions.ratio().call(block_identifier = block_number)
from the contract0xE95A203B1a91a908F9B9CE46459d101078c2c3cb
. - sharesSupply:
contract.functions.totalSupply().call(block_identifier = block_number)
from the contract0xE95A203B1a91a908F9B9CE46459d101078c2c3cb
. - pendingWithdrawal:
contract.functions.getTotalPendingUnstakes().call(block_identifier = block_number)
from the contract0x84db6eE82b7Cf3b47E8F19270abdE5718B936670
. - withdrawalPool:
web3.eth.get_balance(account = '0x67428dE0680494E448F1A19d33C2022a51719348' , block_identifier= block_number)
. - stakingPool:
web3.eth.get_balance(account = '0x84db6eE82b7Cf3b47E8F19270abdE5718B936670' , block_identifier= block_number)
. - MEVaccount balance:
web3.eth.get_balance(account = '0x90B0c836a19A74195d45Fad2d2D3895a7a3eab08' , block_identifier= block_number)
.
Flow
Right-click on the diagram and open it in a new tab to see in a bigger size)
The flow is as follows:
Smart Contracts
The smart contracts that are shown in the diagram are:
- mev_balance (opens in a new tab) (0x90B0c836a19A74195d45Fad2d2D3895a7a3eab08) — receives all MEV & tips.
- stakingPool (opens in a new tab) (0x84db6eE82b7Cf3b47E8F19270abdE5718B936670) — ETH staking pool.
- withdrawalPool (opens in a new tab) (0x67428dE0680494E448F1A19d33C2022a51719348) — withdrawal address for Ankr validators; receives all unstakes and attestation rewards.
- treasury (opens in a new tab) (0x67428dE0680494E448F1A19d33C2022a51719348) — storage of Ankr fees.
- ankrETH (opens in a new tab) — Liquid Staking Token ankETH.
Binance
For the transparency, we decided to apply a “proof-of-reserves” solution to the Binance Liquid Staking ratio. All data is taken from the consensus layer (BNB Beacon; pendingUnstakes) and execution layer (BSC; rest of the formula vars) at the same time. The moment of data fixation is recorded as blocks for both network.
Ratio — the proportion of ankrBNB shares supply in relation to total staked BNB & new BNB that has been earned by validators (block & attestation rewards, MEV, tips).
Formula
Given availableBalance is (poolBalance - stashedAmount - flashUnstakeCollectedFee - totalPendingUnstakes) where:
- shashedAmount — amount for manual claim.
- flashUnstakeCollectedFee — the fee recieved from the swap method.
- totalPendingUnstakes — reserved amount for further distribution.
ratio = sharesSupply / (totalStaked + availableBalance + pendingUnstakeAmount)
Calculation example
Given:
- sharesSupply = 39675028034839723289963
- totalStaked = 41976176945120000000000
- availableBalance = 174722818576387031488
- pendingUnstakes = 483180222750000000000
An easy way to operate numbers as big as these is to open a terminal instance, type python3
and hit Enter, and calculate right there:
41976176945120000000000 + 174722818576387031488 + 483180222750000000000
— 42634079986446387031488.39675028034839723289963 * 10**18 // 42634079986446387031488
— 930594211190968283, precisely the ratio from our API.
Get ratio
RESTful API endpoint that returns the historical ratio calculation.
Host
https://api.staking.ankr.com (opens in a new tab)
Endpoint
GET /v1alpha/beacon/bnb/ratio/history
Request
curl https://api.staking.ankr.com/v1alpha/bnb/ratio/history?page=0&size=10
Request params
page
— page number.size
— page size.
Response
200
{"content":
[
{
"ratio":"930594211190968283",
"sharesSupply":"39675028034839723289963",
"totalStaked":"41976176945120000000000",
"availableBalance":"174722818576387031488",
"pendingUnstakes":"483180222750000000000",
"bscBlock":"35777905",
"bcBlock":"366165883",
"txHash":"0x46b2febd5429107ee4a8abe4814b498a1aeedeb27c787e72c6daf630cc8a0a04"
}
]
}
Response params
content — array of data.
- ratio (wei) — the current ratio; ratio = sharesSupply / (totalStaked + availableBalance + pendingUnstakeAmount).
- sharesSupply — total minted ankrBNB.
- totalStaked — total staked BNB.
- availableBalance — current balance available for staking; availableBalance = poolBalance - stashedAmount - flashUnstakeCollectedFee - totalPendingUnstakes).
- pendingUnstakes — pending unstakes.
- bscBlock — block we got all params to calculate the ratio from, except pendingUnstakes.
- bcBlock — block we get pendingUnstakes from.
- txHash — hash of the transaction where the ratio was published.
How ratio gets validated
Let’s break down the response:
- totalShares = ankrBNB.totalSupply()
- totalStaked = Staking.getTotalDelegated(BNBStakingPool.address)
- availableBalance = BNBStakingPool.getFreeBalance() - BNBStakingPool.getTotalPendingUnstakes()
- pendingUnstakeAmount = response.pendingUnstakes
To get the parts of the ratio formula, pull:
- Data from BNB Smart Chain RPC.
- Data from Binance API.
Data from BNB Smart Chain RPC
The examples are using web3.py.
Getting sharesSupply, availableBalance:
contract = web3.eth.contract(address='0x52F24a5e03aee338Da5fd9Df68D2b6FAe1178827', abi=abi)
sharesSupply = contract.functions.totalSupply().call(block_identifier = block_number)
contract = web3.eth.contract(address='0x9e347Af362059bf2E55839002c699F7A5BaFE86E', abi=abi)
totalStaked = contract.functions.getTotalDelegated().call(block_identifier=block_number)
poolBalance = web3.eth.get_balance(account='0x9e347Af362059bf2E55839002c699F7A5BaFE86E', block_identifier=block_number) #balance request
stashedAmount = contract.functions.getStashedForManualClaims().call(block_identifier=block_number)
flashUnstakeCollectedFee = contract.functions.getFlashUnstakeCollectedFee().call(block_identifier=block_number)
totalPendingUnstakes = contract.functions.getTotalPendingUnstakes().call(block_identifier=block_number)
availableBalance = poolBalance - stashedAmount - flashUnstakeCollectedFee - totalPendingUnstakes
Data from Binance API
Getting pendingUnstakeAmount:
url = 'https://api.binance.org/v1/staking/chains/bsc/delegators/bnb1j3xwg6zjjgwp7yjful42908zs089450827wgvm/ubds?limit=100&offset=0'
response = requests.get(url)
data = response.json()['unbondingDelegations']
To calculate pendingUnstakeAmount on specific block, you need to do SUM unstakes where specific block was between creationHeight
and completeHeight
. Check completeHeight
; if it is null
, it mean the unstake is currently still in progress.
Smart Contracts
The smart contracts that provide data for the ratio calculation are:
- BNBStakingPool (opens in a new tab) (0x9e347Af362059bf2E55839002c699F7A5BaFE86E) — BNB staking pool.
- Staking (opens in a new tab) (0x0000000000000000000000000000000000002001) — Binance system contract.
- ankrBNB (opens in a new tab) (0x52F24a5e03aee338Da5fd9Df68D2b6FAe1178827) — Liquid Staking token ankrBNB.