From ff35b01c45ac890b9eb493f3dd367d8252c0862d Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Mon, 19 Oct 2026 09:06:13 +0000 Subject: [PATCH] memoless swaps: set trade limit and destination address from registered memo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revises ffe488ff (THORChain memoless swaps) For memoless swaps, it is now forbidden to specify a trade limit or destination address in the swap transaction invocation (second pass), as these are already set in the reference memo registered on the THORChain network, so duplicating them on the command line only leads to confusion. If a trade limit was specified in the registration transaction, it’s up to the user to ensure that the swap transaction spend amount is appropriate for the trade limit. Increasing the spend amount is possible, but any significant increase effectively nullifies the protection the trade limit provides. --- mmgen/data/version | 2 +- mmgen/main_txcreate.py | 4 +++- mmgen/tx/base_swap.py | 7 ++---- mmgen/tx/new_swap.py | 32 ++++++++++++---------------- mmgen/tx/online.py | 3 +-- test/cmdtest_d/httpd/thornode/rpc.py | 2 +- test/cmdtest_d/swap.py | 8 +++++-- 7 files changed, 28 insertions(+), 30 deletions(-) diff --git a/mmgen/data/version b/mmgen/data/version index 55808c92..bb23bce8 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -16.3.0dev17 +16.3.0dev18 diff --git a/mmgen/main_txcreate.py b/mmgen/main_txcreate.py index 687ae74b..705df951 100644 --- a/mmgen/main_txcreate.py +++ b/mmgen/main_txcreate.py @@ -34,7 +34,9 @@ opts_data = { ('yes', True, 'quiet', True), ('refnum', bool, 'memoless', True), ('no_refreq_tx', True, 'memoless', True)], - 'requires': [('coin', 'XMR', 'autosign', True)], + 'requires': [ + ('coin', 'XMR', 'autosign', True), + ('refnum', bool, 'trade_limit', False)], 'text': { 'desc': { 'tx': f'Create a transaction with outputs to specified coin or {gc.proj_name} addresses', diff --git a/mmgen/tx/base_swap.py b/mmgen/tx/base_swap.py index 80b77ff7..9ebaf126 100644 --- a/mmgen/tx/base_swap.py +++ b/mmgen/tx/base_swap.py @@ -11,7 +11,6 @@ tx.base_swap: base swap transaction mixin class """ from ..util import ymsg, die, cached_property -from ..addr import CoinAddr class BaseSwapMethods: @@ -85,7 +84,7 @@ class BaseSwapMethods: if rpc.data_for_coin(data, asset.coin)[key]: die(2, f'Trading is {state} on the {self.swap_proto_mod.name} network') - async def parse_network_memo(self, *, data, recv_asset_spec, recv_twmmid, chk_only=False): + async def parse_network_memo(self, *, data, recv_asset_spec, recv_twmmid=None): from ..swap.util import init_swap_proto from ..tw.ctl import TwCtl pm = self.swap_proto_mod.Memo.parse(data['memo']) @@ -100,6 +99,4 @@ class BaseSwapMethods: ymsg( f'Warning: address from registered memo ({ra.twmmid!r}) ' f'does not match our destination address ({recv_twmmid!r})') - return None if chk_only else ( - self._output(pm.asset.coin, self.cfg.network, CoinAddr(pm_proto, pm.address), ra.twmmid), - pm) + return (pm, ra) diff --git a/mmgen/tx/new_swap.py b/mmgen/tx/new_swap.py index f01bedd2..8f467027 100644 --- a/mmgen/tx/new_swap.py +++ b/mmgen/tx/new_swap.py @@ -13,6 +13,7 @@ tx.new_swap: new swap transaction class import sys, time from collections import namedtuple +from ..addr import CoinAddr from ..amt import UniAmt from ..util import msg, ymsg, is_int, die from ..swap.util import get_swap_asset, init_swap_proto @@ -60,7 +61,8 @@ class NewSwap(BaseSwapMethods, New): full_desc = f'{desc} address on the {proto.coin} {proto.network} network' res = await self.get_autochg_addr(proto, arg, exclude=[], desc=full_desc) - self.confirm_autoselected_addr(res.twmmid, res.comment, full_desc) + if not (self.refnum and desc == 'destination'): + self.confirm_autoselected_addr(res.twmmid, res.comment, full_desc) return self._output(proto.coin, proto.network, res.addr, res.twmmid) async def get_chg_output(self, arg, addrfiles): @@ -70,17 +72,6 @@ class NewSwap(BaseSwapMethods, New): message = 'Change address is not an MMGen wallet address!') return chg_output - def check_ref_memo(self, cls, memo, pm_ref): - pm = cls.parse(str(memo)) - if cls.digest(pm) != cls.digest(pm_ref): - if pm.trade_limit != pm_ref.trade_limit: - ymsg( - f'Trade limit has changed! {pm.trade_limit} != {pm_ref.trade_limit}\n' - 'Cannot create this transaction!') - die(2, - 'Newly-created memo does not match registered memo:' - f'\n new: {cls.digest(pm)}\n registered: {cls.digest(pm_ref)}') - async def process_swap_cmdline_args(self, cmd_args, addrfiles): class CmdlineArgs: # listed in command-line order @@ -129,6 +120,8 @@ class NewSwap(BaseSwapMethods, New): # arg 5: recv_spec (receive address spec) if args_in: + if self.refnum: + die(1, 'destination address must be omitted with --refnum') args.recv_spec = get_arg() if args_in: # done parsing, all args consumed @@ -165,10 +158,16 @@ class NewSwap(BaseSwapMethods, New): chg_output = await self.get_chg_output(None, addrfiles) if self.refnum: - recv_output, parsed_ref_memo = await self.parse_network_memo( + pm, resolved_addr = await self.parse_network_memo( data = self.swap_proto_rpc.validate_refnum(self.proto.coin, self.refnum), - recv_asset_spec = self.swap_recv_asset_spec, - recv_twmmid = args.recv_spec) + recv_asset_spec = self.swap_recv_asset_spec) + if pm.trade_limit: + self.swap_cfg.trade_limit = UniAmt(pm.trade_limit, from_unit='satoshi') + recv_output = self._output( + self.recv_proto.coin, + self.recv_proto.network, + CoinAddr(self.recv_proto, pm.address), + resolved_addr.twmmid) else: recv_output = await self.get_swap_output( self.recv_proto, @@ -193,9 +192,6 @@ class NewSwap(BaseSwapMethods, New): self.swap_cfg.trade_limit if isinstance(self.swap_cfg.trade_limit, UniAmt) else None)) - if self.refnum: - self.check_ref_memo(sp.Memo, memo, parsed_ref_memo) - if self.is_memoless: self.memoless_memo = str(memo) diff --git a/mmgen/tx/online.py b/mmgen/tx/online.py index ff346eac..2e20fb64 100644 --- a/mmgen/tx/online.py +++ b/mmgen/tx/online.py @@ -42,8 +42,7 @@ class OnlineSigned(Signed): await self.parse_network_memo( data = data, recv_asset_spec = self.swap_recv_asset_spec, - recv_twmmid = self.swap_recv_addr_mmid, - chk_only = True) + recv_twmmid = self.swap_recv_addr_mmid) def check_swap_expiry(self): from ..util2 import format_elapsed_hr diff --git a/test/cmdtest_d/httpd/thornode/rpc.py b/test/cmdtest_d/httpd/thornode/rpc.py index 305cf0fb..4d0f80e4 100644 --- a/test/cmdtest_d/httpd/thornode/rpc.py +++ b/test/cmdtest_d/httpd/thornode/rpc.py @@ -49,7 +49,7 @@ class ThornodeRPCServer(ThornodeServer): '0c7454c0': { # 0c7454c06d3fc0e520d34283e6f491ce7afe5abb3f569bbdfea1732e04b716ec 'reference': '13579', 'asset': 'LTC.LTC', - 'memo': '=:c:qq6myf0qd6q5tln96ntl37hq0u7940w2xvs9jsuqv0:0/3/0', + 'memo': '=:c:qq6myf0qd6q5tln96ntl37hq0u7940w2xvs9jsuqv0:7967e4/3/0', 'height': '12345678'}} inbounds_info = get_inbounds_info() diff --git a/test/cmdtest_d/swap.py b/test/cmdtest_d/swap.py index 92e09d5f..9cd0d61d 100644 --- a/test/cmdtest_d/swap.py +++ b/test/cmdtest_d/swap.py @@ -372,6 +372,7 @@ class CmdTestSwap( ('swaptxcreate_bad8', 'creating a swap transaction (bad, non-MMGen change address)'), ('swaptxcreate_bad9', 'creating a swap transaction (bad, invalid addrtype)'), ('swaptxcreate_bad10', 'creating a swap transaction (bad, --quote with no amount)'), + ('swaptxcreate_bad11', 'creating a swap transaction (bad, --refnum with --trade-limit)'), ), 'create_neterror': ( 'Swap TX create operations: network error handling', @@ -656,6 +657,9 @@ class CmdTestSwap( def swaptxcreate_bad10(self): return self._swaptxcreate_bad(['BCH', 'LTC'], add_opts=['--quote'], exit_val=1, expect1='send amount') + def swaptxcreate_bad11(self): + return self._swaptxcreate_bad(['BCH', 'LTC'], add_opts=['--refnum=123', '--trade-limit=1%'], exit_val=1, expect1='requires option') + def swaptxcreate_neterror1(self): return self._swaptxcreate_neterror('ltc_global_paused', 'Trading is paused on the') @@ -868,7 +872,7 @@ class CmdTestSwap( self.get_file_with_ext('rawtx', delete_all=True) return self._memoless_swaptxcreate( ['LTC', '2.345', f'{self.subsid}:B:1', 'BCH', f'{self.sid}:C:2'], - opts = ['--memoless'], + opts = ['--memoless', '--trade-limit=2.5%'], refreq = True) refreq_swaptxsign = rune_txsign_refreq @@ -887,7 +891,7 @@ class CmdTestSwap( refnum = True) def refnum_swaptxcreate1(self): - return self._refnum_swaptxcreate(['LTC', '2.345', f'{self.subsid}:B:1', 'BCH', f'{self.sid}:C:2']) + return self._refnum_swaptxcreate(['LTC', '2.345', f'{self.subsid}:B:1', 'BCH']) def refnum_swaptxsign1(self): return self._swaptxsign()