# Choosing a set of feerates

Choosing a good set of feerates is important because they correspond to the feerates that bitpost can choose. We provide a very easy way to create this set, after setting the maximum feerate, with the GET /feerateset endpoint. Alternatively you can build your own array of feerates after setting the maximum feerate.

The code examples below use the bit library.

# Chosing feerates with /feerateset endpoint

Initially, in our tests, we were manually creating an array of feerates for each request. This approach had several issues:

  • Relaying transactions with low feerates when the payment is urgent and the mempool is already congested is unnecessary as these will never be broadcasted.
  • Relaying transactions with overly high feerates because the maximum feerate is greatly overestimated (due to the user's willingness to pay that fee or because the fee estimator itself is overestimating).
  • Using a constant spacing between feerates when there are feerate ranges that are much more likely to be needed/mined.

To solve these issues, we created an endpoint that provides a set of feerates taking into account the probability of them being needed/mined. In the next subscetions we will give examples of its use.

# Examples

The only required parameter of GET /feerateset is maxfeerate. If we want 15 feerates with a maximum feerate of 3000 sat/B we can call: curl -s https://api.bitpost.co/feerateset?maxfeerate=3000&size=15. The parameter size defaults to 50.

{
  "data": {
    "size": 15,
    "minfeerate": 1,
    "feerates": [
      1,
      13.4,
      21.6,
      28.3,
      34.4,
      41.4,
      48.7,
      56.8,
      63,
      69,
      75.1,
      80.7,
      87.5,
      99.5,
      204.4
    ],
    "maxfeerate": 204.4
  },
  "status": "success"
}

As you can observe, the maximum feerate that was returned is nowhere near 3000 sat/B. This is based on historial fee data and the current fee environment at the time of the call. For a better estimation, the target of the request which will use these feerates can also be passed (eg. https://api.bitpost.co/feerateset?maxfeerate=3000&size=20&target=1601653201).

If the request being made will be immediatly broadcasted at the time of the request (delay=0) and it doesn't spend any RBF-enabled parend transaction, the parameter canreducefee can be set to false (the default is true), so that low feerates are excluded. When canreduce=false, the target parameter is required. For example, curl -s https://api.bitpost.co/feerateset?maxfeerate=3000&size=20&canreducefee=false&target=1601653940

{
  "data": {
    "size": 15,
    "minfeerate": 81.6,
    "feerates": [
      81.6,
      82.6,
      83.6,
      84.6,
      85.6,
      86.6,
      87.6,
      88.6,
      89.6,
      90.6,
      91.6,
      92.6,
      93.6,
      107.2,
      204.4
    ],
    "maxfeerate": 204.4
  },
  "status": "success"
}

We can see that unnecessarily low feerates are not included in the feerate set because these would the likelihood of these being mined for the given target is negligible.

# Chosing feerates manually

# Adjusting the user's maximum feerate

As mentioned before, a good way to combine a user-defined maximum feerate without setting an unnecessarily large feerate is to clamp it with fast fee estimation multiplied by a safe margin (eg. 3x). To avoid situations where the mempool is relatively empty and there is no block for a long time, a minimum of 20 sat/B or more should be established. With python/bit it would look like this:

# beware it should be converted to int if you use it with bit
MAX_FEERATE = min(USER_MAX_FEERATE, max(20, bit.network.get_fee(fast=True) * 3))

# Creating an array of fees

An array of fees can be created from 1 sat/B, or a higher minimum if the transaction is urgent and the mempool is congested up to the maximum feerate chosen before. For example a linear array of fees can be created this:

DEFAULT_NUMBER_TXS = 50 # create 50 transactions with different fees
feerates = [int(feerate) for feerate in np.arange(1, MAX_FEERATE + 0.001, step=max(1, (MAX_FEERATE - 1) / (DEFAULT_NUMBER_TXS - 1)))]

# Choosing a maximum feerate

When you choose a transaction fee, you are implicitly accepting any fee that pays less - if a cheaper fee is good enough, it's better. This is why we focus on selecting a maximum fee(rate), which can be much higher than what is necessary and indicates what the user is willing to pay if there is a sudden congestion.

# Prompting the user

Choosing a maximum fee is usually straightforward and more user-friendly than choosing the actual fee. Instead of putting responsability on the user to choose an appropriate fee, the user simply needs to say how much he is willing to pay - in fiat currency or not.

We can also choose a fiat maximum and convert it into a feerate. This is a user-friendly way to do it because we are asking a value that is familiar to the user - a dollar/fiat value instead of a feerate in sats/Byte. Converting an absolute fee - denominated in fiat currency - into a feerate will require the transaction size - which can be approximated by a heuristic or obtained by measuring the size of the final transaction.

Getting an absolute fee in satoshis from fiat, can be easily done with bit:

import bit
MAX_FEE_IN_SATS = bit.network.currency_to_satoshi(5, 'usd')
# supported currencies: https://ofek.dev/bit/guide/rates.html#supported-currencies

# Converting fee to feerate

This conversion is done by diving the fee by the transaction size. Some considerations about obtaining the transaction size can be found in the coming sub-sections.

If the user has a very high tolerance for high fees, the feerate can be capped by the historial high of 2000 sat/B. Multiplying a fast estimation by a safe margin (eg. 3x) works in most cases but might be too low if the mempool is relatively empty and there is no block for a long time. As we will see in the next section, this capping step is not needed when the /feerateset endpoint is called.

# Transaction size: approximation

VIRTUAL_SIZE (bytes) = 10 + 34 * #outputs + sum(input_size)

Where the input size depends on its type:

  • uncompressed P2PKH: 180 bytes
  • P2PKH: 148 bytes
  • P2SH-P2WKH: 90 bytes
  • P2WPKH: 67 bytes
  • 2-of-3 multisig P2SH: 292 bytes
  • 2-of-3 multisig P2SH-P2WSH: 139 bytes
  • 2-of-3 multisig P2WSH: 104 bytes
  • ...

The code below shows how to do the calculation with bit:

# ... code from 'choosing-a-maximum-fee' section
from bit.transaction import select_coins

key = bit.Key.from_bytes(b'REPLACE_WITH_YOUR_RANDOM_STRING') # you need a key with balance > 0
sats_to_send = 600  # in sats

MAX_FEE_IN_SATS = bit.network.currency_to_satoshi(5, 'usd')  # $5 in sats
selected_unspents = select_coins(sats_to_send, MAX_FEE_IN_SATS, [34], 566, absolute_fee=True, unspents=key.get_unspents())

input_length = 0
for utxo in selected_unspents:
    input_length += selected_unspents.vsize

HEURISTIC_SIZE = 10 + input_length + 2 * 34  # for two outputs, adjust accordingly
# beware it should be converted to int if you use it with bit
USER_MAX_FEERATE = MAX_FEE_IN_SATS / HEURISTIC_SIZE  # sat/B

# Transaction size: exact value

Many good bitcoin libraries still provide the serialized transaction's length as the transaction size instead of the virtual size, or they use heuristics to estimate the size. If you want to be sure that the transaction size (and feerate) is correctly calculated, you need to verify it or set the fee manually. Below we demonstrate this issue with two bitcoin libraries with reasonable popularity:

from binascii import unhexlify
from bitcoinlib.transactions import Transaction
from bitcoin.core import CTransaction, CheckTransaction

# virtual size = 259 bytes
tx_hex = '01000000000102cabeeb0c1a8bc9380e358f3f3ed0ec5aea1909ffee1f358a10ca98a633ebd2b8010000001716001410deb5032f5f4a4cb979b14b2e024b319d3625d3ffffffff046f766180a63618c38c5f330e01b44e7318278d11f2b8bd28aab181f8aef3a1000000001716001410deb5032f5f4a4cb979b14b2e024b319d3625d3ffffffff0236020000000000001976a914759d6677091e973b9e9d99f19c68fbf43e3f05f988ac0f4902000000000017a91406f0c4a74f1e5ea2955b46cccc0707063f75d2de87024730440220784db2cc10d5b7881a07e5bcf9da6f5239703e10a2fb785a9b911d4c9a617c4702207126234280ad9ba9f868e3ceddae6a06bd2b98f901dadd8bee6152628f2f808301210245d4f613ee106fa16154c3bb0334da7a023c3590a9034f7f3683cecff34bbcda02483045022100b821e1c49a3bc68f979cdb081ebe0a53caad14ff7da75008d9d507b398b30ec9022018abfbe44adbc08a5f4a144890583ae19c54f8d43775a08e39c010557a2d73f001210245d4f613ee106fa16154c3bb0334da7a023c3590a9034f7f3683cecff34bbcda00000000'

# https://github.com/1200wd/bitcoinlib
Transaction.import_raw(tx_hex).estimate_size() # 232 bytes
Transaction.import_raw(tx_hex).estimate_size(add_change_output=True) # 256 bytes

# Peter Todd's python-bitcoinlib: https://github.com/petertodd/python-bitcoinlib
tx = CTransaction.deserialize(unhexlify(tx_hex)) # no method to directly calculate size
CheckTransaction(tx) # indirectly arrives at 204 bytes

WARNING

Many bitcoin libraries miscalculate the size (and feerate) by a few bytes. Most of the time, this is not a big issue, but it can lead to sub 1 sat/B transactions that are not broadcastable. The same problem occurs with transaction bumps that pay less than the additional 1 sat/B minimum.