new mmgen-cli utility

Communicate with all your coin daemons from a single utility!

Usage information and examples:

    $ mmgen-cli --help
This commit is contained in:
The MMGen Project 2025-03-10 14:28:55 +00:00
commit 94bee46cb8
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
11 changed files with 179 additions and 14 deletions

16
cmds/mmgen-cli 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-cli: Communicate with a coin daemon via its JSON-RPC interface
"""
from mmgen.main import launch
launch(mod='cli')

View file

@ -1,6 +1,7 @@
* [mmgen-addrgen](commands/command-help-addrgen.md)
* [mmgen-addrimport](commands/command-help-addrimport.md)
* [mmgen-autosign](commands/command-help-autosign.md)
* [mmgen-cli](commands/command-help-cli.md)
* [mmgen-keygen](commands/command-help-keygen.md)
* [mmgen-msg](commands/command-help-msg.md)
* [mmgen-passchg](commands/command-help-passchg.md)

View file

@ -0,0 +1,35 @@
```text
MMGEN-CLI: Communicate with a coin daemon via its JSON-RPC interface
USAGE: mmgen-cli [opts] <command> <command args>
OPTIONS:
-h, --help Print this help message
--longhelp Print help message for long (global) options
-a, --ascii-output Ensure that output is ASCII encoded
-w, --wallet NAME Use tracking wallet with name NAME
The utility accepts all MMGen global configuration options and sources the user
config file, allowing users to preconfigure hosts, ports, passwords, datadirs,
tracking wallets and so forth, thus saving a great deal of typing at the
command line. This behavior may be overridden with the --skip-cfg-file option.
Arguments are given in JSON format, with lowercase ‘true’, ‘false’ and ‘null’
for booleans and None, and double-quoted strings in dicts and lists.
EXAMPLES
$ mmgen-cli --wallet=wallet2 listreceivedbyaddress 0 true
$ mmgen-cli --coin=ltc --rpc-host=orion getblockcount
$ mmgen-cli --regtest=1 --wallet=bob getbalance
$ mmgen-cli --coin=eth eth_getBalance 0x00000000219ab540356cBB839Cbe05303d7705Fa latest
$ mmgen-cli createrawtransaction \
'[{"txid":"832f5aa9af55dc453314e26869c8f96db1f2a9acac9f23ae18d396903971e0c6","vout":0}]' \
'[{"1111111111111111111114oLvT2":0.001}]'
MMGEN v15.1.dev19 March 2025 MMGEN-CLI(1)
```

View file

@ -62,6 +62,7 @@ class GlobalConstants(Lockable):
'addrgen': _cc(True, False, True, None, [], 'lmw'),
'addrimport': _cc(True, True, True, None, ['tw'], 'lmw'),
'autosign': _cc(True, True, False, '-rRb', ['rpc'], 'lm'),
'cli': _cc(True, True, True, None, ['tw'], 'lmw'),
'keygen': _cc(True, False, True, None, [], 'lmw'),
'msg': _cc(True, True, True, None, ['msg'], 'lmw'),
'passchg': _cc(False, False, False, None, [], 'lmw'),

74
mmgen/main_cli.py Executable file
View file

@ -0,0 +1,74 @@
#!/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-cli: Communicate with a coin daemon via its JSON-RPC interface
"""
import asyncio, json
from .util2 import cliargs_convert
from .cfg import gc, Config
from .rpc import rpc_init, json_encoder
opts_data = {
'text': {
'desc': 'Communicate with a coin daemon via its JSON-RPC interface',
'usage': '[opts] <command> <command args>',
'options': """
-h, --help Print this help message
--, --longhelp Print help message for long (global) options
-a, --ascii-output Ensure that output is ASCII encoded
-w, --wallet=NAME Use tracking wallet with name NAME
""",
'notes': """
The utility accepts all {pn} global configuration options and sources the user
config file, allowing users to preconfigure hosts, ports, passwords, datadirs,
tracking wallets and so forth, thus saving a great deal of typing at the
command line. This behavior may be overridden with the --skip-cfg-file option.
Arguments are given in JSON format, with lowercase true, false and null
for booleans and None, and double-quoted strings in dicts and lists.
EXAMPLES
$ mmgen-cli --wallet=wallet2 listreceivedbyaddress 0 true
$ mmgen-cli --coin=ltc --rpc-host=orion getblockcount
$ mmgen-cli --regtest=1 --wallet=bob getbalance
$ mmgen-cli --coin=eth eth_getBalance 0x00000000219ab540356cBB839Cbe05303d7705Fa latest
$ mmgen-cli createrawtransaction \\
'[{{"txid":"832f5aa9af55dc453314e26869c8f96db1f2a9acac9f23ae18d396903971e0c6","vout":0}}]' \\
'[{{"1111111111111111111114oLvT2":0.001}}]'
"""
},
'code': {
'notes': lambda cfg, s, help_notes: s.format(
pn = gc.proj_name)
}
}
cfg = Config(opts_data=opts_data)
cmd, *args = cfg._args
async def main():
c = await rpc_init(cfg)
ret = await c.call(cmd, *cliargs_convert(args), wallet=cfg.wallet)
print(
(ascii(ret) if cfg.ascii_output else ret) if isinstance(ret, str) else
json.dumps(ret, cls=json_encoder, indent=4, ensure_ascii=cfg.ascii_output))
asyncio.run(main())

View file

@ -22,6 +22,7 @@ proto.btc.regtest: Coin daemon regression test mode setup and operations
import os, shutil, json
from ...util import msg, gmsg, die, capfirst, suf
from ...util2 import cliargs_convert
from ...protocol import init_proto
from ...rpc import rpc_init, json_encoder
from ...objmethods import MMGenObject
@ -46,16 +47,6 @@ def create_data_dir(cfg, data_dir):
except:
pass
def cliargs_convert(args):
def gen():
for arg in args:
try:
yield json.loads(arg) # list, dict, bool, int, null
except:
yield arg # arbitrary string
return tuple(gen())
class MMGenRegtest(MMGenObject):
rpc_user = 'bobandalice'

View file

@ -197,6 +197,22 @@ def decode_pretty_hexdump(data):
msg('Data not in hexdump format')
return False
def cliargs_convert(args):
# return str instead of float for input into JSON-RPC
def float_parser(n):
return n
import json
def gen():
for arg in args:
try:
yield json.loads(arg, parse_float=float_parser) # list, dict, bool, int, null, float
except json.decoder.JSONDecodeError:
yield arg # arbitrary string
return tuple(gen())
class ExpInt(int):
'encode or parse an integer in exponential notation with specified precision'

View file

@ -102,6 +102,7 @@ scripts =
cmds/mmgen-addrgen
cmds/mmgen-addrimport
cmds/mmgen-autosign
cmds/mmgen-cli
cmds/mmgen-keygen
cmds/mmgen-msg
cmds/mmgen-passchg

View file

@ -193,6 +193,7 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
('addrimport', 'importing addresses'),
('addrimport_dev_addr', "importing dev faucet address 'Ox00a329c..'"),
('fund_dev_address', 'funding the default (Parity dev) address'),
('cli_dev_balance', 'mmgen-cli eth_getBalance'),
),
'msg': (
'message signing',
@ -568,6 +569,14 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
t.expect('version')
return t
def cli_dev_balance(self):
t = self.spawn(
'mmgen-cli',
[f'--coin={self.proto.coin}', '--regtest=1', 'eth_getBalance', '0x'+dfl_devaddr, 'latest'])
if self.daemon.id == 'geth':
t.expect('0x33b2e3c91ec0e9113986000')
return t
async def _wallet_upgrade(self, src_fn, expect1, expect2=None):
if self.proto.coin == 'ETC':
msg(f'skipping test {self.test_name!r} for ETC')

View file

@ -196,6 +196,7 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
'miscellaneous commands',
('daemon_version', 'mmgen-tool daemon_version'),
('halving_calculator_bob', 'halving calculator (Bob)'),
('cli_txcreate', '‘mmgen-cli createrawtransaction’'),
),
'init_bob': (
'creating Bob’s MMGen wallet and tracking wallet',
@ -223,7 +224,7 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
('fund_bob', 'funding Bob’s wallet'),
('fund_alice', 'funding Alice’s wallet'),
('generate', 'mining a block'),
('bob_bal1', 'Bob’s balance'),
('bob_bal1_cli', 'Bob’s balance (via ‘mmgen-cli’)'),
('generate_extra_deterministic', 'generate extra blocks for deterministic run'),
),
'msg': (
@ -547,6 +548,18 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
t.expect('time until halving')
return t
def cli_txcreate(self):
txid = 'beadcafe' * 8
return self.spawn(
'mmgen-cli',
[
'--regtest=1',
f'--coin={self.proto.coin}',
'createrawtransaction',
f'[{{"txid":"{txid}","vout":7}}]',
f'[{{"{self.burn_addr}":0.001}}]'
])
def walletgen(self, user):
t = self.spawn('mmgen-walletgen', ['-q', '-r0', '-p1', f'--{user}'], no_passthru_opts=True)
t.passphrase_new(f'new {dfl_wcls.desc}', rt_pw)
@ -777,8 +790,12 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
def alice_bal2(self):
return self.user_bal('alice', rtBals[8])
def bob_bal1(self):
return self.user_bal('bob', rtFundAmt, self._cashaddr_opt(0))
def bob_bal1_cli(self):
t = self.spawn(
'mmgen-cli',
['--regtest=1', '--wallet=bob', f'--coin={self.proto.coin}', 'getbalance', '*', '0', 'true'])
t.expect(rtFundAmt + '.00')
return t
def bob_bal2(self):
return self.user_bal('bob', rtBals[0], self._cashaddr_opt(1))

View file

@ -230,7 +230,11 @@ init_tests() {
t_ltc_rt="- $cmdtest_py --coin=ltc regtest"
d_eth="operations for Ethereum using devnet"
t_eth="geth $cmdtest_py --coin=eth ethdev"
t_eth="
geth $cmdtest_py --coin=eth ethdev
reth $cmdtest_py --coin=eth --daemon-id=reth ethdev
"
[ "$FAST" ] && t_eth_skip='reth'
d_etc="operations for Ethereum Classic using devnet"
t_etc="parity $cmdtest_py --coin=etc ethdev"