txcreate(tx) -> tx.create() (delete txcreate.py)
This commit is contained in:
parent
e836601324
commit
207632cb40
8 changed files with 226 additions and 255 deletions
|
|
@ -80,7 +80,7 @@ rpc_init()
|
|||
tx_file = cmd_args.pop(0)
|
||||
check_infile(tx_file)
|
||||
|
||||
from mmgen.txcreate import *
|
||||
from mmgen.tx import *
|
||||
from mmgen.txsign import *
|
||||
|
||||
seed_files = get_seed_files(opt,cmd_args) if (cmd_args or opt.send) else None
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ opts_data = lambda: {
|
|||
|
||||
cmd_args = opts.init(opts_data)
|
||||
|
||||
from mmgen.txcreate import *
|
||||
tx = txcreate(cmd_args,do_info=opt.info)
|
||||
rpc_init()
|
||||
|
||||
from mmgen.tx import MMGenTX
|
||||
tx = MMGenTX()
|
||||
tx.create(cmd_args,do_info=opt.info)
|
||||
tx.write_to_file(ask_write=not opt.yes,ask_overwrite=not opt.yes,ask_write_default_yes=False)
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ cmd_args = opts.init(opts_data)
|
|||
|
||||
rpc_init()
|
||||
|
||||
from mmgen.txcreate import *
|
||||
from mmgen.tx import *
|
||||
from mmgen.txsign import *
|
||||
|
||||
seed_files = get_seed_files(opt,cmd_args)
|
||||
|
|
@ -88,7 +88,8 @@ kal = get_keyaddrlist(opt)
|
|||
kl = get_keylist(opt)
|
||||
if kl and kal: kl.remove_dup_keys(kal)
|
||||
|
||||
tx = txcreate(cmd_args,caller='txdo')
|
||||
tx = MMGenTX(caller='txdo')
|
||||
tx.create(cmd_args)
|
||||
txsign(tx,seed_files,kl,kal)
|
||||
tx.write_to_file(ask_write=False)
|
||||
|
||||
|
|
|
|||
|
|
@ -349,10 +349,9 @@ class BTCAmt(Decimal,Hilite,InitErrors):
|
|||
def __neg__(self,other,context=None):
|
||||
return type(self)(Decimal.__neg__(self,other,context))
|
||||
|
||||
class BCHAmt(BTCAmt):
|
||||
pass
|
||||
class LTCAmt(BTCAmt):
|
||||
max_amt = 84000000
|
||||
class BCHAmt(BTCAmt): pass
|
||||
class B2XAmt(BTCAmt): pass
|
||||
class LTCAmt(BTCAmt): max_amt = 84000000
|
||||
|
||||
class CoinAddr(str,Hilite,InitErrors,MMGenObject):
|
||||
color = 'cyan'
|
||||
|
|
|
|||
|
|
@ -70,7 +70,8 @@ class BitcoinProtocol(MMGenObject):
|
|||
sighash_type = 'ALL'
|
||||
block0 = '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'
|
||||
forks = [
|
||||
(478559,'00000000000000000019f112ec0a9982926f1258cdcc558dd7c3b7e5dc7fa148','bch')
|
||||
(478559,'00000000000000000019f112ec0a9982926f1258cdcc558dd7c3b7e5dc7fa148','bch'),
|
||||
(None,'','b2x')
|
||||
]
|
||||
caps = ('rbf','segwit')
|
||||
base_coin = 'BTC'
|
||||
|
|
|
|||
213
mmgen/tx.py
213
mmgen/tx.py
|
|
@ -26,6 +26,69 @@ from binascii import unhexlify
|
|||
from mmgen.common import *
|
||||
from mmgen.obj import *
|
||||
|
||||
pnm = g.proj_name
|
||||
|
||||
wmsg = {
|
||||
'addr_in_addrfile_only': """
|
||||
Warning: output address {mmgenaddr} is not in the tracking wallet, which means
|
||||
its balance will not be tracked. You're strongly advised to import the address
|
||||
into your tracking wallet before broadcasting this transaction.
|
||||
""".strip(),
|
||||
'addr_not_found': """
|
||||
No data for {pnm} address {mmgenaddr} could be found in either the tracking
|
||||
wallet or the supplied address file. Please import this address into your
|
||||
tracking wallet, or supply an address file for it on the command line.
|
||||
""".strip(),
|
||||
'addr_not_found_no_addrfile': """
|
||||
No data for {pnm} address {mmgenaddr} could be found in the tracking wallet.
|
||||
Please import this address into your tracking wallet or supply an address file
|
||||
for it on the command line.
|
||||
""".strip(),
|
||||
'non_mmgen_inputs': """
|
||||
NOTE: This transaction includes non-{pnm} inputs, which makes the signing
|
||||
process more complicated. When signing the transaction, keys for non-{pnm}
|
||||
inputs must be supplied to '{pnl}-txsign' in a file with the '--keys-from-file'
|
||||
option.
|
||||
Selected non-{pnm} inputs: {{}}
|
||||
""".strip().format(pnm=pnm,pnl=pnm.lower()),
|
||||
'not_enough_coin': """
|
||||
Selected outputs insufficient to fund this transaction ({{}} {} needed)
|
||||
""".strip().format(g.coin),
|
||||
'no_change_output': """
|
||||
ERROR: No change address specified. If you wish to create a transaction with
|
||||
only one output, specify a single output address with no {} amount
|
||||
""".strip().format(g.coin),
|
||||
}
|
||||
|
||||
def select_unspent(unspent,prompt):
|
||||
while True:
|
||||
reply = my_raw_input(prompt).strip()
|
||||
if reply:
|
||||
selected = AddrIdxList(fmt_str=','.join(reply.split()),on_fail='return')
|
||||
if selected:
|
||||
if selected[-1] <= len(unspent):
|
||||
return selected
|
||||
msg('Unspent output number must be <= %s' % len(unspent))
|
||||
|
||||
def mmaddr2coinaddr(mmaddr,ad_w,ad_f):
|
||||
|
||||
# assume mmaddr has already been checked
|
||||
coin_addr = ad_w.mmaddr2coinaddr(mmaddr)
|
||||
|
||||
if not coin_addr:
|
||||
if ad_f:
|
||||
coin_addr = ad_f.mmaddr2coinaddr(mmaddr)
|
||||
if coin_addr:
|
||||
msg(wmsg['addr_in_addrfile_only'].format(mmgenaddr=mmaddr))
|
||||
if not keypress_confirm('Continue anyway?'):
|
||||
sys.exit(1)
|
||||
else:
|
||||
die(2,wmsg['addr_not_found'].format(pnm=pnm,mmgenaddr=mmaddr))
|
||||
else:
|
||||
die(2,wmsg['addr_not_found_no_addrfile'].format(pnm=pnm,mmgenaddr=mmaddr))
|
||||
|
||||
return CoinAddr(coin_addr)
|
||||
|
||||
def segwit_is_active(exit_on_error=False):
|
||||
d = g.rpch.getblockchaininfo()
|
||||
if d['chain'] == 'regtest':
|
||||
|
|
@ -184,7 +247,7 @@ class MMGenTX(MMGenObject):
|
|||
desc = 'transaction outputs'
|
||||
member_type = 'MMGenTxOutput'
|
||||
|
||||
def __init__(self,filename=None,md_only=False):
|
||||
def __init__(self,filename=None,md_only=False,caller=None):
|
||||
self.inputs = self.MMGenTxInputList()
|
||||
self.outputs = self.MMGenTxOutputList()
|
||||
self.send_amt = g.proto.coin_amt('0') # total amt minus change
|
||||
|
|
@ -198,6 +261,7 @@ class MMGenTX(MMGenObject):
|
|||
self.blockcount = 0
|
||||
self.chain = None
|
||||
self.coin = None
|
||||
self.caller = caller
|
||||
|
||||
if filename:
|
||||
self.parse_tx_file(filename,md_only=md_only)
|
||||
|
|
@ -876,7 +940,7 @@ class MMGenTX(MMGenObject):
|
|||
self.blockcount = int(blockcount)
|
||||
desc = 'transaction hex data'
|
||||
self.hex = HexStr(self.hex,on_fail='raise')
|
||||
if md_only: return # the following ops will all fail if g.coin doesn't match tx.coin
|
||||
if md_only: return # the following ops will all fail if g.coin doesn't match self.coin
|
||||
desc = 'coin type in metadata'
|
||||
assert self.coin == g.coin,'invalid coin type'
|
||||
desc = 'inputs data'
|
||||
|
|
@ -891,6 +955,151 @@ class MMGenTX(MMGenObject):
|
|||
if not self.chain and not self.inputs[0].addr.is_for_chain('testnet'):
|
||||
self.chain = 'mainnet'
|
||||
|
||||
def get_fee_from_estimate_or_user(self,estimate_fail_msg_shown=[]):
|
||||
|
||||
if opt.tx_fee:
|
||||
desc = 'User-selected'
|
||||
start_fee = opt.tx_fee
|
||||
else:
|
||||
desc = 'Network-estimated'
|
||||
ret = g.rpch.estimatefee(opt.tx_confs)
|
||||
if ret == -1:
|
||||
if not estimate_fail_msg_shown:
|
||||
msg('Network fee estimation for {} confirmations failed'.format(opt.tx_confs))
|
||||
estimate_fail_msg_shown.append(True)
|
||||
start_fee = None
|
||||
else:
|
||||
start_fee = g.proto.coin_amt(ret) * opt.tx_fee_adj * self.estimate_size() / 1024
|
||||
if opt.verbose:
|
||||
msg('{} fee ({} confs): {} {}/kB'.format(desc,opt.tx_confs,ret,g.coin))
|
||||
msg('TX size (estimated): {}'.format(self.estimate_size()))
|
||||
|
||||
return self.get_usr_fee_interactive(start_fee,desc=desc)
|
||||
|
||||
def get_outputs_from_cmdline(self,cmd_args):
|
||||
from mmgen.addr import AddrList,AddrData
|
||||
addrfiles = [a for a in cmd_args if get_extension(a) == AddrList.ext]
|
||||
cmd_args = set(cmd_args) - set(addrfiles)
|
||||
|
||||
ad_f = AddrData()
|
||||
for a in addrfiles:
|
||||
check_infile(a)
|
||||
ad_f.add(AddrList(a))
|
||||
|
||||
ad_w = AddrData(source='tw')
|
||||
|
||||
for a in cmd_args:
|
||||
if ',' in a:
|
||||
a1,a2 = a.split(',',1)
|
||||
if is_mmgen_id(a1) or is_coin_addr(a1):
|
||||
coin_addr = mmaddr2coinaddr(a1,ad_w,ad_f) if is_mmgen_id(a1) else CoinAddr(a1)
|
||||
self.add_output(coin_addr,g.proto.coin_amt(a2))
|
||||
else:
|
||||
die(2,"%s: invalid subargument in command-line argument '%s'" % (a1,a))
|
||||
elif is_mmgen_id(a) or is_coin_addr(a):
|
||||
if self.get_chg_output_idx() != None:
|
||||
die(2,'ERROR: More than one change address listed on command line')
|
||||
coin_addr = mmaddr2coinaddr(a,ad_w,ad_f) if is_mmgen_id(a) else CoinAddr(a)
|
||||
self.add_output(coin_addr,g.proto.coin_amt('0'),is_chg=True)
|
||||
else:
|
||||
die(2,'{}: invalid command-line argument'.format(a))
|
||||
|
||||
if not self.outputs:
|
||||
die(2,'At least one output must be specified on the command line')
|
||||
|
||||
if self.get_chg_output_idx() == None:
|
||||
die(2,('ERROR: No change output specified',wmsg['no_change_output'])[len(self.outputs) == 1])
|
||||
|
||||
self.add_mmaddrs_to_outputs(ad_w,ad_f)
|
||||
self.check_dup_addrs('outputs')
|
||||
|
||||
if not segwit_is_active() and self.has_segwit_outputs():
|
||||
fs = '{} Segwit address requested on the command line, but Segwit is not active on this chain'
|
||||
rdie(2,fs.format(g.proj_name))
|
||||
|
||||
def get_inputs_from_user(self,tw):
|
||||
|
||||
while True:
|
||||
m = 'Enter a range or space-separated list of outputs to spend: '
|
||||
sel_nums = select_unspent(tw.unspent,m)
|
||||
msg('Selected output%s: %s' % (suf(sel_nums,'s'),' '.join(str(i) for i in sel_nums)))
|
||||
|
||||
sel_unspent = tw.MMGenTwOutputList([tw.unspent[i-1] for i in sel_nums])
|
||||
|
||||
t_inputs = sum(s.amt for s in sel_unspent)
|
||||
if t_inputs < self.send_amt:
|
||||
msg(wmsg['not_enough_coin'].format(self.send_amt-t_inputs))
|
||||
continue
|
||||
|
||||
non_mmaddrs = [i for i in sel_unspent if i.twmmid.type == 'non-mmgen']
|
||||
if non_mmaddrs and self.caller != 'txdo':
|
||||
msg(wmsg['non_mmgen_inputs'].format(', '.join(set(sorted([a.addr.hl() for a in non_mmaddrs])))))
|
||||
if not keypress_confirm('Accept?'):
|
||||
continue
|
||||
|
||||
self.copy_inputs_from_tw(sel_unspent) # makes self.inputs
|
||||
|
||||
change_amt = self.sum_inputs() - self.send_amt - self.get_fee_from_estimate_or_user()
|
||||
|
||||
if change_amt >= 0:
|
||||
p = 'Transaction produces {} {} in change'.format(change_amt.hl(),g.coin)
|
||||
if opt.yes or keypress_confirm(p+'. OK?',default_yes=True):
|
||||
if opt.yes: msg(p)
|
||||
return change_amt
|
||||
else:
|
||||
msg(wmsg['not_enough_coin'].format(abs(change_amt)))
|
||||
|
||||
def create(self,cmd_args,do_info=False):
|
||||
|
||||
if opt.comment_file: self.add_comment(opt.comment_file)
|
||||
|
||||
if not do_info: self.get_outputs_from_cmdline(cmd_args)
|
||||
|
||||
do_license_msg()
|
||||
|
||||
from mmgen.tw import MMGenTrackingWallet
|
||||
tw = MMGenTrackingWallet(minconf=opt.minconf)
|
||||
tw.view_and_sort(self)
|
||||
tw.display_total()
|
||||
|
||||
if do_info: sys.exit(0)
|
||||
|
||||
self.send_amt = self.sum_outputs()
|
||||
|
||||
msg('Total amount to spend: {}'.format(
|
||||
('Unknown','{} {}'.format(self.send_amt.hl(),g.coin))[bool(self.send_amt)]
|
||||
))
|
||||
|
||||
change_amt = self.get_inputs_from_user(tw)
|
||||
|
||||
if opt.rbf: self.signal_for_rbf() # only after we have inputs
|
||||
|
||||
chg_idx = self.get_chg_output_idx()
|
||||
|
||||
if change_amt == 0:
|
||||
msg('Warning: Change address will be deleted as transaction produces no change')
|
||||
self.del_output(chg_idx)
|
||||
else:
|
||||
self.update_output_amt(chg_idx,g.proto.coin_amt(change_amt))
|
||||
|
||||
if not self.send_amt:
|
||||
self.send_amt = change_amt
|
||||
|
||||
if not opt.yes:
|
||||
self.add_comment() # edits an existing comment
|
||||
self.create_raw() # creates self.hex, self.txid
|
||||
|
||||
self.add_timestamp()
|
||||
self.add_blockcount()
|
||||
self.chain = g.chain
|
||||
|
||||
assert self.sum_inputs() - self.sum_outputs() <= g.proto.max_tx_fee
|
||||
|
||||
qmsg('Transaction successfully created')
|
||||
|
||||
if not opt.yes:
|
||||
self.view_with_prompt('View decoded transaction?')
|
||||
|
||||
class MMGenBumpTX(MMGenTX):
|
||||
|
||||
min_fee = None
|
||||
|
|
|
|||
|
|
@ -1,242 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C)2013-2017 Philemon <mmgen-py@yandex.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
txcreate: Create a cryptocoin transaction with MMGen- and/or non-MMGen inputs and outputs
|
||||
"""
|
||||
|
||||
from mmgen.common import *
|
||||
from mmgen.tx import *
|
||||
from mmgen.tw import *
|
||||
|
||||
pnm = g.proj_name
|
||||
|
||||
wmsg = {
|
||||
'addr_in_addrfile_only': """
|
||||
Warning: output address {mmgenaddr} is not in the tracking wallet, which means
|
||||
its balance will not be tracked. You're strongly advised to import the address
|
||||
into your tracking wallet before broadcasting this transaction.
|
||||
""".strip(),
|
||||
'addr_not_found': """
|
||||
No data for {pnm} address {mmgenaddr} could be found in either the tracking
|
||||
wallet or the supplied address file. Please import this address into your
|
||||
tracking wallet, or supply an address file for it on the command line.
|
||||
""".strip(),
|
||||
'addr_not_found_no_addrfile': """
|
||||
No data for {pnm} address {mmgenaddr} could be found in the tracking wallet.
|
||||
Please import this address into your tracking wallet or supply an address file
|
||||
for it on the command line.
|
||||
""".strip(),
|
||||
'non_mmgen_inputs': """
|
||||
NOTE: This transaction includes non-{pnm} inputs, which makes the signing
|
||||
process more complicated. When signing the transaction, keys for non-{pnm}
|
||||
inputs must be supplied to '{pnl}-txsign' in a file with the '--keys-from-file'
|
||||
option.
|
||||
Selected non-{pnm} inputs: {{}}
|
||||
""".strip().format(pnm=pnm,pnl=pnm.lower()),
|
||||
'not_enough_coin': """
|
||||
Selected outputs insufficient to fund this transaction ({{}} {} needed)
|
||||
""".strip().format(g.coin),
|
||||
'no_change_output': """
|
||||
ERROR: No change address specified. If you wish to create a transaction with
|
||||
only one output, specify a single output address with no {} amount
|
||||
""".strip().format(g.coin),
|
||||
}
|
||||
|
||||
def select_unspent(unspent,prompt):
|
||||
while True:
|
||||
reply = my_raw_input(prompt).strip()
|
||||
if reply:
|
||||
selected = AddrIdxList(fmt_str=','.join(reply.split()),on_fail='return')
|
||||
if selected:
|
||||
if selected[-1] <= len(unspent):
|
||||
return selected
|
||||
msg('Unspent output number must be <= %s' % len(unspent))
|
||||
|
||||
def mmaddr2coinaddr(mmaddr,ad_w,ad_f):
|
||||
|
||||
# assume mmaddr has already been checked
|
||||
coin_addr = ad_w.mmaddr2coinaddr(mmaddr)
|
||||
|
||||
if not coin_addr:
|
||||
if ad_f:
|
||||
coin_addr = ad_f.mmaddr2coinaddr(mmaddr)
|
||||
if coin_addr:
|
||||
msg(wmsg['addr_in_addrfile_only'].format(mmgenaddr=mmaddr))
|
||||
if not keypress_confirm('Continue anyway?'):
|
||||
sys.exit(1)
|
||||
else:
|
||||
die(2,wmsg['addr_not_found'].format(pnm=pnm,mmgenaddr=mmaddr))
|
||||
else:
|
||||
die(2,wmsg['addr_not_found_no_addrfile'].format(pnm=pnm,mmgenaddr=mmaddr))
|
||||
|
||||
return CoinAddr(coin_addr)
|
||||
|
||||
def get_fee_from_estimate_or_user(tx,estimate_fail_msg_shown=[]):
|
||||
|
||||
if opt.tx_fee:
|
||||
desc = 'User-selected'
|
||||
start_fee = opt.tx_fee
|
||||
else:
|
||||
desc = 'Network-estimated'
|
||||
ret = g.rpch.estimatefee(opt.tx_confs)
|
||||
if ret == -1:
|
||||
if not estimate_fail_msg_shown:
|
||||
msg('Network fee estimation for {} confirmations failed'.format(opt.tx_confs))
|
||||
estimate_fail_msg_shown.append(True)
|
||||
start_fee = None
|
||||
else:
|
||||
start_fee = g.proto.coin_amt(ret) * opt.tx_fee_adj * tx.estimate_size() / 1024
|
||||
if opt.verbose:
|
||||
msg('{} fee ({} confs): {} {}/kB'.format(desc,opt.tx_confs,ret,g.coin))
|
||||
msg('TX size (estimated): {}'.format(tx.estimate_size()))
|
||||
|
||||
return tx.get_usr_fee_interactive(start_fee,desc=desc)
|
||||
|
||||
def get_outputs_from_cmdline(cmd_args,tx):
|
||||
from mmgen.addr import AddrList,AddrData
|
||||
addrfiles = [a for a in cmd_args if get_extension(a) == AddrList.ext]
|
||||
cmd_args = set(cmd_args) - set(addrfiles)
|
||||
|
||||
ad_f = AddrData()
|
||||
for a in addrfiles:
|
||||
check_infile(a)
|
||||
ad_f.add(AddrList(a))
|
||||
|
||||
ad_w = AddrData(source='tw')
|
||||
|
||||
for a in cmd_args:
|
||||
if ',' in a:
|
||||
a1,a2 = a.split(',',1)
|
||||
if is_mmgen_id(a1) or is_coin_addr(a1):
|
||||
coin_addr = mmaddr2coinaddr(a1,ad_w,ad_f) if is_mmgen_id(a1) else CoinAddr(a1)
|
||||
tx.add_output(coin_addr,g.proto.coin_amt(a2))
|
||||
else:
|
||||
die(2,"%s: invalid subargument in command-line argument '%s'" % (a1,a))
|
||||
elif is_mmgen_id(a) or is_coin_addr(a):
|
||||
if tx.get_chg_output_idx() != None:
|
||||
die(2,'ERROR: More than one change address listed on command line')
|
||||
coin_addr = mmaddr2coinaddr(a,ad_w,ad_f) if is_mmgen_id(a) else CoinAddr(a)
|
||||
tx.add_output(coin_addr,g.proto.coin_amt('0'),is_chg=True)
|
||||
else:
|
||||
die(2,'{}: invalid command-line argument'.format(a))
|
||||
|
||||
if not tx.outputs:
|
||||
die(2,'At least one output must be specified on the command line')
|
||||
|
||||
if tx.get_chg_output_idx() == None:
|
||||
die(2,('ERROR: No change output specified',wmsg['no_change_output'])[len(tx.outputs) == 1])
|
||||
|
||||
tx.add_mmaddrs_to_outputs(ad_w,ad_f)
|
||||
tx.check_dup_addrs('outputs')
|
||||
|
||||
if not segwit_is_active() and tx.has_segwit_outputs():
|
||||
fs = '{} Segwit address requested on the command line, but Segwit is not active on this chain'
|
||||
rdie(2,fs.format(g.proj_name))
|
||||
|
||||
def get_inputs_from_user(tw,tx,caller):
|
||||
|
||||
while True:
|
||||
m = 'Enter a range or space-separated list of outputs to spend: '
|
||||
sel_nums = select_unspent(tw.unspent,m)
|
||||
msg('Selected output%s: %s' % (suf(sel_nums,'s'),' '.join(str(i) for i in sel_nums)))
|
||||
|
||||
sel_unspent = tw.MMGenTwOutputList([tw.unspent[i-1] for i in sel_nums])
|
||||
|
||||
t_inputs = sum(s.amt for s in sel_unspent)
|
||||
if t_inputs < tx.send_amt:
|
||||
msg(wmsg['not_enough_coin'].format(tx.send_amt-t_inputs))
|
||||
continue
|
||||
|
||||
non_mmaddrs = [i for i in sel_unspent if i.twmmid.type == 'non-mmgen']
|
||||
if non_mmaddrs and caller != 'txdo':
|
||||
msg(wmsg['non_mmgen_inputs'].format(', '.join(set(sorted([a.addr.hl() for a in non_mmaddrs])))))
|
||||
if not keypress_confirm('Accept?'):
|
||||
continue
|
||||
|
||||
tx.copy_inputs_from_tw(sel_unspent) # makes tx.inputs
|
||||
|
||||
change_amt = tx.sum_inputs() - tx.send_amt - get_fee_from_estimate_or_user(tx)
|
||||
|
||||
if change_amt >= 0:
|
||||
p = 'Transaction produces {} {} in change'.format(change_amt.hl(),g.coin)
|
||||
if opt.yes or keypress_confirm(p+'. OK?',default_yes=True):
|
||||
if opt.yes: msg(p)
|
||||
return change_amt
|
||||
else:
|
||||
msg(wmsg['not_enough_coin'].format(abs(change_amt)))
|
||||
|
||||
def txcreate(cmd_args,do_info=False,caller='txcreate'):
|
||||
|
||||
rpc_init()
|
||||
|
||||
tx = MMGenTX()
|
||||
|
||||
if opt.comment_file: tx.add_comment(opt.comment_file)
|
||||
|
||||
if not do_info: get_outputs_from_cmdline(cmd_args,tx)
|
||||
|
||||
do_license_msg()
|
||||
|
||||
tw = MMGenTrackingWallet(minconf=opt.minconf)
|
||||
tw.view_and_sort(tx)
|
||||
tw.display_total()
|
||||
|
||||
if do_info: sys.exit(0)
|
||||
|
||||
tx.send_amt = tx.sum_outputs()
|
||||
|
||||
msg('Total amount to spend: {}'.format(
|
||||
('Unknown','{} {}'.format(tx.send_amt.hl(),g.coin))[bool(tx.send_amt)]
|
||||
))
|
||||
|
||||
change_amt = get_inputs_from_user(tw,tx,caller)
|
||||
|
||||
if opt.rbf: tx.signal_for_rbf() # only after we have inputs
|
||||
|
||||
chg_idx = tx.get_chg_output_idx()
|
||||
|
||||
if change_amt == 0:
|
||||
msg('Warning: Change address will be deleted as transaction produces no change')
|
||||
tx.del_output(chg_idx)
|
||||
else:
|
||||
tx.update_output_amt(chg_idx,g.proto.coin_amt(change_amt))
|
||||
|
||||
if not tx.send_amt:
|
||||
tx.send_amt = change_amt
|
||||
|
||||
dmsg('tx: %s' % tx)
|
||||
|
||||
if not opt.yes:
|
||||
tx.add_comment() # edits an existing comment
|
||||
tx.create_raw() # creates tx.hex, tx.txid
|
||||
|
||||
tx.add_timestamp()
|
||||
tx.add_blockcount()
|
||||
tx.chain = g.chain
|
||||
|
||||
assert tx.sum_inputs() - tx.sum_outputs() <= g.proto.max_tx_fee
|
||||
|
||||
qmsg('Transaction successfully created')
|
||||
|
||||
dmsg('TX (final): %s' % tx)
|
||||
|
||||
if not opt.yes:
|
||||
tx.view_with_prompt('View decoded transaction?')
|
||||
|
||||
return tx
|
||||
|
|
@ -807,7 +807,7 @@ def rpc_init(reinit=False):
|
|||
try:
|
||||
assert block0 == g.proto.block0,'Incorrect Genesis block for {}'.format(g.proto.__name__)
|
||||
for fork in g.proto.forks:
|
||||
if latest < fork[0]: break
|
||||
if fork[0] == None or latest < fork[0]: break
|
||||
bhash = conn.getblockhash(fork[0])
|
||||
assert bhash == fork[1], (
|
||||
'Bad block hash at fork block {}. Is this the {} chain?'.format(fork[0],fork[2].upper()))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue