Files
basicswap/basicswap/util/__init__.py
T
tecnovert d92fa0c61d Change default key derivation paths.
To allow account keys to be imported into electrum.
Only applies when using descriptor wallets.
To match keys from legacy (sethdseed) wallets set the {COIN}_USE_LEGACY_KEY_PATHS environment variable before prepare.py.
2025-07-26 01:54:34 +02:00

235 lines
5.2 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (c) 2018-2023 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import json
import time
import decimal
COIN = 100000000
decimal_ctx = decimal.Context()
decimal_ctx.prec = 20
class TemporaryError(ValueError):
pass
class AutomationConstraint(ValueError):
pass
class AutomationConstraintTemporary(ValueError):
pass
class InactiveCoin(Exception):
def __init__(self, coinid):
self.coinid = coinid
def __str__(self):
return str(self.coinid)
class LockedCoinError(Exception):
def __init__(self, coinid):
self.coinid = coinid
def __str__(self):
return "Coin must be unlocked: " + str(self.coinid)
def ensure(v, err_string):
if not v:
raise ValueError(err_string)
def toBool(s) -> bool:
if isinstance(s, bool):
return s
if isinstance(s, int):
return False if s == 0 else True
return s.lower() in ["1", "true"]
def jsonDecimal(obj):
if isinstance(obj, decimal.Decimal):
return str(obj)
raise TypeError
def dumpj(jin, indent=4):
return json.dumps(jin, indent=indent, default=jsonDecimal)
def dumpje(jin):
return json.dumps(jin, default=jsonDecimal).replace('"', '\\"')
def SerialiseNum(n: int) -> bytes:
# For script
if n == 0:
return bytes((0x00,))
if n > 0 and n <= 16:
return bytes((0x50 + n,))
rv = bytearray()
neg = n < 0
absvalue = -n if neg else n
while absvalue:
rv.append(absvalue & 0xFF)
absvalue >>= 8
if rv[-1] & 0x80:
rv.append(0x80 if neg else 0)
elif neg:
rv[-1] |= 0x80
return bytes((len(rv),)) + rv
def DeserialiseNum(b: bytes, o: int = 0) -> int:
# For script
if b[o] == 0:
return 0
if b[o] > 0x50 and b[o] <= 0x50 + 16:
return b[o] - 0x50
v = 0
nb = b[o]
o += 1
for i in range(0, nb):
v |= b[o + i] << (8 * i)
# If the input vector's most significant byte is 0x80, remove it from the result's msb and return a negative.
if b[o + nb - 1] & 0x80:
return -(v & ~(0x80 << (8 * (nb - 1))))
return v
def float_to_str(f: float) -> str:
# stackoverflow.com/questions/38847690
d1 = decimal_ctx.create_decimal(repr(f))
return format(d1, "f")
def make_int(
v, scale: int = 8, r: int = 0
) -> int: # r = 0, no rounding (fail), r > 0 round off, r < 0 floor
if isinstance(v, float):
v = float_to_str(v)
elif isinstance(v, int):
return v * 10**scale
sign = 1
if v[0] == "-":
v = v[1:]
sign = -1
ep = 10**scale
have_dp = False
rv = 0
for c in v:
if c == ".":
rv *= ep
have_dp = True
continue
if not c.isdigit():
raise ValueError("Invalid char: " + c)
if have_dp:
ep //= 10
if ep <= 0:
if r == 0:
raise ValueError("Mantissa too long")
if r > 0:
# Round off
if int(c) > 4:
rv += 1
break
rv += ep * int(c)
else:
rv = rv * 10 + int(c)
if not have_dp:
rv *= ep
return rv * sign
def validate_amount(amount, scale: int = 8) -> bool:
str_amount = float_to_str(amount) if isinstance(amount, float) else str(amount)
has_decimal = False
for c in str_amount:
if c == "." and not has_decimal:
has_decimal = True
continue
if not c.isdigit():
raise ValueError("Invalid amount")
ar = str_amount.split(".")
if len(ar) > 1 and len(ar[1]) > scale:
raise ValueError("Too many decimal places in amount {}".format(str_amount))
return True
def format_amount(i: int, display_scale: int, scale: int = None) -> str:
if not isinstance(i, int):
raise ValueError(
"Amount must be an integer."
) # Raise error instead of converting as amounts should always be integers
if scale is None:
scale = display_scale
ep = 10**scale
n = abs(i)
quotient = n // ep
remainder = n % ep
if display_scale != scale:
remainder %= 10**display_scale
rv = "{}.{:0>{scale}}".format(quotient, remainder, scale=display_scale)
if i < 0:
rv = "-" + rv
return rv
def format_timestamp(value: int, with_seconds: bool = False) -> str:
str_format = "%Y-%m-%d %H:%M"
if with_seconds:
str_format += ":%S"
str_format += " %z"
return time.strftime(str_format, time.localtime(value))
def b2i(b: bytes) -> int:
# bytes32ToInt
return int.from_bytes(b, byteorder="big")
def i2b(i: int) -> bytes:
# intToBytes32
return i.to_bytes(32, byteorder="big")
def b2h(b: bytes) -> str:
return b.hex()
def h2b(h: str) -> bytes:
if h.startswith("0x"):
h = h[2:]
return bytes.fromhex(h)
def i2h(x: int) -> str:
return b2h(i2b(x))
def zeroIfNone(value) -> int:
if value is None:
return 0
return value
def hex_or_none(value: bytes) -> str:
if value is None:
return "None"
return value.hex()