RUNE tracking wallet support
Testing/demo:
$ test/cmdtest.py --demo --coin=rune rune
This commit is contained in:
parent
32581129d5
commit
2993fdba9e
14 changed files with 280 additions and 5 deletions
|
|
@ -1 +1 @@
|
|||
15.1.dev41
|
||||
15.1.dev42
|
||||
|
|
|
|||
|
|
@ -415,3 +415,7 @@ class MMGenPWIDString(MMGenLabel):
|
|||
desc = 'password ID string'
|
||||
forbidden = list(' :/\\')
|
||||
trunc_ok = False
|
||||
|
||||
class Hostname(MMGenLabel):
|
||||
max_len = 256
|
||||
color = 'pink'
|
||||
|
|
|
|||
18
mmgen/proto/rune/addrdata.py
Executable file
18
mmgen/proto/rune/addrdata.py
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
#!/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
|
||||
|
||||
"""
|
||||
proto.rune.addrdata: THORChain TwAddrData class
|
||||
"""
|
||||
|
||||
from ...addrdata import TwAddrDataWithStore
|
||||
|
||||
class THORChainTwAddrData(TwAddrDataWithStore):
|
||||
pass
|
||||
|
|
@ -13,6 +13,7 @@ proto.rune.params: THORChain protocol
|
|||
"""
|
||||
|
||||
from ...protocol import CoinProtocol, decoded_addr, _nw
|
||||
from ...obj import Hostname
|
||||
from ...addr import CoinAddr
|
||||
from ...contrib import bech32
|
||||
|
||||
|
|
@ -27,7 +28,7 @@ class mainnet(CoinProtocol.Secp256k1):
|
|||
coin_amt = 'UniAmt'
|
||||
max_tx_fee = 1 # TODO
|
||||
caps = ()
|
||||
mmcaps = ()
|
||||
mmcaps = ('tw', 'rpc_init', 'rpc_remote')
|
||||
base_proto = 'THORChain'
|
||||
base_proto_coin = 'RUNE'
|
||||
base_coin = 'RUNE'
|
||||
|
|
@ -41,6 +42,10 @@ class mainnet(CoinProtocol.Secp256k1):
|
|||
encode_wif = btc_mainnet.encode_wif
|
||||
decode_wif = btc_mainnet.decode_wif
|
||||
|
||||
rpc_remote_params = {'server_domain': Hostname('ninerealms.com')}
|
||||
rpc_remote_http_params = {'host': Hostname('thornode.ninerealms.com')}
|
||||
rpc_remote_rpc_params = {'host': Hostname('rpc.ninerealms.com')}
|
||||
|
||||
def decode_addr(self, addr):
|
||||
hrp, data = bech32.bech32_decode(addr)
|
||||
assert hrp == self.bech32_hrp, f'{hrp!r}: invalid bech32 hrp (should be {self.bech32_hrp!r})'
|
||||
|
|
@ -58,6 +63,15 @@ class mainnet(CoinProtocol.Secp256k1):
|
|||
|
||||
class testnet(mainnet): # testnet is stagenet
|
||||
bech32_hrp = 'sthor'
|
||||
rpc_remote_http_params = {'host': Hostname('stagenet-thornode.ninerealms.com')}
|
||||
rpc_remote_rpc_params = {'host': Hostname('stagenet-rpc.ninerealms.com')}
|
||||
|
||||
class regtest(testnet): # regtest is deprecated testnet
|
||||
bech32_hrp = 'tthor'
|
||||
rpc_remote_params = {
|
||||
'server_domain': Hostname('localhost')}
|
||||
rpc_remote_http_params = {
|
||||
'proto': 'http',
|
||||
'host': Hostname('localhost:18800'),
|
||||
'verify': False}
|
||||
rpc_remote_rpc_params = rpc_remote_http_params
|
||||
|
|
|
|||
50
mmgen/proto/rune/rpc/remote.py
Executable file
50
mmgen/proto/rune/rpc/remote.py
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
#!/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
|
||||
|
||||
"""
|
||||
proto.rune.rpc.remote: THORChain base protocol remote RPC client for the MMGen Project
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from ....http import HTTPClient
|
||||
from ....rpc.remote import RemoteRPCClient
|
||||
|
||||
class ThornodeRemoteHTTPClient(HTTPClient):
|
||||
|
||||
http_hdrs = {'Content-Type': 'application/json'}
|
||||
timeout = 5
|
||||
|
||||
def __init__(self, cfg, *, proto=None, host=None):
|
||||
for k, v in cfg._proto.rpc_remote_http_params.items():
|
||||
setattr(self, k, v)
|
||||
super().__init__(cfg, proto=proto, host=host)
|
||||
|
||||
class THORChainRemoteRPCClient(RemoteRPCClient):
|
||||
server_proto = 'THORChain'
|
||||
|
||||
def __init__(self, cfg, proto):
|
||||
for k, v in proto.rpc_remote_params.items():
|
||||
setattr(self, k, v)
|
||||
super().__init__(cfg, proto)
|
||||
self.caps = ('lbl_id',)
|
||||
self.http = ThornodeRemoteHTTPClient(cfg)
|
||||
|
||||
# throws exception on error
|
||||
def get_balance(self, addr, *, block):
|
||||
http_res = self.http.get(path=f'/bank/balances/{addr}')
|
||||
data = json.loads(http_res)
|
||||
if data['result'] is None:
|
||||
from ....util import die
|
||||
die('RPCFailure', f'address ‘{addr}’ not found in blockchain')
|
||||
else:
|
||||
rune_res = [d for d in data['result'] if d['denom'] == 'rune']
|
||||
assert len(rune_res) == 1, f'{rune_res}: result length is not one!'
|
||||
return self.proto.coin_amt(int(rune_res[0]['amount']), from_unit='satoshi')
|
||||
20
mmgen/proto/rune/tw/addresses.py
Executable file
20
mmgen/proto/rune/tw/addresses.py
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
#!/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
|
||||
|
||||
"""
|
||||
proto.rune.tw.addresses: THORChain protocol tracking wallet address list class
|
||||
"""
|
||||
|
||||
from ....tw.addresses import TwAddresses
|
||||
|
||||
from .view import THORChainTwView
|
||||
|
||||
class THORChainTwAddresses(THORChainTwView, TwAddresses):
|
||||
pass
|
||||
18
mmgen/proto/rune/tw/ctl.py
Executable file
18
mmgen/proto/rune/tw/ctl.py
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
#!/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
|
||||
|
||||
"""
|
||||
proto.rune.tw.ctl: THORChain tracking wallet control class
|
||||
"""
|
||||
|
||||
from ....tw.store import TwCtlWithStore
|
||||
|
||||
class THORChainTwCtl(TwCtlWithStore):
|
||||
use_cached_balances = True
|
||||
19
mmgen/proto/rune/tw/unspent.py
Executable file
19
mmgen/proto/rune/tw/unspent.py
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
#!/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
|
||||
|
||||
"""
|
||||
proto.rune.tw.unspent: THORChain tracking wallet unspent outputs class
|
||||
"""
|
||||
|
||||
from ....tw.unspent import TwUnspentOutputs
|
||||
from .view import THORChainTwView
|
||||
|
||||
class THORChainTwUnspentOutputs(THORChainTwView, TwUnspentOutputs):
|
||||
pass
|
||||
21
mmgen/proto/rune/tw/view.py
Executable file
21
mmgen/proto/rune/tw/view.py
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
#!/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
|
||||
|
||||
"""
|
||||
proto.rune.tw.view: THORChain protocol base class for tracking wallet view classes
|
||||
"""
|
||||
|
||||
class THORChainTwView:
|
||||
|
||||
def gen_subheader(self, cw, color):
|
||||
yield from super().gen_subheader(cw, color)
|
||||
if self.proto.network == 'mainnet':
|
||||
from ....color import red
|
||||
yield red('For demonstration purposes only! DO NOT SPEND to these addresses!') # TODO
|
||||
|
|
@ -94,6 +94,8 @@ packages =
|
|||
mmgen.proto.eth.tw
|
||||
mmgen.proto.ltc
|
||||
mmgen.proto.rune
|
||||
mmgen.proto.rune.rpc
|
||||
mmgen.proto.rune.tw
|
||||
mmgen.proto.secp256k1
|
||||
mmgen.proto.xchain
|
||||
mmgen.proto.xmr
|
||||
|
|
|
|||
|
|
@ -12,9 +12,25 @@
|
|||
test.cmdtest_d.httpd.thornode: Thornode WSGI http server
|
||||
"""
|
||||
|
||||
import re, json
|
||||
from wsgiref.util import request_uri
|
||||
|
||||
from . import HTTPD
|
||||
|
||||
class ThornodeServer(HTTPD):
|
||||
name = 'thornode server'
|
||||
port = 18800
|
||||
content_type = 'application/json'
|
||||
request_pat = r'/bank/balances/(\S+)'
|
||||
|
||||
def make_response_body(self, method, environ):
|
||||
req_str = request_uri(environ)
|
||||
m = re.search(self.request_pat, req_str)
|
||||
assert m[1], f'‘{req_str}’: malformed query path'
|
||||
data = {
|
||||
'result': [
|
||||
{'denom': 'foocoin', 'amount': 321321321321},
|
||||
{'denom': 'rune', 'amount': 987654321321},
|
||||
{'denom': 'barcoin', 'amount': 123123123123},
|
||||
]}
|
||||
return json.dumps(data).encode()
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ cmd_groups_dfl = {
|
|||
# 'chainsplit': ('CmdTestChainsplit', {}),
|
||||
'ethdev': ('CmdTestEthdev', {}),
|
||||
'ethbump': ('CmdTestEthBump', {}),
|
||||
'rune': ('CmdTestRune', {}),
|
||||
'xmrwallet': ('CmdTestXMRWallet', {}),
|
||||
'xmr_autosign': ('CmdTestXMRAutosign', {}),
|
||||
}
|
||||
|
|
@ -250,6 +251,7 @@ cfgs = { # addr_idx_lists (except 31, 32, 33, 34) must contain exactly 8 address
|
|||
'47': {}, # ethswap
|
||||
'48': {}, # ethswap_eth
|
||||
'49': {}, # autosign_automount
|
||||
'50': {}, # rune
|
||||
'59': {}, # autosign_eth
|
||||
'99': {}, # dummy
|
||||
}
|
||||
|
|
|
|||
86
test/cmdtest_d/rune.py
Executable file
86
test/cmdtest_d/rune.py
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
#!/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
|
||||
|
||||
"""
|
||||
test.cmdtest_d.rune: THORChain RUNE tests for the cmdtest.py test suite
|
||||
"""
|
||||
|
||||
from .include.common import dfl_sid
|
||||
from .httpd.thornode import ThornodeServer
|
||||
from .ethdev import CmdTestEthdevMethods
|
||||
from .base import CmdTestBase
|
||||
from .shared import CmdTestShared
|
||||
from .swap import CmdTestSwapMethods
|
||||
|
||||
class CmdTestRune(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
|
||||
'THORChain RUNE tracking wallet and transacting operations'
|
||||
networks = ('rune',)
|
||||
passthru_opts = ('coin', 'http_timeout')
|
||||
tmpdir_nums = [50]
|
||||
color = True
|
||||
menu_prompt = 'efresh balance:\b'
|
||||
|
||||
cmd_group_in = (
|
||||
('subgroup.init', []),
|
||||
('subgroup.main', ['init']),
|
||||
)
|
||||
cmd_subgroups = {
|
||||
'init': (
|
||||
'initializing wallets',
|
||||
('addrgen', 'generating addresses'),
|
||||
('addrimport', 'importing addresses'),
|
||||
),
|
||||
'main': (
|
||||
'tracking wallet and transaction operations',
|
||||
('twview', 'viewing unspent outputs in tracking wallet'),
|
||||
('bal_refresh', 'refreshing address balance in tracking wallet'),
|
||||
('thornode_server_stop', 'stopping Thornode server'),
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, cfg, trunner, cfgs, spawn):
|
||||
CmdTestBase.__init__(self, cfg, trunner, cfgs, spawn)
|
||||
if trunner is None:
|
||||
return
|
||||
|
||||
self.eth_opts = [f'--outdir={self.tmpdir}', '--regtest=1', '--quiet']
|
||||
self.eth_opts_noquiet = [f'--outdir={self.tmpdir}', '--regtest=1']
|
||||
|
||||
self.rune_opts = self.eth_opts
|
||||
|
||||
from mmgen.protocol import init_proto
|
||||
self.proto = init_proto(cfg, network_id=self.proto.coin + '_rt', need_amt=True)
|
||||
self.spawn_env['MMGEN_BOGUS_SEND'] = ''
|
||||
|
||||
self.thornode_server = ThornodeServer()
|
||||
self.thornode_server.start()
|
||||
|
||||
def addrgen(self):
|
||||
return self._addrgen()
|
||||
|
||||
def addrimport(self):
|
||||
return self._addrimport()
|
||||
|
||||
def twview(self):
|
||||
return self.spawn('mmgen-tool', self.rune_opts + ['twview'])
|
||||
|
||||
def bal_refresh(self):
|
||||
t = self.spawn('mmgen-tool', self.rune_opts + ['listaddresses', 'interactive=1'])
|
||||
t.expect(self.menu_prompt, 'R')
|
||||
t.expect('menu): ', '3\n')
|
||||
t.expect('(y/N): ', 'y')
|
||||
t.expect(r'Total RUNE: \S*\D9876.54321321\D', regex=True)
|
||||
t.expect('address #3 refreshed')
|
||||
t.expect(self.menu_prompt, 'q')
|
||||
return t
|
||||
|
||||
def thornode_server_stop(self):
|
||||
return CmdTestSwapMethods._thornode_server_stop(
|
||||
self, attrname='thornode_server', name='thornode server')
|
||||
|
|
@ -19,10 +19,10 @@ groups_desc="
|
|||
"
|
||||
|
||||
init_groups() {
|
||||
dfl_tests='dep alt obj color daemon mod hash ref tool tool2 gen help autosign btc btc_tn btc_rt altref altgen bch bch_rt ltc ltc_rt eth etc xmr'
|
||||
dfl_tests='dep alt obj color daemon mod hash ref tool tool2 gen help autosign btc btc_tn btc_rt altref altgen bch bch_rt ltc ltc_rt eth etc rune xmr'
|
||||
extra_tests='dep dev lint pylint autosign_live ltc_tn bch_tn'
|
||||
noalt_tests='dep alt obj color daemon mod hash ref tool tool2 gen help autosign btc btc_tn btc_rt pylint'
|
||||
quick_tests='dep alt obj color daemon mod hash ref tool tool2 gen help autosign btc btc_rt altref altgen eth etc xmr'
|
||||
noalt_tests='dep alt obj color daemon mod hash ref tool tool2 gen help autosign btc btc_tn btc_rt'
|
||||
quick_tests='dep alt obj color daemon mod hash ref tool tool2 gen help autosign btc btc_rt altref altgen eth etc rune xmr'
|
||||
qskip_tests='lint btc_tn bch bch_rt ltc ltc_rt'
|
||||
noalt_ok_tests='lint'
|
||||
|
||||
|
|
@ -273,6 +273,11 @@ init_tests() {
|
|||
"
|
||||
[ "$SKIP_PARITY" ] && t_etc_skip='parity'
|
||||
|
||||
d_rune="operations for THORChain RUNE using testnet"
|
||||
t_rune="
|
||||
- $cmdtest_py --coin=rune rune
|
||||
"
|
||||
|
||||
d_xmr="Monero xmrwallet operations"
|
||||
t_xmr="
|
||||
- $HTTP_LONG_TIMEOUT$cmdtest_py$PEXPECT_LONG_TIMEOUT --coin=xmr --exclude help
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue