diff --git a/.pylintrc b/.pylintrc index 844d4cc..514a0f1 100644 --- a/.pylintrc +++ b/.pylintrc @@ -143,7 +143,6 @@ overgeneral-exceptions=builtins.BaseException,builtins.Exception # Ignore missing members accessed in mocking ignored-modules= cryptnox_sdk_py, - lazy_import, appdirs, tabulate, ecdsa, @@ -153,11 +152,8 @@ ignored-modules= hexbytes, web3, requests, - boto3, - botocore, cryptography, winsdk, - stdiomask, smartcard, Cryptodome # List of classes names for which member attributes should not be checked diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6e10f95..d13d59c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,18 @@ Changelog ========= +Unreleased +---------- + +Changed +^^^^^^^ +- Masked PIN, PUK and passphrase input on Linux and macOS is handled by the CLI itself; the ``stdiomask`` dependency is removed +- Ethereum support is loaded only by the commands that use it, so commands such as ``history`` and ``info`` start noticeably faster; the ``lazy-import`` dependency is removed + +Fixed +^^^^^ +- Windows: PIN, PUK and BIP39 passphrase entry now accepts the full Unicode range. Characters outside ASCII were previously discarded as they were typed, so a passphrase containing them was not the one the user entered. Arrow and function keys are ignored instead of adding characters to the entry, and Ctrl+C cancels the prompt again + `1.0.5 `_ ------------------------------------------------------------------------------------------------ diff --git a/Pipfile b/Pipfile index acdefdf..4fb3917 100644 --- a/Pipfile +++ b/Pipfile @@ -11,19 +11,16 @@ pytest-pylint = "*" pytest = "*" [packages] -argparse = "*" appdirs = "*" base58 = "*" -boto3 = "*" ecdsa = ">=0.19.2" -colander = "*" cryptnox-sdk-py = ">=1.0.5" -lazy-import = "*" -pytz = "*" +eth-account = "*" +eth-utils = "*" +hexbytes = "*" requests = ">=2.33.0" solders = ">=0.27.0" tabulate = "*" -stdiomask = "*" web3 = ">=7.15.0" winsdk = {version = "*", sys_platform = "== 'win32'"} diff --git a/cryptnox_cli/command/card/info.py b/cryptnox_cli/command/card/info.py index 877a8f0..255f3e1 100644 --- a/cryptnox_cli/command/card/info.py +++ b/cryptnox_cli/command/card/info.py @@ -7,7 +7,6 @@ from typing import List, Dict import cryptnox_sdk_py -import web3 from argparse import Namespace from tabulate import tabulate @@ -160,6 +159,8 @@ def _get_btc_info(config, pubkey) -> dict: @staticmethod def _get_eth_info(config, public_key) -> dict: + import web3 + if public_key is None: return {"name": "ETH", "address": "Bad derivation type", "network": ""} network = enums.EthNetwork[config.get("network", "infura").upper()] @@ -240,6 +241,8 @@ def _get_solana_info(config, pubkey) -> dict: @staticmethod def _get_bnb_info(public_key) -> dict: + import web3 + # BNB on Binance Smart Chain (BSC) is EVM-compatible: same secp256k1 # curve, same derivation path, and same address format as Ethereum. if public_key is None: diff --git a/cryptnox_cli/command/erc_token/contract.py b/cryptnox_cli/command/erc_token/contract.py index ced31a5..01ec562 100644 --- a/cryptnox_cli/command/erc_token/contract.py +++ b/cryptnox_cli/command/erc_token/contract.py @@ -13,7 +13,6 @@ import cryptnox_sdk_py import requests -import web3 from tabulate import tabulate from ..helper.helper_methods import sign @@ -41,6 +40,8 @@ def abi(value: int): def gas(gas_price: int, set_price: int, set_limit: int, default_limit: int = LIMIT["transfer"]) -> Tuple[int, int]: + import web3 + if set_price: price = set_price else: @@ -65,6 +66,7 @@ def gas(gas_price: int, set_price: int, set_limit: int, def transfer(card, endpoint, network, api_key, contract_address: str, to: str, amount: float, price: int, limit: int, derivation: cryptnox_sdk_py.Derivation = cryptnox_sdk_py.Derivation.CURRENT_KEY) -> int: + import web3 try: endpoint = wallet.Api(endpoint, network, api_key) @@ -159,6 +161,8 @@ def transfer(card, endpoint, network, api_key, contract_address: str, to: str, a def _confirm_token_sending(contract: str, address: str, to: str, token_balance: float, symbol: str, value: Union[Decimal, int], balance: int, price: int, limit: float): + import web3 + gas_price = web3.Web3.from_wei(price, "ether") total_gas = Decimal(gas_price * limit) balance = web3.Web3.from_wei(balance, "ether") diff --git a/cryptnox_cli/command/erc_token/info.py b/cryptnox_cli/command/erc_token/info.py index 204dd2b..dbea4ba 100644 --- a/cryptnox_cli/command/erc_token/info.py +++ b/cryptnox_cli/command/erc_token/info.py @@ -17,7 +17,6 @@ from cryptography.hazmat.primitives.asymmetric import ec from eth_typing import ChecksumAddress from tabulate import tabulate -from web3 import Web3 from ..cards import Cards from ..helper.cards import CardManager @@ -158,6 +157,8 @@ def execute(self, card: cryptnox_sdk_py.Card) -> int: @staticmethod def _balance(endpoint, address): + from web3 import Web3 + try: w3 = Web3(Web3.HTTPProvider(endpoint)) print(f"Balance: {Web3.from_wei(w3.eth.get_balance(address), 'ether')} ETH") @@ -212,6 +213,8 @@ def _owner( abi: str, account: str, token_id: int) -> None: + from web3 import Web3 + print(f"Checking owner on contract: {contract_address}...") w3 = Web3(Web3.HTTPProvider(endpoint)) try: @@ -232,6 +235,8 @@ def _owner( @staticmethod def _token_balance(endpoint: str, contract_address: ChecksumAddress, abi: str, account: str) -> None: + from web3 import Web3 + print(f"Checking token on contract: {contract_address}...") w3 = Web3(Web3.HTTPProvider(endpoint)) try: diff --git a/cryptnox_cli/command/eth.py b/cryptnox_cli/command/eth.py index d436608..6e2d98e 100644 --- a/cryptnox_cli/command/eth.py +++ b/cryptnox_cli/command/eth.py @@ -10,7 +10,6 @@ import cryptnox_sdk_py import requests -import web3 from argparse import Namespace from tabulate import tabulate @@ -118,6 +117,8 @@ def list(self) -> int: return 0 def logs(self): + import web3 + try: config_contract = get_configuration(self.card)["hidden"]["eth"]["contract"][self.data.alias] except KeyError: @@ -275,6 +276,8 @@ def _get_endpoint(self, card): return wallet.Api(config["eth"]["endpoint"], network, config["eth"]["api_key"]) def _add(self, card): + import web3 + abi = _get_processed_json_argument(self.data.abi) try: @@ -365,6 +368,8 @@ def _functions(self, card): return None def _call(self, card): + import web3 + config = get_configuration(card) try: config = config["hidden"]["eth"]["contract"][self.data.alias] @@ -409,6 +414,8 @@ def _call(self, card): return 0 def _transact(self, card): + import web3 + self._check(card) config = get_configuration(card) @@ -505,6 +512,8 @@ def _transact(self, card): @staticmethod def _confirm(public_key, address, balance, value, price, limit): + import web3 + gas_price = web3.Web3.from_wei(price, "ether") gas = Decimal(gas_price * limit) balance = web3.Web3.from_wei(balance, "ether") @@ -595,6 +604,8 @@ def _send(self, card) -> int: @staticmethod def _send_funds(card, derivation, endpoint, address, amount, price, limit): + import web3 + path = b"" if derivation == cryptnox_sdk_py.Derivation.CURRENT_KEY else wallet.Api.PATH public_key = card.get_public_key(derivation, path=path, compressed=False) diff --git a/cryptnox_cli/command/helper/masked_input.py b/cryptnox_cli/command/helper/masked_input.py new file mode 100644 index 0000000..b14c017 --- /dev/null +++ b/cryptnox_cli/command/helper/masked_input.py @@ -0,0 +1,222 @@ +# -*- coding: utf-8 -*- +""" +Masked entry of PINs, PUKs and passphrases: one mask character is echoed per +character entered, on Windows consoles and POSIX terminals alike. +""" +import codecs +import os +import sys + +# UTF-16 surrogate ranges. A character outside the Basic Multilingual Plane, +# an emoji for instance, reaches a Windows console as two of these halves. +_HIGH_SURROGATE = range(0xD800, 0xDC00) +_LOW_SURROGATE = range(0xDC00, 0xE000) + + +def _masked_input(read_char, prompt, mask): + """ + Read a secret, echoing one mask character per character entered. + + :param read_char: Callable returning the next UTF-16 code unit as a + one-character string, an empty string for a key that + carries no character, such as an arrow or function key, + or raising EOFError when the input ends. + :param str prompt: Text to show before the entry + :param str mask: Character to echo for each character entered + + :return: The entered text + :rtype: str + + An astral character arrives as a surrogate pair and is joined back into one + entry, so it echoes one mask character and one backspace removes all of it. + Only ASCII control characters are left out. Separators and format + characters, such as the ideographic space a Japanese keyboard produces, are + part of a passphrase and change the wallet it derives. + """ + entered = [] + pending = '' + sys.stdout.write(prompt) + sys.stdout.flush() + while True: + char = read_char() + if not char: + continue + if ord(char) in _HIGH_SURROGATE: + pending = char + continue + if pending: + if ord(char) in _LOW_SURROGATE: + # Rebuild the character the pair stands for. Joining the halves + # as text would leave two unusable ones that cannot be encoded. + char = chr(0x10000 + (ord(pending) - 0xD800) * 0x400 + + (ord(char) - 0xDC00)) + pending = '' + elif ord(char) in _LOW_SURROGATE: + continue # Half of a character without its partner, not text + + if char in ('\r', '\n'): # Enter + return ''.join(entered) + if char == '\x03': # Ctrl+C + raise KeyboardInterrupt + if char in ('\b', '\x7f'): # Backspace/Del + if entered: + sys.stdout.write('\b \b') + sys.stdout.flush() + entered.pop() + elif ord(char) > 31: + entered.append(char) + sys.stdout.write(mask) + sys.stdout.flush() + + +def _console_reader(handle, kernel32): + """ + Build a read_char callable over a Windows console input handle. + + Keys are taken from ReadConsoleInputW rather than msvcrt, because the + console records say whether a key produced a character. Through msvcrt an + arrow key is announced by a lead byte that cannot be told apart from a + typed 'a with grave accent', which would either swallow the next character + or add the arrow's scan code to the secret. + """ + import ctypes + import ctypes.wintypes + + class CharUnion(ctypes.Union): + """Character of a key event, as text or as a byte.""" + _fields_ = [("UnicodeChar", ctypes.c_wchar), ("AsciiChar", ctypes.c_char)] + + class KeyEvent(ctypes.Structure): + """KEY_EVENT_RECORD of the Windows console API.""" + _fields_ = [("bKeyDown", ctypes.wintypes.BOOL), ("wRepeatCount", ctypes.wintypes.WORD), + ("wVirtualKeyCode", ctypes.wintypes.WORD), ("wVirtualScanCode", ctypes.wintypes.WORD), + ("uChar", CharUnion), ("dwControlKeyState", ctypes.wintypes.DWORD)] + + class EventUnion(ctypes.Union): + """Event of an input record. Only key events are read.""" + _fields_ = [("KeyEvent", KeyEvent), ("padding", ctypes.c_byte * 16)] + + class InputRecord(ctypes.Structure): + """INPUT_RECORD of the Windows console API.""" + _fields_ = [("EventType", ctypes.wintypes.WORD), ("Event", EventUnion)] + + key_event = 0x0001 + vk_menu = 0x12 # Alt + record = InputRecord() + read = ctypes.wintypes.DWORD() + buffered = [] + + def read_char(): + while not buffered: + if not kernel32.ReadConsoleInputW(handle, ctypes.byref(record), 1, + ctypes.byref(read)) or not read.value: + raise EOFError + if record.EventType != key_event: + continue + # A key delivers its character on both key-down and key-up, so only + # key-down is taken. The exception is Alt: a character composed with + # Alt and the numeric keypad arrives on the Alt key-up alone, and the + # classic console's paste delivers each half of a character outside + # the Basic Multilingual Plane, an emoji for instance, the same way. + if (not record.Event.KeyEvent.bKeyDown + and record.Event.KeyEvent.wVirtualKeyCode != vk_menu): + continue + char = record.Event.KeyEvent.uChar.UnicodeChar + if char == '\x00': + continue # A key that produces no character, such as an arrow + buffered.extend([char] * max(1, record.Event.KeyEvent.wRepeatCount)) + return buffered.pop(0) + + return read_char + + +def _getpass_windows(prompt, mask): + """ + Masked input for a Windows console. + + Line editing, echo and Ctrl+C handling are switched off for the duration, + so every keypress is delivered as it is typed and Ctrl+C reaches the loop + as a character. The console mode is restored afterwards. When standard + input is not a console there is nothing to mask and the line is read as is. + """ + import ctypes + + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + # A HANDLE is pointer sized; left untyped, ctypes would cut it to 32 bits + kernel32.GetStdHandle.restype = ctypes.c_void_p + std_input_handle = -10 + handle = ctypes.c_void_p(kernel32.GetStdHandle(std_input_handle)) + mode = ctypes.c_ulong() + if not kernel32.GetConsoleMode(handle, ctypes.byref(mode)): + return input(prompt) + + # ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT + # | ENABLE_WINDOW_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT + line_editing_and_echo = 0x0001 | 0x0002 | 0x0004 | 0x0008 | 0x0200 + try: + kernel32.SetConsoleMode(handle, mode.value & ~line_editing_and_echo) + return _masked_input(_console_reader(handle, kernel32), prompt, mask) + finally: + kernel32.SetConsoleMode(handle, mode.value) + sys.stdout.write('\n') + sys.stdout.flush() + + +def _getpass_posix(prompt, mask): + """ + Masked input for POSIX terminals. + The terminal is switched to cbreak mode: keys arrive one at a time without + echo, while Ctrl+C still raises KeyboardInterrupt. Bytes are decoded + incrementally, so a multi-byte UTF-8 character counts as one entry. + When stdin is not a terminal there is nothing to mask and the line is read as is. + """ + import termios + import tty + + try: + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + except (AttributeError, ValueError, termios.error): + return input(prompt) + + decoder = codecs.getincrementaldecoder(sys.stdin.encoding or 'utf-8')(errors='ignore') + entered = [] + sys.stdout.write(prompt) + sys.stdout.flush() + try: + tty.setcbreak(fd) + while True: + data = os.read(fd, 1) + if not data: + raise EOFError + char = decoder.decode(data) + if not char: # Incomplete multi-byte character + continue + if char in ('\n', '\r'): # Enter + return ''.join(entered) + if char in ('\b', '\x7f'): # Backspace/Del + if entered: + sys.stdout.write('\b \b') + sys.stdout.flush() + entered.pop() + elif ord(char) > 31: + # Only ASCII control characters are left out. Separators and format + # characters (ideographic space, no-break space, zero width joiner) + # are part of a passphrase and change the wallet it derives. + entered.append(char) + sys.stdout.write(mask) + sys.stdout.flush() + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + sys.stdout.write('\n') + sys.stdout.flush() + + +def getpass(prompt='Password: ', mask='*'): + """ + Cross-platform getpass that raises KeyboardInterrupt on Ctrl+C. + """ + if sys.platform == 'win32': + return _getpass_windows(prompt, mask) + + return _getpass_posix(prompt, mask) diff --git a/cryptnox_cli/command/helper/notification.py b/cryptnox_cli/command/helper/notification.py index f814535..64a0cb7 100644 --- a/cryptnox_cli/command/helper/notification.py +++ b/cryptnox_cli/command/helper/notification.py @@ -10,7 +10,6 @@ from typing import Dict, Any import requests as requests -import web3 try: from config import ( @@ -57,6 +56,8 @@ def __init__(self): @staticmethod def _check(data): + import web3 + try: w3 = wallet.Web3Api(None, wallet.endpoint_factory(data["endpoint"], data["network"], diff --git a/cryptnox_cli/command/helper/security.py b/cryptnox_cli/command/helper/security.py index 58724b5..7e5f4dd 100644 --- a/cryptnox_cli/command/helper/security.py +++ b/cryptnox_cli/command/helper/security.py @@ -9,42 +9,7 @@ import cryptnox_sdk_py from .. import user_keys - - -def _getpass(prompt='Password: ', mask='*'): - """ - Cross-platform getpass that raises KeyboardInterrupt on Ctrl+C. - On Windows, getch() returns 0x03 for Ctrl+C in raw mode, - so we catch it directly without polling. - """ - import sys - if sys.platform == 'win32': - from msvcrt import getch - entered = [] - sys.stdout.write(prompt) - sys.stdout.flush() - while True: - key = ord(getch()) - if key == 3: # Ctrl+C - sys.stdout.write('\n') - sys.stdout.flush() - raise KeyboardInterrupt - elif key == 13: # Enter - sys.stdout.write('\n') - sys.stdout.flush() - return ''.join(entered) - elif key in (8, 127): # Backspace/Del - if entered: - sys.stdout.write('\b \b') - sys.stdout.flush() - entered.pop() - elif 32 <= key <= 126: # Printable ASCII - entered.append(chr(key)) - sys.stdout.write(mask) - sys.stdout.flush() - else: - from stdiomask import getpass - return getpass(prompt, mask) +from .masked_input import getpass class ExitException(Exception): @@ -95,7 +60,7 @@ def _secret_with_exit(text, required=True): """ while True: try: - value = _getpass(text).strip() + value = getpass(text).strip() except KeyboardInterrupt: raise ExitException() if required and not value: diff --git a/cryptnox_cli/command/helper/ui.py b/cryptnox_cli/command/helper/ui.py index 2867a71..1042216 100644 --- a/cryptnox_cli/command/helper/ui.py +++ b/cryptnox_cli/command/helper/ui.py @@ -10,7 +10,7 @@ from typing import List, Union, Dict import cryptnox_sdk_py -from .security import _getpass as getpass +from .masked_input import getpass from tabulate import tabulate from . import security diff --git a/cryptnox_cli/command/user_keys/piv/piv.py b/cryptnox_cli/command/user_keys/piv/piv.py index 06b00cb..b969fbf 100644 --- a/cryptnox_cli/command/user_keys/piv/piv.py +++ b/cryptnox_cli/command/user_keys/piv/piv.py @@ -7,10 +7,10 @@ import cryptnox_sdk_py from cryptography import x509 from cryptography.hazmat.primitives.serialization import PublicFormat, Encoding -from stdiomask import getpass from . import piv_card from .. import user_key_base +from ...helper.masked_input import getpass class Piv(user_key_base.UserKey): diff --git a/cryptnox_cli/main.py b/cryptnox_cli/main.py index a7ca923..fa6c649 100644 --- a/cryptnox_cli/main.py +++ b/cryptnox_cli/main.py @@ -3,13 +3,13 @@ """ Command line interface for Cryptnox Cards """ +import re import sys import traceback from os import makedirs from pathlib import Path import argparse -import lazy_import from appdirs import user_log_dir if getattr(sys, 'frozen', False): @@ -35,13 +35,6 @@ options ) -cryptnox_sdk_py = lazy_import.lazy_module("cryptnox_sdk_py") -json = lazy_import.lazy_module("json") -re = lazy_import.lazy_module("re") -tabulate = lazy_import.lazy_callable("tabulate.tabulate") -requests = lazy_import.lazy_module("requests") -web3 = lazy_import.lazy_module("web3") - APPLICATION_NAME = "Cryptnox CLI" diff --git a/cryptnox_cli/wallet/eth/api.py b/cryptnox_cli/wallet/eth/api.py index 6f85483..2b9c339 100644 --- a/cryptnox_cli/wallet/eth/api.py +++ b/cryptnox_cli/wallet/eth/api.py @@ -4,22 +4,18 @@ """ from typing import ( Any, - Dict, Union + Dict, TYPE_CHECKING, Union ) import ecdsa from cryptnox_sdk_py import Derivation -from eth_account._utils.legacy_transactions import ( - encode_transaction, - serializable_unsigned_transaction_from_dict -) -from eth_utils.curried import keccak -from hexbytes import HexBytes -from web3 import Web3 from . import endpoint as ep from .. import validators +if TYPE_CHECKING: + from web3 import Web3 + try: from lib import cryptos import enums @@ -29,10 +25,14 @@ def address(public_key: str) -> str: + from eth_utils.curried import keccak + return keccak(hexstr=("0x" + public_key[2:]))[-20:].hex() def checksum_address(public_key: str) -> str: + from web3 import Web3 + return Web3.to_checksum_address(address(public_key)) @@ -62,9 +62,13 @@ def contract(self, address="", abi=""): return self._web3.eth.contract(address=address, abi=abi) def get_transaction_count(self, address: str, blocks: str = None) -> int: + from web3 import Web3 + return self._web3.eth.get_transaction_count(Web3.to_checksum_address(address), blocks) def get_balance(self, address: str) -> int: + from web3 import Web3 + return self._web3.eth.get_balance(Web3.to_checksum_address(address)) @property @@ -76,6 +80,12 @@ def network(self): return self.endpoint.network def transaction_hash(self, transaction: Dict[str, Any], vrs: bool = False): + from eth_account._utils.legacy_transactions import ( + encode_transaction, + serializable_unsigned_transaction_from_dict + ) + from eth_utils.curried import keccak + try: del transaction["maxFeePerGas"] del transaction["maxPriorityFeePerGas"] @@ -87,6 +97,12 @@ def transaction_hash(self, transaction: Dict[str, Any], vrs: bool = False): return keccak(encoded_transaction) def push(self, transaction, signature, public_key): + from eth_account._utils.legacy_transactions import ( + encode_transaction, + serializable_unsigned_transaction_from_dict + ) + from hexbytes import HexBytes + unsigned_transaction = serializable_unsigned_transaction_from_dict(transaction) var_v, var_r, var_s = Api._decode_vrs(signature, self._chain_id, self.transaction_hash(transaction), @@ -133,7 +149,9 @@ def _provider(self) -> str: return self.endpoint.provider @property - def _web3(self) -> Web3: + def _web3(self) -> "Web3": + from web3 import Web3 + return Web3(Web3.HTTPProvider(self._provider)) diff --git a/dev-requirements.txt b/dev-requirements.txt index d511bba..4876751 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -32,7 +32,6 @@ aiohttp==3.14.1; python_version >= '3.9' aiosignal==1.4.0; python_version >= '3.7' annotated-types==0.7.0; python_version >= '3.8' appdirs==1.4.4 -argparse==1.4.0 attrs==25.1.0; python_version >= '3.7' base58==2.1.1; python_version >= '3.5' bitarray==3.0.0 @@ -40,7 +39,6 @@ certifi==2025.11.12; python_version >= '3.6' cffi==2.0.0; platform_python_implementation != 'PyPy' charset-normalizer==3.4.0; python_full_version >= '3.7.0' ckzg==2.0.1 -colander==2.0; python_version >= '3.7' cryptnox-sdk-py==1.0.2 cryptography==48.0.1; python_version >= '3.7' cytoolz==1.0.0; implementation_name == 'cpython' @@ -57,7 +55,6 @@ frozenlist==1.5.0; python_version >= '3.8' hexbytes==1.2.1; python_version >= '3.8' and python_version < '4' idna==3.15; python_version >= '3.6' iso8601==2.1.0; python_version >= '3.7' and python_version < '4.0' -lazy-import==0.2.2 multidict==6.1.0; python_version >= '3.8' parsimonious==0.10.0 propcache==0.2.0; python_version >= '3.8' @@ -67,17 +64,14 @@ pydantic==2.10.1; python_version >= '3.8' pydantic-core==2.27.1; python_version >= '3.8' pyscard==2.2.0; python_version >= '3.9' python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' -pytz==2024.2 pyunormalize==16.0.0; python_version >= '3.6' pywin32==308; platform_system == 'Windows' regex==2024.11.6; python_version >= '3.8' requests==2.33.0; python_version >= '3.8' rlp==4.1.0; python_version >= '3.8' and python_version < '5' six==1.17.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' -stdiomask==0.0.6 tabulate==0.9.0; python_version >= '3.7' toolz==1.1.0; python_version >= '3.8' -translationstring==1.4 types-requests==2.32.0.20241016; python_version >= '3.8' urllib3==2.7.0; python_version >= '3.8' web3==7.15.0; python_version >= '3.8' and python_version < '4' diff --git a/docs/conf.py b/docs/conf.py index 03722ad..dc991ac 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -66,7 +66,6 @@ # Cryptnox SDK (PyPI package: cryptnox-sdk-py) 'cryptnox_sdk_py', # Input/Output - 'stdiomask', 'tabulate', # Web3 and Ethereum 'web3', diff --git a/docs/cryptnox_cli.command.helper.rst b/docs/cryptnox_cli.command.helper.rst index e3bc023..1a6df7b 100644 --- a/docs/cryptnox_cli.command.helper.rst +++ b/docs/cryptnox_cli.command.helper.rst @@ -28,6 +28,14 @@ cryptnox_cli.command.helper.helper\_methods module :undoc-members: :show-inheritance: +cryptnox_cli.command.helper.masked_input module +----------------------------------------------- + +.. automodule:: cryptnox_cli.command.helper.masked_input + :members: + :undoc-members: + :show-inheritance: + cryptnox_cli.command.helper.notification module ---------------------------------------------- diff --git a/requirements.txt b/requirements.txt index 63ec026..773290a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,6 @@ aiohttp==3.14.1; python_version >= '3.9' aiosignal==1.4.0; python_version >= '3.7' annotated-types==0.7.0; python_version >= '3.8' appdirs==1.4.4 -argparse==1.4.0 attrs==25.1.0; python_version >= '3.7' base58==2.1.1; python_version >= '3.5' bitarray==3.0.0 @@ -12,7 +11,6 @@ certifi==2025.11.12; python_version >= '3.6' cffi==2.0.0; platform_python_implementation != 'PyPy' charset-normalizer==3.4.0; python_full_version >= '3.7.0' ckzg==2.0.1 -colander==2.0; python_version >= '3.7' cryptnox-sdk-py>=1.0.5; python_version >= '3.11' and python_version <= '3.14' cryptography==48.0.1; python_version >= '3.7' cytoolz==1.0.0; implementation_name == 'cpython' @@ -29,7 +27,6 @@ frozenlist==1.5.0; python_version >= '3.8' hexbytes==1.2.1; python_version >= '3.8' and python_version < '4' idna==3.15; python_version >= '3.6' iso8601==2.1.0; python_version >= '3.7' and python_version < '4.0' -lazy-import==0.2.2 multidict==6.1.0; python_version >= '3.8' parsimonious==0.10.0 propcache==0.2.0; python_version >= '3.8' @@ -39,7 +36,6 @@ pydantic==2.10.1; python_version >= '3.8' pydantic-core==2.27.1; python_version >= '3.8' pyscard==2.2.0; python_version >= '3.9' python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' -pytz==2024.2 pyunormalize==16.0.0; python_version >= '3.6' pywin32==308; platform_system == 'Windows' regex==2024.11.6; python_version >= '3.8' @@ -47,10 +43,8 @@ requests==2.33.0; python_version >= '3.8' rlp==4.1.0; python_version >= '3.8' and python_version < '5' six==1.17.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' solders>=0.27.0; python_version >= '3.7' -stdiomask==0.0.6 tabulate==0.9.0; python_version >= '3.7' toolz==1.1.0; python_version >= '3.8' -translationstring==1.4 types-requests==2.32.0.20241016; python_version >= '3.8' typing-extensions==4.13.2; python_version >= '3.8' urllib3==2.7.0; python_version >= '3.8' diff --git a/setup.cfg b/setup.cfg index 33351e0..576fa4c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,18 +39,16 @@ packages_dir = cryptnox_cli zip_safe = False include_package_data = True install_requires = - argparse appdirs base58 ecdsa>=0.19.2 - colander cryptnox-sdk-py>=1.0.5 - lazy-import - pytz + eth-account + eth-utils + hexbytes requests>=2.33.0 solders>=0.27.0 tabulate - stdiomask web3>=7.15.0 winsdk;platform_system=='Windows' diff --git a/setup.spec b/setup.spec index 2298d79..06112ca 100644 --- a/setup.spec +++ b/setup.spec @@ -1,13 +1,8 @@ # -*- mode: python ; coding: utf-8 -*- -import os import sys from PyInstaller.utils.hooks import collect_all -# Find lazy_import VERSION file -import lazy_import -lazy_import_version = os.path.join(os.path.dirname(lazy_import.__file__), 'VERSION') - # Collect all submodules, data files, and binaries from cryptnox_sdk_py sdk_datas, sdk_binaries, sdk_hiddenimports = collect_all('cryptnox_sdk_py') @@ -18,11 +13,9 @@ a = Analysis( datas=[ # Only include non-Python data files ('cryptnox_cli\\contract_abi\\*.json', 'cryptnox_cli\\contract_abi'), - ('cryptnox_cli\\lib\\cryptos\\english.txt', 'cryptnox_cli\\lib\\cryptos'), - (lazy_import_version, 'lazy_import') + ('cryptnox_cli\\lib\\cryptos\\english.txt', 'cryptnox_cli\\lib\\cryptos') ] + sdk_datas, hiddenimports=sdk_hiddenimports + [ - 'lazy_import', 'six', 'winsdk', 'winsdk.windows', @@ -32,7 +25,6 @@ a = Analysis( 'smartcard', 'multiprocessing', 'tabulate', - 'coincurve', 'eth_keys', 'requests', 'urllib3', @@ -49,6 +41,7 @@ a = Analysis( 'cryptnox_cli.command.info', 'cryptnox_cli.command.initialize', 'cryptnox_cli.command.seed', + 'cryptnox_cli.command.solana', 'cryptnox_cli.command.cards', 'cryptnox_cli.command.server', 'cryptnox_cli.command.reset',