Reorganize payments and transactions

This commit is contained in:
Michał Sałaban 2018-01-25 08:50:09 +01:00
parent 8ae386904a
commit a16ae37a94
7 changed files with 146 additions and 98 deletions

View File

@ -10,7 +10,7 @@ from .. import exceptions
from ..account import Account from ..account import Account
from ..address import address, Address, SubAddress from ..address import address, Address, SubAddress
from ..numbers import from_atomic, to_atomic, PaymentID from ..numbers import from_atomic, to_atomic, PaymentID
from ..transaction import Transaction, Payment, Transfer from ..transaction import Transaction, IncomingPayment, OutgoingPayment
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
@ -173,11 +173,10 @@ class JSONRPCWallet(object):
'account_index': account, 'account_index': account,
'payment_id': str(payment_id)}) 'payment_id': str(payment_id)})
pmts = [] pmts = []
for tx in _payments['payments']: for data in _payments['payments']:
data = self._tx2dict(tx)
# Monero <= 0.11 : no address is passed because there's only one # Monero <= 0.11 : no address is passed because there's only one
data['local_address'] = data['local_address'] or self._master_address data['address'] = data['address'] or self._master_address
pmts.append(Payment(**data)) pmts.append(self._inpayment(data))
return pmts return pmts
def get_transactions_in(self, account=0, confirmed=True, unconfirmed=False): def get_transactions_in(self, account=0, confirmed=True, unconfirmed=False):
@ -186,8 +185,7 @@ class JSONRPCWallet(object):
txns = _txns.get('in', []) txns = _txns.get('in', [])
if unconfirmed: if unconfirmed:
txns.extend(_txns.get('pool', [])) txns.extend(_txns.get('pool', []))
return [Payment(**self._tx2dict(tx)) for tx in return [self._inpayment(tx) for tx in sorted(txns, key=operator.itemgetter('timestamp'))]
sorted(txns, key=operator.itemgetter('timestamp'))]
def get_transactions_out(self, account=0, confirmed=True, unconfirmed=True): def get_transactions_out(self, account=0, confirmed=True, unconfirmed=True):
_txns = self.raw_request('get_transfers', _txns = self.raw_request('get_transfers',
@ -195,33 +193,47 @@ class JSONRPCWallet(object):
txns = _txns.get('out', []) txns = _txns.get('out', [])
if unconfirmed: if unconfirmed:
txns.extend(_txns.get('pool', [])) txns.extend(_txns.get('pool', []))
return [Transfer(**self._tx2dict(tx)) for tx in return [self._outpayment(tx) for tx in sorted(txns, key=operator.itemgetter('timestamp'))]
sorted(txns, key=operator.itemgetter('timestamp'))]
def get_transaction(self, txhash): def get_transaction(self, txhash):
_tx = self.raw_request('get_transfer_by_txid', {'txid': str(txhash)})['transfer'] _tx = self.raw_request('get_transfer_by_txid', {'txid': str(txhash)})['transfer']
try: if _tx['type'] == 'in':
_class = {'in': Payment, 'out': Transfer}[_tx['type']] return self._inpayment(tx)
except KeyError: elif _tx['type'] == 'out':
_class = Transaction return self._outpayment(tx)
return _class(**self._tx2dict(_tx)) return Payment(**self._paymentdict(tx))
def _tx2dict(self, tx): def _paymentdict(self, data):
pid = tx.get('payment_id', None) pid = data.get('payment_id', None)
return { return {
'hash': tx.get('txid', tx.get('tx_hash')), 'txhash': data.get('txid', data.get('tx_hash')),
'timestamp': datetime.fromtimestamp(tx['timestamp']) if 'timestamp' in tx else None,
'amount': from_atomic(tx['amount']),
'fee': from_atomic(tx['fee']) if 'fee' in tx else None,
'height': tx.get('height', tx.get('block_height')) or None,
'payment_id': None if pid is None else PaymentID(pid), 'payment_id': None if pid is None else PaymentID(pid),
'note': tx.get('note'), 'amount': from_atomic(data['amount']),
# NOTE: address will be resolved only after PR#3010 has been merged to Monero 'timestamp': datetime.fromtimestamp(data['timestamp']) if 'timestamp' in data else None,
'local_address': address(tx['address']) if 'address' in tx else None, 'note': data.get('note'),
'key': tx.get('key'), 'transaction': self._tx(data)
'blob': tx.get('blob', None),
} }
def _inpayment(self, data):
p = self._paymentdict(data)
p.update({'received_by': address(data['address']) if 'address' in data else None})
return IncomingPayment(**p)
def _outpayment(self, data):
p = self._paymentdict(data)
p.update({'sent_from': address(data['address']) if 'address' in data else None})
return OutgoingPayment(**p)
def _tx(self, data):
return Transaction(**{
'hash': data.get('txid', data.get('tx_hash')),
'fee': from_atomic(data['fee']) if 'fee' in data else None,
'key': data.get('key'),
'height': data.get('height', data.get('block_height')) or None,
'timestamp': datetime.fromtimestamp(data['timestamp']) if 'timestamp' in data else None,
'blob': data.get('blob', None),
})
def transfer(self, destinations, priority, ringsize, def transfer(self, destinations, priority, ringsize,
payment_id=None, unlock_time=0, account=0, payment_id=None, unlock_time=0, account=0,
relay=True): relay=True):
@ -247,7 +259,7 @@ class JSONRPCWallet(object):
'tx_hash_list', 'amount_list', 'fee_list', 'tx_key_list', 'tx_blob_list')]))] 'tx_hash_list', 'amount_list', 'fee_list', 'tx_key_list', 'tx_blob_list')]))]
for d in _pertx: for d in _pertx:
d['payment_id'] = payment_id d['payment_id'] = payment_id
return [Transfer(**self._tx2dict(tx)) for tx in _pertx] return [self._tx(data) for data in _pertx]
def raw_request(self, method, params=None): def raw_request(self, method, params=None):
hdr = {'Content-Type': 'application/json'} hdr = {'Content-Type': 'application/json'}

View File

@ -8,8 +8,8 @@ class Daemon(object):
def get_height(self): def get_height(self):
return self._backend.get_info()['height'] return self._backend.get_info()['height']
def send_transaction(self, blob): def send_transaction(self, tx):
return self._backend.send_transaction(blob) return self._backend.send_transaction(tx.blob)
def get_mempool(self): def get_mempool(self):
return self._backend.get_mempool() return self._backend.get_mempool()

View File

@ -1,45 +1,48 @@
class Transaction(object): class Payment(object):
hash = None tx_hash = None
height = None
timestamp = None
fee = None
blob = None
def __init__(self, hash=None, **kwargs):
self.hash = hash
self.height = kwargs.get('height', self.height)
self.timestamp = kwargs.get('timestamp', self.timestamp)
self.fee = kwargs.get('fee', self.fee)
self.blob = kwargs.get('blob', self.blob)
def __repr__(self):
return self.hash
class LocalTransaction(Transaction):
"""A transaction that concerns local wallet, either incoming or outgoing."""
payment_id = None payment_id = None
amount = None amount = None
local_address = None timestamp = None
transaction = None
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(LocalTransaction, self).__init__(**kwargs) self.tx_hash = kwargs.get('tx_hash', self.tx_hash)
self.payment_id = kwargs.get('payment_id', self.payment_id)
self.amount = kwargs.get('amount', self.amount) self.amount = kwargs.get('amount', self.amount)
self.local_address = kwargs.get('local_address', self.local_address) self.timestamp = kwargs.get('timestamp', self.timestamp)
self.payment_id = kwargs.get('payment_id', self.payment_id)
self.transaction = kwargs.get('transaction', self.transaction)
class Payment(LocalTransaction): class IncomingPayment(Payment):
"""Incoming Transaction""" received_by = None
pass
def __init__(self, **kwargs):
super(IncomingPayment, self).__init__(**kwargs)
self.received_by = kwargs.get('received_by', self.received_by)
class Transfer(LocalTransaction): class OutgoingPayment(Payment):
"""Outgoing Transaction""" sent_from = None
key = None
note = '' note = ''
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(Transfer, self).__init__(**kwargs) super(OutgoingPayment, self).__init__(**kwargs)
self.sent_from = kwargs.get('sent_from', self.sent_from)
self.note = kwargs.get('note', self.sent_from)
class Transaction(object):
hash = None
fee = None
height = None
timestamp = None
key = None
blob = None
def __init__(self, **kwargs):
self.hash = kwargs.get('hash', self.hash)
self.fee = kwargs.get('fee', self.fee)
self.height = kwargs.get('height', self.height)
self.timestamp = kwargs.get('timestamp', self.timestamp)
self.key = kwargs.get('key', self.key) self.key = kwargs.get('key', self.key)
self.note = kwargs.get('note', self.note) self.blob = kwargs.get('blob', self.blob)

View File

@ -8,7 +8,7 @@ except ImportError:
from monero.wallet import Wallet from monero.wallet import Wallet
from monero.address import Address from monero.address import Address
from monero.transaction import Transaction, Payment, Transfer from monero.transaction import IncomingPayment, OutgoingPayment, Transaction
from monero.backends.jsonrpc import JSONRPCWallet from monero.backends.jsonrpc import JSONRPCWallet
class SubaddrWalletTestCase(unittest.TestCase): class SubaddrWalletTestCase(unittest.TestCase):
@ -162,12 +162,14 @@ class SubaddrWalletTestCase(unittest.TestCase):
'unlock_time': 0}]}} 'unlock_time': 0}]}}
pay_in = self.wallet.get_transactions_in() pay_in = self.wallet.get_transactions_in()
self.assertEqual(len(list(pay_in)), 3) self.assertEqual(len(list(pay_in)), 3)
for tx in pay_in: for pmt in pay_in:
self.assertIsInstance(tx, Payment) self.assertIsInstance(pmt, IncomingPayment)
# Once PR#3010 has been merged to Monero, update the JSON and enable the following: # Once PR#3010 has been merged to Monero, update the JSON and enable the following:
# self.assertIsInstance(tx.local_address, Address) # self.assertIsInstance(pmt.received_by, Address)
self.assertIsInstance(tx.amount, Decimal) self.assertIsInstance(pmt.amount, Decimal)
self.assertIsInstance(tx.fee, Decimal) self.assertIsInstance(pmt.transaction, Transaction)
self.assertIsInstance(pmt.transaction.fee, Decimal)
self.assertIsInstance(pmt.transaction.height, int)
@patch('monero.backends.jsonrpc.requests.post') @patch('monero.backends.jsonrpc.requests.post')
def test_get_transactions_out(self, mock_post): def test_get_transactions_out(self, mock_post):
@ -257,20 +259,22 @@ class SubaddrWalletTestCase(unittest.TestCase):
'unlock_time': 0}]}} 'unlock_time': 0}]}}
pay_out = self.wallet.get_transactions_out() pay_out = self.wallet.get_transactions_out()
self.assertEqual(len(list(pay_out)), 6) self.assertEqual(len(list(pay_out)), 6)
for tx in pay_out: for pmt in pay_out:
self.assertIsInstance(tx, Transfer) self.assertIsInstance(pmt, OutgoingPayment)
# Once PR#3010 has been merged to Monero, update the JSON and enable the following: # Once PR#3010 has been merged to Monero, update the JSON and enable the following:
# self.assertIsInstance(tx.local_address, Address) # self.assertIsInstance(pmt.sent_from, Address)
self.assertIsInstance(tx.amount, Decimal) self.assertIsInstance(pmt.amount, Decimal)
self.assertIsInstance(tx.fee, Decimal) self.assertIsInstance(pmt.timestamp, datetime)
self.assertIsInstance(tx.timestamp, datetime) self.assertIsInstance(pmt.transaction, Transaction)
self.assertIsInstance(pmt.transaction.fee, Decimal)
self.assertIsInstance(pmt.transaction.height, int)
@patch('monero.backends.jsonrpc.requests.post') @patch('monero.backends.jsonrpc.requests.post')
def test_get_payments(self, mock_post): def test_get_payments(self, mock_post):
mock_post.return_value.status_code = 200 mock_post.return_value.status_code = 200
mock_post.return_value.json.return_value = self.get_accounts_result mock_post.return_value.json.return_value = self.get_accounts_result
self.wallet = Wallet(JSONRPCWallet()) self.wallet = Wallet(JSONRPCWallet())
mock_post.return_value.status_code = 200 mock_post.return_value.status_code = 200
mock_post.return_value.json.return_value = {'id': 0, mock_post.return_value.json.return_value = {'id': 0,
'jsonrpc': '2.0', 'jsonrpc': '2.0',
'result': {'payments': [{'address': 'BZ9V9tfTDgHYsnAxgeMLaGCUb6yMaGNiZJwiBWQrE23MXcRqSde9DKa9LnPw31o2G8QrdKdUNM7VWhd3dr22ivk54QGqZ6u', 'result': {'payments': [{'address': 'BZ9V9tfTDgHYsnAxgeMLaGCUb6yMaGNiZJwiBWQrE23MXcRqSde9DKa9LnPw31o2G8QrdKdUNM7VWhd3dr22ivk54QGqZ6u',
@ -282,8 +286,35 @@ class SubaddrWalletTestCase(unittest.TestCase):
'unlock_time': 0}]}} 'unlock_time': 0}]}}
payments = self.wallet.get_payments(payment_id=0xfeedbadbeef12345) payments = self.wallet.get_payments(payment_id=0xfeedbadbeef12345)
self.assertEqual(len(list(payments)), 1) self.assertEqual(len(list(payments)), 1)
for payment in payments: for pmt in payments:
self.assertIsInstance(payment, Payment) self.assertIsInstance(pmt, IncomingPayment)
self.assertIsInstance(payment.local_address, Address) self.assertIsInstance(pmt.received_by, Address)
self.assertIsInstance(payment.amount, Decimal) self.assertIsInstance(pmt.amount, Decimal)
self.assertIsInstance(payment.height, int) self.assertIsInstance(pmt.transaction, Transaction)
self.assertIsInstance(pmt.transaction.height, int)
@patch('monero.backends.jsonrpc.requests.post')
def test_send_transfer(self, mock_post):
mock_post.return_value.status_code = 200
mock_post.return_value.json.return_value = self.get_accounts_result
self.wallet = Wallet(JSONRPCWallet())
mock_post.return_value.status_code = 200
mock_post.return_value.json.return_value = {'id': 0,
'jsonrpc': '2.0',
'result': {'amount_list': [3000000000000],
'fee_list': [3866640000],
'multisig_txset': '',
'tx_blob_list': ['020001020005a3d60be09109dfd202e617d904090789e33b02c001885d17d80101a4de34b9e76f8e9b87f50766b33a1f00f49d020002c5fc19a94f82a9cefeaebf5f9f7d8330ff28cc7b1c6dba65d5dd3bfe4693056a00028711ea3e11b97250e7563838e4ea203594c4b33784c2d9777aa738061a31946d2c020901b259965ad1f33d2d014c8cdd4a341a75cf01fef00e6b344b47150e55ae06333b5585b20eb0f4afff160380fde0b30ee3c5ebbb3bbaf11b30f4ef79ee96122175077cec9866b88aa123fdd9f55e2d06cb3815f7e8e88dbcd4f6776aafd13cdf21c86b38671bb4641dd4d696fff29a0546817024cb9f9330fe86a43c1e4088a9019a601aa80025e3f24d0ab5066fdd09254ec751d101960c3422db7657a43b764f015679b3da7221aa349d40ddcce60964af135cbef245c4a2d51bd2b8605b6ce7576f32e9f0b06d91b3840828902cd4c35a1a59fdef21441a02a78229df304f8569f259c3ec218ffe45722020fbfde6a74b0d36753d6afae377e771d454839f8e76762e23dbf35ee354716f7d15f42e7e03048d917984a814fdef3ea4556d0946b30a790e8e0739e8e9f933da61e3ddd2699df0bcf55ac1c149cfbea618830933830bd3937b1f27613cd0959b6020dda7ced635d3c41d31694a526788a7c783e8476594006e2dee7d9fd606d793618fc062ed8cc178c4e1376524a6029a618b882cbfc86f40cc0a036b11d30500290abc052823a5f3b180cb7e05e0c9316a7cbfc9e94c370e0817b3ad5c90c0d9070606883d72d6706f1b5069652a8a98c20d60774ff6dd3ba2fa09d39af8c5f7cca2e592cdd0df79482d44baba5531f24961967c43c720aa8f3d93b376c67064960baacf4c347dc243cee2b44e8194fe14e1efe785924671cf8ff3bf545ab8a05213bcce3adef4dae1988e9d33926722be5927c952f6b8f36e62a8fafdb699c8ffdc61720a6d869bfc2719a05fe9f13c72e295869955bfb88eb79d95d19285340049906d497d9d847c09b7e4009d8b0c09a535e993583270227e00c883822a32fbf1c206e5de38438c1d637fd87600f6d56b673e608625acfebbfb8c32632da3282346588078d4af03c84ef78a141af8ca92d236b7f464aad3f4e66e34689d0bb2fbed1a0b9fc342e5e8a51505b69c9cb7d401778576e36c68475ad738e35f867ad6498428d59fdf3e92d42cb91515675a0a8412a85e359c4aa9f25a684b2e5fa0d3faee9afef115113579b423f2386edf9b8961c1a37967a7718b20594421fdb537069c643f03475f2396ad61faa904c1f35ce2436f6260c51c429a58358c1f19961bb8a9b3516e2eef13551315ab586e68caae69c4c09b40ff6fff4f67fdc2f700600b5a97f280b6a94c11068065d8edf1cbb10203e559ee94ba3cbea4eee1cd36790012196284ba15b3d7427c3fd3647cb65c9230d2fafbd96c978e0545e404317805c549d2bfcb7bc68bdbce5a94f2f090487d58e2be4d011b44532d8543338c64cf91dc869021b94b5c2b7dabf666b2a57c703e4a1cbd6702fceb42cff1108e06b4412abbc5f68cae62fc01bd9ac96e36ce07d1536115d44c752b37d5d90ea44614a76a67ac7da251511a8fed42a012478f29031603da3eef2cde3d7a9e342c92fcc88f1437a50ec0bb18670ece373de05945dd05976433a32bf352715fe5c8960940a9cff86b97bf57786ccfbf7ddac06bb657ed936d6c112bdc6bf6e5f731ef0e066f554b95f530932ac950ebddd01eb04bf36073da30bf1addf137a0f5852fa8480dfc7e2962424c5d3d6f420f85217d30b481dae95db7d87d8da86cc259675b33a546c0eff502768ea78a296ca55b608dd492c2c213415c45b3167c051f45fc04c12c67a06d984f8deef6079dcd0f6d07513c735630d53ff281d9a2024e84f749164db0fe0826690c460342b5c48a4fb2f7cae80efef65cea5b733bf8624314e74efd5212c23925ab08adaa5bd78bfb24bc5e4133328e478235a42538c0d01edb065ff17ed0116d1e33864dbedc31304c04d31ae7451048979c9ca4e92d6bcdb5e4ac42caa22665729967504dd1242f95f71dcbc59cd9d62c831f774ac9ebe0619318f31229075535793924dc79874fff17954686c4463b1094d1e3f8a98c115fec2d2b1e37c11d06f7a24c1dbf7871b7510d8f5785b47a5c62203820add67f0264dfe71ac6185e457d3be8944ba5ff3d78847b21349a2a5813b79fadce4fa2f393edaa4904c7564d8036664b0191090e82bf275b552e8cd5f04ee30511a8edad21bb8041588e71296836d34e86e8f7c6edc4327b7d1122377b19fb6e8961c26a0ab1b4e3a5e0f6d2b6b4448e395985981584c78da0b7678d2a338d553d07be430c0e967c86e0859eb69422959d6eacb43ed9acc678d515466ce9bca08c03ad2a09affa136893566df52a553ca67f529fe9fa72f15b7fd60c769a5168d53c02d701937d20d79284f34e1b7094c40491df2619c03efacc3989d340d7b611c348f9098fa80e007874cd7bdb6eb67c23e17f8446ca01b6ea1fe1475b3cb5a031d50e0424f2efb010259a038d693ca1ab3bc1abf5c56880c2d14355790934617c2b260a74883c88310fa0bb0d2aa0a5778532b3bff7a80586a2fd761e2620c3bcad1006cc328cdd3a9c790d30d260e473a85ffc8200dae9bc2992cd446d55103668db0e2f85976ec87bf7bda26c4c6492f76e493beef5daf29f0b723fa8d9cb4eba4f03396c65b052fbf4521dfd7ed611fe3bbbb577ab46ff87002bad166e6a61873b0e2a8b5be203a6a0c4bd12c7a5cc82284d12b089b4cf245d38d93749383a85b202deba9fbcab44b9f343117669ccf91cf16df5af4309941c718e937228ec2ace05c3fbf708be2d079c92b1045c0167116dc2c52323b23f8514eddbe1e3d30cb005'],
'tx_hash_list': ['401d8021975a0fee16fe84acbfc4d8ba6312e563fa245baba2aac382e787fb60'],
'tx_key_list': ['7061d4d939b563a11e344c60938410e2e63ea72c43741fae81b8805cebe5570a']}}
txns = self.wallet.transfer(
'9wFuzNoQDck1pnS9ZhG47kDdLD1BUszSbWpGfWcSRy9m6Npq9NoHWd141KvGag8hu2gajEwzRXJ4iJwmxruv9ofc2CwnYCE',
3)
self.assertEqual(len(txns), 1)
txn = txns[0]
self.assertIsInstance(txn, Transaction)
self.assertIsInstance(txn.fee, Decimal)
self.assertEqual(txn.hash,
'401d8021975a0fee16fe84acbfc4d8ba6312e563fa245baba2aac382e787fb60')
self.assertEqual(txn.key,
'7061d4d939b563a11e344c60938410e2e63ea72c43741fae81b8805cebe5570a')

View File

@ -6,6 +6,7 @@ import sys
from monero.backends.jsonrpc import JSONRPCDaemon from monero.backends.jsonrpc import JSONRPCDaemon
from monero.daemon import Daemon from monero.daemon import Daemon
from monero.transaction import Transaction
from monero import exceptions from monero import exceptions
def url_data(url): def url_data(url):
@ -35,8 +36,9 @@ else:
d = Daemon(JSONRPCDaemon(**args.daemon_rpc_url)) d = Daemon(JSONRPCDaemon(**args.daemon_rpc_url))
for name, blob in blobs: for name, blob in blobs:
logging.debug("Sending {}".format(name)) logging.debug("Sending {}".format(name))
tx = Transaction(blob=blob)
try: try:
res = d.send_transaction(blob) res = d.send_transaction(tx)
except exceptions.TransactionBroadcastError as e: except exceptions.TransactionBroadcastError as e:
print("{} not sent, reason: {}".format(name, e.details['reason'])) print("{} not sent, reason: {}".format(name, e.details['reason']))
logging.debug(e.details) logging.debug(e.details)

View File

@ -51,14 +51,14 @@ elif args.verbosity > 1:
logging.basicConfig(level=level, format="%(asctime)-15s %(message)s") logging.basicConfig(level=level, format="%(asctime)-15s %(message)s")
w = Wallet(JSONRPCWallet(**args.wallet_rpc_url)) w = Wallet(JSONRPCWallet(**args.wallet_rpc_url))
txfrs = w.accounts[args.account].transfer_multiple( txns = w.accounts[args.account].transfer_multiple(
args.destinations, priority=prio, ringsize=args.ring_size, payment_id=args.payment_id, args.destinations, priority=prio, ringsize=args.ring_size, payment_id=args.payment_id,
relay=args.outdir is None) relay=args.outdir is None)
for tx in txfrs: for tx in txns:
print(u"Transaction {hash}:\nXMR: {amount:21.12f} @ {fee:13.12f} fee\n" print(u"Transaction {hash}:\nfee: {fee:21.12f}\n"
u"Payment ID: {payment_id}\nTx key: {key}\nSize: {size} B".format( u"Tx key: {key}\nSize: {size} B".format(
hash=tx.hash, amount=tx.amount, fee=tx.fee, hash=tx.hash, fee=tx.fee,
payment_id=tx.payment_id, key=tx.key, size=len(tx.blob) >> 1)) key=tx.key, size=len(tx.blob) >> 1))
if args.outdir: if args.outdir:
outname = os.path.join(args.outdir, tx.hash + '.tx') outname = os.path.join(args.outdir, tx.hash + '.tx')
outfile = open(outname, 'wb') outfile = open(outname, 'wb')

View File

@ -35,15 +35,15 @@ def get_wallet():
_TXHDR = "timestamp height id/hash " \ _TXHDR = "timestamp height id/hash " \
" amount fee {dir:95s} payment_id" " amount fee {dir:95s} payment_id"
def tx2str(tx): def pmt2str(pmt):
return "{time} {height:7d} {hash} {amount:17.12f} {fee:13.12f} {addr} {payment_id}".format( return "{time} {height:7d} {hash} {amount:17.12f} {fee:13.12f} {addr} {payment_id}".format(
time=tx.timestamp.strftime("%d-%m-%y %H:%M:%S") if getattr(tx, 'timestamp', None) else None, time=pmt.timestamp.strftime("%d-%m-%y %H:%M:%S") if getattr(pmt, 'timestamp', None) else None,
height=tx.height or 0, height=pmt.transaction.height or 0,
hash=tx.hash, hash=pmt.transaction.hash,
amount=tx.amount, amount=pmt.amount,
fee=tx.fee or 0, fee=pmt.transaction.fee or 0,
payment_id=tx.payment_id, payment_id=pmt.payment_id,
addr=getattr(tx, 'local_address', None) or '') addr=getattr(pmt, 'local_address', None) or '')
def a2str(a): def a2str(a):
return "{addr} {label}".format( return "{addr} {label}".format(
@ -85,23 +85,23 @@ if len(w.accounts) > 1:
print("\nIncoming transactions:") print("\nIncoming transactions:")
print(_TXHDR.format(dir='received by')) print(_TXHDR.format(dir='received by'))
for tx in ins: for tx in ins:
print(tx2str(tx)) print(pmt2str(tx))
outs = acc.get_transactions_out(unconfirmed=True) outs = acc.get_transactions_out(unconfirmed=True)
if outs: if outs:
print("\nOutgoing transactions:") print("\nOutgoing transactions:")
print(_TXHDR.format(dir='sent from')) print(_TXHDR.format(dir='sent from'))
for tx in outs: for tx in outs:
print(tx2str(tx)) print(pmt2str(tx))
else: else:
ins = w.get_transactions_in(unconfirmed=True) ins = w.get_transactions_in(unconfirmed=True)
if ins: if ins:
print("\nIncoming transactions:") print("\nIncoming transactions:")
print(_TXHDR.format(dir='received by')) print(_TXHDR.format(dir='received by'))
for tx in ins: for tx in ins:
print(tx2str(tx)) print(pmt2str(tx))
outs = w.get_transactions_out(unconfirmed=True) outs = w.get_transactions_out(unconfirmed=True)
if outs: if outs:
print("\nOutgoing transactions:") print("\nOutgoing transactions:")
print(_TXHDR.format(dir='sent from')) print(_TXHDR.format(dir='sent from'))
for tx in outs: for tx in outs:
print(tx2str(tx)) print(pmt2str(tx))