Browse Source

new `mmgen-cli` utility

Communicate with all your coin daemons from a single utility!

Usage information and examples:

    $ mmgen-cli --help
The MMGen Project 2 days ago
parent
commit
94bee46cb8

+ 16 - 0
cmds/mmgen-cli

@@ -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')

+ 1 - 0
doc/wiki/MMGen-Wallet-command-help.md

@@ -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)

+ 35 - 0
doc/wiki/commands/command-help-cli.md

@@ -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)
+```

+ 1 - 0
mmgen/cfg.py

@@ -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 - 0
mmgen/main_cli.py

@@ -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())

+ 1 - 10
mmgen/proto/btc/regtest.py

@@ -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'

+ 16 - 0
mmgen/util2.py

@@ -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'
 

+ 1 - 0
setup.cfg

@@ -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

+ 9 - 0
test/cmdtest_d/ct_ethdev.py

@@ -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')

+ 20 - 3
test/cmdtest_d/ct_regtest.py

@@ -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))

+ 5 - 1
test/test-release.d/cfg.sh

@@ -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"