mmgen-swaptx{create,do}: add --stream-interval option

This commit is contained in:
The MMGen Project 2025-05-19 09:23:55 +00:00
commit 4cad3c3bd5
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
8 changed files with 69 additions and 27 deletions

View file

@ -1 +1 @@
15.1.dev36
15.1.dev37

View file

@ -35,6 +35,10 @@ class help_notes:
pfx, sfx, sep, conj = (('', '', ',', ' or '), ("", "", "’,‘", "’ or ‘"))[use_quotes]
return pfx + sep.join(u[0] for u in cu[:-1]) + ('', conj)[len(cu)>1] + cu[-1][0] + sfx
def stream_interval(self):
from ..tx.new_swap import get_swap_proto_mod
return get_swap_proto_mod(self.cfg.swap_proto).SwapCfg(self.cfg).si.dfl
def fee_spec_names(self, *, proto=None, linebreak=' '):
cu = (proto or self.proto).coin_amt.units
return (

View file

@ -73,6 +73,7 @@ opts_data = {
-- -m, --minconf= n Minimum number of confirmations required to spend
+ outputs (default: 1)
-- -q, --quiet Suppress warnings; overwrite files without prompting
-s -r, --stream-interval=N Set block interval for streaming swap (default: {si})
bt -R, --no-rbf Make transaction non-replaceable (non-replace-by-fee
+ according to BIP 125)
-s -s, --swap-proto Swap protocol to use (Default: {x_dfl},
@ -97,6 +98,7 @@ opts_data = {
a_info = help_notes('account_info_desc'),
fu = help_notes('rel_fee_desc'),
fl = help_notes('fee_spec_letters'),
si = help_notes('stream_interval'),
fe_all = fmt_list(cfg._autoset_opts['fee_estimate_mode'].choices, fmt='no_spc'),
fe_dfl = cfg._autoset_opts['fee_estimate_mode'].choices[0],
x_all = fmt_list(cfg._autoset_opts['swap_proto'].choices, fmt='no_spc'),

View file

@ -94,6 +94,7 @@ opts_data = {
+ for password hashing (default: '{gc.dfl_hash_preset}')
-- -P, --passwd-file= f Get {pnm} wallet passphrase from file 'f'
-- -q, --quiet Suppress warnings; overwrite files without prompting
-s -r, --stream-interval=N Set block interval for streaming swap (default: {si})
bt -R, --no-rbf Make transaction non-replaceable (non-replace-by-fee
+ according to BIP 125)
-s -s, --swap-proto Swap protocol to use (Default: {x_dfl},
@ -145,6 +146,7 @@ column below:
fl = help_notes('fee_spec_letters'),
dsl = help_notes('dfl_seed_len'),
ss = help_notes('dfl_subseeds'),
si = help_notes('stream_interval'),
tx_proxies = help_notes('tx_proxies'),
ss_max = SubSeedIdxRange.max_idx,
fe_all = fmt_list(cfg._autoset_opts['fee_estimate_mode'].choices, fmt='no_spc'),

View file

@ -13,10 +13,13 @@ swap.cfg: swap configuration class the MMGen Wallet suite
"""
import re
from collections import namedtuple
from ..amt import UniAmt
from ..util import die
_mmd = namedtuple('swap_config_option', ['min', 'max', 'dfl'])
class SwapCfg:
# The trade limit, i.e., set 100000000 to get a minimum of 1 full asset, else a refund
@ -24,7 +27,7 @@ class SwapCfg:
trade_limit = None
# Swap interval for streaming swap in blocks. Optional. If 0, do not stream
stream_interval = 3
si = _mmd(1, 20, 3) # stream_interval
# Swap quantity for streaming swap.
# The interval value determines the frequency of swaps in blocks
@ -38,9 +41,25 @@ class SwapCfg:
if cfg.trade_limit is not None:
self.set_trade_limit(desc='parameter for --trade-limit')
if cfg.stream_interval is None:
self.stream_interval = self.si.dfl
else:
self.set_stream_interval(desc='parameter for --stream-interval')
def set_trade_limit(self, *, desc):
s = self.cfg.trade_limit
if re.match(r'-*[0-9]+(\.[0-9]+)*%*$', s):
self.trade_limit = 1 - float(s[:-1]) / 100 if s.endswith('%') else UniAmt(s)
else:
die('SwapCfgValueError', f'{s}: invalid {desc}')
def set_stream_interval(self, *, desc):
s = self.cfg.stream_interval
from ..util import is_int
if not is_int(s):
die('SwapCfgValueError', f'{s}: invalid {desc} (not an integer)')
self.stream_interval = si = int(s)
if si < self.si.min:
die('SwapCfgValueError', f'{si}: invalid {desc} (< {self.si.min})')
if si > self.si.max:
die('SwapCfgValueError', f'{si}: invalid {desc} (> {self.si.max})')

View file

@ -415,8 +415,8 @@ class CmdTestEthSwapEth(CmdTestEthSwapMethods, CmdTestSwapMethods, CmdTestEthdev
return self._swaptxcreate_ui_common(
self._swaptxcreate(
['ETH', '8.765', 'BTC', f'{dfl_sid}:B:4'],
add_opts = ['--trade-limit=3%']),
expect = ':2019e4/3/0')
add_opts = ['--trade-limit=3%' ,'--stream-interval=7']),
expect = ':2019e4/7/0')
def swaptxcreate3a(self):
t = self._swaptxcreate(['ETH', '0.7654321', 'ETH.MM1'], add_opts=['--gas=fallback'])

View file

@ -534,16 +534,16 @@ class CmdTestSwap(CmdTestSwapMethods, CmdTestRegtest, CmdTestAutosignThreaded):
return self._swaptxcreate_ui_common(
self._swaptxcreate(
['BCH', '1.234', f'{self.sid}:C:{idx}', 'LTC', f'{self.sid}:B:3'],
add_opts = ['--trade-limit=0%']),
expect = ':3541e5/3/0')
add_opts = ['--trade-limit=0%', '--stream-interval=1']),
expect = ':3541e5/1/0')
def swaptxcreate2(self):
t = self._swaptxcreate(
['BCH', 'LTC'],
add_opts = ['--no-quiet', '--trade-limit=3.337%'])
add_opts = ['--no-quiet', '--stream-interval=10', '--trade-limit=3.337%'])
t.expect('Enter a number> ', '1')
t.expect('OK? (Y/n): ', 'y')
return self._swaptxcreate_ui_common(t, reload_quote=True, expect=':1386e6/3/0')
return self._swaptxcreate_ui_common(t, reload_quote=True, expect=':1386e6/10/0')
def swaptxcreate3(self):
return self._swaptxcreate_ui_common(

View file

@ -16,20 +16,23 @@ class unit_tests:
def cfg(self, name, ut, desc='Swap configuration'):
for tl_arg, tl_chk in (
(None, None),
('1', UniAmt('1')),
('33', UniAmt('33')),
('2%', 0.98),
('-2%', 1.02),
('3.333%', 0.96667),
('-3.333%', 1.03333),
('1.2345', UniAmt('1.2345'))):
cfg_data = {'trade_limit': tl_arg}
for tl_arg, tl_chk, si_arg in (
(None, None, None),
('1', UniAmt('1'), None),
('33', UniAmt('33'), 7),
('2%', 0.98, 14),
('-2%', 1.02, 1),
('3.333%', 0.96667, 1),
('-3.333%', 1.03333, 3),
('1.2345', UniAmt('1.2345'), 10)):
cfg_data = {
'trade_limit': tl_arg,
'stream_interval': None if si_arg is None else str(si_arg)}
sc = SwapCfg(Config(cfg_data))
vmsg(f' trade_limit: {tl_arg} => {sc.trade_limit}')
vmsg(f' stream_interval: {si_arg} => {sc.stream_interval}')
assert sc.trade_limit == tl_chk
assert sc.stream_interval == 3
assert sc.stream_interval == sc.si.dfl if si_arg is None else si_arg
assert sc.stream_quantity == 0
vmsg('\n Testing error handling')
@ -40,9 +43,21 @@ class unit_tests:
def bad2():
SwapCfg(Config({'trade_limit': '1.23x'}))
def bad3():
SwapCfg(Config({'stream_interval': 30}))
def bad4():
SwapCfg(Config({'stream_interval': 0}))
def bad5():
SwapCfg(Config({'stream_interval': 'x'}))
ut.process_bad_data((
('bad1', 'SwapCfgValueError', 'invalid parameter', bad1),
('bad2', 'SwapCfgValueError', 'invalid parameter', bad2),
('bad3', 'SwapCfgValueError', 'invalid parameter', bad3),
('bad4', 'SwapCfgValueError', 'invalid parameter', bad4),
('bad5', 'SwapCfgValueError', 'invalid parameter', bad5),
), pfx='')
return True
@ -78,15 +93,15 @@ class unit_tests:
vmsg(f'\nTesting asset {cyan(asset_name)}:')
for limit, limit_chk, suf in (
('123.4567', 12340000000, '1234e7/3/0'),
('1.234567', 123400000, '1234e5/3/0'),
('0.01234567', 1234000, '1234e3/3/0'),
('0.00012345', 12345, '12345/3/0'),
(None, 0, '0/3/0'),
for limit, limit_chk, si, suf in (
('123.4567', 12340000000, None, '1234e7/3/0'),
('1.234567', 123400000, 1, '1234e5/1/0'),
('0.01234567', 1234000, 10, '1234e3/10/0'),
('0.00012345', 12345, 20, '12345/20/0'),
(None, 0, 3, '0/3/0'),
):
vmsg('\nTesting memo initialization:')
swap_cfg = SwapCfg(Config({'trade_limit': limit}))
swap_cfg = SwapCfg(Config({'trade_limit': limit, 'stream_interval': si}))
m = Memo(
swap_cfg,
proto,
@ -112,7 +127,7 @@ class unit_tests:
assert p.asset == token or coin.upper()
assert p.address == addr.views[addr.view_pref]
assert p.trade_limit == limit_chk
assert p.stream_interval == 3
assert p.stream_interval == si or swap_cfg.si.dfl, f'{p.stream_interval} != {swap_cfg.si.dfl}'
assert p.stream_quantity == 0 # auto
vmsg('\nTesting is_partial_memo():')