swaptxcreate, swaptxdo: create entry points, NewSwap tx class
This commit is contained in:
parent
dc028988cb
commit
e4b6d0536c
16 changed files with 167 additions and 24 deletions
16
cmds/mmgen-swaptxcreate
Executable file
16
cmds/mmgen-swaptxcreate
Executable 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
16
cmds/mmgen-swaptxdo
Executable 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')
|
||||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
27
mmgen/proto/btc/tx/new_swap.py
Executable 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
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
22
mmgen/tx/new_swap.py
Executable 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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="()")}')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue