From 8a23f9867f5381d4a0d5736dd710fdbfb572cfc7 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Fri, 2 Oct 2026 10:02:32 +0000 Subject: [PATCH] new `mmgen-remote` utility Command invocation syntax is the same as for the `mmgen-tool` utility. Currently, only THORChain public endpoints are supported Help: $ mmgen-remote --coin=rune --help Sample invocation: $ mmgen-remote --log --proxy=localhost:9050 --coin=rune lastblock coin=btc Testing: $ test/cmdtest.py -e remote --- cmds/mmgen-remote | 16 +++++ mmgen/cfg.py | 1 + mmgen/data/release_date | 2 +- mmgen/data/version | 2 +- mmgen/help/remote.py | 46 +++++++++++++ mmgen/main_remote.py | 87 ++++++++++++++++++++++++ mmgen/proto/rune/params.py | 2 + mmgen/proto/rune/rpc/remote.py | 74 ++++++++++++++++++-- mmgen/util.py | 13 ++++ setup.cfg | 1 + test/cmdtest_d/httpd/thornode/midgard.py | 68 ++++++++++++++++++ test/cmdtest_d/httpd/thornode/rpc.py | 26 +++++++ test/cmdtest_d/include/cfg.py | 1 + test/cmdtest_d/remote.py | 72 ++++++++++++++++++++ test/cmdtest_d/rune.py | 8 ++- test/ref/thorchain/inbound.json | 1 + test/test-release.d/cfg.sh | 1 + 17 files changed, 414 insertions(+), 7 deletions(-) create mode 100755 cmds/mmgen-remote create mode 100755 mmgen/help/remote.py create mode 100755 mmgen/main_remote.py create mode 100755 test/cmdtest_d/httpd/thornode/midgard.py create mode 100755 test/cmdtest_d/remote.py create mode 100644 test/ref/thorchain/inbound.json diff --git a/cmds/mmgen-remote b/cmds/mmgen-remote new file mode 100755 index 00000000..917ac8de --- /dev/null +++ b/cmds/mmgen-remote @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# +# MMGen Wallet, a terminal-based cryptocurrency wallet +# Copyright (C)2013-2026 The MMGen Project +# 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-remote: Retrieve data from a remote JSON-RPC server +""" + +from mmgen.main import launch +launch(mod='remote') diff --git a/mmgen/cfg.py b/mmgen/cfg.py index 5c3214c7..4f172b39 100755 --- a/mmgen/cfg.py +++ b/mmgen/cfg.py @@ -81,6 +81,7 @@ class GlobalConstants(Lockable): 'passchg': _cc(False, False, False, None, [], 'lmw'), 'passgen': _cc(False, False, False, None, [], 'lmw'), 'regtest': _cc(True, True, True, None, ['tw'], 'lmw'), + 'remote': _cc(True, True, True, None, [], 'lmw'), 'seedjoin': _cc(False, False, False, None, [], 'lmw'), 'seedsplit': _cc(False, False, False, None, [], 'lmw'), 'subwalletgen': _cc(False, False, False, None, [], 'lmw'), diff --git a/mmgen/data/release_date b/mmgen/data/release_date index ccdc8274..bc7495d7 100644 --- a/mmgen/data/release_date +++ b/mmgen/data/release_date @@ -1 +1 @@ -September 2026 +October 2026 diff --git a/mmgen/data/version b/mmgen/data/version index be2f4af2..d288fd40 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -16.3.0dev11 +16.3.0dev12 diff --git a/mmgen/help/remote.py b/mmgen/help/remote.py new file mode 100755 index 00000000..b9d3a1a0 --- /dev/null +++ b/mmgen/help/remote.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# +# MMGen Wallet, a terminal-based cryptocurrency wallet +# Copyright (C)2013-2026 The MMGen Project +# 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 + +""" +help.remote: command help for the ‘mmgen-remote’ utility +""" + +from ..tool.help import gen_main_help +from ..cfg import gc +from ..color import yellow +from ..util import fmt_with_color + +def help(proto, cfg): + + warning = """ + Communicating with a remote server can harm your privacy! + To improve anonymity, proxy requests via Tor or I2P + """ + + footer = f""" + For individual command usage, invoke ‘{gc.prog_name} --coin={cfg.coin.lower()} help COMMAND’ + """ + + match cfg.coin: + case 'RUNE': + from ..proto.rune.rpc.remote import THORChainRemoteRPCClient as rpc + clist = '\n ' + '\n'.join(gen_main_help( + mods_data = rpc.mods, + get_mod_cls_func = lambda x: rpc, + indent = ' ')) + case _: + clist = f'No configured commands for coin {cfg.coin}\n Supported coins: RUNE' + warning = '' + footer = '' + + return '{a}\nCOMMANDS:\n {b}{c}\n'.format( + a = '\n' + fmt_with_color(warning.strip(), yellow) if warning else '', + b = clist, + c = '\n' + footer.strip() if footer else '') diff --git a/mmgen/main_remote.py b/mmgen/main_remote.py new file mode 100755 index 00000000..1b3135cb --- /dev/null +++ b/mmgen/main_remote.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# +# MMGen Wallet, a terminal-based cryptocurrency wallet +# Copyright (C)2013-2026 The MMGen Project +# 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-remote: Retrieve data from a remote JSON-RPC server +""" + +from .cfg import Config +from .util import Msg, die, fmt_list, async_run + +opts_data = { + 'filter_codes': ['-'], + 'text': { + 'desc': 'Retrieve data from a remote JSON-RPC server', + 'usage': '[OPTS] COMMAND ARGS', + 'options': """ + -- -h, --help Print this help message + -- --, --longhelp Print help message for long (global) options + -- -d, --outdir=D Output logfile to directory ‘D’ instead of working dir + -- -l, --log Log output to file + X- -s, --swap-proto Swap protocol to use (Default: {x_dfl}, Choices: {x_all}) + X- -x, --proxy=P Connect to remote server(s) via SOCKS5h proxy P (host:port) + + Use special value ‘env’ to honor *_PROXY env vars instead. + """, + 'notes': '{n_rc}', + }, + 'code': { + 'options': lambda cfg, proto, help_notes, s: s.format( + 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_mod, help_notes, s: s.format( + n_rc = help_mod('remote')) + } +} + +cfg = Config(opts_data=opts_data) + +def log(text, cmd, args, kwargs): + args_disp = '-'.join(args) + '-'.join(f'{k}={v}' for k,v in kwargs.items()) + fn = 'mmgen-remote-{a}-{b}{c}.json'.format( + a = cfg.coin, + b = cmd.replace('_','-'), + c = '-' + args_disp if args_disp else '') + from .fileutil import write_data_to_file + write_data_to_file(cfg, fn, text) + +async def main(): + from .tool import get_cmds, process_args + + match cfg.coin: + case 'RUNE': + from .proto.rune.rpc.remote import THORChainRemoteRPCClient as cls + cmds = get_cmds(cls.mods) + case _: + cmds = () + + match cfg._args: + case ['help']: + from .help.remote import help + Msg(help(cfg._proto, cfg)) + case ['help', cmd]: + if cmd in cmds: + from .tool.help import gen_tool_cmd_usage + Msg('\n'.join(gen_tool_cmd_usage(None, cmd, lambda x: cls))) + else: + fs = '‘{}’: invalid command\n\nSupported commands:\n{}' + die(1, fs.format(cmd, fmt_list(cmds, fmt='col', indent=' ' * 4))) + case [cmd, *cmd_args] if cmd in cmds: + args, kwargs = process_args(cmd, cmd_args, cls) + res = getattr(cls(cfg, cfg._proto), cmd)(*args, **kwargs) + import json + from .ui import colorize_json + text = json.dumps(res, indent=2) + cfg._util.stdout_or_pager_nl(colorize_json(text) if cfg.color else text) + if cfg.log: + log(text, cmd, args, kwargs) + case _: + cfg._usage() + +async_run(cfg, main) diff --git a/mmgen/proto/rune/params.py b/mmgen/proto/rune/params.py index 09137d19..f58150ea 100755 --- a/mmgen/proto/rune/params.py +++ b/mmgen/proto/rune/params.py @@ -50,6 +50,7 @@ class mainnet(CoinProtocol.Secp256k1): rpc_remote_rest_params = {'host': Hostname('gateway.liquify.com/chain/thorchain_api')} rpc_remote_rpc_params = {'host': Hostname('gateway.liquify.com/chain/thorchain_rpc')} rpc_remote_swap_params = {'host': Hostname('gateway.liquify.com/chain/thorchain_api')} + rpc_remote_midgard_params = {'host': Hostname('gateway.liquify.com/chain/thorchain_midgard')} def decode_addr(self, addr): hrp, data = bech32.bech32_decode(addr) @@ -82,3 +83,4 @@ class regtest(testnet): # regtest is deprecated testnet 'verify': False} rpc_remote_rpc_params = rpc_remote_rest_params rpc_remote_swap_params = rpc_remote_rest_params | {'host': Hostname('localhost:18900')} + rpc_remote_midgard_params = rpc_remote_rest_params | {'host': Hostname('localhost:19000')} diff --git a/mmgen/proto/rune/rpc/remote.py b/mmgen/proto/rune/rpc/remote.py index a2245757..7780d779 100755 --- a/mmgen/proto/rune/rpc/remote.py +++ b/mmgen/proto/rune/rpc/remote.py @@ -20,10 +20,15 @@ from ....rpc.remote import RemoteRPCClient # throws exception on error: def process_response(json_response, errmsg): data = json.loads(json_response) - if data['result'] is None: - from ....util import die - die('RPCFailure', errmsg) - return data['result'] + if isinstance(data, dict) and 'result' in data: + if data['result'] is None: + from ....util import die + die('RPCFailure', errmsg) + return data['result'] + return data + +def data_for_coin(ret, coin): + return next(iter([e for e in ret if e['chain'] == coin.upper()] or [None])) if coin else ret # HTTP POST, JSON-RPC response: class ThornodeRemoteRPCClient(RemoteJSONClient): @@ -35,10 +40,26 @@ class ThornodeRemoteRESTClient(RemoteJSONClient): params = 'rpc_remote_rest_params' timeout = 15 +class ThornodeRemoteMidgardClient(RemoteJSONClient): + params = 'rpc_remote_midgard_params' + timeout = 30 + class THORChainRemoteRPCClient(RemoteRPCClient): "retrieve data from a remote THORChain JSON-RPC endpoint" server_proto = 'THORChain' + mods = { + 'get': ( + 'ping', + 'health', + 'pools', + 'lastblock', + 'mimir', + 'actions', + 'inbound_addrs', + 'acct_info', + 'balance', + 'tx_info')} def __init__(self, cfg, proto): for k, v in proto.rpc_remote_params.items(): @@ -47,6 +68,51 @@ class THORChainRemoteRPCClient(RemoteRPCClient): self.caps = ('lbl_id',) self.rest_api = ThornodeRemoteRESTClient(cfg, proto) self.rpc_api = ThornodeRemoteRPCClient(cfg, proto) + self.midgard_api = ThornodeRemoteMidgardClient(cfg, proto) + + def ping(self): + "ping the remote REST endpoint" + return process_response( + self.rest_api.get(path='/thorchain/ping'), + errmsg = f'pinging remote endpoint {self.rest_api.host} failed') + + def health(self): + "check the health of the THORChain network" + return process_response( + self.midgard_api.get(path='/v2/health'), + errmsg = f'getting health status from {self.midgard_api.host} failed') + + def pools(self): + "get available pools on the THORChain network" + return process_response( + self.midgard_api.get(path='/v2/pools'), + errmsg = f'getting pools from {self.midgard_api.host} failed') + + def lastblock(self, *, coin: str=None): # noqa: RUF013 + "get last block info for all coins or a given coin" + return data_for_coin(process_response( + self.rest_api.get(path='/thorchain/lastblock'), + errmsg = 'unable to retrieve lastblock data'), coin) + + def mimir(self): + "get Mimir constants for the THORChain network" + return process_response( + self.rest_api.get(path='/thorchain/mimir'), + errmsg = 'get mimir info failed') + + def actions(self, *, addr: str=None, txid: str=None): # noqa: RUF013 + "get actions for a given address or TxID" + assert addr or txid, 'one of addr or txid must be specified' + qs = '&'.join(f'{k}={v}' for k, v in (('address', addr), ('txid', txid)) if v) + return process_response( + self.midgard_api.get(path=f'/v2/actions?{qs}'), + errmsg = 'get actions info failed') + + def inbound_addrs(self, *, coin: str=None): # noqa: RUF013 + "get current inbound (vault) addresses" + return data_for_coin(process_response( + self.rest_api.get(path='/thorchain/inbound_addresses'), + errmsg = 'unable to retrieve inbound (vault) addresses'), coin) def acct_info(self, addr: str, *, block=None): "get account information for a given address" diff --git a/mmgen/util.py b/mmgen/util.py index 28c7b7b8..a538705e 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -60,8 +60,10 @@ class Util: if cfg.pager: from .ui import do_pager self.stdout_or_pager = do_pager + self.stdout_or_pager_nl = do_pager else: self.stdout_or_pager = Msg_r + self.stdout_or_pager_nl = Msg def compare_chksums( self, @@ -192,6 +194,17 @@ def fmt(s, *, indent='', strip_char=None, append='\n'): + ('\n'+indent).join(l.lstrip(strip_char) for l in s.strip().splitlines()) + append) +def fmt_with_color(s, color_func, *, indent='', strip_char=None, append='\n'): + """ + de-indent multiple lines of text, or indent with specified string, with color + + each line is colored separately as a workaround for a ‘less’ pager ANSI color bug + """ + return ( + indent + + ('\n'+indent).join(color_func(l.lstrip(strip_char)) for l in s.strip().splitlines()) + + append) + def fmt_list(iterable, *, fmt='dfl', indent='', conv=None): "pretty-format a list" _conv, sep, lq, rq = { diff --git a/setup.cfg b/setup.cfg index 1bb88286..6765e896 100644 --- a/setup.cfg +++ b/setup.cfg @@ -131,6 +131,7 @@ scripts = cmds/mmgen-passchg cmds/mmgen-passgen cmds/mmgen-regtest + cmds/mmgen-remote cmds/mmgen-seedjoin cmds/mmgen-seedsplit cmds/mmgen-subwalletgen diff --git a/test/cmdtest_d/httpd/thornode/midgard.py b/test/cmdtest_d/httpd/thornode/midgard.py new file mode 100755 index 00000000..8e4f464c --- /dev/null +++ b/test/cmdtest_d/httpd/thornode/midgard.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# +# MMGen Wallet, a terminal-based cryptocurrency wallet +# Copyright (C)2013-2026 The MMGen Project +# 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 + +""" +test.cmdtest_d.httpd.thornode.midgard: Thornode Midgard HTTP server +""" + +import re, json +from wsgiref.util import request_uri + +from . import ThornodeServer + +actions_info = { + 'actions': [{ + 'date': '987654321', + 'height': '1234', + }, + ], + 'count': '1', + 'meta': { + 'nextPageToken': '123456789', + 'prevPageToken': '123456788'}} + +health_info = { + 'database': True, + 'inSync': True, + 'scannerHeight': '987654321'} + +class ThornodeMidgardServer(ThornodeServer): + port = 19000 + name = 'thornode Midgard server' + + def make_response_body(self, method, environ): + + class responses: + + # pylint: disable=unsubscriptable-object + + def health(m): + return health_info + + def actions(m): + assert m[1] in ( # FIXME - for now, just check well-formedness of query string + '?address=thor1abcdefg', + '?txid=deadbeef', + '?address=thor1abcdefg&txid=deadbeef') + return actions_info + + pat_info = ( + ('health', r'/v2/health'), + ('actions', r'/v2/actions?(.*)')) + + req_str = request_uri(environ) + + for name, pat in pat_info: + if m := re.search(pat, req_str): + assert method == 'GET' + res = getattr(responses, name)(m) + return json.dumps({'result': res}).encode() + + raise ValueError(f'‘{req_str}’: malformed query path') diff --git a/test/cmdtest_d/httpd/thornode/rpc.py b/test/cmdtest_d/httpd/thornode/rpc.py index be151ad7..b27798c5 100755 --- a/test/cmdtest_d/httpd/thornode/rpc.py +++ b/test/cmdtest_d/httpd/thornode/rpc.py @@ -17,6 +17,19 @@ from wsgiref.util import request_uri from . import ThornodeServer +ping_info = {'ping': 'pong'} + +lastblock_info = [{ + 'chain': 'BTC', + 'last_observed_in': 969302, + 'last_signed_out': 28043381, + 'thorchain': 28043387 + }, { + 'chain': 'ETH', + 'last_observed_in': 26090346, + 'last_signed_out': 28043381, + 'thorchain': 28043387}] + class ThornodeRPCServer(ThornodeServer): port = 18800 name = 'thornode RPC server' @@ -74,7 +87,20 @@ class ThornodeRPCServer(ThornodeServer): res.update({'hash': 'BD30B8FD9FC278A26E7A81ABB36C4709260DE64087EB578900BD23CEBF11D1A2'}) return res + def ping(m, length): + return ping_info + + def lastblock(m, length): + return lastblock_info + + def inbound_addrs(m, length): + with open('test/ref/thorchain/inbound.json') as fh: + return json.loads(fh.read()) + pat_info = ( # these are matched in order, so more specific patterns must go first + ('ping', 'GET', r'/thorchain/ping'), + ('lastblock', 'GET', r'/thorchain/lastblock(.*)'), + ('inbound_addrs', 'GET', r'/thorchain/inbound_addresses'), ('balance', 'GET', r'/bank/balances/(\S+)'), ('acct_info', 'GET', r'/auth/accounts/(\S+)'), ('tx_info', 'POST', r'/tx$'), diff --git a/test/cmdtest_d/include/cfg.py b/test/cmdtest_d/include/cfg.py index fea4c33c..a3ea08b3 100755 --- a/test/cmdtest_d/include/cfg.py +++ b/test/cmdtest_d/include/cfg.py @@ -43,6 +43,7 @@ cmd_groups_dfl = { 'regtest': gd('CmdTestRegtest', {}), 'op_return': gd('CmdTestOpReturn', {}), 'swap': gd('CmdTestSwap', {}), + 'remote': gd('CmdTestRemote', {}), 'ethswap': gd('CmdTestEthSwap', {}), # 'chainsplit': gd('CmdTestChainsplit', {}), 'ethdev': gd('CmdTestEthdev', {}), diff --git a/test/cmdtest_d/remote.py b/test/cmdtest_d/remote.py new file mode 100755 index 00000000..bbb67367 --- /dev/null +++ b/test/cmdtest_d/remote.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# +# MMGen Wallet, a terminal-based cryptocurrency wallet +# Copyright (C)2013-2026 The MMGen Project +# 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 + +""" +test.cmdtest_d.remote: THORChain remote RPC tests for the cmdtest.py test suite +""" + +from .base import CmdTestBase +from .swap import CmdTestSwapMethods +from .rune import CmdTestRuneMethods + +class CmdTestRemote(CmdTestBase, CmdTestSwapMethods, CmdTestRuneMethods): + 'fetching data from remote JSON-RPC server' + + networks = ('btc',) + tmpdir_nums = [37] + thornode_servers = ('rpc', 'midgard') + + cmd_group = ( + ('ping', 'pinging remote REST server'), + ('health', 'checking health of THORChain network'), + ('inbounds', 'getting inbound addresses (all coins)'), + ('inbound_ltc', 'getting inbound address for LTC'), + ('lastblock', 'getting lastblock info'), + ('lastblock_btc', 'getting lastblock info for BTC'), + ('actions_addr', 'getting actions for address'), + ('actions_txid', 'getting actions for txid'), + ('actions_addr_txid', 'getting actions for txid and addr'), + ('stop_thornode_servers','stopping the Thornode RPC and Midgard servers')) + + def __init__(self, cfg, trunner, cfgs, spawn): + super().__init__(cfg, trunner, cfgs, spawn) + if trunner is None: + return + self.start_thornode_servers() + + def ping(self, *, asset=None): + return self._rune_remote('ping') + + def health(self, *, asset=None): + return self._rune_remote('health') + + def inbounds(self): + return self._rune_remote('inbound_addrs') + + def inbound_ltc(self): + return self._rune_remote('inbound_addrs', ['coin=ltc']) + + def lastblock(self, *, asset=None): + return self._rune_remote('lastblock') + + def lastblock_btc(self, *, asset=None): + return self._rune_remote('lastblock', ['coin=btc']) + + def actions_addr(self, *, asset=None): + return self._rune_remote('actions', ['addr=thor1abcdefg']) + + def actions_txid(self, *, asset=None): + return self._rune_remote('actions', ['txid=deadbeef']) + + def actions_addr_txid(self, *, asset=None): + return self._rune_remote('actions', ['addr=thor1abcdefg', 'txid=deadbeef']) + + def stop_servers(self): + self.stop_thornode_servers(['rpc','midgard']) diff --git a/test/cmdtest_d/rune.py b/test/cmdtest_d/rune.py index f2743672..9f7abba4 100755 --- a/test/cmdtest_d/rune.py +++ b/test/cmdtest_d/rune.py @@ -16,7 +16,7 @@ from hashlib import md5 from mmgen.fileutil import get_data_from_file -from .include.common import dfl_words_file +from .include.common import dfl_words_file, cleanup_env from .include.proxy import TestProxy from .ethdev import CmdTestEthdevMethods from .base import CmdTestBase @@ -77,6 +77,12 @@ class CmdTestRuneMethods: return 'ok' return t + def _rune_remote(self, cmd, cmd_args=(), color=True): + return self.spawn( + 'mmgen-remote', + ['--coin=rune', '--regtest=1', f'--color={int(color)}'] + [cmd] + list(cmd_args), + env = cleanup_env(self.cfg)) + class CmdTestRune(CmdTestRuneMethods, CmdTestEthdevMethods, CmdTestBase, CmdTestShared): 'THORChain RUNE tracking wallet and transacting operations' networks = ('rune',) diff --git a/test/ref/thorchain/inbound.json b/test/ref/thorchain/inbound.json new file mode 100644 index 00000000..fe4112b2 --- /dev/null +++ b/test/ref/thorchain/inbound.json @@ -0,0 +1 @@ +[{"chain":"BCH","pub_key":"thorpub1addwnpepqvg02tp8umuqp36wy5ptpeqavkdu58gk0z8e7amnjyh3cpslfq9fqxhlmvp","address":"qpxmqf20pnzywzmjevclquekjzlvq5hx25s4ucj53r","halted":false,"global_trading_paused":false,"chain_trading_paused":false,"chain_lp_actions_paused":false,"observed_fee_rate":"2","gas_rate":"3","gas_rate_units":"satsperbyte","outbound_tx_size":"1500","outbound_fee":"74450","dust_threshold":"10000"},{"chain":"BTC","pub_key":"thorpub1addwnpepqvg02tp8umuqp36wy5ptpeqavkdu58gk0z8e7amnjyh3cpslfq9fqxhlmvp","address":"bc1qfkcz2ncvc3rskuktx8c8xd5shmq99ej45gq8wu","halted":false,"global_trading_paused":false,"chain_trading_paused":false,"chain_lp_actions_paused":false,"observed_fee_rate":"4","gas_rate":"6","gas_rate_units":"satsperbyte","outbound_tx_size":"1000","outbound_fee":"2120","dust_threshold":"1000"},{"chain":"ETH","pub_key":"thorpub1addwnpepqvg02tp8umuqp36wy5ptpeqavkdu58gk0z8e7amnjyh3cpslfq9fqxhlmvp","address":"0x41c88d3c0eda3f4df0bb4e5d0f4c81cc0232902c","router":"0xD37BbE5744D730a1d98d8DC97c42F0Ca46aD7146","halted":false,"global_trading_paused":false,"chain_trading_paused":false,"chain_lp_actions_paused":false,"observed_fee_rate":"10","gas_rate":"15","gas_rate_units":"gwei","outbound_tx_size":"100000","outbound_fee":"9190","dust_threshold":"1000"},{"chain":"LTC","pub_key":"thorpub1addwnpepqvg02tp8umuqp36wy5ptpeqavkdu58gk0z8e7amnjyh3cpslfq9fqxhlmvp","address":"rltc1qlvwym7fha5cuvs0c2vy2l86r7a8ayt8chwqsyr","halted":false,"global_trading_paused":false,"chain_trading_paused":false,"chain_lp_actions_paused":false,"observed_fee_rate":"74","gas_rate":"111","gas_rate_units":"satsperbyte","outbound_tx_size":"250","outbound_fee":"355075","dust_threshold":"100000"}] \ No newline at end of file diff --git a/test/test-release.d/cfg.sh b/test/test-release.d/cfg.sh index 2573fcd9..abead130 100755 --- a/test/test-release.d/cfg.sh +++ b/test/test-release.d/cfg.sh @@ -311,6 +311,7 @@ init_tests() { t_rune=" - $cmdtest_py --coin=rune rune - $cmdtest_py --coin=ltc runeswap + - $cmdtest_py remote " [ "$SOC" ] && xmr_env=(