From 40fd464a121a93a6784db113f818b0b6fbcf34d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sa=C5=82aban?= Date: Wed, 29 Nov 2017 04:38:29 +0100 Subject: [PATCH] Add transfer handling --- monero/__init__.py | 2 +- monero/account.py | 18 +++++++--- monero/backends/jsonrpc.py | 68 +++++++++++++++++++++++++++----------- monero/exceptions.py | 9 +++++ monero/numbers.py | 6 ++++ monero/wallet.py | 17 +++++++--- 6 files changed, 92 insertions(+), 28 deletions(-) diff --git a/monero/__init__.py b/monero/__init__.py index a321028..2f73d17 100644 --- a/monero/__init__.py +++ b/monero/__init__.py @@ -1,5 +1,5 @@ from .address import Address from .account import Account from .wallet import Wallet -from .numbers import from_atomic, to_atomic +from .numbers import from_atomic, to_atomic, as_monero from . import prio diff --git a/monero/account.py b/monero/account.py index 0f22785..09683d2 100644 --- a/monero/account.py +++ b/monero/account.py @@ -27,11 +27,21 @@ class Account(object): def get_payments_out(self): return self._backend.get_payments_out(account=self.index) - def transfer(self, address, amount, priority=prio.NORMAL, mixin=5): - pass + def transfer(self, address, amount, priority=prio.NORMAL, mixin=5, unlock_time=0): + return self._backend.transfer( + [(address, amount)], + priority, + mixin, + unlock_time, + account=self.index) - def transfer_multi(self, destinations, priority=prio.NORMAL, mixin=5): + def transfer_multiple(self, destinations, priority=prio.NORMAL, mixin=5, unlock_time=0): """ destinations = [(address, amount), ...] """ - pass + return self._backend.transfer( + destinations, + priority, + mixin, + unlock_time, + account=self.index) diff --git a/monero/backends/jsonrpc.py b/monero/backends/jsonrpc.py index 1898189..c3502c1 100644 --- a/monero/backends/jsonrpc.py +++ b/monero/backends/jsonrpc.py @@ -56,22 +56,58 @@ class JSONRPC(object): def get_payments_in(self, account=0): _payments = self.raw_request('get_transfers', {'account_index': account, 'in': True, 'out': False, 'pool': False}) - return map(self._pythonify_tx, _payments.get('in', [])) + return map(self._pythonify_payment, _payments.get('in', [])) def get_payments_out(self, account=0): _payments = self.raw_request('get_transfers', {'account_index': account, 'in': False, 'out': True, 'pool': False}) - return map(self._pythonify_tx, _payments.get('out', '')) + return map(self._pythonify_payment, _payments.get('out', '')) + + def _pythonify_payment(self, pm): + return { + 'id': pm['txid'], + 'when': datetime.fromtimestamp(pm['timestamp']), + 'amount': from_atomic(pm['amount']), + 'fee': from_atomic(pm['fee']), + 'height': pm['height'], + 'payment_id': pm['payment_id'], + 'note': pm['note'] + } + + def transfer(self, destinations, priority, mixin, unlock_time, account=0): + print(destinations) + data = { + 'account_index': account, + 'destinations': list(map( + lambda dst: {'address': str(Address(dst[0])), 'amount': to_atomic(dst[1])}, + destinations)), + 'mixin': mixin, + 'priority': priority, + 'unlock_time': 0, + 'get_tx_keys': True, + 'get_tx_hex': True, + 'new_algorithm': True, + } + _transfers = self.raw_request('transfer_split', data) + keys = ('hash', 'amount', 'fee', 'key', 'blob') + return list(map( + self._pythonify_tx, + [ dict(_tx) for _tx in map( + lambda vs: zip(keys,vs), + zip( + *[_transfers[k] for k in ( + 'tx_hash_list', 'amount_list', 'fee_list', 'tx_key_list', 'tx_blob_list') + ] + )) + ])) def _pythonify_tx(self, tx): return { - 'id': tx['txid'], - 'when': datetime.fromtimestamp(tx['timestamp']), + 'id': tx['hash'], 'amount': from_atomic(tx['amount']), 'fee': from_atomic(tx['fee']), - 'height': tx['height'], - 'payment_id': tx['payment_id'], - 'note': tx['note'] + 'key': tx['key'], + 'blob': tx.get('blob', None), } def raw_request(self, method, params=None): @@ -97,7 +133,7 @@ class JSONRPC(object): # TODO: resolve code, raise exception _log.error(u"JSON RPC error:\n{result}".format(result=_ppresult)) if err['code'] in _err2exc: - raise _err2exc[err['code']](err['message'], method=method, data=data, result=result) + raise _err2exc[err['code']](err['message']) else: raise RPCError( "Method '{method}' failed with RPC Error of unknown code {code}, " @@ -105,17 +141,8 @@ class JSONRPC(object): return result['result'] -class RPCError(exceptions.MoneroException): - def __init__(self, message, method=None, data=None, result=None): - self.method = method - self.data = data - self.result = result - super().__init__(message) - - def __str__(self): - return "'{method}': {error}".format( - method=self.method, - error=super().__str__()) +class RPCError(exceptions.BackendException): + pass class Unauthorized(RPCError): @@ -127,5 +154,8 @@ class MethodNotFound(RPCError): _err2exc = { + -2: exceptions.WrongAddress, + -4: exceptions.NotEnoughUnlockedMoney, + -20: exceptions.AmountIsZero, -32601: MethodNotFound, } diff --git a/monero/exceptions.py b/monero/exceptions.py index 50971f8..b20d342 100644 --- a/monero/exceptions.py +++ b/monero/exceptions.py @@ -7,5 +7,14 @@ class BackendException(MoneroException): class AccountException(MoneroException): pass +class WrongAddress(AccountException): + pass + class NotEnoughMoney(AccountException): pass + +class NotEnoughUnlockedMoney(NotEnoughMoney): + pass + +class AmountIsZero(AccountException): + pass diff --git a/monero/numbers.py b/monero/numbers.py index 7bd6ea8..3d330eb 100644 --- a/monero/numbers.py +++ b/monero/numbers.py @@ -3,7 +3,13 @@ from decimal import Decimal PICONERO = Decimal('0.000000000001') def to_atomic(amount): + """Convert Monero decimal to atomic integer of piconero.""" return int(amount * 10**12) def from_atomic(amount): + """Convert atomic integer of piconero to Monero decimal.""" return (Decimal(amount) * PICONERO).quantize(PICONERO) + +def as_monero(amount): + """Return the amount rounded to maximal Monero precision.""" + return Decimal(amount).quantize(PICONERO) diff --git a/monero/wallet.py b/monero/wallet.py index 3763c52..e18b85e 100644 --- a/monero/wallet.py +++ b/monero/wallet.py @@ -34,11 +34,20 @@ class Wallet(object): def get_payments_out(self): return self.accounts[0].get_payments_out() - def transfer(self, address, amount, priority=prio.NORMAL, mixin=5): - self.accounts[0].transfer(address, amount, priority=priority, mixin=mixin) + def transfer(self, address, amount, priority=prio.NORMAL, mixin=5, unlock_time=0): + return self.accounts[0].transfer( + address, + amount, + priority=priority, + mixin=mixin, + unlock_time=unlock_time) - def transfer_multi(self, destinations, priority=prio.NORMAL, mixin=5): + def transfer_multiple(self, destinations, priority=prio.NORMAL, mixin=5, unlock_time=0): """ destinations = [(address, amount), ...] """ - return self.accounts[0].transfer_multi(destinations, priority=priority, mixin=mixin) + return self.accounts[0].transfer_multiple( + destinations, + priority=priority, + mixin=mixin, + unlock_time=unlock_time)