Source code for bitcoinlib.values

# -*- coding: utf-8 -*-
#
#    BitcoinLib - Python Cryptocurrency Library
#    VALUE class - representing cryptocurrency values
#    © 2020 November - 1200 Web Development <http://1200wd.com/>
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

from bitcoinlib.networks import *
from bitcoinlib.config.config import NETWORK_DENOMINATORS


[docs]def value_to_satoshi(value, network=None): """ Convert Value object or value string to smallest denominator amount as integer :param value: Value object, value string as accepted by Value class or numeric value amount :type value: str, int, float, Value :param network: Specify network to validate value string :type network: str, Network :return int: """ if isinstance(value, str): value = Value(value) if isinstance(value, Value): if network and value.network != network: raise ValueError("Value uses different network (%s) then supplied network: %s" % (value.network.name, network)) value = value.value_sat return value
[docs]class Value: """ Class to represent and convert cryptocurrency values """
[docs] @classmethod def from_satoshi(cls, value, denominator=None, network=DEFAULT_NETWORK): """ Initialize Value class with smallest denominator as input. Such as represented in script and transactions cryptocurrency values. :param value: Amount of Satoshi's / smallest denominator for this network :type value: int :param denominator: Denominator as integer or string. Such as 0.001 or m for milli, 1000 or k for kilo, etc. See NETWORK_DENOMINATORS for list of available denominator symbols. :type denominator: int, float, str :param network: Specify network if not supplied already in the value string :type network: str, Network :return Value: """ if not isinstance(network, Network): network = Network(network) if denominator is None: denominator = network.denominator else: if isinstance(denominator, str): dens = [den for den, symb in NETWORK_DENOMINATORS.items() if symb == denominator] if dens: denominator = dens[0] value = value * (network.denominator / denominator) return cls(value or 0, denominator, network)
def __init__(self, value, denominator=None, network=DEFAULT_NETWORK): """ Create a new Value class. Specify value as integer, float or string. If a string is provided the amount, denominator and currency will be extracted if provided Examples: Initialize value class >>> Value(10) Value(value=10.00000000000000, denominator=1.00000000, network='bitcoin') >>> Value('15 mBTC') Value(value=0.01500000000000, denominator=0.00100000, network='bitcoin') >>> Value('10 sat') Value(value=0.00000010000000, denominator=0.00000001, network='bitcoin') >>> Value('1 doge') Value(value=1.00000000000000, denominator=1.00000000, network='dogecoin') >>> Value(500, 'm') Value(value=0.50000000000000, denominator=0.00100000, network='bitcoin') >>> Value(500, 0.001) Value(value=0.50000000000000, denominator=0.00100000, network='bitcoin') All frequently used arithmetic, comparision and logical operators can be used on the Value object. So you can compare Value object, add them together, divide or multiply them, etc. Values need to use the same network / currency if you work with multiple Value objects. I.e. Value('1 BTC') + Value('1 LTC') raises an error. # Examples: Value operators >>> Value('50000 sat') == Value('5000 fin') # 1 Satoshi equals 10 Finney, see https://en.bitcoin.it/wiki/Units True >>> Value('1 btc') > Value('2 btc') False >>> Value('1000 LTC') / 5 Value(value=200.00000000000000, denominator=1.00000000, network='litecoin') >>> Value('0.002 BTC') + 0.02 Value(value=0.02200000000000, denominator=1.00000000, network='bitcoin') The Value class can be represented in several formats. # Examples: Format Value class >>> int(Value("10.1 BTC")) 10 >>> float(Value("10.1 BTC")) 10.1 >>> round(Value("10.123 BTC"), 2).str() '10.12000000 BTC' >>> hex(Value("10.1 BTC")) '0x3c336080' :param value: Value as integer, float or string. Numeric values must be supllied in smallest denominator such as Satoshi's. String values must be in the format: <value> [<denominator>][<currency_symbol>] :type value: int, float, str :param denominator: Denominator as integer or string. Such as 0.001 or m for milli, 1000 or k for kilo, etc. See NETWORK_DENOMINATORS for list of available denominator symbols. :type denominator: int, float, str :param network: Specify network if not supplied already in the value string :type network: str, Network """ self.network = network if not isinstance(network, Network): self.network = Network(network) if isinstance(denominator, str): dens = [den for den, symb in NETWORK_DENOMINATORS.items() if symb == denominator] if dens: denominator = dens[0] den_arg = denominator if isinstance(value, str): value_items = value.split() value = value_items[0] cur_code = self.network.currency_code den_input = 1 if len(value_items) > 1: cur_code = value_items[1] network_names = [n for n in NETWORK_DEFINITIONS if NETWORK_DEFINITIONS[n]['currency_code'].upper() == cur_code.upper()] if network_names: self.network = Network(network_names[0]) self.currency = cur_code else: for den, symb in NETWORK_DENOMINATORS.items(): if len(symb) and cur_code[:len(symb)] == symb: cur_code = cur_code[len(symb):] network_names = [n for n in NETWORK_DEFINITIONS if NETWORK_DEFINITIONS[n]['currency_code'].upper() == cur_code.upper()] if network_names: self.network = Network(network_names[0]) self.currency = cur_code elif len(cur_code): raise ValueError("Currency symbol not recognised") den_input = den break self.value = float(value) * den_input self.denominator = den_input if den_arg is None else den_arg else: self.denominator = den_arg or 1.0 self.value = float(value) * self.denominator def __str__(self): return self.str() def __repr__(self): return "Value(value=%.14f, denominator=%.8f, network='%s')" % \ (self.value, self.denominator, self.network.name) def __int__(self): return int(self.value) def __float__(self): if self.value > self.network.denominator: return round(self.value, -int(math.log10(self.network.denominator))) else: return self.value def __lt__(self, other): if self.network != other.network: raise ValueError("Cannot compare values from different networks") return self.value < other.value def __le__(self, other): if self.network != other.network: raise ValueError("Cannot compare values from different networks") return self.value <= other.value def __eq__(self, other): if isinstance(other, Value): if self.network != other.network: raise ValueError("Cannot compare values from different networks") return self.value == other.value else: other = Value(other) return self.value == other.value and self.network == other.network def __ne__(self, other): return not self.__eq__(other) def __ge__(self, other): if self.network != other.network: raise ValueError("Cannot compare values from different networks") return self.value >= other.value def __gt__(self, other): if self.network != other.network: raise ValueError("Cannot compare values from different networks") return self.value > other.value def __add__(self, other): if isinstance(other, Value): if self.network != other.network: raise ValueError("Cannot calculate with values from different networks") other = other.value return Value((self.value + other) / self.denominator, self.denominator, self.network) def __iadd__(self, other): if isinstance(other, Value): if self.network != other.network: raise ValueError("Cannot calculate with values from different networks") other = other.value return Value((self.value + other) / self.denominator, self.denominator, self.network) def __isub__(self, other): if isinstance(other, Value): if self.network != other.network: raise ValueError("Cannot calculate with values from different networks") other = other.value return Value((self.value - other) / self.denominator, self.denominator, self.network) def __sub__(self, other): if isinstance(other, Value): if self.network != other.network: raise ValueError("Cannot calculate with values from different networks") other = other.value return Value((self.value - other) / self.denominator, self.denominator, self.network) def __mul__(self, other): return Value((self.value * other) / self.denominator, self.denominator, self.network) def __truediv__(self, other): return Value((self.value / other) / self.denominator, self.denominator, self.network) def __floordiv__(self, other): return Value(((self.value / self.denominator) // other), self.denominator, self.network) def __round__(self, n=0): val = round(self.value / self.denominator, n) * self.denominator return Value(val, self.denominator, self.network) def __index__(self): return self.value_sat
[docs] def str(self, denominator=None, decimals=None, currency_repr='code'): """ Get string representation of Value with requested denominator and number of decimals. >>> Value(1200000, 'sat').str('m') # milli Bitcoin '12.00000 mBTC' >>> Value(12000.3, 'sat').str(1) # Use denominator = 1 for Bitcoin '0.00012000 BTC' >>> Value(12000, 'sat').str('auto') '120.00 µBTC' >>> Value(0.005).str('m') '5.00000 mBTC' >>> Value(12000, 'sat').str('auto', decimals=0) '120 µBTC' >>> Value('13000000 Doge').str('auto') # Yeah, mega Dogecoins... '13.00000000 MDOGE' >>> Value('2100000000').str('auto') '2.10000000 GBTC' >>> Value('1.5 BTC').str(currency_repr='symbol') '1.50000000 ₿' >>> Value('1.5 BTC').str(currency_repr='name') '1.50000000 bitcoins' :param denominator: Denominator as integer or string. Such as 0.001 or m for milli, 1000 or k for kilo, etc. See NETWORK_DENOMINATORS for list of available denominator symbols. If not provided the default self.denominator value is used. Use value 'auto' to automatically determine best denominator for human readability. :type denominator: int, float, str :param decimals: Number of decimals to use :type decimals: float :param currency_repr: Representation of currency. I.e. code: BTC, name: bitcoins, symbol: ₿ :type currency_repr: str :return str: """ if denominator is None: denominator = self.denominator elif denominator == 'auto': # First try denominator=1 and smallest denominator (satoshi) if 0.001 <= self.value < 1000: denominator = 1 elif 1 <= self.value / self.network.denominator < 1000: denominator = self.network.denominator else: # Try other frequently used denominators for den, symb in NETWORK_DENOMINATORS.items(): if symb in ['n', 'fin', 'da', 'c', 'd', 'h']: continue if 1 <= self.value / den < 1000: denominator = den elif isinstance(denominator, str): dens = [den for den, symb in NETWORK_DENOMINATORS.items() if symb == denominator[:len(symb)] and len(symb)] if len(dens) > 1: dens = [den for den, symb in NETWORK_DENOMINATORS.items() if symb == denominator] if dens: denominator = dens[0] if denominator in NETWORK_DENOMINATORS: den_symb = NETWORK_DENOMINATORS[denominator] else: raise ValueError("Denominator not found in NETWORK_DENOMINATORS definition") if decimals is None: decimals = -int(math.log10(self.network.denominator / denominator)) if decimals > 8: decimals = 8 if decimals < 0: decimals = 0 balance = round(self.value / denominator, decimals) cur_code = self.network.currency_code if currency_repr == 'symbol': cur_code = self.network.currency_symbol if currency_repr == 'name': cur_code = self.network.currency_name_plural if 'sat' in den_symb and self.network.name == 'bitcoin': cur_code = '' return ("%%.%df %%s%%s" % decimals) % (balance, den_symb, cur_code)
[docs] def str_unit(self, decimals=None, currency_repr='code'): """ String representation of this Value. Wrapper for the :func:`str` method, but always uses 1 as denominator, meaning main denominator such as BTC, LTC. >>> Value('12000 sat').str_unit() '0.00012000 BTC' :param decimals: Number of decimals to use :type decimals: float :param currency_repr: Representation of currency. I.e. code: BTC, name: Bitcoin, symbol: ₿ :type currency_repr: str :return str: """ return self.str(1, decimals, currency_repr)
[docs] def str_auto(self, decimals=None, currency_repr='code'): """ String representation of this Value. Wrapper for the :func:`str` method, but automatically determines the denominator depending on the value. >>> Value('0.0000012 BTC').str_auto() '120 sat' >>> Value('0.0005 BTC').str_auto() '500.00 µBTC' :param decimals: Number of decimals to use :type decimals: float :param currency_repr: Representation of currency. I.e. code: BTC, name: Bitcoin, symbol: ₿ :type currency_repr: str :return str: """ return self.str('auto', decimals, currency_repr)
@property def value_sat(self): """ Value in smallest denominator, i.e. Satoshi for the Bitcoin network :return int: """ return round(self.value / self.network.denominator)
[docs] def to_bytes(self, length=8, byteorder='little'): """ Representation of value_sat (value in smallest denominator: satoshi's) as bytes string. Used for script or transaction serialization. >>> Value('1 sat').to_bytes() b'\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00' :param length: Length of bytes string to return, default is 8 bytes :type length: int :param byteorder: Order of bytes: little or big endian. Default is 'little' :type byteorder: str :return bytes: """ return self.value_sat.to_bytes(length, byteorder)
[docs] def to_hex(self, length=16, byteorder='little'): """ Representation of value_sat (value in smallest denominator: satoshi's) as hexadecimal string. >>> Value('15 sat').to_hex() '0f00000000000000' :param length: Length of hexadecimal string to return, default is 16 characters :type length: int :param byteorder: Order of bytes: little or big endian. Default is 'little' :type byteorder: str :return: """ return self.value_sat.to_bytes(length // 2, byteorder).hex()