diff --git a/mmgen/devinit.py b/mmgen/devinit.py index f32a6a90..7acb4905 100755 --- a/mmgen/devinit.py +++ b/mmgen/devinit.py @@ -43,14 +43,15 @@ class MMGenObjectDevTools: pexit = lambda *args, **kwargs: MMGenObject_call('pexit', *args, **kwargs) pfmt = lambda *args, **kwargs: MMGenObject_call('pfmt', *args, **kwargs) - # Check that all immutables have been initialized. Expensive, so do only when testing. + # Check that all immutable attrs in ‘valid_attrs’ exist and have been initialized. + # Expensive, so do only when testing. def immutable_attr_init_check(self): cls = type(self) for attrname in self.valid_attrs: - - for o in (cls, cls.__bases__[0]): # assume there's only one base class + # existence check: + for o in cls.__mro__[:3]: # allow for 2 levels of subclassing if attrname in o.__dict__: attr = o.__dict__[attrname] break @@ -58,6 +59,7 @@ class MMGenObjectDevTools: from .util import die die(4, f'unable to find descriptor {cls.__name__}.{attrname}') + # initialization check: if type(attr).__name__ == 'ImmutableAttr' and attrname not in self.__dict__: from .util import die die(4, f'attribute {attrname!r} of {cls.__name__} has not been initialized in constructor!') diff --git a/mmgen/help/help_notes.py b/mmgen/help/help_notes.py index 6a1b8063..bcbdb9c7 100755 --- a/mmgen/help/help_notes.py +++ b/mmgen/help/help_notes.py @@ -22,11 +22,12 @@ class help_notes: def txcreate_args(self): return ( - '' if self.proto.base_coin == 'ETH' else - '[ ...] ') + '[ADDR,AMT ...] ADDR ' + if self.proto.base_proto == 'Bitcoin' else + 'ADDR,AMT') def account_info_desc(self): - return 'account info' if self.proto.base_coin == 'ETH' else 'unspent outputs' + return 'unspent outputs' if self.proto.base_proto == 'Bitcoin' else 'account info' def fee_spec_letters(self, use_quotes=False): cu = self.proto.coin_amt.units @@ -77,14 +78,20 @@ class help_notes: def address_types(self): from ..addr import MMGenAddrType - return '\n '.join([ - "'{}','{:<12} - {}".format(k, v.name + "'", v.desc) - for k, v in MMGenAddrType.mmtypes.items() - ]) + return """ +ADDRESS TYPES: + + Code Type Description + ---- ---- ----------- + """ + format('\n '.join(['‘{}’ {:<12} - {}'.format(k, v.name, v.desc) + for k, v in MMGenAddrType.mmtypes.items()])) def fmt_codes(self): from ..wallet import format_fmt_codes - return '\n '.join(format_fmt_codes().splitlines()) + return """ +FMT CODES: + + """ + '\n '.join(format_fmt_codes().splitlines()) def coin_id(self): return self.proto.coin_id @@ -147,20 +154,7 @@ seed, the same seed length and hash preset parameters must always be used. addr = t.privhex2addr('bead' * 16) sample_addr = addr.views[addr.view_pref] - if self.proto.base_coin == 'ETH': - return f""" -EXAMPLES: - - Send 0.123 {self.proto.coin} to an external {self.proto.name} address: - - $ {gc.prog_name} {sample_addr},0.123 - - Send 0.123 {self.proto.coin} to another account in wallet 01ABCDEF: - - $ {gc.prog_name} 01ABCDEF:{mmtype}:7,0.123 -""" - else: - return f""" + return f""" EXAMPLES: Send 0.123 {self.proto.coin} to an external {self.proto.name} address, returning the change to a @@ -190,23 +184,47 @@ EXAMPLES: address of specified type: $ {gc.prog_name} {mmtype} +""" if self.proto.base_proto == 'Bitcoin' else f""" +EXAMPLES: + + Send 0.123 {self.proto.coin} to an external {self.proto.name} address: + + $ {gc.prog_name} {sample_addr},0.123 + + Send 0.123 {self.proto.coin} to another account in wallet 01ABCDEF: + + $ {gc.prog_name} 01ABCDEF:{mmtype}:7,0.123 """ def txcreate(self): - return f""" + outputs_info = ( + """ +Outputs are specified in the form ADDRESS,AMOUNT or ADDRESS. The first form +creates an output sending the given amount to the given address. The bare +address form designates the given address as either the change output or the +sole output of the transaction (excluding any data output). Exactly one bare +address argument is required. + +For convenience, the bare address argument may be given as ADDRTYPE_CODE or +SEED_ID:ADDRTYPE_CODE (see ADDRESS TYPES below). In the first form, the first +unused address of type ADDRTYPE_CODE for each Seed ID in the tracking wallet +will be displayed in a menu, with the user prompted to select one. In the +second form, the user specifies the Seed ID as well, allowing the script to +select the transaction’s change output or single output without prompting. +See EXAMPLES below. +""" + if self.proto.base_proto == 'Bitcoin' else """ +The transaction output is specified in the form ADDRESS,AMOUNT. +""") + + return """ The transaction’s outputs are listed on the command line, while its inputs are chosen from a list of the wallet’s unspent outputs via an interactive menu. Alternatively, inputs may be specified using the --inputs option. -All addresses on the command line can be either {self.proto.name} addresses or MMGen -IDs in the form :
:. - -Outputs are specified in the form
,, with the change output -specified by address only. Alternatively, the change output may be an -addrlist ID in the form :
, in which case the -first unused address in the tracking wallet matching the requested ID will -be automatically selected as the change output. - +Addresses on the command line can be either native coin addresses or MMGen +IDs in the form SEED_ID:ADDRTYPE_CODE:INDEX. +{oinfo} If the transaction fee is not specified on the command line (see FEE SPECIFICATION below), it will be calculated dynamically using network fee estimation for the default (or user-specified) number of confirmations. @@ -214,12 +232,7 @@ If network fee estimation fails, the user will be prompted for a fee. Network-estimated fees will be multiplied by the value of --fee-adjust, if specified. - -To send the value of all inputs (minus TX fee) to a single output, specify -a single address with no amount on the command line. Alternatively, an -addrlist ID may be specified, and the address will be chosen automatically -as described above for the change output. -""" +""".format(oinfo=outputs_info) def txsign(self): from ..proto.btc.params import mainnet diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index 3802258c..ffbc60af 100755 --- a/mmgen/main_addrgen.py +++ b/mmgen/main_addrgen.py @@ -90,17 +90,14 @@ range(s). {n_addrkey}If available, the libsecp256k1 library will be used for address generation. -ADDRESS TYPES: - {n_at} - NOTES FOR ALL GENERATOR COMMANDS {n_sw}{n_pw}{n_bw} -FMT CODES: +{n_at} - {n_fmt} +{n_fmt} """ }, 'code': { diff --git a/mmgen/main_msg.py b/mmgen/main_msg.py index cec39124..6b752667 100755 --- a/mmgen/main_msg.py +++ b/mmgen/main_msg.py @@ -126,16 +126,13 @@ export - dump signed MMGen message file to ‘signatures.json’, including only The `create` operation takes one or more ADDRESS_SPEC arguments with the following format: - SEED_ID:ADDR_TYPE:ADDR_IDX_SPEC + SEED_ID:ADDRTYPE_CODE:ADDR_IDX_SPEC -where ADDR_TYPE is an address type letter from the list below, and -ADDR_IDX_SPEC is a comma-separated list of address indexes or hyphen- -separated address index ranges. +where ADDRTYPE_CODE is a one-letter address type code from the list below, and +ADDR_IDX_SPEC is a comma-separated list of address indexes or hyphen-separated +address index ranges. - - ADDRESS TYPES - - {n_at} +{n_at} NOTES diff --git a/mmgen/main_passgen.py b/mmgen/main_passgen.py index 17ae022b..059595b7 100755 --- a/mmgen/main_passgen.py +++ b/mmgen/main_passgen.py @@ -110,9 +110,7 @@ EXAMPLES: {n_bw} -FMT CODES: - - {n_fmt} +{n_fmt} """ }, 'code': { diff --git a/mmgen/main_seedjoin.py b/mmgen/main_seedjoin.py index 80d8cbcd..18e8ad1e 100755 --- a/mmgen/main_seedjoin.py +++ b/mmgen/main_seedjoin.py @@ -73,9 +73,7 @@ For usage examples, see the help screen for the 'mmgen-seedsplit' command. {n_pw} -FMT CODES: - - {f} +{f} """ }, 'code': { diff --git a/mmgen/main_txbump.py b/mmgen/main_txbump.py index 0eec421d..8cb92453 100755 --- a/mmgen/main_txbump.py +++ b/mmgen/main_txbump.py @@ -84,9 +84,7 @@ opts_data = { Seed source files must have the canonical extensions listed in the 'FileExt' column below: -FMT CODES: - - {f} +{f} """ }, 'code': { diff --git a/mmgen/main_txcreate.py b/mmgen/main_txcreate.py index 3a20b8aa..329acacb 100755 --- a/mmgen/main_txcreate.py +++ b/mmgen/main_txcreate.py @@ -66,7 +66,7 @@ opts_data = { -- -y, --yes Answer 'yes' to prompts, suppress non-essential output e- -X, --cached-balances Use cached balances """, - 'notes': '\n{c}\n{F}\n{x}', + 'notes': '\n{c}\n{n_at}\n\n{F}\n{x}', }, 'code': { 'usage': lambda cfg, proto, help_notes, s: s.format( @@ -82,7 +82,8 @@ opts_data = { 'notes': lambda cfg, help_notes, s: s.format( c = help_notes('txcreate'), F = help_notes('fee'), - x = help_notes('txcreate_examples')) + x = help_notes('txcreate_examples'), + n_at = help_notes('address_types')) } } diff --git a/mmgen/main_txdo.py b/mmgen/main_txdo.py index d97564b5..741596ac 100755 --- a/mmgen/main_txdo.py +++ b/mmgen/main_txdo.py @@ -95,9 +95,9 @@ opts_data = { Seed source files must have the canonical extensions listed in the 'FileExt' column below: -FMT CODES: +{n_at} - {f} +{f} {x}""" }, @@ -124,6 +124,7 @@ FMT CODES: c = help_notes('txcreate'), F = help_notes('fee'), s = help_notes('txsign'), + n_at = help_notes('address_types'), f = help_notes('fmt_codes'), x = help_notes('txcreate_examples')), } diff --git a/mmgen/main_txsign.py b/mmgen/main_txsign.py index 420683fb..63ac6c9e 100755 --- a/mmgen/main_txsign.py +++ b/mmgen/main_txsign.py @@ -74,9 +74,7 @@ opts_data = { Seed source files must have the canonical extensions listed in the 'FileExt' column below: -FMT CODES: - - {f} +{f} """ }, 'code': { diff --git a/mmgen/main_wallet.py b/mmgen/main_wallet.py index ceebd856..ffee39a0 100755 --- a/mmgen/main_wallet.py +++ b/mmgen/main_wallet.py @@ -122,9 +122,7 @@ opts_data = { {n_ss}{n_sw}{n_pw}{n_bw} -FMT CODES: - - {f} +{f} """ }, 'code': { diff --git a/mmgen/obj.py b/mmgen/obj.py index 63cff9d1..fc98cfae 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -107,10 +107,9 @@ class ImmutableAttr: # Descriptor self.typeconv = typeconv assert isinstance(dtype, self.ok_dtypes), 'ImmutableAttr_check1' - if include_proto: - assert typeconv, 'ImmutableAttr_check2' + if set_none_ok: - assert typeconv and not isinstance(dtype, str), 'ImmutableAttr_check3' + assert typeconv and not isinstance(dtype, str), 'ImmutableAttr_check2' if typeconv: # convert this attribute's type diff --git a/mmgen/rpc.py b/mmgen/rpc.py index 842d0648..49d54253 100755 --- a/mmgen/rpc.py +++ b/mmgen/rpc.py @@ -21,7 +21,6 @@ rpc: Cryptocoin RPC library for the MMGen suite """ import sys, re, base64, json, asyncio, importlib -from decimal import Decimal from collections import namedtuple from .util import msg, ymsg, die, fmt, fmt_list, pp_fmt, oneshot_warning @@ -75,7 +74,7 @@ class IPPort(HiliteStr, InitErrors): class json_encoder(json.JSONEncoder): def default(self, o): - if isinstance(o, Decimal): + if type(o).__name__.endswith('Amt'): return str(o) else: return json.JSONEncoder.default(self, o) diff --git a/test/cmdtest_d/ct_regtest.py b/test/cmdtest_d/ct_regtest.py index 146bd21d..ce43312b 100755 --- a/test/cmdtest_d/ct_regtest.py +++ b/test/cmdtest_d/ct_regtest.py @@ -63,7 +63,8 @@ pat_date_time = r'\b\d\d\d\d-\d\d-\d\d\s+\d\d:\d\d\b' dfl_wcls = get_wallet_cls('mmgen') -tx_fee = rtFundAmt = rtFee = rtBals = rtBals_gb = rtBobOp3 = rtAmts = {} # ruff +tx_fee = rtFundAmt = rtFee = rtBals = rtBals_gb = rtBobOp3 = rtAmts = None # ruff + rt_pw = 'abc-α' rt_data = { 'tx_fee': {'btc':'0.0001', 'bch':'0.001', 'ltc':'0.01'}, diff --git a/test/include/pexpect.py b/test/include/pexpect.py index 10114a47..360245c2 100755 --- a/test/include/pexpect.py +++ b/test/include/pexpect.py @@ -82,7 +82,7 @@ class MMGenPexpect: def view_tx(self, view): self.expect(r'View.* transaction.*\? .*: ', view, regex=True) - if view not in 'n\n': + if view not in 'vn\n': self.expect('to continue: ', '\n') def do_comment(self, add_comment, has_label=False):