swaptxcreate, swaptxdo: create entry points, NewSwap tx class

This commit is contained in:
The MMGen Project 2025-02-15 09:54:18 +00:00
commit e4b6d0536c
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
16 changed files with 167 additions and 24 deletions

16
cmds/mmgen-swaptxcreate Executable file
View file

@ -0,0 +1,16 @@
#!/usr/bin/env python3
#
# MMGen Wallet, a terminal-based cryptocurrency wallet
# Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen-wallet
# https://gitlab.com/mmgen/mmgen-wallet
"""
mmgen-swaptxcreate: Create an unsigned DEX swap transaction with MMGen or non-MMGen inputs
"""
from mmgen.main import launch
launch(mod='txcreate')

16
cmds/mmgen-swaptxdo Executable file
View file

@ -0,0 +1,16 @@
#!/usr/bin/env python3
#
# MMGen Wallet, a terminal-based cryptocurrency wallet
# Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen-wallet
# https://gitlab.com/mmgen/mmgen-wallet
"""
mmgen-swaptxdo: Create, sign and broadcast a DEX swap transaction
"""
from mmgen.main import launch
launch(mod='txdo')

View file

@ -376,6 +376,7 @@ class Config(Lockable):
_autoset_opts = {
'fee_estimate_mode': _ov('nocase_pfx', ['conservative', 'economical']),
'rpc_backend': _ov('nocase_pfx', ['auto', 'httplib', 'curl', 'aiohttp', 'requests']),
'swap_proto': _ov('nocase_pfx', ['thorchain']),
}
_auto_typeset_opts = {

View file

@ -46,10 +46,10 @@ def show_hash_presets(cfg):
def gen_arg_tuple(cfg, func, text):
def help_notes(k):
def help_notes(k, *args, **kwargs):
import importlib
return getattr(importlib.import_module(
f'{cfg._opts.help_pkg}.help_notes').help_notes(proto, cfg), k)()
f'{cfg._opts.help_pkg}.help_notes').help_notes(proto, cfg), k)(*args, **kwargs)
def help_mod(modname):
import importlib

View file

@ -20,8 +20,10 @@ class help_notes:
self.proto = proto
self.cfg = cfg
def txcreate_args(self):
def txcreate_args(self, target):
return (
'COIN1 [AMT CHG_ADDR] COIN2 [ADDR]'
if target == 'swaptx' else
'[ADDR,AMT ... | DATA_SPEC] ADDR <change addr, addrlist ID or addr type>'
if self.proto.base_proto == 'Bitcoin' else
'ADDR,AMT')

View file

@ -24,11 +24,19 @@ mmgen-txcreate: Create a cryptocoin transaction with MMGen- and/or non-MMGen
from .cfg import gc, Config
from .util import fmt_list, async_run
target = gc.prog_name.split('-')[1].removesuffix('create')
opts_data = {
'filter_codes': ['-'],
'filter_codes': {
'tx': ['-', 't'],
'swaptx': ['-', 's'],
}[target],
'sets': [('yes', True, 'quiet', True)],
'text': {
'desc': f'Create a transaction with outputs to specified coin or {gc.proj_name} addresses',
'desc': {
'tx': f'Create a transaction with outputs to specified coin or {gc.proj_name} addresses',
'swaptx': f'Create a DEX swap transaction with {gc.proj_name} inputs and outputs',
}[target],
'usage': '[opts] {u_args} [addr file ...]',
'options': """
-- -h, --help Print this help message
@ -54,15 +62,17 @@ opts_data = {
-- -I, --inputs= i Specify transaction inputs (comma-separated list of
+ MMGen IDs or coin addresses). Note that ALL unspent
+ outputs associated with each address will be included.
b- -l, --locktime= t Lock time (block height or unix seconds) (default: 0)
bt -l, --locktime= t Lock time (block height or unix seconds) (default: 0)
b- -L, --autochg-ignore-labels Ignore labels when autoselecting change addresses
-- -m, --minconf= n Minimum number of confirmations required to spend
+ outputs (default: 1)
-- -q, --quiet Suppress warnings; overwrite files without prompting
b- -R, --no-rbf Make transaction non-replaceable (non-replace-by-fee
bt -R, --no-rbf Make transaction non-replaceable (non-replace-by-fee
+ according to BIP 125)
-- -v, --verbose Produce more verbose output
b- -V, --vsize-adj= f Adjust transaction's estimated vsize by factor 'f'
-s -x, --swap-proto Swap protocol to use (Default: {x_dfl},
+ Choices: {x_all})
-- -y, --yes Answer 'yes' to prompts, suppress non-essential output
e- -X, --cached-balances Use cached balances
""",
@ -70,7 +80,7 @@ opts_data = {
},
'code': {
'usage': lambda cfg, proto, help_notes, s: s.format(
u_args = help_notes('txcreate_args')),
u_args = help_notes('txcreate_args', target)),
'options': lambda cfg, proto, help_notes, s: s.format(
cfg = cfg,
cu = proto.coin,
@ -78,7 +88,9 @@ opts_data = {
fu = help_notes('rel_fee_desc'),
fl = help_notes('fee_spec_letters'),
fe_all = fmt_list(cfg._autoset_opts['fee_estimate_mode'].choices, fmt='no_spc'),
fe_dfl = cfg._autoset_opts['fee_estimate_mode'].choices[0]),
fe_dfl = cfg._autoset_opts['fee_estimate_mode'].choices[0],
x_all = fmt_list(cfg._autoset_opts['swap_proto'].choices, fmt='no_spc'),
x_dfl = cfg._autoset_opts['swap_proto'].choices[0]),
'notes': lambda cfg, help_notes, s: s.format(
c = help_notes('txcreate'),
F = help_notes('fee'),
@ -98,7 +110,7 @@ async def main():
Signable.automount_transaction(asi).check_create_ok()
from .tx import NewTX
tx1 = await NewTX(cfg=cfg, proto=cfg._proto)
tx1 = await NewTX(cfg=cfg, proto=cfg._proto, target=target)
from .rpc import rpc_init
tx1.rpc = await rpc_init(cfg)

View file

@ -24,11 +24,19 @@ from .cfg import gc, Config
from .util import die, fmt_list, async_run
from .subseed import SubSeedIdxRange
target = gc.prog_name.split('-')[1].removesuffix('do')
opts_data = {
'filter_codes': ['-'],
'filter_codes': {
'tx': ['-', 't'],
'swaptx': ['-', 's'],
}[target],
'sets': [('yes', True, 'quiet', True)],
'text': {
'desc': f'Create, sign and send an {gc.proj_name} transaction',
'desc': {
'tx': f'Create, sign and send an {gc.proj_name} transaction',
'swaptx': f'Create, sign and send a DEX swap transaction with {gc.proj_name} inputs and outputs',
}[target],
'usage': '[opts] {u_args} [addr file ...] [seed source ...]',
'options': """
-- -h, --help Print this help message
@ -62,7 +70,7 @@ opts_data = {
-- -k, --keys-from-file=f Provide additional keys for non-{pnm} addresses
-- -K, --keygen-backend=n Use backend 'n' for public key generation. Options
+ for {coin_id}: {kgs}
b- -l, --locktime= t Lock time (block height or unix seconds) (default: 0)
bt -l, --locktime= t Lock time (block height or unix seconds) (default: 0)
b- -L, --autochg-ignore-labels Ignore labels when autoselecting change addresses
-- -m, --minconf=n Minimum number of confirmations required to spend
+ outputs (default: 1)
@ -75,7 +83,7 @@ opts_data = {
-- -p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
+ for password hashing (default: '{gc.dfl_hash_preset}')
-- -P, --passwd-file= f Get {pnm} wallet passphrase from file 'f'
b- -R, --no-rbf Make transaction non-replaceable (non-replace-by-fee
bt -R, --no-rbf Make transaction non-replaceable (non-replace-by-fee
+ according to BIP 125)
-- -q, --quiet Suppress warnings; overwrite files without prompting
-- -u, --subseeds= n The number of subseed pairs to scan for (default: {ss},
@ -83,6 +91,8 @@ opts_data = {
+ wallet is scanned for subseeds.
-- -v, --verbose Produce more verbose output
b- -V, --vsize-adj= f Adjust transaction's estimated vsize by factor 'f'
-s -x, --swap-proto Swap protocol to use (Default: {x_dfl},
+ Choices: {x_all})
e- -X, --cached-balances Use cached balances
-- -y, --yes Answer 'yes' to prompts, suppress non-essential output
-- -z, --show-hash-presets Show information on available hash presets
@ -103,7 +113,7 @@ column below:
},
'code': {
'usage': lambda cfg, proto, help_notes, s: s.format(
u_args = help_notes('txcreate_args')),
u_args = help_notes('txcreate_args', target)),
'options': lambda cfg, proto, help_notes, s: s.format(
gc = gc,
cfg = cfg,
@ -119,7 +129,9 @@ column below:
ss = help_notes('dfl_subseeds'),
ss_max = SubSeedIdxRange.max_idx,
fe_all = fmt_list(cfg._autoset_opts['fee_estimate_mode'].choices, fmt='no_spc'),
fe_dfl = cfg._autoset_opts['fee_estimate_mode'].choices[0]),
fe_dfl = cfg._autoset_opts['fee_estimate_mode'].choices[0],
x_all = fmt_list(cfg._autoset_opts['swap_proto'].choices, fmt='no_spc'),
x_dfl = cfg._autoset_opts['swap_proto'].choices[0]),
'notes': lambda cfg, help_notes, s: s.format(
c = help_notes('txcreate'),
F = help_notes('fee'),
@ -139,7 +151,7 @@ seed_files = get_seed_files(cfg, cfg._args)
async def main():
tx1 = await NewTX(cfg=cfg, proto=cfg._proto)
tx1 = await NewTX(cfg=cfg, proto=cfg._proto, target=target)
from .rpc import rpc_init
tx1.rpc = await rpc_init(cfg)

27
mmgen/proto/btc/tx/new_swap.py Executable file
View file

@ -0,0 +1,27 @@
#!/usr/bin/env python3
#
# MMGen Wallet, a terminal-based cryptocurrency wallet
# Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen-wallet
# https://gitlab.com/mmgen/mmgen-wallet
"""
proto.btc.tx.new_swap: Bitcoin new swap transaction class
"""
from ....tx.new_swap import NewSwap as TxNewSwap
from .new import New
class NewSwap(New, TxNewSwap):
desc = 'Bitcoin swap transaction'
async def process_swap_cmd_args(self, cmd_args):
import sys
from ....util import msg
msg(' '.join(cmd_args))
sys.exit(0)
raise NotImplementedError('Work in Progress!')
return cmd_args

View file

@ -45,6 +45,9 @@ def _get_cls_info(clsname, modname, kwargs):
die(1, f'{ext!r}: unrecognized file extension for CompletedTX')
clsname = cls.__name__
modname = cls.__module__.rsplit('.', maxsplit=1)[-1]
elif clsname == 'New' and kwargs['target'] == 'swaptx':
clsname = 'NewSwap'
modname = 'new_swap'
kwargs['proto'] = proto
@ -94,6 +97,7 @@ BaseTX = _get('Base', 'base')
UnsignedTX = _get('Unsigned', 'unsigned')
NewTX = _get_async('New', 'new')
NewSwapTX = _get_async('NewSwap', 'new_swap')
CompletedTX = _get_async('Completed', 'completed')
SignedTX = _get_async('Signed', 'signed')
OnlineSignedTX = _get_async('OnlineSigned', 'online')

View file

@ -82,6 +82,10 @@ class New(Base):
chg_autoselected = False
_funds_available = namedtuple('funds_available', ['is_positive', 'amt'])
def __init__(self, *args, target=None, **kwargs):
self.target = target
super().__init__(*args, **kwargs)
def warn_insufficient_funds(self, amt, coin):
msg(self.msg_insufficient_funds.format(amt.hl(), coin))
@ -412,6 +416,8 @@ class New(Base):
ad_f, cmd_args = self.get_addrdata_from_files(cmd_args) # pops from end of cmd_args
from ..addrdata import TwAddrData
ad_w = await TwAddrData(self.cfg, self.proto, twctl=self.twctl)
if self.target == 'swaptx':
cmd_args = await self.process_swap_cmd_args(cmd_args)
await self.process_cmd_args(cmd_args, ad_f, ad_w)
from ..ui import do_license_msg

22
mmgen/tx/new_swap.py Executable file
View file

@ -0,0 +1,22 @@
#!/usr/bin/env python3
#
# MMGen Wallet, a terminal-based cryptocurrency wallet
# Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
# Licensed under the GNU General Public License, Version 3:
# https://www.gnu.org/licenses
# Public project repositories:
# https://github.com/mmgen/mmgen-wallet
# https://gitlab.com/mmgen/mmgen-wallet
"""
tx.new_swap: new swap transaction class
"""
from .new import New
class NewSwap(New):
desc = 'swap transaction'
async def process_swap_cmd_args(self, cmd_args):
raise NotImplementedError('Work in Progress!')
return cmd_args

View file

@ -107,6 +107,8 @@ scripts =
cmds/mmgen-seedjoin
cmds/mmgen-seedsplit
cmds/mmgen-subwalletgen
cmds/mmgen-swaptxcreate
cmds/mmgen-swaptxdo
cmds/mmgen-tool
cmds/mmgen-txbump
cmds/mmgen-txcreate

View file

@ -975,7 +975,7 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
if self.daemon.id == 'geth': # yet another Geth bug
await asyncio.sleep(0.5)
from mmgen.tx import NewTX
tx = await NewTX(cfg=cfg, proto=self.proto)
tx = await NewTX(cfg=cfg, proto=self.proto, target='tx')
tx.rpc = await self.rpc
res = await tx.get_receipt(txid)
imsg(f'Gas sent: {res.gas_sent.hl():<9} {(res.gas_sent*res.gas_price).hl2(encl="()")}')

View file

@ -148,7 +148,7 @@ class CmdTestHelp(CmdTestBase):
scripts = (
'walletgen', 'walletconv', 'walletchk', 'passchg', 'subwalletgen',
'addrgen', 'keygen', 'passgen',
'txsign', 'txdo', 'txbump'),
'txdo', 'swaptxdo', 'txsign', 'txbump'),
expect = 'Available parameters.*Preset',
pager = False)

View file

@ -26,7 +26,8 @@ class CmdTestSwap(CmdTestRegtest):
('setup', 'regtest (Bob and Alice) mode setup'),
('subgroup.init_bob', []),
('subgroup.fund_bob', ['init_bob']),
('subgroup.data', ['init_bob']),
('subgroup.data', ['fund_bob']),
('subgroup.swap', ['fund_bob']),
('stop', 'stopping regtest daemon'),
)
cmd_subgroups = {
@ -38,8 +39,9 @@ class CmdTestSwap(CmdTestRegtest):
),
'fund_bob': (
'funding Bob’s wallet',
('fund_bob', 'funding Bob’s wallet'),
('bob_bal1', 'Bob’s balance'),
('fund_bob1', 'funding Bob’s wallet (bech32)'),
('fund_bob2', 'funding Bob’s wallet (native Segwit)'),
('bob_bal', 'displaying Bob’s balance'),
),
'data': (
'OP_RETURN data operations',
@ -51,6 +53,11 @@ class CmdTestSwap(CmdTestRegtest):
('data_tx2_do', 'Creating and sending a transaction with OP_RETURN data (binary)'),
('data_tx2_chk', 'Checking the sent transaction'),
('generate3', 'Generate 3 blocks'),
('bob_listaddrs', 'Display Bob’s addresses'),
),
'swap': (
'Swap operations',
('bob_swaptxcreate1', 'Create a swap transaction'),
),
}
@ -70,9 +77,15 @@ class CmdTestSwap(CmdTestRegtest):
def addrimport_bob(self):
return self.addrimport('bob', mmtypes=['S', 'B'])
def fund_bob(self):
def fund_bob1(self):
return self.fund_wallet('bob', 'B', '500')
def fund_bob2(self):
return self.fund_wallet('bob', 'S', '500')
def bob_bal(self):
return self.user_bal('bob', '1000')
def data_tx1_create(self):
return self._data_tx_create('1', 'B:2', 'B:3', 'data', sample1)
@ -149,3 +162,13 @@ class CmdTestSwap(CmdTestRegtest):
def generate3(self):
return self.generate(3)
def bob_listaddrs(self):
t = self.spawn('mmgen-tool', ['--bob', 'listaddresses'])
return t
def bob_swaptxcreate1(self):
t = self.spawn(
'mmgen-swaptxcreate',
['-d', self.tmpdir, '-B', '--bob', 'BTC', '1.234', f'{self.sid}:S:3', 'LTC'])
return t

View file

@ -114,7 +114,7 @@ class unit_tests:
d.start()
proto = init_proto(cfg, 'btc', need_amt=True)
await NewTX(cfg=cfg, proto=proto)
await NewTX(cfg=cfg, proto=proto, target='tx')
d.stop()
d.remove_datadir()