RUNE user-level transaction support
Transaction sends and balance queries are done via the remote Thornode server
at ninerealms.com, so it’s recommended to proxy them via Tor, as shown below.
Sample create-send-sign workflow (assumes `autosign` set in cfg file):
$ mmgen-txcreate --proxy=localhost:9050 -q --coin=rune 12ABCDEF:X:2,1.234
remove device - insert - wait for signing - remove - insert
$ mmgen-txsend --proxy=localhost:9050 -q
Check/refresh tracking wallet after sending:
$ mmgen-tool --proxy=localhost:9050 --coin=rune twview interactive=1
Testing/demo:
$ pip install pure-protobuf
$ test/cmdtest.py --coin=rune --demo rune
This commit is contained in:
parent
b6cfd2f85f
commit
ef76cf6460
17 changed files with 406 additions and 21 deletions
|
|
@ -51,8 +51,9 @@ class GlobalConstants(Lockable):
|
|||
min_time_precision = 18
|
||||
|
||||
# must match CoinProtocol.coins
|
||||
core_coins = ('btc', 'bch', 'ltc', 'eth', 'etc', 'zec', 'xmr')
|
||||
core_coins = ('btc', 'bch', 'ltc', 'eth', 'etc', 'zec', 'xmr', 'rune')
|
||||
rpc_coins = ('btc', 'bch', 'ltc', 'eth', 'etc', 'xmr')
|
||||
remote_rpc_coins = ('rune',)
|
||||
btc_fork_rpc_coins = ('btc', 'bch', 'ltc')
|
||||
eth_fork_coins = ('eth', 'etc')
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
15.1.dev43
|
||||
15.1.dev44
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ opts_data = {
|
|||
-- -a, --autosign Create a transaction for offline autosigning (see
|
||||
+ ‘mmgen-autosign’). The removable device is mounted and
|
||||
+ unmounted automatically
|
||||
-- -A, --fee-adjust= f Adjust transaction fee by factor 'f' (see below)
|
||||
L- -A, --fee-adjust= f Adjust transaction fee by factor ‘f’ (see below)
|
||||
-- -B, --no-blank Don't blank screen before displaying {a_info}
|
||||
-- -c, --comment-file=f Source the transaction's comment from file 'f'
|
||||
b- -C, --fee-estimate-confs=c Desired number of confirmations for fee estimation
|
||||
|
|
@ -53,7 +53,7 @@ opts_data = {
|
|||
e- -D, --contract-data=D Path to file containing hex-encoded contract data
|
||||
b- -E, --fee-estimate-mode=M Specify the network fee estimate mode. Choices:
|
||||
+ {fe_all}. Default: {fe_dfl!r}
|
||||
-- -f, --fee= f Transaction fee, as a decimal {cu} amount or as
|
||||
L- -f, --fee= f Transaction fee, as a decimal {cu} amount or as
|
||||
+ {fu} (an integer followed by {fl}).
|
||||
+ See FEE SPECIFICATION below. If omitted, fee will be
|
||||
+ calculated using network fee estimation.
|
||||
|
|
@ -81,9 +81,12 @@ opts_data = {
|
|||
-s -S, --list-assets List available swap assets
|
||||
-- -v, --verbose Produce more verbose output
|
||||
b- -V, --vsize-adj= f Adjust transaction's estimated vsize by factor 'f'
|
||||
-s -x, --proxy=P Fetch the swap quote via SOCKS5h proxy ‘P’ (host:port).
|
||||
Ls -x, --proxy=P Fetch the swap quote via SOCKS5h proxy ‘P’ (host:port).
|
||||
+ Use special value ‘env’ to honor *_PROXY environment
|
||||
+ vars instead.
|
||||
X- -x, --proxy=P Connect to remote server(s) via SOCKS5h proxy ‘P’
|
||||
+ (host:port). Use special value ‘env’ to honor *_PROXY
|
||||
+ environment vars instead.
|
||||
-- -y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||
e- -X, --cached-balances Use cached balances
|
||||
""",
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ opts_data = {
|
|||
'options': """
|
||||
-- -h, --help Print this help message
|
||||
-- --, --longhelp Print help message for long (global) options
|
||||
-- -A, --fee-adjust= f Adjust transaction fee by factor 'f' (see below)
|
||||
L- -A, --fee-adjust= f Adjust transaction fee by factor ‘f’ (see below)
|
||||
-- -b, --brain-params=l,p Use seed length 'l' and hash preset 'p' for
|
||||
+ brainwallet input
|
||||
-- -B, --no-blank Don't blank screen before displaying {a_info}
|
||||
|
|
@ -53,7 +53,7 @@ opts_data = {
|
|||
-- -e, --echo-passphrase Print passphrase to screen when typing it
|
||||
b- -E, --fee-estimate-mode=M Specify the network fee estimate mode. Choices:
|
||||
+ {fe_all}. Default: {fe_dfl!r}
|
||||
-- -f, --fee= f Transaction fee, as a decimal {cu} amount or as
|
||||
L- -f, --fee= f Transaction fee, as a decimal {cu} amount or as
|
||||
+ {fu} (an integer followed by {fl}).
|
||||
+ See FEE SPECIFICATION below. If omitted, fee will be
|
||||
+ calculated using network fee estimation.
|
||||
|
|
@ -107,9 +107,12 @@ opts_data = {
|
|||
-- -v, --verbose Produce more verbose output
|
||||
b- -V, --vsize-adj= f Adjust transaction's estimated vsize by factor 'f'
|
||||
e- -w, --wait Wait for transaction confirmation
|
||||
-s -x, --proxy=P Fetch the swap quote via SOCKS5h proxy ‘P’ (host:port).
|
||||
Ls -x, --proxy=P Fetch the swap quote via SOCKS5h proxy ‘P’ (host:port).
|
||||
+ Use special value ‘env’ to honor *_PROXY environment
|
||||
+ vars instead.
|
||||
X- -x, --proxy=P Connect to remote server(s) via SOCKS5h proxy ‘P’
|
||||
+ (host:port). Use special value ‘env’ to honor *_PROXY
|
||||
+ environment vars instead.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -358,6 +358,8 @@ class UserOpts(Opts):
|
|||
'e' - Ethereum or Ethereum code fork
|
||||
'r' - coin supporting RPC
|
||||
'h' - Bitcoin Cash
|
||||
'L' - local RPC coin
|
||||
'X' - remote RPC coin
|
||||
'-' - other coin
|
||||
Cmd codes:
|
||||
'p' - proto required
|
||||
|
|
@ -372,10 +374,11 @@ class UserOpts(Opts):
|
|||
return ret(
|
||||
coin = caps.coin_codes or (
|
||||
None if coin is None else
|
||||
['-', 'r', 'R', 'b', 'h'] if coin == 'bch' else
|
||||
['-', 'r', 'R', 'b'] if coin in gc.btc_fork_rpc_coins else
|
||||
['-', 'r', 'R', 'e'] if coin in gc.eth_fork_coins else
|
||||
['-', 'r'] if coin in gc.rpc_coins else
|
||||
['-', 'r', 'R', 'b', 'h', 'L'] if coin == 'bch' else
|
||||
['-', 'r', 'R', 'b', 'L'] if coin in gc.btc_fork_rpc_coins else
|
||||
['-', 'r', 'R', 'e', 'L'] if coin in gc.eth_fork_coins else
|
||||
['-', 'r', 'L'] if coin in gc.rpc_coins else
|
||||
['-', 'X'] if coin in gc.remote_rpc_coins else
|
||||
['-']),
|
||||
cmd = (
|
||||
['-']
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class mainnet(CoinProtocol.Secp256k1):
|
|||
preferred_mmtypes = ('X',)
|
||||
dfl_mmtype = 'X'
|
||||
coin_amt = 'UniAmt'
|
||||
max_tx_fee = 1 # TODO
|
||||
max_tx_fee = 0.1
|
||||
caps = ()
|
||||
mmcaps = ('tw', 'rpc', 'rpc_init')
|
||||
base_proto = 'THORChain'
|
||||
|
|
|
|||
|
|
@ -13,9 +13,4 @@ proto.rune.tw.view: THORChain protocol base class for tracking wallet view class
|
|||
"""
|
||||
|
||||
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
|
||||
pass
|
||||
|
|
|
|||
60
mmgen/proto/rune/tx/base.py
Executable file
60
mmgen/proto/rune/tx/base.py
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
#!/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.tx.base: THORChain base transaction class
|
||||
"""
|
||||
|
||||
from ....tx.base import Base as TxBase
|
||||
|
||||
class Base(TxBase):
|
||||
|
||||
dfl_fee = 2000000
|
||||
dfl_gas = 800000
|
||||
usr_contract_data = None
|
||||
disable_fee_check = False
|
||||
|
||||
@property
|
||||
def nondata_outputs(self):
|
||||
return self.outputs
|
||||
|
||||
@property
|
||||
def usr_fee(self):
|
||||
return self.proto.coin_amt(self.dfl_fee, from_unit='satoshi')
|
||||
|
||||
def add_blockcount(self):
|
||||
pass
|
||||
|
||||
def is_replaceable(self):
|
||||
return False
|
||||
|
||||
def check_serialized_integrity(self):
|
||||
if self.signed:
|
||||
from .protobuf import RuneTx
|
||||
tx = RuneTx.loads(bytes.fromhex(self.serialized))
|
||||
|
||||
o = self.txobj
|
||||
b = tx.body.messages[0].body
|
||||
|
||||
if s := self.proto.encode_addr_bech32x(b.fromAddress) != o['from']:
|
||||
raise ValueError(f'{s}: invalid ‘from’ address in serialized data')
|
||||
|
||||
if s := self.proto.encode_addr_bech32x(b.toAddress) != o['to']:
|
||||
raise ValueError(f'{s}: invalid ‘to’ address in serialized data')
|
||||
|
||||
if d := self.proto.coin_amt(int(b.amount[0].amount), from_unit='satoshi') != o['amt']:
|
||||
raise ValueError(f'{d}: invalid send amount in serialized data')
|
||||
|
||||
if n := tx.authInfo.signerInfos[0].sequence != o['sequence']:
|
||||
raise ValueError(f'{n}: invalid sequence number in serialized data')
|
||||
|
||||
if self.is_swap:
|
||||
if b.memo != self.swap_memo.encode():
|
||||
raise ValueError(f'{b.memo}: invalid swap memo in serialized data')
|
||||
29
mmgen/proto/rune/tx/completed.py
Executable file
29
mmgen/proto/rune/tx/completed.py
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
#!/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.tx.completed: THORChain completed transaction class
|
||||
"""
|
||||
|
||||
from ....tx import completed as TxBase
|
||||
|
||||
from ...vm.tx.completed import Completed as VmCompleted
|
||||
|
||||
from .base import Base
|
||||
|
||||
class Completed(VmCompleted, Base, TxBase.Completed):
|
||||
|
||||
@property
|
||||
def total_gas(self):
|
||||
return self.txobj['gas']
|
||||
|
||||
@property
|
||||
def fee(self):
|
||||
return self.proto.coin_amt(self.dfl_fee, from_unit='satoshi')
|
||||
49
mmgen/proto/rune/tx/info.py
Executable file
49
mmgen/proto/rune/tx/info.py
Executable file
|
|
@ -0,0 +1,49 @@
|
|||
#!/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.tx.info: THORChain transaction info class
|
||||
"""
|
||||
|
||||
from ....tx.info import TxInfo, mmid_disp
|
||||
from ....color import blue, pink
|
||||
from ....obj import NonNegativeInt
|
||||
|
||||
class TxInfo(TxInfo):
|
||||
|
||||
def format_body(self, blockcount, nonmm_str, max_mmwid, enl, *, terse, sort):
|
||||
tx = self.tx
|
||||
t = tx.txobj
|
||||
fs = """
|
||||
From: {f}{f_mmid}
|
||||
To: {t}{t_mmid}
|
||||
Amount: {a} {c}
|
||||
Gas limit: {G}
|
||||
Sequence: {N}
|
||||
""".strip().replace('\t', '') + ('\nMemo: {m}' if tx.is_swap else '')
|
||||
return fs.format(
|
||||
f = t['from'].hl(0),
|
||||
t = t['to'].hl(0) if tx.outputs else blue('None'),
|
||||
a = t['amt'].hl(),
|
||||
N = NonNegativeInt(t['sequence']).hl(),
|
||||
m = pink(tx.swap_memo) if tx.is_swap else None,
|
||||
c = tx.proto.dcoin if tx.outputs else '',
|
||||
G = NonNegativeInt(tx.total_gas).hl(),
|
||||
f_mmid = mmid_disp(tx.inputs[0], nonmm_str),
|
||||
t_mmid = mmid_disp(tx.outputs[0], nonmm_str) if tx.outputs else '') + '\n\n'
|
||||
|
||||
def format_abs_fee(self, iwidth, /, *, color=None):
|
||||
return self.tx.fee.fmt(iwidth, color=color)
|
||||
|
||||
def format_rel_fee(self):
|
||||
return ''
|
||||
|
||||
def format_verbose_footer(self):
|
||||
return ''
|
||||
38
mmgen/proto/rune/tx/new.py
Executable file
38
mmgen/proto/rune/tx/new.py
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
#!/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.tx.new: THORChain new transaction class
|
||||
"""
|
||||
|
||||
from ....tx import new as TxBase
|
||||
|
||||
from ...vm.tx.new import New as VmNew
|
||||
|
||||
from .base import Base
|
||||
|
||||
class New(VmNew, Base, TxBase.New):
|
||||
|
||||
async def get_fee(self, fee, outputs_sum, start_fee_desc):
|
||||
return await self.twctl.get_balance(self.inputs[0].addr)
|
||||
|
||||
async def set_gas(self, *, to_addr=None, force=False):
|
||||
self.gas = self.dfl_gas
|
||||
|
||||
async def make_txobj(self): # called by create_serialized()
|
||||
acct_info = self.rpc.get_account_info(self.inputs[0].addr)
|
||||
self.txobj = {
|
||||
'from': self.inputs[0].addr,
|
||||
'to': self.outputs[0].addr if self.outputs else None,
|
||||
'amt': self.outputs[0].amt if self.outputs else self.swap_amt,
|
||||
'gas': self.gas,
|
||||
'account_number': int(acct_info['account_number']),
|
||||
'sequence': int(acct_info['sequence']),
|
||||
'chain_id': self.proto.chain_id}
|
||||
51
mmgen/proto/rune/tx/online.py
Executable file
51
mmgen/proto/rune/tx/online.py
Executable file
|
|
@ -0,0 +1,51 @@
|
|||
#!/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.tx.online: THORChain online signed transaction class
|
||||
"""
|
||||
|
||||
from ....util import pp_msg, die
|
||||
from ....tx import online as TxBase
|
||||
|
||||
from .signed import Signed
|
||||
|
||||
class OnlineSigned(Signed, TxBase.OnlineSigned):
|
||||
|
||||
async def test_sendable(self, txhex):
|
||||
res = self.rpc.tx_op(bytes.fromhex(txhex), op='check_tx')
|
||||
if res['code'] == 0:
|
||||
return True
|
||||
else:
|
||||
pp_msg(res)
|
||||
return False
|
||||
|
||||
async def send_checks(self):
|
||||
pass
|
||||
|
||||
async def send_with_node(self, txhex):
|
||||
res = self.rpc.tx_op(bytes.fromhex(txhex), op='broadcast_tx_sync') # broadcast_tx_async
|
||||
if res['code'] == 0:
|
||||
return res['hash'].lower()
|
||||
else:
|
||||
pp_msg(res)
|
||||
die(2, 'Transaction send failed')
|
||||
|
||||
async def post_network_send(self, coin_txid):
|
||||
return True
|
||||
|
||||
class Sent(TxBase.Sent, OnlineSigned):
|
||||
pass
|
||||
|
||||
class AutomountOnlineSigned(TxBase.AutomountOnlineSigned, OnlineSigned):
|
||||
pass
|
||||
|
||||
class AutomountSent(TxBase.AutomountSent, AutomountOnlineSigned):
|
||||
pass
|
||||
41
mmgen/proto/rune/tx/signed.py
Executable file
41
mmgen/proto/rune/tx/signed.py
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
#!/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.tx.signed: THORChain signed transaction class
|
||||
"""
|
||||
|
||||
from ....tx import signed as TxBase
|
||||
from ....obj import CoinTxID, NonNegativeInt
|
||||
from .completed import Completed
|
||||
|
||||
class Signed(Completed, TxBase.Signed):
|
||||
|
||||
desc = 'signed transaction'
|
||||
|
||||
def parse_txfile_serialized_data(self):
|
||||
from .protobuf import RuneTx
|
||||
tx = RuneTx.loads(bytes.fromhex(self.serialized))
|
||||
|
||||
b = tx.body.messages[0].body
|
||||
i = tx.authInfo
|
||||
|
||||
self.txobj = {
|
||||
'from': self.proto.encode_addr_bech32x(b.fromAddress),
|
||||
'to': self.proto.encode_addr_bech32x(b.toAddress),
|
||||
'amt': self.proto.coin_amt(int(b.amount[0].amount), from_unit='satoshi'),
|
||||
'gas': NonNegativeInt(i.fee.gasLimit),
|
||||
'sequence': NonNegativeInt(i.signerInfos[0].sequence)}
|
||||
|
||||
txid = CoinTxID(tx.txid)
|
||||
assert txid == self.coin_txid, 'serialized txid doesn’t match txid in MMGen transaction file'
|
||||
|
||||
class AutomountSigned(TxBase.AutomountSigned, Signed):
|
||||
pass
|
||||
54
mmgen/proto/rune/tx/unsigned.py
Executable file
54
mmgen/proto/rune/tx/unsigned.py
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
#!/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.tx.unsigned: THORChain unsigned transaction class
|
||||
"""
|
||||
|
||||
from ....tx import unsigned as TxBase
|
||||
from ....obj import CoinTxID, NonNegativeInt
|
||||
from ....addr import CoinAddr
|
||||
|
||||
from ...vm.tx.unsigned import Unsigned as VmUnsigned
|
||||
|
||||
from .completed import Completed
|
||||
|
||||
class Unsigned(VmUnsigned, Completed, TxBase.Unsigned):
|
||||
|
||||
def parse_txfile_serialized_data(self):
|
||||
d = self.serialized
|
||||
self.txobj = {
|
||||
'from': CoinAddr(self.proto, d['from']),
|
||||
'to': CoinAddr(self.proto, d['to']) if d['to'] else None,
|
||||
'amt': self.proto.coin_amt(d['amt']),
|
||||
'gas': NonNegativeInt(d['gas']),
|
||||
'account_number': NonNegativeInt(d['account_number']),
|
||||
'sequence': NonNegativeInt(d['sequence']),
|
||||
'chain_id': d['chain_id']}
|
||||
|
||||
async def do_sign(self, o, wif):
|
||||
from .protobuf import build_tx, send_tx_parms
|
||||
tx = build_tx(
|
||||
self.cfg,
|
||||
self.proto,
|
||||
send_tx_parms(
|
||||
o['from'],
|
||||
o['to'],
|
||||
o['amt'],
|
||||
o['gas'],
|
||||
o['account_number'],
|
||||
o['sequence'],
|
||||
wifkey = wif))
|
||||
self.serialized = bytes(tx).hex()
|
||||
self.coin_txid = CoinTxID(tx.txid)
|
||||
tx.verify_sig(self.proto, o['account_number'])
|
||||
|
||||
class AutomountUnsigned(TxBase.AutomountUnsigned, Unsigned):
|
||||
pass
|
||||
|
|
@ -47,7 +47,7 @@ class CoinProtocol(MMGenObject):
|
|||
'etc': proto_info('EthereumClassic', 4),
|
||||
'zec': proto_info('Zcash', 2),
|
||||
'xmr': proto_info('Monero', 5),
|
||||
'rune': proto_info('THORChain', 2)
|
||||
'rune': proto_info('THORChain', 4)
|
||||
}
|
||||
|
||||
class Base(Lockable):
|
||||
|
|
|
|||
|
|
@ -69,6 +69,19 @@ class ThornodeRPCServer(ThornodeServer):
|
|||
'codespace': ''
|
||||
}
|
||||
}
|
||||
elif m := re.search(r'/broadcast_tx_sync$', req_str):
|
||||
assert method == 'POST'
|
||||
txhex = environ['wsgi.input'].read(24).decode().removeprefix('tx=0x').upper()
|
||||
if txhex.startswith('0A540A52'):
|
||||
data = {
|
||||
'result': {
|
||||
'code': 0,
|
||||
'codespace': '',
|
||||
'data': '',
|
||||
'hash': '14463C716CF08A814868DB779156BCD85A1DF8EE49E924900A74482E9DEE132D',
|
||||
'log': ''
|
||||
}
|
||||
}
|
||||
else:
|
||||
raise ValueError(f'‘{req_str}’: malformed query path')
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
test.cmdtest_d.rune: THORChain RUNE tests for the cmdtest.py test suite
|
||||
"""
|
||||
|
||||
from .include.common import dfl_sid
|
||||
from .include.common import dfl_sid, dfl_words_file
|
||||
from .httpd.thornode.rpc import ThornodeRPCServer
|
||||
from .ethdev import CmdTestEthdevMethods
|
||||
from .base import CmdTestBase
|
||||
|
|
@ -42,6 +42,10 @@ class CmdTestRune(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
|
|||
'tracking wallet and transaction operations',
|
||||
('twview', 'viewing unspent outputs in tracking wallet'),
|
||||
('bal_refresh', 'refreshing address balance in tracking wallet'),
|
||||
('txcreate1', 'creating a transaction'),
|
||||
('txsign1', 'signing the transaction'),
|
||||
('txsend1_test', 'testing whether the transaction can be sent'),
|
||||
('txsend1', 'sending the transaction'),
|
||||
),
|
||||
}
|
||||
|
||||
|
|
@ -81,6 +85,47 @@ class CmdTestRune(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
|
|||
t.expect(self.menu_prompt, 'q')
|
||||
return t
|
||||
|
||||
def txcreate1(self):
|
||||
t = self.spawn('mmgen-txcreate', self.rune_opts + ['98831F3A:X:2,54.321'])
|
||||
t.expect(self.menu_prompt, 'q')
|
||||
t.expect('spend from: ', '3\n')
|
||||
t.expect('(y/N): ', 'y') # add comment?
|
||||
t.expect('Comment: ', 'RUNE Boy\n')
|
||||
t.expect('view: ', 'y')
|
||||
t.expect('to continue: ', 'z')
|
||||
t.expect('(y/N): ', 'y') # save?
|
||||
t.written_to_file('Unsigned transaction')
|
||||
return t
|
||||
|
||||
def txsign1(self):
|
||||
return self.txsign_ui_common(
|
||||
self.spawn(
|
||||
'mmgen-txsign',
|
||||
self.rune_opts + [self.get_file_with_ext('rawtx'), dfl_words_file],
|
||||
no_passthru_opts = ['coin']),
|
||||
has_label = True)
|
||||
|
||||
def txsend1_test(self):
|
||||
return self._txsend(add_args=['--test'])
|
||||
|
||||
def txsend1(self):
|
||||
return self._txsend()
|
||||
|
||||
def _txsend(self, add_args=[]):
|
||||
t = self.spawn(
|
||||
'mmgen-txsend',
|
||||
self.rune_opts + add_args + [self.get_file_with_ext('sigtx')],
|
||||
no_passthru_opts = ['coin'])
|
||||
t.expect('view: ', 'y')
|
||||
t.expect('to continue: ', 'z')
|
||||
t.expect('(y/N): ', 'n') # edit comment?
|
||||
if add_args == ['--test']:
|
||||
t.expect('can be sent')
|
||||
else:
|
||||
t.expect('to confirm: ', 'YES\n')
|
||||
t.written_to_file('Sent transaction')
|
||||
return t
|
||||
|
||||
def thornode_server_stop(self):
|
||||
return CmdTestSwapMethods._thornode_server_stop(
|
||||
self, attrname='thornode_server', name='thornode server')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue