faster-eth-utils 5.3.22__cp312-cp312-musllinux_1_2_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of faster-eth-utils might be problematic. Click here for more details.

Files changed (53) hide show
  1. faster_eth_utils/__init__.py +144 -0
  2. faster_eth_utils/__json/eth_networks.json +1 -0
  3. faster_eth_utils/__main__.py +5 -0
  4. faster_eth_utils/abi.cpython-312-x86_64-linux-musl.so +0 -0
  5. faster_eth_utils/abi.py +868 -0
  6. faster_eth_utils/address.cpython-312-x86_64-linux-musl.so +0 -0
  7. faster_eth_utils/address.py +138 -0
  8. faster_eth_utils/applicators.cpython-312-x86_64-linux-musl.so +0 -0
  9. faster_eth_utils/applicators.py +196 -0
  10. faster_eth_utils/conversions.cpython-312-x86_64-linux-musl.so +0 -0
  11. faster_eth_utils/conversions.py +189 -0
  12. faster_eth_utils/crypto.cpython-312-x86_64-linux-musl.so +0 -0
  13. faster_eth_utils/crypto.py +16 -0
  14. faster_eth_utils/currency.cpython-312-x86_64-linux-musl.so +0 -0
  15. faster_eth_utils/currency.py +144 -0
  16. faster_eth_utils/curried/__init__.py +297 -0
  17. faster_eth_utils/debug.cpython-312-x86_64-linux-musl.so +0 -0
  18. faster_eth_utils/debug.py +20 -0
  19. faster_eth_utils/decorators.cpython-312-x86_64-linux-musl.so +0 -0
  20. faster_eth_utils/decorators.py +115 -0
  21. faster_eth_utils/encoding.cpython-312-x86_64-linux-musl.so +0 -0
  22. faster_eth_utils/encoding.py +6 -0
  23. faster_eth_utils/exceptions.cpython-312-x86_64-linux-musl.so +0 -0
  24. faster_eth_utils/exceptions.py +11 -0
  25. faster_eth_utils/functional.cpython-312-x86_64-linux-musl.so +0 -0
  26. faster_eth_utils/functional.py +87 -0
  27. faster_eth_utils/hexadecimal.cpython-312-x86_64-linux-musl.so +0 -0
  28. faster_eth_utils/hexadecimal.py +76 -0
  29. faster_eth_utils/humanize.cpython-312-x86_64-linux-musl.so +0 -0
  30. faster_eth_utils/humanize.py +201 -0
  31. faster_eth_utils/logging.py +152 -0
  32. faster_eth_utils/module_loading.cpython-312-x86_64-linux-musl.so +0 -0
  33. faster_eth_utils/module_loading.py +31 -0
  34. faster_eth_utils/network.cpython-312-x86_64-linux-musl.so +0 -0
  35. faster_eth_utils/network.py +91 -0
  36. faster_eth_utils/numeric.cpython-312-x86_64-linux-musl.so +0 -0
  37. faster_eth_utils/numeric.py +43 -0
  38. faster_eth_utils/py.typed +0 -0
  39. faster_eth_utils/pydantic.py +103 -0
  40. faster_eth_utils/toolz.cpython-312-x86_64-linux-musl.so +0 -0
  41. faster_eth_utils/toolz.py +84 -0
  42. faster_eth_utils/types.cpython-312-x86_64-linux-musl.so +0 -0
  43. faster_eth_utils/types.py +65 -0
  44. faster_eth_utils/typing/__init__.py +18 -0
  45. faster_eth_utils/typing/misc.py +14 -0
  46. faster_eth_utils/units.cpython-312-x86_64-linux-musl.so +0 -0
  47. faster_eth_utils/units.py +31 -0
  48. faster_eth_utils-5.3.22.dist-info/METADATA +192 -0
  49. faster_eth_utils-5.3.22.dist-info/RECORD +53 -0
  50. faster_eth_utils-5.3.22.dist-info/WHEEL +5 -0
  51. faster_eth_utils-5.3.22.dist-info/licenses/LICENSE +21 -0
  52. faster_eth_utils-5.3.22.dist-info/top_level.txt +3 -0
  53. faster_eth_utils__mypyc.cpython-312-x86_64-linux-musl.so +0 -0
@@ -0,0 +1,138 @@
1
+ import re
2
+ from typing import (
3
+ Any,
4
+ Final,
5
+ TypeGuard,
6
+ )
7
+
8
+ import cchecksum
9
+ from eth_typing import (
10
+ Address,
11
+ AnyAddress,
12
+ ChecksumAddress,
13
+ HexAddress,
14
+ HexStr,
15
+ )
16
+
17
+ from .conversions import (
18
+ hexstr_if_str,
19
+ to_hex,
20
+ )
21
+ from .hexadecimal import (
22
+ decode_hex,
23
+ remove_0x_prefix,
24
+ )
25
+ from .types import (
26
+ is_bytes,
27
+ is_text,
28
+ )
29
+
30
+ _HEX_ADDRESS_REGEXP: Final = re.compile("(0x)?[0-9a-f]{40}", re.IGNORECASE | re.ASCII)
31
+
32
+
33
+ to_checksum_address: Final = cchecksum.to_checksum_address
34
+ """
35
+ Makes a checksum address given a supported format.
36
+ We use the `cchecksum` implementation for max speed.
37
+ """
38
+
39
+
40
+ def is_hex_address(value: Any) -> TypeGuard[HexAddress]:
41
+ """
42
+ Checks if the given string of text type is an address in hexadecimal encoded form.
43
+ """
44
+ if not is_text(value):
45
+ return False
46
+ return _HEX_ADDRESS_REGEXP.fullmatch(value) is not None
47
+
48
+
49
+ def is_binary_address(value: Any) -> TypeGuard[bytes]:
50
+ """
51
+ Checks if the given string is an address in raw bytes form.
52
+ """
53
+ if not is_bytes(value):
54
+ return False
55
+ elif len(value) != 20:
56
+ return False
57
+ else:
58
+ return True
59
+
60
+
61
+ def is_address(value: Any) -> bool:
62
+ """
63
+ Is the given string an address in any of the known formats?
64
+ """
65
+ if is_hex_address(value) or is_binary_address(value):
66
+ return True
67
+
68
+ return False
69
+
70
+
71
+ def to_normalized_address(value: AnyAddress | str | bytes) -> HexAddress:
72
+ """
73
+ Converts an address to its normalized hexadecimal representation.
74
+ """
75
+ try:
76
+ hex_address = hexstr_if_str(to_hex, value).lower()
77
+ except AttributeError:
78
+ raise TypeError(f"Value must be any string, instead got type {type(value)}")
79
+ if is_address(hex_address):
80
+ return HexAddress(HexStr(hex_address))
81
+ else:
82
+ raise ValueError(
83
+ f"Unknown format {repr(value)}, attempted to normalize to "
84
+ f"{repr(hex_address)}"
85
+ )
86
+
87
+
88
+ def is_normalized_address(value: Any) -> TypeGuard[HexAddress]:
89
+ """
90
+ Returns whether the provided value is an address in its normalized form.
91
+ """
92
+ return is_address(value) and value == to_normalized_address(value)
93
+
94
+
95
+ def to_canonical_address(address: AnyAddress | str | bytes) -> Address:
96
+ """
97
+ Convert a valid address to its canonical form (20-length bytes).
98
+ """
99
+ return Address(decode_hex(to_normalized_address(address)))
100
+
101
+
102
+ def is_canonical_address(address: Any) -> TypeGuard[Address]:
103
+ """
104
+ Returns `True` if the `value` is an address in its canonical form.
105
+ """
106
+ return is_binary_address(address) and address == to_canonical_address(address)
107
+ 8
108
+
109
+ def is_same_address(
110
+ left: AnyAddress | str | bytes, right: AnyAddress | str | bytes
111
+ ) -> bool:
112
+ """
113
+ Checks if both addresses are same or not.
114
+ """
115
+ if not is_address(left) or not is_address(right):
116
+ raise ValueError("Both values must be valid addresses")
117
+ if left == right:
118
+ return True
119
+ return to_normalized_address(left) == to_normalized_address(right)
120
+
121
+
122
+ def is_checksum_address(value: Any) -> TypeGuard[ChecksumAddress]:
123
+ if not is_hex_address(value):
124
+ return False
125
+ return value == to_checksum_address(value)
126
+
127
+
128
+ def _is_checksum_formatted(value: Any) -> bool:
129
+ unprefixed_value = remove_0x_prefix(value)
130
+ return (
131
+ not unprefixed_value.islower()
132
+ and not unprefixed_value.isupper()
133
+ and not unprefixed_value.isnumeric()
134
+ )
135
+
136
+
137
+ def is_checksum_formatted_address(value: Any) -> bool:
138
+ return is_hex_address(value) and _is_checksum_formatted(value)
@@ -0,0 +1,196 @@
1
+ from collections.abc import Callable, Generator, Mapping, Sequence
2
+ from typing import (
3
+ TYPE_CHECKING,
4
+ Any,
5
+ TypeGuard,
6
+ TypeVar,
7
+ cast,
8
+ overload,
9
+ )
10
+
11
+ import warnings
12
+
13
+ from .decorators import (
14
+ return_arg_type,
15
+ )
16
+ from .pydantic import (
17
+ CamelModel,
18
+ )
19
+ from .toolz import (
20
+ compose,
21
+ curry,
22
+ )
23
+
24
+ if TYPE_CHECKING:
25
+ from _typeshed import SupportsBool
26
+ # We have to sacrifice a little bit of specificity on dinosaur Python3.8
27
+
28
+ TArg = TypeVar("TArg")
29
+ TReturn = TypeVar("TReturn")
30
+ TOther = TypeVar("TOther")
31
+
32
+ Formatters = Callable[[list[Any]], list[Any]]
33
+
34
+
35
+ @return_arg_type(2)
36
+ def apply_formatter_at_index(
37
+ formatter: Callable[[TArg], TReturn],
38
+ at_index: int,
39
+ value: Sequence[TArg | TOther],
40
+ ) -> Generator[TOther | TReturn, None, None]:
41
+ try:
42
+ item = value[at_index]
43
+ except IndexError:
44
+ raise IndexError(
45
+ f"Not enough values in iterable to apply formatter. Got: {len(value)}. "
46
+ f"Need: {at_index + 1}"
47
+ ) from None
48
+
49
+ yield from cast(Sequence[TOther], value[:at_index])
50
+ yield formatter(cast(TArg, item))
51
+ yield from cast(Sequence[TOther], value[at_index + 1 :])
52
+
53
+
54
+ def combine_argument_formatters(*formatters: Callable[..., Any]) -> Formatters:
55
+ warnings.warn(
56
+ DeprecationWarning(
57
+ "combine_argument_formatters(formatter1, formatter2)([item1, item2])"
58
+ "has been deprecated and will be removed in a subsequent major version "
59
+ "release of the eth-utils library. Update your calls to use "
60
+ "apply_formatters_to_sequence([formatter1, formatter2], [item1, item2]) "
61
+ "instead."
62
+ ),
63
+ stacklevel=2,
64
+ )
65
+
66
+ _formatter_at_index = curry(apply_formatter_at_index)
67
+ return compose( # type: ignore [no-any-return]
68
+ *(
69
+ _formatter_at_index(formatter, index)
70
+ for index, formatter in enumerate(formatters)
71
+ )
72
+ )
73
+
74
+
75
+ @return_arg_type(1)
76
+ def apply_formatters_to_sequence(
77
+ formatters: list[Callable[[Any], TReturn]], sequence: Sequence[Any]
78
+ ) -> Generator[TReturn, None, None]:
79
+ num_formatters = len(formatters)
80
+ num_items = len(sequence)
81
+ if num_formatters == num_items:
82
+ for formatter, item in zip(formatters, sequence):
83
+ yield formatter(item)
84
+ elif num_formatters > num_items:
85
+ raise IndexError(
86
+ f"Too many formatters for sequence: {num_formatters} formatters for "
87
+ f"{sequence!r}"
88
+ )
89
+ else:
90
+ raise IndexError(
91
+ f"Too few formatters for sequence: {num_formatters} formatters for "
92
+ f"{sequence!r}"
93
+ )
94
+
95
+
96
+ @overload
97
+ def apply_formatter_if(
98
+ condition: Callable[[TArg], TypeGuard[TOther]],
99
+ formatter: Callable[[TOther], TReturn],
100
+ value: TArg,
101
+ ) -> TArg | TReturn: ...
102
+
103
+
104
+ @overload
105
+ def apply_formatter_if(
106
+ condition: Callable[[TArg], bool], formatter: Callable[[TArg], TReturn], value: TArg
107
+ ) -> TArg | TReturn: ...
108
+
109
+
110
+ def apply_formatter_if( # type: ignore [misc]
111
+ condition: Callable[[TArg], TypeGuard[TOther]] | Callable[[TArg], bool],
112
+ formatter: Callable[[TOther], TReturn] | Callable[[TArg], TReturn],
113
+ value: TArg,
114
+ ) -> TArg | TReturn:
115
+ if condition(value):
116
+ return formatter(value) # type: ignore [arg-type]
117
+ else:
118
+ return value
119
+
120
+
121
+ def apply_formatters_to_dict(
122
+ formatters: dict[Any, Any],
123
+ value: dict[Any, Any] | CamelModel,
124
+ unaliased: bool = False,
125
+ ) -> dict[Any, Any]:
126
+ """
127
+ Apply formatters to a dictionary of values. If the value is a pydantic model,
128
+ it will be serialized to a dictionary first, taking into account the
129
+ ``unaliased`` parameter.
130
+
131
+ :param formatters: The formatters to apply to the dictionary.
132
+ :param value: The dictionary-like object to apply the formatters to.
133
+ :param unaliased: If the model is a ``CamelModel``, whether to turn off
134
+ serialization by alias (camelCase).
135
+ :return: A generator that yields the formatted key-value pairs.
136
+ """
137
+ if isinstance(value, CamelModel):
138
+ value = value.model_dump(by_alias=not unaliased)
139
+
140
+ def get_value(key: Any, val: Any) -> Any:
141
+ if key not in formatters:
142
+ return val
143
+ try:
144
+ return formatters[key](val)
145
+ except ValueError as exc:
146
+ raise ValueError(
147
+ f"Could not format invalid value {val!r} as field {key!r}"
148
+ ) from exc
149
+ except TypeError as exc:
150
+ raise TypeError(
151
+ f"Could not format invalid type {val!r} as field {key!r}"
152
+ ) from exc
153
+
154
+ return {key: get_value(key, val) for key, val in value.items()}
155
+
156
+
157
+ @return_arg_type(1)
158
+ def apply_formatter_to_array(
159
+ formatter: Callable[[TArg], TReturn], value: Sequence[TArg]
160
+ ) -> Generator[TReturn, None, None]:
161
+ for item in value:
162
+ yield formatter(item)
163
+
164
+
165
+ def apply_one_of_formatters(
166
+ formatter_condition_pairs: tuple[
167
+ tuple[Callable[[TArg], "SupportsBool"], Callable[[TArg], TReturn]], ...
168
+ ],
169
+ value: TArg,
170
+ ) -> TReturn:
171
+ for condition, formatter in formatter_condition_pairs:
172
+ if condition(value):
173
+ return formatter(value)
174
+ else:
175
+ raise ValueError(
176
+ "The provided value did not satisfy any of the formatter conditions"
177
+ )
178
+
179
+
180
+ def apply_key_map(
181
+ key_mappings: dict[Any, Any], value: Mapping[Any, Any]
182
+ ) -> dict[Any, Any]:
183
+ key_conflicts = (
184
+ set(value.keys())
185
+ .difference(key_mappings.keys())
186
+ .intersection(v for k, v in key_mappings.items() if v in value)
187
+ )
188
+ if key_conflicts:
189
+ raise KeyError(
190
+ f"Could not apply key map due to conflicting key(s): {key_conflicts}"
191
+ )
192
+
193
+ def get_key(key: Any) -> Any:
194
+ return key_mappings[key] if key in key_mappings else key
195
+
196
+ return {get_key(key): item for key, item in value.items()}
@@ -0,0 +1,189 @@
1
+ from typing import (
2
+ TypeVar,
3
+ Union,
4
+ )
5
+ from collections.abc import Callable
6
+
7
+ from eth_typing import (
8
+ HexStr,
9
+ Primitives,
10
+ )
11
+
12
+ from .encoding import (
13
+ big_endian_to_int,
14
+ int_to_big_endian,
15
+ )
16
+ from .hexadecimal import (
17
+ add_0x_prefix,
18
+ decode_hex,
19
+ encode_hex,
20
+ is_hexstr,
21
+ remove_0x_prefix,
22
+ )
23
+ from .types import (
24
+ is_string,
25
+ )
26
+
27
+ T = TypeVar("T")
28
+
29
+ BytesLike = Union[Primitives, bytearray, memoryview]
30
+
31
+
32
+ def to_hex(
33
+ primitive: BytesLike | None = None,
34
+ hexstr: HexStr | None = None,
35
+ text: str | None = None,
36
+ ) -> HexStr:
37
+ """
38
+ Auto converts any supported value into its hex representation.
39
+ Trims leading zeros, as defined in:
40
+ https://github.com/ethereum/wiki/wiki/JSON-RPC#hex-value-encoding
41
+ """
42
+ if hexstr is not None:
43
+ return add_0x_prefix(HexStr(hexstr.lower()))
44
+
45
+ if text is not None:
46
+ return encode_hex(text.encode("utf-8"))
47
+
48
+ if isinstance(primitive, bool):
49
+ return HexStr("0x1") if primitive else HexStr("0x0")
50
+
51
+ if isinstance(primitive, (bytes, bytearray)):
52
+ return encode_hex(primitive)
53
+
54
+ if isinstance(primitive, memoryview):
55
+ return encode_hex(bytes(primitive))
56
+
57
+ elif is_string(primitive):
58
+ raise TypeError(
59
+ "Unsupported type: The primitive argument must be one of: bytes,"
60
+ "bytearray, int or bool and not str"
61
+ )
62
+
63
+ if isinstance(primitive, int):
64
+ return HexStr(hex(primitive))
65
+
66
+ raise TypeError(
67
+ f"Unsupported type: '{repr(type(primitive))}'. Must be one of: bool, str, "
68
+ "bytes, bytearray or int."
69
+ )
70
+
71
+
72
+ def to_int(
73
+ primitive: BytesLike | None = None,
74
+ hexstr: HexStr | None = None,
75
+ text: str | None = None,
76
+ ) -> int:
77
+ """
78
+ Converts value to its integer representation.
79
+ Values are converted this way:
80
+
81
+ * primitive:
82
+
83
+ * bytes, bytearray, memoryview: big-endian integer
84
+ * bool: True => 1, False => 0
85
+ * int: unchanged
86
+ * hexstr: interpret hex as integer
87
+ * text: interpret as string of digits, like '12' => 12
88
+ """
89
+ if hexstr is not None:
90
+ return int(hexstr, 16)
91
+ elif text is not None:
92
+ return int(text)
93
+ elif isinstance(primitive, (bytes, bytearray)):
94
+ return big_endian_to_int(primitive)
95
+ elif isinstance(primitive, memoryview):
96
+ return big_endian_to_int(bytes(primitive))
97
+ elif isinstance(primitive, str):
98
+ raise TypeError("Pass in strings with keyword hexstr or text")
99
+ elif isinstance(primitive, (int, bool)):
100
+ return int(primitive)
101
+ else:
102
+ raise TypeError(
103
+ "Invalid type. Expected one of int/bool/str/bytes/bytearray. Got "
104
+ f"{type(primitive)}"
105
+ )
106
+
107
+
108
+ def to_bytes(
109
+ primitive: BytesLike | None = None,
110
+ hexstr: HexStr | None = None,
111
+ text: str | None = None,
112
+ ) -> bytes:
113
+ if isinstance(primitive, bool):
114
+ return b"\x01" if primitive else b"\x00"
115
+ elif isinstance(primitive, (bytearray, memoryview)):
116
+ return bytes(primitive)
117
+ elif isinstance(primitive, bytes):
118
+ return primitive
119
+ elif isinstance(primitive, int):
120
+ return int_to_big_endian(primitive)
121
+ elif hexstr is not None:
122
+ if len(hexstr) % 2:
123
+ hexstr = HexStr(f"0x0{remove_0x_prefix(hexstr)}")
124
+ return decode_hex(hexstr)
125
+ elif text is not None:
126
+ return text.encode("utf-8")
127
+ raise TypeError(
128
+ "expected a bool, int, byte or bytearray in first arg, "
129
+ "or keyword of hexstr or text"
130
+ )
131
+
132
+
133
+ def to_text(
134
+ primitive: BytesLike | None = None,
135
+ hexstr: HexStr | None = None,
136
+ text: str | None = None,
137
+ ) -> str:
138
+ if hexstr is not None:
139
+ return to_bytes(hexstr=hexstr).decode("utf-8")
140
+ elif text is not None:
141
+ return text
142
+ elif isinstance(primitive, str):
143
+ return to_text(hexstr=primitive)
144
+ elif isinstance(primitive, (bytes, bytearray)):
145
+ return primitive.decode("utf-8")
146
+ elif isinstance(primitive, memoryview):
147
+ return bytes(primitive).decode("utf-8")
148
+ elif isinstance(primitive, int):
149
+ return int_to_big_endian(primitive).decode("utf-8")
150
+ raise TypeError("Expected an int, bytes, bytearray or hexstr.")
151
+
152
+
153
+ def text_if_str(
154
+ to_type: Callable[..., T], text_or_primitive: bytes | int | str
155
+ ) -> T:
156
+ """
157
+ Convert to a type, assuming that strings can be only unicode text (not a hexstr).
158
+
159
+ :param to_type function: takes the arguments (primitive, hexstr=hexstr, text=text),
160
+ eg~ to_bytes, to_text, to_hex, to_int, etc
161
+ :param text_or_primitive bytes, str, int: value to convert
162
+ """
163
+ if isinstance(text_or_primitive, str):
164
+ return to_type(text=text_or_primitive)
165
+ else:
166
+ return to_type(text_or_primitive)
167
+
168
+
169
+ def hexstr_if_str(
170
+ to_type: Callable[..., T], hexstr_or_primitive: bytes | int | str
171
+ ) -> T:
172
+ """
173
+ Convert to a type, assuming that strings can be only hexstr (not unicode text).
174
+
175
+ :param to_type function: takes the arguments (primitive, hexstr=hexstr, text=text),
176
+ eg~ to_bytes, to_text, to_hex, to_int, etc
177
+ :param hexstr_or_primitive bytes, str, int: value to convert
178
+ """
179
+ if isinstance(hexstr_or_primitive, str):
180
+ if remove_0x_prefix(HexStr(hexstr_or_primitive)) and not is_hexstr(
181
+ hexstr_or_primitive
182
+ ):
183
+ raise ValueError(
184
+ "when sending a str, it must be a hex string. "
185
+ f"Got: {repr(hexstr_or_primitive)}"
186
+ )
187
+ return to_type(hexstr=hexstr_or_primitive)
188
+ else:
189
+ return to_type(hexstr_or_primitive)
@@ -0,0 +1,16 @@
1
+ from eth_hash.auto import (
2
+ keccak as keccak_256,
3
+ )
4
+ from eth_typing import HexStr
5
+
6
+ from .conversions import (
7
+ to_bytes,
8
+ )
9
+
10
+
11
+ def keccak(
12
+ primitive: bytes | int | bool | None = None,
13
+ hexstr: HexStr | None = None,
14
+ text: str | None = None,
15
+ ) -> bytes:
16
+ return bytes(keccak_256(to_bytes(primitive, hexstr, text)))
@@ -0,0 +1,144 @@
1
+ import decimal
2
+ from decimal import (
3
+ localcontext,
4
+ )
5
+ from typing import (
6
+ Final,
7
+ Union,
8
+ final,
9
+ )
10
+
11
+ from .types import (
12
+ is_integer,
13
+ is_string,
14
+ )
15
+ from .units import (
16
+ units,
17
+ )
18
+
19
+
20
+ @final
21
+ class denoms:
22
+ wei: Final = int(units["wei"])
23
+ kwei: Final = int(units["kwei"])
24
+ babbage: Final = int(units["babbage"])
25
+ femtoether: Final = int(units["femtoether"])
26
+ mwei: Final = int(units["mwei"])
27
+ lovelace: Final = int(units["lovelace"])
28
+ picoether: Final = int(units["picoether"])
29
+ gwei: Final = int(units["gwei"])
30
+ shannon: Final = int(units["shannon"])
31
+ nanoether: Final = int(units["nanoether"])
32
+ nano: Final = int(units["nano"])
33
+ szabo: Final = int(units["szabo"])
34
+ microether: Final = int(units["microether"])
35
+ micro: Final = int(units["micro"])
36
+ finney: Final = int(units["finney"])
37
+ milliether: Final = int(units["milliether"])
38
+ milli: Final = int(units["milli"])
39
+ ether: Final = int(units["ether"])
40
+ kether: Final = int(units["kether"])
41
+ grand: Final = int(units["grand"])
42
+ mether: Final = int(units["mether"])
43
+ gether: Final = int(units["gether"])
44
+ tether: Final = int(units["tether"])
45
+
46
+
47
+ MIN_WEI: Final = 0
48
+ MAX_WEI: Final = 2**256 - 1
49
+ DECIMAL_ZERO: Final = decimal.Decimal(0)
50
+
51
+ _NumberType = Union[int, float, str, decimal.Decimal]
52
+
53
+
54
+ def _from_wei(number: int, unit_value: decimal.Decimal) -> int | decimal.Decimal:
55
+ if number == 0:
56
+ return 0
57
+
58
+ if number < MIN_WEI or number > MAX_WEI:
59
+ raise ValueError("value must be between 0 and 2**256 - 1")
60
+
61
+ with localcontext() as ctx:
62
+ ctx.prec = 999
63
+ d_number = decimal.Decimal(value=number, context=ctx)
64
+ result_value = d_number / unit_value
65
+
66
+ return result_value
67
+
68
+
69
+ def _to_wei(number: _NumberType, unit_value: decimal.Decimal) -> int:
70
+ if is_integer(number) or is_string(number):
71
+ d_number = decimal.Decimal(value=number) # type: ignore [arg-type]
72
+ elif isinstance(number, float):
73
+ d_number = decimal.Decimal(value=str(number))
74
+ elif isinstance(number, decimal.Decimal):
75
+ d_number = number
76
+ else:
77
+ raise TypeError("Unsupported type. Must be one of integer, float, or string")
78
+
79
+ if d_number == DECIMAL_ZERO:
80
+ return 0
81
+
82
+ s_number = str(number)
83
+
84
+ if d_number < 1 and "." in s_number:
85
+ with localcontext() as ctx:
86
+ multiplier = len(s_number) - s_number.index(".") - 1
87
+ ctx.prec = multiplier
88
+ d_number = decimal.Decimal(value=number, context=ctx) * 10**multiplier # type: ignore [arg-type]
89
+ unit_value /= 10**multiplier
90
+
91
+ with localcontext() as ctx:
92
+ ctx.prec = 999
93
+ result_value = decimal.Decimal(value=d_number, context=ctx) * unit_value
94
+
95
+ if result_value < MIN_WEI or result_value > MAX_WEI:
96
+ raise ValueError("Resulting wei value must be between 0 and 2**256 - 1")
97
+
98
+ return int(result_value)
99
+
100
+
101
+ def from_wei(number: int, unit: str) -> int | decimal.Decimal:
102
+ """
103
+ Takes a number of wei and converts it to any other ether unit.
104
+ """
105
+ unit_key = unit.lower()
106
+ if unit_key not in units:
107
+ raise ValueError(f"Unknown unit. Must be one of {'/'.join(units.keys())}")
108
+
109
+ unit_value = units[unit_key]
110
+
111
+ return _from_wei(number, unit_value)
112
+
113
+
114
+ def to_wei(number: _NumberType, unit: str) -> int:
115
+ """
116
+ Takes a number of a unit and converts it to wei.
117
+ """
118
+ unit_key = unit.lower()
119
+ if unit_key not in units:
120
+ raise ValueError(f"Unknown unit. Must be one of {'/'.join(units.keys())}")
121
+
122
+ unit_value = units[unit_key]
123
+
124
+ return _to_wei(number, unit_value)
125
+
126
+
127
+ def from_wei_decimals(number: int, decimals: int) -> int | decimal.Decimal:
128
+ """
129
+ Takes a number of wei and converts it to a decimal with the specified
130
+ number of decimals.
131
+ """
132
+ unit_value = decimal.Decimal(10) ** decimal.Decimal(value=decimals)
133
+
134
+ return _from_wei(number, unit_value)
135
+
136
+
137
+ def to_wei_decimals(number: _NumberType, decimals: int) -> int:
138
+ """
139
+ Takes a number of a unit and converts it to wei with the specified
140
+ number of decimals.
141
+ """
142
+ unit_value = decimal.Decimal(10) ** decimal.Decimal(value=decimals)
143
+
144
+ return _to_wei(number, unit_value)