ETH: transaction sending via Etherscan
- the HTML transaction broadcast form is used, so no API key is required
- the request can be proxied through Tor
- availability of the service can be checked with the --test option
Example:
# check availability:
$ mmgen-txsend --autosign --coin=eth --tx-proxy=etherscan --proxy=localhost:9050 --test
# send:
$ mmgen-txsend --autosign --coin=eth --tx-proxy=etherscan --proxy=localhost:9050
Testing:
$ test/cmdtest.py --coin=eth -e -X txsend_etherscan ethdev
This commit is contained in:
parent
7e5ee50d0b
commit
1eb0de7938
15 changed files with 411 additions and 19 deletions
|
|
@ -2,21 +2,31 @@
|
|||
MMGEN-TXSEND: Send a signed MMGen cryptocoin transaction
|
||||
USAGE: mmgen-txsend [opts] [signed transaction file]
|
||||
OPTIONS:
|
||||
-h, --help Print this help message
|
||||
--longhelp Print help message for long (global) options
|
||||
-a, --autosign Send an autosigned transaction created by ‘mmgen-txcreate
|
||||
--autosign’. The removable device is mounted and unmounted
|
||||
automatically. The transaction file argument must be omitted
|
||||
when using this option
|
||||
-A, --abort Abort an unsent transaction created by ‘mmgen-txcreate
|
||||
--autosign’ and delete it from the removable device. The
|
||||
transaction may be signed or unsigned.
|
||||
-d, --outdir d Specify an alternate directory 'd' for output
|
||||
-q, --quiet Suppress warnings; overwrite files without prompting
|
||||
-s, --status Get status of a sent transaction (or the current transaction,
|
||||
whether sent or unsent, when used with --autosign)
|
||||
-v, --verbose Be more verbose
|
||||
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||
-h, --help Print this help message
|
||||
--longhelp Print help message for long (global) options
|
||||
-a, --autosign Send an autosigned transaction created by ‘mmgen-txcreate
|
||||
--autosign’. The removable device is mounted and unmounted
|
||||
automatically. The transaction file argument must be omitted
|
||||
when using this option
|
||||
-A, --abort Abort an unsent transaction created by ‘mmgen-txcreate
|
||||
--autosign’ and delete it from the removable device. The
|
||||
transaction may be signed or unsigned.
|
||||
-d, --outdir d Specify an alternate directory 'd' for output
|
||||
-H, --dump-hex F Instead of sending to the network, dump the transaction hex
|
||||
to file ‘F’. Use filename ‘-’ to dump to standard output.
|
||||
-m, --mark-sent Mark the transaction as sent by adding it to the removable
|
||||
device. Used in combination with --autosign when a trans-
|
||||
action has been successfully sent out-of-band.
|
||||
-n, --tx-proxy P Send transaction via public TX proxy ‘P’ (supported proxies:
|
||||
‘etherscan’). This is done via a publicly accessible web
|
||||
page, so no API key or registration is required
|
||||
-q, --quiet Suppress warnings; overwrite files without prompting
|
||||
-s, --status Get status of a sent transaction (or current transaction,
|
||||
whether sent or unsent, when used with --autosign)
|
||||
-t, --test Test whether the transaction can be sent without sending it
|
||||
-v, --verbose Be more verbose
|
||||
-x, --proxy P Connect to TX proxy via SOCKS5 proxy ‘P’ (host:port)
|
||||
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||
|
||||
MMGEN v15.1.dev18 March 2025 MMGEN-TXSEND(1)
|
||||
MMGEN v15.1.dev20 March 2025 MMGEN-TXSEND(1)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -381,7 +381,9 @@ class Config(Lockable):
|
|||
'fee_estimate_mode': _ov('nocase_pfx', ['conservative', 'economical']),
|
||||
'rpc_backend': _ov('nocase_pfx', ['auto', 'httplib', 'curl', 'aiohttp', 'requests']),
|
||||
'swap_proto': _ov('nocase_pfx', ['thorchain']),
|
||||
'tx_proxy': _ov('nocase_pfx', ['etherscan']) # , 'blockchair'
|
||||
}
|
||||
_dfl_none_autoset_opts = ('tx_proxy',)
|
||||
|
||||
_auto_typeset_opts = {
|
||||
'seed_len': int,
|
||||
|
|
@ -719,7 +721,8 @@ class Config(Lockable):
|
|||
val = None
|
||||
|
||||
if val is None:
|
||||
setattr(self, key, self._autoset_opts[key].choices[0])
|
||||
if key not in self._dfl_none_autoset_opts:
|
||||
setattr(self, key, self._autoset_opts[key].choices[0])
|
||||
else:
|
||||
setattr(self, key, get_autoset_opt(key, val, src=src))
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
15.1.dev19
|
||||
15.1.dev20
|
||||
|
|
|
|||
|
|
@ -106,6 +106,10 @@ FMT CODES:
|
|||
from ..util import fmt_list
|
||||
return fmt_list(CoinDaemon.get_network_ids(self.cfg), fmt='bare')
|
||||
|
||||
def tx_proxies(self):
|
||||
from ..util import fmt_list
|
||||
return fmt_list(self.cfg._autoset_opts['tx_proxy'].choices, fmt='fancy')
|
||||
|
||||
def rel_fee_desc(self):
|
||||
from ..tx import BaseTX
|
||||
return BaseTX(cfg=self.cfg, proto=self.proto).rel_fee_desc
|
||||
|
|
|
|||
|
|
@ -49,13 +49,21 @@ opts_data = {
|
|||
-m, --mark-sent Mark the transaction as sent by adding it to the removable
|
||||
device. Used in combination with --autosign when a trans-
|
||||
action has been successfully sent out-of-band.
|
||||
-n, --tx-proxy=P Send transaction via public TX proxy ‘P’ (supported proxies:
|
||||
{tx_proxies}). This is done via a publicly accessible web
|
||||
page, so no API key or registration is required
|
||||
-q, --quiet Suppress warnings; overwrite files without prompting
|
||||
-s, --status Get status of a sent transaction (or current transaction,
|
||||
whether sent or unsent, when used with --autosign)
|
||||
-t, --test Test whether the transaction can be sent without sending it
|
||||
-v, --verbose Be more verbose
|
||||
-x, --proxy=P Connect to TX proxy via SOCKS5 proxy ‘P’ (host:port)
|
||||
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||
"""
|
||||
},
|
||||
'code': {
|
||||
'options': lambda cfg, proto, help_notes, s: s.format(
|
||||
tx_proxies = help_notes('tx_proxies'))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,6 +78,10 @@ if cfg.mark_sent and not cfg.autosign:
|
|||
if cfg.test and cfg.dump_hex:
|
||||
die(1, '--test cannot be used in combination with --dump-hex')
|
||||
|
||||
if cfg.tx_proxy:
|
||||
from .tx.tx_proxy import check_client
|
||||
check_client(cfg)
|
||||
|
||||
if cfg.dump_hex and cfg.dump_hex != '-':
|
||||
from .fileutil import check_outfile_dir
|
||||
check_outfile_dir(cfg.dump_hex)
|
||||
|
|
@ -162,6 +174,12 @@ async def main():
|
|||
await post_send(tx)
|
||||
else:
|
||||
await post_send(tx)
|
||||
elif cfg.tx_proxy:
|
||||
from .tx.tx_proxy import send_tx
|
||||
if send_tx(cfg, tx):
|
||||
if (not cfg.autosign or
|
||||
keypress_confirm(cfg, 'Mark transaction as sent on removable device?')):
|
||||
await post_send(tx)
|
||||
elif cfg.test:
|
||||
await tx.test_sendable()
|
||||
elif await tx.send():
|
||||
|
|
|
|||
|
|
@ -332,6 +332,9 @@ class HexStr(HiliteStr, InitErrors):
|
|||
class CoinTxID(HexStr):
|
||||
color, width, hexcase = ('purple', 64, 'lower')
|
||||
|
||||
def is_coin_txid(s):
|
||||
return get_obj(CoinTxID, s=s, silent=True, return_bool=True)
|
||||
|
||||
class WalletPassword(HexStr):
|
||||
color, width, hexcase = ('blue', 32, 'lower')
|
||||
|
||||
|
|
|
|||
223
mmgen/tx/tx_proxy.py
Executable file
223
mmgen/tx/tx_proxy.py
Executable file
|
|
@ -0,0 +1,223 @@
|
|||
#!/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
|
||||
|
||||
"""
|
||||
tx.tx_proxy: tx proxy classes
|
||||
"""
|
||||
|
||||
from ..color import green, pink, orange
|
||||
from ..util import msg, msg_r, die
|
||||
|
||||
class TxProxyClient:
|
||||
|
||||
proto = 'https'
|
||||
verify = True
|
||||
timeout = 60
|
||||
http_hdrs = {
|
||||
'User-Agent': 'curl/8.7.1',
|
||||
'Proxy-Connection': 'Keep-Alive'}
|
||||
|
||||
def __init__(self, cfg):
|
||||
self.cfg = cfg
|
||||
import requests
|
||||
self.session = requests.Session()
|
||||
self.session.trust_env = False # ignore *_PROXY environment vars
|
||||
self.session.headers = self.http_hdrs
|
||||
if self.cfg.proxy:
|
||||
self.session.proxies.update({
|
||||
'http': f'socks5h://{self.cfg.proxy}',
|
||||
'https': f'socks5h://{self.cfg.proxy}'
|
||||
})
|
||||
|
||||
def call(self, name, path, err_fs, timeout, *, data=None):
|
||||
url = self.proto + '://' + self.host + path
|
||||
kwargs = {
|
||||
'url': url,
|
||||
'timeout': timeout or self.timeout,
|
||||
'verify': self.verify}
|
||||
if data:
|
||||
kwargs['data'] = data
|
||||
res = getattr(self.session, name)(**kwargs)
|
||||
if res.status_code != 200:
|
||||
die(2, '\n' + err_fs.format(s=res.status_code, u=url, d=data))
|
||||
return res.content.decode()
|
||||
|
||||
def get(self, *, path, timeout=None):
|
||||
err_fs = 'HTTP Get failed with status code {s}\n URL: {u}'
|
||||
return self.call('get', path, err_fs, timeout)
|
||||
|
||||
def post(self, *, path, data, timeout=None):
|
||||
err_fs = 'HTTP Post failed with status code {s}\n URL: {u}\n DATA: {d}'
|
||||
return self.call('post', path, err_fs, timeout, data=data)
|
||||
|
||||
def get_form(self, timeout=None):
|
||||
return self.get(path=self.form_path, timeout=timeout)
|
||||
|
||||
def post_form(self, *, data, timeout=None):
|
||||
return self.post(path=self.form_path, data=data, timeout=timeout)
|
||||
|
||||
def get_form_element(self, text):
|
||||
from lxml import html
|
||||
root = html.document_fromstring(text)
|
||||
res = [e for e in root.forms if e.attrib.get('action', '').endswith(self.form_path)]
|
||||
assert res, 'no matching forms!'
|
||||
assert len(res) == 1, 'more than one matching form!'
|
||||
return res[0]
|
||||
|
||||
def cache_fn(self, desc):
|
||||
return f'{self.name}-{desc}.html'
|
||||
|
||||
def save_response(self, data, desc):
|
||||
from ..fileutil import write_data_to_file
|
||||
write_data_to_file(
|
||||
self.cfg,
|
||||
self.cache_fn(desc),
|
||||
data,
|
||||
desc = f'{desc} page from {orange(self.host)}')
|
||||
|
||||
class BlockchairTxProxyClient(TxProxyClient):
|
||||
|
||||
name = 'blockchair'
|
||||
host = 'blockchair.com'
|
||||
form_path = '/broadcast'
|
||||
assets = {
|
||||
'avax': 'avalanche',
|
||||
'btc': 'bitcoin',
|
||||
'bch': 'bitcoin-cash',
|
||||
'bnb': 'bnb',
|
||||
'dash': 'dash',
|
||||
'doge': 'dogecoin',
|
||||
'eth': 'ethereum',
|
||||
'etc': 'ethereum-classic',
|
||||
'ltc': 'litecoin',
|
||||
'zec': 'zcash',
|
||||
}
|
||||
active_assets = () # tried with ETH, doesn’t work
|
||||
|
||||
def create_post_data(self, *, form_text, coin, tx_hex):
|
||||
|
||||
coin = coin.lower()
|
||||
assert coin in self.assets, f'coin {coin} not supported by {self.name}'
|
||||
asset = self.assets[coin]
|
||||
|
||||
form = self.get_form_element(form_text)
|
||||
data = {}
|
||||
|
||||
e = form.find('.//input')
|
||||
assert e.attrib['name'] == '_token', 'input name incorrect!'
|
||||
data['_token'] = e.attrib['value']
|
||||
|
||||
e = form.find('.//textarea')
|
||||
assert e.attrib['name'] == 'data', 'textarea name incorrect!'
|
||||
data['data'] = '0x' + tx_hex
|
||||
|
||||
e = form.find('.//button')
|
||||
assert e is not None, 'missing button!'
|
||||
|
||||
e = form.find('.//select')
|
||||
assert e.attrib['name'] == 'blockchain', 'select element name incorrect!'
|
||||
|
||||
assets = [f.get('value') for f in e.iter() if f.get('value')]
|
||||
assert asset in assets, f'coin {coin} ({asset}) not currently supported by {self.name}'
|
||||
|
||||
data['blockchain'] = asset
|
||||
|
||||
return data
|
||||
|
||||
def get_txid(self, *, result_text):
|
||||
msg(f'Response parsing TBD. Check the cached response at {self.cache_fn("result")}')
|
||||
|
||||
class EtherscanTxProxyClient(TxProxyClient):
|
||||
name = 'etherscan'
|
||||
host = 'etherscan.io'
|
||||
form_path = '/pushTx'
|
||||
assets = {'eth': 'ethereum'}
|
||||
active_assets = ('eth',)
|
||||
|
||||
def create_post_data(self, *, form_text, coin, tx_hex):
|
||||
|
||||
form = self.get_form_element(form_text)
|
||||
data = {}
|
||||
|
||||
for e in form.findall('.//input'):
|
||||
data[e.attrib['name']] = e.attrib['value']
|
||||
|
||||
if len(data) != 4:
|
||||
msg('')
|
||||
self.save_response(form_text, 'form')
|
||||
die(3, f'{len(data)}: unexpected number of keys in data (expected 4)')
|
||||
|
||||
e = form.find('.//textarea')
|
||||
data[e.attrib['name']] = '0x' + tx_hex
|
||||
|
||||
return data
|
||||
|
||||
def get_txid(self, *, result_text):
|
||||
import json
|
||||
from ..obj import CoinTxID, is_coin_txid
|
||||
form = self.get_form_element(result_text)
|
||||
json_text = form.find('div/div/div')[1].tail
|
||||
txid = json.loads(json_text)['result'].removeprefix('0x')
|
||||
if is_coin_txid(txid):
|
||||
return CoinTxID(txid)
|
||||
else:
|
||||
return False
|
||||
|
||||
def send_tx(cfg, tx):
|
||||
|
||||
c = get_client(cfg)
|
||||
msg(f'Using {pink(cfg.tx_proxy.upper())} tx proxy')
|
||||
|
||||
if not cfg.test:
|
||||
tx.confirm_send()
|
||||
|
||||
msg_r(f'Retrieving form from {orange(c.host)}...')
|
||||
form_text = c.get_form(timeout=180)
|
||||
msg('done')
|
||||
|
||||
msg_r('Parsing form...')
|
||||
post_data = c.create_post_data(
|
||||
form_text = form_text,
|
||||
coin = cfg.coin,
|
||||
tx_hex = tx.serialized)
|
||||
msg('done')
|
||||
|
||||
if cfg.test:
|
||||
msg(f'Form retrieved from {orange(c.host)} and parsed')
|
||||
msg(green('Transaction can be sent'))
|
||||
return False
|
||||
|
||||
msg_r('Sending data...')
|
||||
result_text = c.post_form(data=post_data, timeout=180)
|
||||
msg('done')
|
||||
|
||||
msg_r('Parsing response...')
|
||||
txid = c.get_txid(result_text=result_text)
|
||||
msg('done')
|
||||
|
||||
msg('Transaction ' + (f'sent: {txid.hl()}' if txid else 'send failed'))
|
||||
c.save_response(result_text, 'result')
|
||||
|
||||
return bool(txid)
|
||||
|
||||
tx_proxies = {
|
||||
'blockchair': BlockchairTxProxyClient,
|
||||
'etherscan': EtherscanTxProxyClient
|
||||
}
|
||||
|
||||
def get_client(cfg, *, check_only=False):
|
||||
proxy = tx_proxies[cfg.tx_proxy]
|
||||
if cfg.coin.lower() in proxy.active_assets:
|
||||
return True if check_only else proxy(cfg)
|
||||
else:
|
||||
die(1, f'Coin {cfg.coin} not supported by TX proxy {pink(proxy.name.upper())}')
|
||||
|
||||
def check_client(cfg):
|
||||
return get_client(cfg, check_only=True)
|
||||
|
|
@ -46,5 +46,6 @@
|
|||
semantic-version = semantic-version;
|
||||
pexpect = pexpect; # test suite
|
||||
pycoin = pycoin; # test suite
|
||||
lxml = lxml;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ install_requires =
|
|||
aiohttp
|
||||
requests
|
||||
pexpect
|
||||
lxml
|
||||
scrypt; platform_system != "Windows" # must be installed by hand on MSYS2
|
||||
semantic-version; platform_system != "Windows" # scripts/create-token.py
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ from .common import (
|
|||
)
|
||||
from .ct_base import CmdTestBase
|
||||
from .ct_shared import CmdTestShared
|
||||
from .etherscan import run_etherscan_server
|
||||
|
||||
del_addrs = ('4', '1')
|
||||
dfl_sid = '98831F3A'
|
||||
|
|
@ -178,6 +179,12 @@ token_bals_getbalance = lambda k: {
|
|||
|
||||
coin = cfg.coin
|
||||
|
||||
def etherscan_server_start():
|
||||
import threading
|
||||
t = threading.Thread(target=run_etherscan_server, name='Etherscan server thread')
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
class CmdTestEthdev(CmdTestBase, CmdTestShared):
|
||||
'Ethereum transacting, token deployment and tracking wallet operations'
|
||||
networks = ('eth', 'etc')
|
||||
|
|
@ -237,6 +244,8 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
|
|||
('txview1_sig', 'viewing the signed transaction'),
|
||||
('tx_status0_bad', 'getting the transaction status'),
|
||||
('txsign1_ni', 'signing the transaction (non-interactive)'),
|
||||
('txsend_etherscan_test','sending the transaction via Etherscan (simulation, with --test)'),
|
||||
('txsend_etherscan', 'sending the transaction via Etherscan (simulation)'),
|
||||
('txsend1', 'sending the transaction'),
|
||||
('bal1', f'the {coin} balance'),
|
||||
|
||||
|
|
@ -450,6 +459,9 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
|
|||
self.message = 'attack at dawn'
|
||||
self.spawn_env['MMGEN_BOGUS_SEND'] = ''
|
||||
|
||||
if type(self) is CmdTestEthdev:
|
||||
etherscan_server_start() # TODO: stop server when test group finishes executing
|
||||
|
||||
@property
|
||||
async def rpc(self):
|
||||
from mmgen.rpc import rpc_init
|
||||
|
|
@ -715,7 +727,7 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
|
|||
+ [txfile, dfl_words_file])
|
||||
return self.txsign_ui_common(t, ni=ni, has_label=True)
|
||||
|
||||
def txsend(self, ext='{}.regtest.sigtx', add_args=[]):
|
||||
def txsend(self, ext='{}.regtest.sigtx', add_args=[], test=False):
|
||||
ext = ext.format('-α' if cfg.debug_utf8 else '')
|
||||
txfile = self.get_file_with_ext(ext, no_dot=True)
|
||||
t = self.spawn('mmgen-txsend', self.eth_args + add_args + [txfile])
|
||||
|
|
@ -723,6 +735,7 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
|
|||
t,
|
||||
quiet = not cfg.debug,
|
||||
bogus_send = False,
|
||||
test = test,
|
||||
has_label = True)
|
||||
return t
|
||||
|
||||
|
|
@ -765,6 +778,10 @@ class CmdTestEthdev(CmdTestBase, CmdTestShared):
|
|||
return self.tx_status(ext='{}.regtest.sigtx', expect_str='neither in mempool nor blockchain', exit_val=1)
|
||||
def txsign1_ni(self):
|
||||
return self.txsign(ni=True, dev_send=True)
|
||||
def txsend_etherscan_test(self):
|
||||
return self.txsend(add_args=['--tx-proxy=ether', '--test'], test='tx_proxy')
|
||||
def txsend_etherscan(self):
|
||||
return self.txsend(add_args=['--tx-proxy=ethersc'])
|
||||
def txsend1(self):
|
||||
return self.txsend()
|
||||
def txview1_sig(self): # do after send so that TxID is displayed
|
||||
|
|
|
|||
|
|
@ -179,6 +179,9 @@ class CmdTestShared:
|
|||
if bogus_send:
|
||||
txid = ''
|
||||
t.expect('BOGUS transaction NOT sent')
|
||||
elif test == 'tx_proxy':
|
||||
t.expect('can be sent')
|
||||
return True
|
||||
else:
|
||||
m = 'TxID: ' if test else 'Transaction sent: '
|
||||
txid = strip_ansi_escapes(t.expect_getend(m))
|
||||
|
|
|
|||
32
test/cmdtest_d/etherscan.py
Executable file
32
test/cmdtest_d/etherscan.py
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from http.server import HTTPServer, CGIHTTPRequestHandler
|
||||
|
||||
from mmgen.util import msg
|
||||
from mmgen.util2 import port_in_use
|
||||
|
||||
class handler(CGIHTTPRequestHandler):
|
||||
header = b'HTTP/1.1 200 OK\nContent-type: text/html\n\n'
|
||||
|
||||
def do_response(self, target):
|
||||
with open(f'test/ref/ethereum/etherscan-{target}.html') as fh:
|
||||
text = fh.read()
|
||||
self.wfile.write(self.header + text.encode())
|
||||
|
||||
def do_GET(self):
|
||||
return self.do_response('form')
|
||||
|
||||
def do_POST(self):
|
||||
return self.do_response('result')
|
||||
|
||||
def run_etherscan_server(server_class=HTTPServer, handler_class=handler):
|
||||
|
||||
if port_in_use(28800):
|
||||
msg('Port 28800 in use. Assuming etherscan server is running')
|
||||
return True
|
||||
|
||||
msg('Etherscan server listening on port 28800')
|
||||
server_address = ('localhost', 28800)
|
||||
httpd = server_class(server_address, handler_class)
|
||||
httpd.serve_forever()
|
||||
msg('Etherscan server exiting')
|
||||
10
test/overlay/fakemods/mmgen/tx/tx_proxy.py
Executable file
10
test/overlay/fakemods/mmgen/tx/tx_proxy.py
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
from .tx_proxy_orig import *
|
||||
|
||||
class overlay_fake_EtherscanTxProxyClient:
|
||||
proto = 'http'
|
||||
host = 'localhost:28800'
|
||||
verify = False
|
||||
|
||||
EtherscanTxProxyClient.proto = overlay_fake_EtherscanTxProxyClient.proto
|
||||
EtherscanTxProxyClient.host = overlay_fake_EtherscanTxProxyClient.host
|
||||
EtherscanTxProxyClient.verify = overlay_fake_EtherscanTxProxyClient.verify
|
||||
34
test/ref/ethereum/etherscan-form.html
Normal file
34
test/ref/ethereum/etherscan-form.html
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<!doctype html>
|
||||
<html id="html" lang="en">
|
||||
<head><title>
|
||||
Broadcast Raw Transaction | Etherscan
|
||||
</title>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<form action="/search" method="GET">
|
||||
<input type="hidden" value="" id="hdnSearchLabel" />
|
||||
</form>
|
||||
|
||||
<form method="post" action="./pushTx" id="ctl00" class="js-validate">
|
||||
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="foo" />
|
||||
<input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="bar" />
|
||||
<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="baz" />
|
||||
<div>
|
||||
<div>
|
||||
<label for="signedTransactionHex">Enter signed transaction hex</label>
|
||||
<div class="js-form-message">
|
||||
<textarea name="ctl00$ContentPlaceHolder1$txtRawTx" rows="8" cols="20" maxlength="50000" id="ContentPlaceHolder1_txtRawTx" required="" placeholder="e.g. 0x.." data-bg-msg="Please enter signed transaction hex">
|
||||
</textarea>
|
||||
</div>
|
||||
<p>Tip: You can also broadcast programatically via our <a href="https://docs.etherscan.io/api-endpoints/geth-parity-proxy" target="_blank">[eth_sendRawTransaction]</a>. Accepts the paramater "hex" for prefilling the input box above (i.e <a href="/pushTx?hex=0x000000">Click here</a>)</p>
|
||||
</div>
|
||||
|
||||
<div class="card-footer bg-light">
|
||||
<input type="submit" name="ctl00$ContentPlaceHolder1$btnSubmit" value="Send Transaction" id="ContentPlaceHolder1_btnSubmit" class="btn btn-primary" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
33
test/ref/ethereum/etherscan-result.html
Normal file
33
test/ref/ethereum/etherscan-result.html
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<!doctype html>
|
||||
<html id="html" lang="en">
|
||||
<head><title>
|
||||
Broadcast Raw Transaction | Etherscan
|
||||
</title><meta charset="utf-8"/>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/search" method="GET">
|
||||
<input id="hdnIsTestNet" value="False" type="hidden" />
|
||||
</form>
|
||||
|
||||
<form method="post" action="./pushTx" id="ctl00" class="js-validate">
|
||||
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="foo" />
|
||||
<input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="bar" />
|
||||
<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="baz" />
|
||||
<div>
|
||||
<div>
|
||||
<div role='alert'><button type='button'></button><strong>Success! </strong>{"jsonrpc":"2.0","id":1,"result":"0xbeadcafebeadcafebeadcafebeadcafebeadcafebeadcafebeadcafebeadcafe"}
|
||||
<span class='d-block'><i></i> Transaction Hash: <a href='/tx/0xbeadcafebeadcafebeadcafebeadcafebeadcafebeadcafebeadcafebeadcafe'><span class='text-primary'><a href='/tx/0xbeadcafebeadcafebeadcafebeadcafebeadcafebeadcafebeadcafebeadcafe'>0xbeadcafebeadcafebeadcafebeadcafebeadcafebeadcafebeadcafebeadcafe</a></span></b></a></span></div>
|
||||
<label for="signedTransactionHex">Enter signed transaction hex</label>
|
||||
<div>
|
||||
<textarea name="ctl00$ContentPlaceHolder1$txtRawTx" rows="8" cols="20" maxlength="50000" id="ContentPlaceHolder1_txtRawTx" required="" placeholder="e.g. 0x.." data-bg-msg="Please enter signed transaction hex">
|
||||
0xdeadbeef</textarea>
|
||||
</div>
|
||||
<p>Tip: You can also broadcast programatically via our <a href="https://docs.etherscan.io/api-endpoints/geth-parity-proxy" target="_blank">[eth_sendRawTransaction]</a>. Accepts the paramater "hex" for prefilling the input box above (i.e <a href="/pushTx?hex=0x000000">Click here</a>)</p>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" name="ctl00$ContentPlaceHolder1$btnSubmit" value="Send Transaction" id="ContentPlaceHolder1_btnSubmit" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue