For Developers
RESTful API
Trustless Ratio

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.

  1. 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 and https://beaconcha.in/api/v1/validator/eth1/0x4069d8a3de3a72eca86ca5e0a4b94619085e7362.
  2. 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 contract 0xE95A203B1a91a908F9B9CE46459d101078c2c3cb.
  • sharesSupply: contract.functions.totalSupply().call(block_identifier = block_number) from the contract 0xE95A203B1a91a908F9B9CE46459d101078c2c3cb.
  • pendingWithdrawal: contract.functions.getTotalPendingUnstakes().call(block_identifier = block_number) from the contract 0x84db6eE82b7Cf3b47E8F19270abdE5718B936670.
  • 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:

ETH flow

Smart Contracts

The smart contracts that are shown in the diagram are:

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:

  1. 41976176945120000000000 + 174722818576387031488 + 483180222750000000000 — 42634079986446387031488.
  2. 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:

  1. Data from BNB Smart Chain RPC.
  2. 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: